ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

AI工程韧性实战:从原型到生产的RAG与Agent架构设计

AI工程韧性实战:从原型到生产的RAG与Agent架构设计 1. 从原型到生产AI工程韧性问题的本质1.1 为什么原型跑通只是万里长征第一步做过AI应用的人都有一个共同体验demo阶段一切顺利上线之后问题层出不穷。本地跑通的LLM调用链到了生产环境可能因为一次API超时、一段异常输入、一个检索结果为空就彻底崩溃。这不是个别现象而是AI工程领域的普遍困境。我见过太多团队在原型阶段花了两周做出一个令人惊艳的RAG问答系统然后花了三个月都没能把它稳定地跑在生产环境里。问题出在哪里原型关注的是“能不能跑通”而生产关注的是“跑不通的时候怎么办”。这两者之间的鸿沟就是韧性工程要解决的核心问题。所谓韧性不是追求系统永远不出错而是系统在出错时能够优雅降级、自动恢复、给出有意义的反馈而不是直接把异常抛给用户。一个LLM请求失败了能不能自动重试并切换到备用模型检索结果不相关时能不能触发查询改写而不是硬着头皮生成知识库为空时能不能明确告诉用户“我暂时没有这方面的信息”而不是编造一个答案这些才是AI工程从玩具走向产品的关键分水岭。1.2 韧性AI工程的三个核心支柱从我的实践经验来看一个有韧性的AI工程系统需要同时做好三件事。第一是可观测性。你需要知道系统内部发生了什么。LLM的每次调用耗时多少、token消耗多少、返回内容是否被截断、检索的召回率和精确率如何、用户对回答的反馈是什么——这些数据如果不可见后续所有优化都是盲人摸象。很多团队上线后只监控了一个HTTP状态码这远远不够。第二是容错与降级。任何一个外部依赖都可能失败LLM服务商限流、向量数据库连接超时、嵌入模型加载失败、网络抖动导致请求中断。韧性系统需要为每一条可能的失败路径设计应对策略而不是假设一切永远正常。第三是持续迭代能力。AI系统不是一次部署就完事的你需要持续收集bad case、更新知识库、调整prompt、切换模型版本。CI/CD流水线在传统软件工程中已经成熟但在AI场景下需要额外考虑模型版本管理、数据漂移检测、A/B测试等环节。这三个支柱共同构成了从原型到生产的桥梁。下面我会逐一拆解每个环节的具体做法和踩坑经验。2. 核心架构拆解Harness、RAG与Agent的协作关系2.1 Harness到底是什么和Agent有什么区别最近“harness”这个词在AI工程圈子里频繁出现很多人把它和Agent混为一谈。我刚开始接触时也困惑过后来在实际项目中才慢慢理清。简单来说Agent是决策者Harness是执行环境。Agent负责“想”——根据用户输入决定下一步做什么调用哪个工具生成什么内容。Harness负责“做”——提供Agent运行所需的基础设施包括工具注册与调用、上下文管理、错误处理、日志记录、权限控制等。打个比方Agent像一个司机Harness像整辆车。司机决定往哪开但车本身的发动机、刹车、安全带、仪表盘都是Harness提供的。没有HarnessAgent就是一个裸的LLM调用什么都做不了。在实际工程中Harness通常包含以下组件工具注册表管理Agent可以调用的所有工具搜索、计算、数据库查询等包括每个工具的参数schema、超时设置、重试策略。上下文管理器维护对话历史、检索结果、中间步骤的上下文窗口确保不超出LLM的token限制。执行引擎按顺序或并行执行Agent规划出的步骤处理步骤之间的依赖关系。错误处理器当某个步骤失败时决定是重试、跳过、降级还是终止。可观测层记录每一步的输入输出、耗时、token消耗供后续分析和调试。理解了这层关系你就明白为什么单独讨论“Agent框架”意义不大——真正决定系统韧性的是Harness的设计质量。2.2 RAG在韧性架构中的定位与常见瓶颈RAG检索增强生成是目前最主流的LLM落地模式之一但它的瓶颈远比想象中多。我在多个项目中总结下来RAG的韧性挑战主要集中在以下几个环节。检索阶段的问题用户query和知识库文档之间的语义鸿沟是最常见的。用户问“怎么退货”知识库里写的是“售后流程说明”如果嵌入模型不够好可能完全匹配不上。另外当知识库规模增大到几十万条以上时向量检索的召回率会明显下降这时候需要引入混合检索关键词向量或者重排序rerank来补救。生成阶段的问题检索到的内容可能包含噪声、过时信息或者相互矛盾的说法。LLM在面对这些内容时可能选择性地忽略关键信息也可能把不相关的内容强行拼凑进回答。更麻烦的是当检索结果为空时很多LLM会“幻觉”出一个看起来合理的答案而不是承认自己不知道。知识库维护的问题知识库不是建好就完事的。文档会更新、会过期、会新增。如果没有一套自动化的知识库更新和版本管理机制RAG系统很快就会输出过时甚至错误的答案。针对这些瓶颈韧性RAG的设计思路是在检索前做query改写和扩展在检索后做相关性过滤和重排序在生成时加入“无相关信息时明确拒答”的指令在系统层面建立知识库的定期更新和验证流程。2.3 一个典型的韧性AI工程架构长什么样把上面的组件串起来一个具备韧性的AI工程架构大致分为四层。接入层处理用户请求的接收、鉴权、限流、格式校验。这一层需要做好输入的长度限制和内容过滤防止超长输入导致token爆炸或者恶意输入触发异常行为。编排层这是Harness的核心所在。负责Agent的规划、工具的调度、上下文的组装、多轮对话的状态管理。编排层需要实现完整的错误处理逻辑包括超时重试、备用方案切换、降级响应生成。能力层包括LLM调用、向量检索、嵌入计算、重排序等具体能力。每个能力都需要封装成独立的服务模块具备自己的健康检查和熔断机制。数据层知识库、对话历史、日志、监控指标。这一层需要保证数据的一致性和可追溯性尤其是知识库的版本管理必须能够回溯到某个时间点的知识状态。这四层之间通过明确定义的接口通信任何一层的故障都不会直接导致整个系统崩溃。比如LLM服务不可用时编排层可以切换到备用模型或者返回缓存的相似回答向量数据库连接失败时可以降级为关键词检索。3. 实操落地从零搭建一个有韧性的RAG系统3.1 环境准备与技术选型的关键考量假设我们要搭建一个面向内部文档的RAG问答系统支持PDF、Markdown、网页等多种格式的知识入库。技术选型上我倾向于以下组合组件选型理由LLM本地部署开源模型 云端API备用兼顾数据安全和可用性嵌入模型多语言嵌入模型支持中英文混合文档向量数据库支持混合检索的方案向量关键词双路召回编排框架自研轻量Harness避免框架锁定灵活控制部署方式容器化部署便于扩缩容和版本管理选型时最容易踩的坑是过度依赖某个大而全的框架。很多框架在demo阶段很好用但到了生产环境你会发现它的错误处理逻辑不透明、性能瓶颈难以定位、版本升级带来不兼容。我的建议是核心编排逻辑自己写只把框架用在非关键路径上。环境准备的具体步骤# 创建项目目录结构 mkdir -p rag-system/{config,src,data,logs,tests} cd rag-system # 初始化Python环境建议3.10以上 python -m venv venv source venv/bin/activate # 安装核心依赖 pip install fastapi uvicorn httpx numpy pip install sentence-transformers # 嵌入模型 pip install rank-bm25 # 关键词检索 pip install pypdf markdown # 文档解析注意嵌入模型的选择直接影响检索质量。建议先用小规模数据对比2-3个候选模型的实际召回效果再决定最终使用哪个。不要只看公开榜单的排名因为你的数据分布和榜单测试集可能完全不同。3.2 知识入库流水线的韧性设计知识入库是整个RAG系统的地基。如果入库阶段就有问题后面检索和生成再怎么优化都是白搭。一个韧性的入库流水线需要处理以下环节文档解析不同格式的文档需要不同的解析器。PDF可能包含扫描件需要OCR、表格需要结构化提取、多栏排版需要正确的阅读顺序。我的做法是为每种格式写独立的解析器解析失败时记录原始文件并跳过而不是让整个入库流程中断。文本分块分块策略直接影响检索效果。块太大检索到的内容包含太多噪声块太小可能丢失关键上下文。我通常采用递归分块策略先按段落分如果段落超过阈值再按句子分同时保留一定的重叠窗口通常10%-20%的重叠。def recursive_chunk(text, max_size500, overlap50): 递归分块优先按段落其次按句子 if len(text) max_size: return [text] # 尝试按段落分割 paragraphs text.split(\n\n) if len(paragraphs) 1: chunks [] current for p in paragraphs: if len(current) len(p) max_size: current p \n\n else: if current: chunks.append(current.strip()) current p \n\n if current: chunks.append(current.strip()) return chunks # 按句子分割 sentences text.replace(。, 。\n).replace(., .\n).split(\n) # ... 类似逻辑嵌入计算嵌入计算是计算密集型操作大批量入库时需要考虑并行化和失败重试。我通常把嵌入计算做成独立的异步任务队列每个文档块作为一个任务失败自动重试3次仍然失败则记录到死信队列人工处理。索引更新向量索引的更新需要保证原子性。如果入库过程中系统崩溃不能出现部分文档已索引、部分未索引的不一致状态。我的做法是先在临时索引中构建完成后原子切换到正式索引。实操心得知识入库一定要做幂等设计。同一份文档重复入库不应该产生重复的向量记录。我通常用文档内容的哈希值作为唯一标识入库前先检查是否已存在。3.3 检索环节的容错与优化策略检索是RAG系统中最容易出问题的环节。用户的问题千奇百怪知识库的覆盖范围总是有限的。韧性检索的核心思路是永远给用户一个有用的结果即使不是最完美的结果。查询改写用户的原始query往往不适合直接检索。比如用户问“你们那个退货政策是啥来着”直接拿这句话去检索可能效果很差。我会在检索前加一步query改写用LLM把口语化的query转成更适合检索的形式比如“退货政策 退货流程 退款条件”。async def rewrite_query(original_query, llm_client): 将用户口语化query改写为检索友好的形式 prompt f将以下用户问题改写为适合知识库检索的关键词组合。 只输出改写后的查询不要解释。 用户问题{original_query} 改写后 try: result await llm_client.generate(prompt, max_tokens100) return result.strip() except Exception as e: # 改写失败时降级使用原始query logger.warning(fQuery rewrite failed: {e}) return original_query混合检索单一向量检索在遇到专有名词、缩写、代码标识符时效果很差。我会同时跑一路关键词检索BM25然后把两路结果合并去重再用重排序模型精排。这样即使向量检索漏掉了关键词检索还能兜底。相关性阈值检索结果需要设置相关性阈值。低于阈值的结果不应该被送入生成阶段否则LLM会被迫在不相关内容中“找答案”增加幻觉风险。阈值设定需要根据实际数据调优我通常从0.7开始试观察bad case后调整。空结果处理当检索结果为空或全部低于阈值时系统应该明确告知用户“没有找到相关信息”并给出可能的下一步建议比如换个说法、联系人工客服等而不是让LLM自由发挥。3.4 生成阶段的降级与兜底机制生成阶段是用户直接感知的环节这里的韧性设计直接影响用户体验。Prompt中的防幻觉指令在system prompt中明确要求LLM只基于检索到的内容回答如果检索内容不足以回答问题必须明确说“根据现有资料无法回答”。这个指令看起来简单但实际效果取决于LLM的指令遵循能力。我建议在prompt中加入few-shot示例展示“有答案”和“无答案”两种情况的正确回应方式。超时与重试LLM调用必须设置合理的超时时间。我通常设置首token超时15秒总超时60秒。超时后自动重试一次如果仍然失败则降级到备用模型。备用模型可以是更小更快的模型虽然质量略低但响应更快。输出校验LLM的输出需要经过校验才能返回给用户。校验内容包括是否包含敏感信息、是否引用了不存在的信息源、回答长度是否合理。校验不通过时可以触发重新生成或者返回预设的兜底话术。流式输出的中断处理如果采用流式输出需要处理生成中途失败的情况。我的做法是已经输出的内容保留追加一个“回答生成中断请重试”的提示而不是直接清空已输出内容。4. CI/CD与可观测性让韧性可持续4.1 AI工程的CI/CD和传统软件有什么不同传统软件的CI/CD流水线主要关注代码变更跑测试、构建镜像、部署到环境。AI工程在此基础上还需要关注模型变更、数据变更、prompt变更。模型版本管理LLM服务商随时可能更新模型版本同一个模型名称在不同时间调用的行为可能不同。你需要记录每次请求使用的具体模型版本并且在模型更新时跑回归测试确认关键场景的表现没有退化。Prompt版本管理Prompt是AI系统的核心逻辑之一但很多团队把prompt硬编码在代码里改一个词就要重新部署。更好的做法是把prompt模板化、版本化支持热更新和A/B测试。数据漂移检测知识库的内容会随时间变化用户的提问分布也会变化。你需要定期检测检索命中率、用户满意度等指标发现异常时及时告警。评估流水线每次代码或prompt变更后自动跑一轮评估集对比变更前后的关键指标回答准确率、检索召回率、平均响应时间等。评估集应该覆盖典型场景和边界场景。4.2 可观测性建设你需要监控哪些指标可观测性不是简单地接一个日志系统就完事了。AI系统的可观测性需要覆盖三个层面。系统层指标请求量、响应时间P50/P95/P99、错误率、各外部依赖的可用性。这些和传统Web服务类似但需要额外关注LLM调用的token消耗和费用。AI层指标检索召回率、检索精确率、重排序命中率、LLM输出的平均长度、拒答率、用户反馈点赞/点踩。这些指标直接反映AI系统的质量。业务层指标用户满意度、问题解决率、平均对话轮次、转人工率。这些指标反映AI系统是否真正解决了用户的问题。我通常用一张仪表盘把这些指标集中展示设置合理的告警阈值。比如检索召回率连续1小时低于80%就触发告警LLM调用P95延迟超过30秒就触发告警。4.3 灰度发布与A/B测试在AI场景的落地AI系统的变更风险比传统软件更高因为很多问题是“软性”的——系统不会崩溃但回答质量可能悄悄下降。灰度发布和A/B测试是控制风险的有效手段。灰度发布新版本的prompt或模型先对5%的流量生效观察24小时。如果关键指标没有明显退化再逐步扩大到20%、50%、100%。灰度期间需要同时监控新旧版本的指标对比。A/B测试对于有争议的变更比如换一个嵌入模型可以设计A/B实验让两组用户分别使用不同版本对比用户满意度和问题解决率。A/B测试需要注意样本量足够大、实验周期足够长避免被短期波动误导。回滚机制任何变更都必须有快速回滚的方案。prompt变更回滚很简单模型变更回滚可能需要保留旧版本的模型权重或者API端点。回滚时间应该控制在分钟级别。5. 常见问题与排查技巧实录5.1 检索质量突然下降怎么排查这是RAG系统最常见的问题之一。排查思路按以下顺序进行确认知识库是否有变更最近是否有人删除了文档、更新了内容、调整了分块策略知识库变更后需要重建索引如果忘了这一步检索结果会和新知识库不一致。检查嵌入模型是否正常嵌入模型服务可能因为内存不足、版本更新等原因行为异常。用一组固定的测试query跑一遍对比嵌入向量的相似度是否和之前一致。检查query分布是否变化如果用户突然开始问一些之前没问过的问题类型可能是检索策略需要调整。查看最近的query日志看看是否有新的模式。检查向量索引状态向量索引可能因为并发写入、磁盘满等原因损坏。检查索引文件大小、记录数是否正常。5.2 LLM输出不稳定或格式错误的应对LLM输出格式错误是另一个高频问题。尤其是当你要求LLM输出JSON格式时它可能返回带markdown代码块的JSON、缺少字段的JSON、甚至完全不是JSON的内容。应对策略使用结构化输出功能如果LLM服务商支持JSON mode或function calling优先使用这些功能而不是靠prompt约束。输出解析容错写一个健壮的解析器能够处理常见的格式偏差比如去掉markdown代码块标记、补全缺失的括号。重试与修复解析失败时把错误信息和原始输出一起发给LLM要求它修复格式。通常重试一次就能解决。降级方案如果重试后仍然失败降级为纯文本输出或者返回预设的兜底话术。5.3 知识库更新后索引不一致的处理知识库更新和索引重建之间的时间窗口会导致不一致。用户可能检索到已删除的文档或者检索不到刚添加的文档。我的处理方案是双缓冲索引维护两个索引一个在线服务一个用于重建。重建完成后原子切换。版本标记每个文档块带有版本号检索时过滤掉已删除的版本。增量更新对于小规模更新直接增量写入索引但需要保证写入的原子性和一致性。更新通知知识库更新后通过消息队列通知索引服务触发异步重建。5.4 常见问题速查表问题现象可能原因排查方法解决方案检索结果不相关嵌入模型不匹配用测试query对比相似度更换嵌入模型或微调LLM回答包含幻觉检索结果为空或噪声大检查检索召回率和精确率加相关性阈值优化检索响应时间过长LLM调用超时或重试查看各环节耗时分布设置合理超时启用缓存系统频繁崩溃外部依赖不稳定检查各依赖的健康状态加熔断和降级机制用户反馈差Prompt或检索策略问题分析bad case日志迭代prompt和检索策略避坑技巧每次修改prompt或检索策略后一定要跑一遍回归测试集。我见过太多次“修了一个问题引入两个新问题”的情况。回归测试集不需要很大50-100条覆盖典型场景即可但必须每次变更都跑。6. 一些个人体会做AI工程这几年我最大的感受是原型和生产之间的距离比大多数人想象的要大得多。原型阶段你可以容忍90%的准确率因为demo只需要展示成功案例。但生产环境里那10%的失败案例会被用户反复遇到最终摧毁信任。韧性不是某一个技术点而是一种系统性的设计思维。它要求你在每一个环节都问自己如果这里出错了系统会怎样用户会看到什么我能不能让错误的影响更小、恢复更快另外不要追求一步到位的完美架构。我见过团队花三个月设计了一个“完美”的韧性架构结果上线后发现真实瓶颈和他们预想的完全不同。更好的做法是先上线一个最小可用版本通过可观测性数据找到真正的薄弱环节然后有针对性地加固。最后分享一个实用建议把每一次线上故障都当成改进韧性的机会。每次故障后不仅要修复当前问题还要问自己“这类问题能不能在架构层面避免”。比如LLM超时导致回答失败除了加超时重试还可以考虑预生成常见问题的缓存答案。这种从故障中学习的习惯是让系统越来越韧性的关键。
返回列表