做AI Agent开发这段时间我最大的感触是把大模型接口接进来只占20%的工作量剩下80%全在提示词工程、工作流编排和RAG知识库构建这三件事上。很多人拿着教程上来就先跑工具调用结果做出来的东西充其量是个能聊天、偶尔调个函数的玩具离真正能交付的Agent还差得很远。这篇文章我想把这三件事完整地串起来讲一遍提示词工程怎么做到位工作流怎么编排才不死板RAG知识库怎么构建才真正有用最后再给一个可落地的融合骨架和上线前必须踩的坑。内容偏实战适合已经能调用大模型API、想把Agent做成产品的开发者也适合刚入门想系统建立认知的读者。按顺序读完跟着思路走一遍你会有一种原来如此的通透感。1. Agent不是调API的壳先搞清楚三个模块的分工边界1.1 为什么你的Agent像玩具我先说一个常见现象很多初学Agent的人第一个Demo长这样——用户发一句话程序把这句话拼到Prompt里调一次大模型API拿到结果就返回。最多再加个function calling告诉模型可以调天气接口于是看起来有工具了。然后呢用户问帮我订个下周三下午去某地的机票顺便看看那天会不会下雨这个Agent就懵了。它不是不会调工具而是不知道先干什么、后干什么不知道工具返回的数据该怎么用到下一步决策里更不知道某地这种代词背后需要从知识库里检索用户画像和出行偏好。问题就出在你只做了接口调用没做Agent能力设计。一个真正合格的Agent必须具备三个底层能力理解被清晰约束的指令并稳定遵循——这是提示词工程负责的。把一个复杂任务拆成有序步骤并在每步之后根据结果决定下一步——这是工作流编排负责的。在回答时引用和依赖外部私有知识而不是只靠模型记忆——这是RAG知识库构建负责的。这三件事不是可选项是缺一不可的基础件。1.2 提示词、工作流、RAG到底各管哪一段用一个比喻来说明如果把Agent比作一家公司。提示词工程是公司章程和岗位职责说明书。它规定了这家公司接什么单、不接什么单、遇到事情按什么流程处理、说话用什么风格、哪些话绝对不能讲。公司能不能高效运转章程写得清不清楚是决定性的。工作流编排是业务流程和审批节点。一个订单进来先由客服接待再由仓管确认库存再由财务开发票每一步都有前置条件和后置动作。工作流就是把大模型的单次推理串成多次决策循环。RAG知识库是公司的档案室。员工可以凭记忆处理常规问题但碰到新品参数、历史报价、内部制度就必须去档案室查资料查完再作答。RAG要解决的问题就是让Agent在关键时刻知道哪里有资料、怎么查、查完怎么引用。这三者顺序上也有依赖性工作流是骨架提示词是血肉RAG是弹药库。骨架决定了Agent的形态血肉决定了Agent的言行弹药库决定了Agent的深度。很多项目失败不是因为模型不够强而是三者的边界没划清楚——把知识硬塞进提示词把流程逻辑写在代码里把本该检索的活交给了模型自由发挥最后全部变形。1.3 一个快速的自我检查清单在你开始动手编码之前先花15分钟回答下面几个问题答案越明确后面开发越顺利这个Agent的核心任务是什么用户期望它做完一件事还是回答一个问题完成这个任务最多需要调用几次工具、经历几轮推理超过5轮的复杂流程能不能拆成子任务哪些信息是模型本身知道的哪些信息只存在于你自己的文档、数据库、系统里如果Agent答错了后果有多严重需不需要护栏和人工兜底我见过最快的翻车案例是有人把一个需要5个系统联动、来回确认的业务场景硬生生设计成单轮Prompt 一个状态标记结果上线当天就出现信息错乱。先想清楚边界再做设计这是Agent开发的第一原则。2. 提示词工程把Agent的系统盘写到什么程度才算合格2.1 System Prompt的四个层次Agent的提示词和普通问答的Prompt有本质区别。问答Prompt追求这一轮答得好Agent Prompt追求在几百轮交互里始终不跑偏。所以System Prompt不能只写一两句话得按层次来组织第一层角色与目标定义。明确Agent是什么、服务谁、要达成什么业务目标。比如你是一个零售行业的售前导购Agent目标是帮助用户找到合适的商品并完成下单这句话不能省它决定了后续所有决策的方向。第二层行为边界和禁用规则。这是最容易写漏的部分。举例说一个客服Agent边界可能是只回答售前咨询不做售后退换不承诺具体物流时效不生成与商品无关的内容。禁用规则往往要写在Prompt里而不是靠模型自觉。第三层工具选择逻辑。告诉Agent什么时候该调工具什么时候不该调。比如当用户询问库存时必须调用库存查询工具不得根据记忆猜测。当用户闲聊时不要调用任何工具直接以对话方式回应。第四层输出格式与风格约束。包括回答的格式JSON还是自然语言、长度上限、语气规范、敏感词的应对策略。一个合格的System Prompt最终要让你自己觉得哪怕换一个完全没调教过的新模型照着这份规则也能做出七八分像样的事。我见过太多人把System Prompt写成了公司简介那没用它应该是操作手册。2.2 工具调用Function Calling的提示词细节工具调用是Agent的关键接触点这个环节的提示词工程经常被忽略。很多人只是把工具名和参数描述丢给模型却不知道如何描述工具直接影响模型能不能正确选择。我自己总结了一套工具描述的写法名称短、明确、动词开头。search_product_info就比get_data_11好一万倍。功能描述说明工具能干什么的同时一定要写清楚在什么情况下不适合用它。比如库存查询工具的描述里写本工具仅返回当前可售库存不返回历史库存数据。参数描述每个参数都得写单位和取值范围。delivery_date参数不写格式模型可能传Christmas Day你后端解析直接崩溃。返回值说明即使模型看不到工具内部代码也要在描述里告诉它返回结构。比如返回字段包含status, sku_list, total_count其中status为success或fail。我踩过的坑是有一个工具返回了嵌套很深的JSON我在描述里只写了返回商品信息结果模型经常不知道如何提取关键字段导致后续决策错乱。后来我把返回结构的关键三层全部写进描述工具调用准确率一下提升了将近30%。还有一个细节工具数量不是越多越好。模型在工具选择上对数量多、描述长的场景会明显降智。如果超过15个工具优先考虑把同类工具合并或者让工作流先做一次意图路由只把相关工具子集交给模型。2.3 少样本示例的摆放和思维链的取舍Agent的System Prompt里放不放示例Few-shot、放几个、放哪里都有讲究。我的经验是示例要放在工具调用的具体场景里不要放通用对话范例。与其给模型十个热情回答的例子不如给它三个完整的决策轨迹用户这个手机支持5G吗 思考路径用户询问产品参数需要调用product_spec工具获取规格信息而不是凭记忆回答。 工具调用product_spec(product_idP10086, fields[network_support]) 工具返回{network_support: [5G, 4G, 3G]} 最终回答支持的这款手机支持5G网络。这种思考路径 工具调用 返回 最终回答的四段式示例是最贴合Agent场景的提示词内容。它能教会模型的不是说什么话而是怎么走流程。关于思维链CoTChain of Thought我的观点是在Agent里不能用得太野。你要让模型在工具调用前后做简短的推理但不要鼓励它长篇大论地自言自语。在System Prompt里可以明确一句在你的内部推理中只需简短说明选择某个工具或得出结论的原因不要输出大段分析。这能显著降低推理消耗也减少模型在回复里夹带私货的可能。3. 工作流编排让模型从憋大招变成一步步办事3.1 ReAct模式Agent最基础也最实用的循环ReAct是Reasoning Acting的合称大意是让模型交替进行思考和行动大模型生成下一步需要做什么的意图Agent执行对应的动作调工具、查数据库、发请求拿到结果后再交还给模型模型根据结果产生新的意图直到最终能给出答案。一个标准的ReAct循环长这样输入 - 模型思考 - 动作工具调用 - 观察工具返回 - 模型再思考 - ... - 最终答案工程上实现ReAct核心是一个循环 退出条件的设计def run_agent(user_input: str, state: dict) - str: # state里保存会话历史、工具返回、重试计数等 for step in range(MAX_STEPS): # 必须有最大轮次限制 # 1. 让模型生成动作或者最终答案 response llm_with_tools( messagesbuild_messages(user_input, state) ) # 2. 判断这次输出是调工具还是给答案 if response.has_tool_call(): # 记录中间结果 state[intermediate_steps].append(response.tool_calls) # 执行工具把返回值写回state state[tool_outputs] execute_tools(response.tool_calls) continue else: # 模型认为可以收尾了 return response.content return fallback_reply(经过多轮尝试仍未完成请转人工)这段代码看着简单但里面有几个非常关键的工程决策MAX_STEPS必须设。我见过最离谱的Agent在没有轮次限制的情况下围绕一个简单问题循环了47次烧掉一大笔费用。每轮循环都要把前一轮工具返回的内容完整传给模型而不是只保留最后一条。原因很简单模型做第5步决策时可能需要参考第2步的查询结果。必须有模型一直不给最终答案的兜底逻辑。超时后是转人工还是返回部分结果要提前设计。3.2 Plan-and-Execute复杂任务不能走一步看一步ReAct适合任务步骤不固定、需要随机应变的场景。但有些任务天然有顺序——先查库存再算价格再下订单——如果用ReAct让模型自由发挥它有可能第2步就开始下订单把流程走乱了。这时候要用Plan-and-Execute模式先让模型一次性产出完整计划再按计划逐步执行每执行完一步检查结果是否符合预期如果偏离再修正计划。举个例子一个供应链采购Agent的任务流程计划产出 1. 解析采购清单识别商品编码及数量 2. 查询各供应商库存及报价 3. 比对履约时效给出推荐 4. 生成采购建议并推送给审批人一旦计划确定执行器就按照计划步骤依次触发每步调用对应的工具。这里要注意的是计划是指导不是圣旨。执行过程中如果发现某一步数据异常比如商品编码查无此货应该允许模型回到计划环节重新规划而不是硬着头皮往下走。我做Agent编排时的判断标准是任务步骤可能因用户反馈而变的用ReAct任务步骤相对固定、失败点清晰的用Plan-and-Execute任务步骤涉及多个Agent配合的用下面说的多Agent协作。3.3 多Agent协作分好工才能办好事当一个任务非常庞大时与其让一个Agent包打天下不如拆成多个各司其职的子Agent。常见的拆分思路有两类第一类是按职能拆分入口Agent负责理解用户意图并做路由专业Agent分别负责订单、售后、报价等具体领域每个Agent只维护自己领域的提示词和工具集。这样做的好处是专业Agent的System Prompt可以写得非常聚焦工具集不必全部堆给模型模型的选择负担大幅降低。第二类是按流程拆分规划Agent负责拆解任务执行Agent负责调工具审核Agent负责检查执行结果是否满足要求。这类架构常用于对准确性要求高的场景比如自动生成营销文案先让执笔Agent生成初稿再由审核Agent对照品牌规范检查不符合则打回重写。但多Agent不是银弹最大的坑是上下文传递的失真。Agent A产出结果把文本拼进Agent B的PromptB看到的信息可能丢了细节或多了噪音。我的建议是Agent之间的传递尽量用结构化数据JSON而不是自然语言。比如A向B传的不是这个用户可能喜欢偏商务风格的产品而是{user_preference: business, confidence: 0.85}这样B拿到的是可解析、可判断的信息而不是需要二次理解的一段话。另外无论采用哪种协作方式顶层一定要有一个状态管理对象。我习惯用一张状态表记录每个Agent的输入、输出、状态pending/running/success/failed任何环节失败都能回溯定位。没有状态管理的多Agent系统出了问题基本没法查。4. RAG知识库构建从文档清洗到检索重排的完整链路4.1 文档解析和分块RAG里最糙但最要命的环节很多人一提RAG就想到向量检索一头扎进Embedding模型选型里却忽略了上游最脏的活把文档变成能被检索的片段。这一步有三个子问题要解决文档解析。PDF要考虑是文本型还是扫描型扫描件需要OCRWord要考虑嵌表格和图片里的信息怎么抽取网页要考虑去掉导航、页脚、广告噪音。我处理过某企业的产品手册原PDF有300多页直接解析出来的文本里混着大量目录页码和重复页眉如果不清理检索时匹配到的全是指数页眉之类的垃圾信息。分块策略。最常见的做法有两种策略做法适用场景固定窗口分块按固定字符数切块间有一定重叠文本结构松散的聊天记录、公告结构感知分块按标题层级H1/H2/H3、段落、列表切技术文档、规章制度、产品手册固定窗口分块务必设重叠区域我一般用chunk_size800, chunk_overlap100这样的起步参数再根据实际检索效果调整。结构感知分块的前提是解析阶段保留了文档结构信息这是Markdown格式文档的最大优势所以我在做知识库之前能转Markdown的文档都会先转成Markdown。还有一个容易被忽略的点分块长度要和Embedding模型的能力匹配。有些Embedding模型对超过512个token的文本支持很差超过这个长度后语义向量质量明显下降。如果你的Embedding模型上限是512 token就老老实实把chunk_size控制在模型上限的七八成以内。4.2 嵌入、向量检索和混合检索怎么选分块完成后每一块文本需要变成向量存进向量数据库。Embedding模型的选择主要看三个维度语义理解能力、向量维度、中文支持度。商业API的Embedding模型用起来方便但要注意成本开源的Embedding模型可以本地部署省费用且数据不出域不过需要一点点部署功底。向量检索的本质是找语义最相近的块它有个固有缺陷对精确词匹配和专有名词不敏感。比如用户搜AC-300型号的保修期如果知识库里写的是AC300保修一年向量检索可能因为AC-300和AC300的字符差异匹配不到。这时候就要引入混合检索。混合检索 向量检索 关键词检索如BM25。常见做法是两路并行走各自返回TopK候选再用RRFReciprocal Rank Fusion之类的算法合并排序。工程上大概长这样# 伪代码混合检索流程 vector_hits vector_search(query, top_k20) keyword_hits keyword_search_with_bm25(query, top_k20) # RRF合并名次越靠前得分越高 score_map {} for rank, doc in enumerate(vector_hits): doc_id doc[id] score_map[doc_id] score_map.get(doc_id, 0) 1 / (60 rank) for rank, doc in enumerate(keyword_hits): doc_id doc[id] score_map[doc_id] score_map.get(doc_id, 0) 1 / (60 rank)混合检索在含大量型号、编号、业务术语的知识库上几乎是必备方案别偷懒。4.3 检索完还要重排为什么Top3不一定是好答案向量检索和混合检索给出了候选片段但它们的排序依据是整体相似度而不是对当前问题是否真正有用。一个块可能语义上和问题很搭但里面根本没有用户要的信息。所以高质量的RAG管线要在检索后加一步重排Rerank。重排模型会对问题 候选片段做精细的交叉编码匹配给每个候选重新打分再把最相关的2-4个片段作为上下文送给大模型。我的实践经验是检索阶段放宽召回20个候选重排阶段收紧只取前3个。这样既保证不遗漏又控制上下文长度。打一个比方检索是海选简历筛选条件放宽重排是终面精挑细选真正能干活的人。重排之后还有一个细节上下文组装时的引用标注。送进提示词的文档块最好带上来源标记比如[1]对应产品手册第3页[2]对应售后政策第2条。这样大模型回答时能引用来源标号用户在界面上也能点开原文核对。这不仅是体验问题也方便出问题时回溯。4.4 知识库更新的三个隐藏痛点最后讲一个很少被前端教程提及、但生产环境绕不开的问题知识库不是一次性构建完就结束的。它有更新链路而更新链路最容易出三类问题第一脏数据进库。更新的文档格式不统一、有乱码直接切块入库检索时就会返回垃圾内容。必须在上游做校验至少检查空块、超长块、纯符号块。第二向量不一致。当你更换Embedding模型或者调整分块参数之后如果只把新文档向量化入库而老文档向量还沿用旧模型生成就相当于用两套语言体系在一个档案室里查资料语义检索会严重错乱。遇到模型或参数调整务必全量重建索引别混着用。第三知识冲突与时效。同一个问题知识库里既有旧政策文本又有新政策文本RAG把两段冲突内容都召回了大模型就容易给出自相矛盾的回答。一套可落地的做法是给知识文档加版本号/生效日期在检索和重排阶段优先过滤已过期版本。5. 三件套融合实例一个客服Agent的代码骨架和取舍细节5.1 整体架构先画好链路再写代码前面三部分讲的都是零件现在把它们组装成一个完整的Agent。我拿一个常见的企业客服Agent举例完整链路如下用户输入 -- 会话管理模块读取历史、状态 -- 意图路由分类售前咨询/订单查询/售后投诉/闲聊 -- 专业子Agent分派 -- 售前子Agent先RAG检索产品资料 - 必要时调库存工具 - 生成回答 -- 订单子Agent调订单查询工具 - 从返回的JSON中提取关键字段 - 生成回答 -- 售后子Agent先RAG检索售后政策 - 判断是否在保修范围 - 生成结论 -- 输出审核敏感词过滤/置信度检查 -- 返回用户这个架构有几点刻意为之先路由再处理。不把所有工具和知识库内容塞给一个Agent而是让意图路由决定哪个专业Agent上下文里能看到哪些工具和知识降低模型选错工具的概率。RAG和工具调用是并行的两种信息来源。用户在售前场景问这个笔记本能装Linux吗——知识库里有该型号的技术规格RAG检索就能回答但要问现在有货吗——这属于动态数据必须调库存接口。在System Prompt里必须写清楚两者的使用优先级否则模型可能会从知识库里翻出一个过期的库存数字。输出审核独立成层。不让生成答案的模型自己给自己判卷。5.2 关键代码骨架把状态和循环管起来下面给一个简化但可运行的Python骨架重点看状态管理和模块配合方式from dataclasses import dataclass, field from typing import List, Dict, Optional dataclass class AgentState: session_id: str user_input: str history: List[Dict] field(default_factorylist) intent: Optional[str] None rag_context: List[Dict] field(default_factorylist) tool_outputs: List[Dict] field(default_factorylist) steps: int 0 status: str pending # pending/running/success/failed final_answer: str def route_intent(state: AgentState) - str: # 基于小模型或规则做意图分类返回presale/order/aftersale/chitchat ... def retrieve_rag(state: AgentState, query: str) - List[Dict]: # 混合检索 rerank返回 top3 文档块带上来源 ... def call_tool(state: AgentState, tool_calls: List[Dict]) - List[Dict]: # 根据工具名分派到具体函数返回结果JSON ... def build_messages(state: AgentState) - List[Dict]: system_prompt load_system_prompt(state.intent) messages [{role: system, content: system_prompt}] for h in state.history: messages.append(h) messages.append({role: user, content: state.user_input}) # 把RAG检索结果和工具返回按固定格式拼进上下文 if state.rag_context: rag_text format_rag_context(state.rag_context) # 带[1][2]来源标记 messages.append({role: user, content: f参考资料\n{rag_text}\n请基于资料和工具信息回答。}) if state.tool_outputs: messages.append({role: user, content: f工具返回数据\n{json.dumps(state.tool_outputs, ensure_asciiFalse)}}) return messages def run_agent(state: AgentState) - AgentState: state.status running # 1. 意图路由 state.intent route_intent(state) # 2. 按需做RAG检索 if state.intent in (presale, aftersale): state.rag_context retrieve_rag(state, state.user_input) # 3. ReAct循环调用模型和工具 for _ in range(5): # 最大5轮防死循环 state.steps 1 resp llm_call(build_messages(state)) if resp.has_tool_call: tool_results call_tool(state, resp.tool_calls) state.tool_outputs.extend(tool_results) # 执行完工具后回到循环顶部让模型看到工具结果 continue state.final_answer resp.content state.status success if not resp.needs_verification else pending break if state.status ! success: state.final_answer generate_fallback(state) return state这个骨架刻意保持简单但已经包含了最核心的机制状态贯穿、意图路由前置、RAG结果显式注入、ReAct循环受限、失败兜底。你在自己的项目里完全可以直接照着这个结构扩展。5.3 融合设计的三个取舍点取舍一先检索还是先规划如果任务强依赖知识库内容比如根据最新售后政策判断是否可以退货要先检索让模型在规划时就带着参考资料。如果任务不太依赖知识库比如查订单物流可以先执行工具检索可以跳过。我在架构里把检索放在意图路由之后、模型循环之前本质上是让是否检索由业务场景决定而不是由模型每次自己决定。取舍二上下文塞多少RAG检索出Top3文档块、工具返回了一堆字段如果全部塞进上下文模型会看得眼花缭乱。我的经验是工具返回的JSON要做字段裁剪只保留需要展示和决策的字段RAG文档块每条控制在500字以内。上下文越干净模型越不容易被干扰。取舍三模型选型要不要区分很多Agent体系只用一个模型。但工程上意图路由、工具调用、最终生成这三个环节对模型能力的要求完全不同。意图路由可以用轻量快速的模型工具调用和生成用强模型。这么设计能省不少成本和延迟。当然这会增加系统复杂度小规模项目可以先统一模型等瓶颈出现了再拆分。6. 上线前的评测与排坑多数Agent死在细节里6.1 评测集和指标不能靠感觉还行Agent的评测比传统软件复杂因为它有太多不确定性。我建议至少准备两套评测集回归评测集覆盖核心用户场景的典型问题大约50到100条。每条标注了期望行为调用哪个工具、返回什么结论。对抗评测集针对你预期之外的输入比如越权提问、模糊提问、信息不全的提问。评测维度不要只看答得对不对至少看下面几项指标观测点我用的最低标准任务完成率用户目标是否达成核心场景不低于90%工具调用正确率该调工具时调了、不该调时没调不低于85%无效循环率在MaxSteps内是否退出的比例应为0引用准确性RAG引用编号对应的内容是否支撑结论高于80%返工率生成结果被人工纠偏的比例越低越好我见过最典型的评测翻车是开发时只用正确答案来评判忘了统计无效循环率。结果一个隐性问题——模型常常在工具调用和最终回答之间反复横跳直到耗尽步骤——直到上线后才被用户撞见。6.2 我印象最深的五个生产级问题问题一上下文被工具返回撑爆。某个工具返回了一个巨大的JSON数组模型把这堆原始数据原样拼进下一轮Prompt导致上下文超限Agent直接报错。解决方法是工具返回后必须设置结构摘要层把大JSON压缩成一两句话的核心信息再回填。问题二模型对工具返回格式过度敏感。有一次工具返回了中文状态码成功而不是success模型没识别出这是成功状态绕了三个弯才给出正确回答。解决方法是工具返回值规范化所有状态统一用英文枚举并在System Prompt里明确标注。问题三RAG召回的内容互相打架。同一产品的两批文档一个说质保两年一个说质保一年。混合检索把两段都召回了模型就直接自己觉得选了一个。解决方法是知识库文档统一增加生效日期字段检索过滤失效版本并且给文档块加优先级标签。问题四Agent陷入沉默循环。模型连续多轮产生工具调用意图但工具执行失败后返回了空结果模型拿着空结果又要调下一个工具循环往复。解决方法是工具执行失败时返回一个显眼的错误对象同时让System Prompt写明如果连续两次工具调用失败停止执行并告知用户稍后重试。问题五意图路由误判导致专业Agent错位。用户问我想给朋友推荐这款产品有没有优惠我的意图路由把它分成了售前咨询但句子里有推荐二字它又可能被分到营销活动。这类边界情况必须靠对抗评测集反复打磨必要时在路由层配几个关键词规则兜底。6.3 上线前最后一遍自查清单把下面这个清单过一遍比多做两个功能更有价值所有工具调用是否都有超时和重试工具失败后Agent能否优雅退出最大循环轮数是否设置触顶后是否有兜底话术和用户转人工通道System Prompt里的禁用规则是否覆盖了业务上最不想看到的几种输出RAG检索结果为空时Agent是直接说不知道还是编一个答案我强烈建议让它在检索为空时明确说当前知识库中未找到相关信息而不是自由发挥。敏感信息是否会被工具返回带出来是否做了字段脱敏日志是否完整记录每轮思考、工具调用参数、返回值和延迟没有日志出问题只能干瞪眼。说了这么多最后聊点个人体会。我做了好几个Agent项目后发现真正决定项目上限的往往不是模型能力而是你对场景的理解深度你愿不愿意把业务规则揉进提示词里把流程边界划清楚把知识库的脏数据洗干净。这些活不热闹甚至有点枯燥但一件都省不掉。如果这篇文章能帮你少走几次弯路、少熬几个排查Bug的深夜那就值得你收藏了。