
如果你最近在看 AI Agent 相关的实践项目多半会撞上一个很有意思的现象同一个底层模型有人拿它做出来的 Agent 能稳定跑完复杂的多步骤任务有人做出来的却总在简单环节上翻车。模型没换训练权重没动差异全出在套在模型外面那层壳上。这篇就把这个同一个模型换个外壳差很多的现象拆开讲清楚聊聊外壳到底在解决什么问题、它如何同时决定 Agent 的下限和上限以及我们在实际搭建外壳时应该把功夫花在哪。“外壳”这个词听起来轻巧实际包含提示词结构、工具协议、上下文管理、编排逻辑、状态维护和人工确认机制。这一整套东西不动声色地决定了模型是认真完成你的目标还是在上下文里迷路是稳定输出可靠结果还是偶尔给你一步错步步错是扛得住用户的真实流量还是稍微一并发就报“模型繁忙”。这篇内容适合正在搭 Agent 的应用开发者、做技术选型的负责人、以及研究大模型落地的产品经理不讲虚的直接聊逻辑和实操。1. 为什么模型没变干活的样子却完全不一样1.1 大模型不是执行器而是语义补全器要理解外壳的价值先要接受一个底层事实大模型不是一台严格按指令执行的计算机它本质上是一个庞大的语义补全器。给它一段前文它做的事情就是预测下一个最合理的 token然后再预测下下个。所谓思考其实是它在你的 prompt 框架内做续写。这就带来一个关键推论模型输出的质量极大程度上取决于你给它看了什么样的前文。你给的信息结构越清晰、目标越可分辨、约束越明确它就越容易沿着正确的语义路径补全你给的信息混乱、任务目标含糊、工具描述模糊它就会按它脑子里最可能的下一句话去发挥而这往往不是你想要的。这也解释了一个日常现象同一个模型 API直接让它在对话框里完成复杂任务和把它放进一个设计良好的 Agent 外壳里完成同样的任务成功率完全不同。你的 prompt 结构、工具定义、上下文组织方式就是那个前文的物理载体。外壳不是在帮模型思考而是在帮模型正确地进入补全状态。1.2 同样一个模型两种接法的差距有多大我来举一个很常见的业务场景客服 Agent 要完成查订单、判断是否可退、执行退款这个三步操作。同一个 GPT-4 级别接口用两种不同壳去接结果是天差地别的。第一种做法很朴素把所有工具的函数描述堆在 system prompt 里然后把用户的历史对话一股脑塞进上下文告诉模型你可以调用工具来解决用户问题。实际跑下来你会看到模型有时选错工具把查订单的工具误认为退款工具有时不懂得先查后退直接虚构一个退款结果有时因为上下文太长把订单号给漏了甚至从早期对话里翻出一个完全不相干的数字当订单号。第二种做法在外壳上加了三样东西意图路由、结构化上下文摘要、严格 JSON Schema 工具协议。用户消息进来后外壳先判断当前意图是查询还是售后再连同用户身份、近期订单摘要、当前会话状态整理成一个紧凑的结构化上下文最后把所有工具的入参约束用 schema 锁死模型只能按格式输出工具调用。同样一个模型在这个外壳里几乎不再乱选工具退款流程的成功率直线上升。差距不在模型而在信息是怎么被组织到模型面前的。这组对比很直观地回答了为什么换个外壳差很多——模型的下限不是模型自己兜底的是外壳用结构、约束和流程帮它兜底的。1.3 上限和下限分别指什么一个容易被忽略的点在于外壳同时影响 Agent 的上限和下限但影响的方式不同。所谓下限是 Agent 在最常规、最简单任务上的稳定性。它不要求惊艳但要求可依赖一百次调用里不能出现几次乱来、漏步骤、输出坏 JSON、对话一长就状态错乱。这些稳定性指标几乎全由外壳决定模型反而问题不大因为简单任务模型本身就能做关键是外壳有没有把路铺平、把出错路径堵住。所谓上限是 Agent 在复杂长任务上的可能性边界。它能不能把十几个子步骤串联起来不丢中间状态能不能在决策点上综合多个信息源做判断能不能在遇到分支情况时走到合理的兜底路径。这些能力有一部分来自模型的知识和推理底盘但更大的部分来自编排架构。如果外壳没有状态管理、没有记忆分层、没有流程分支模型再强也发挥不出来。所以对一个工程团队来说模型选型解决的是底盘质感问题而外壳设计解决的既是你这辆车不会轻易散架的问题也是你能不能跑远程复杂路况的问题。两者都重要但从投入产出比看多数团队的瓶颈在后者。2. 外壳的下半身工具协议和上下文管理决定稳定下限2.1 工具描述不是注释而是模型的操作手册在实际工程里工具描述写得不好是 Agent 翻车的头号原因。很多初版外壳里工具函数只写了名字和参数类型比如get_order(order_id: str)然后就没有了。模型看到这种描述其实不知道这个工具是干嘛用的、什么时候该用它、用了会返回什么东西。大模型理解工具的方式完全依赖文字描述。工具怎么命名、怎么描述功能边界、参数要写多细、什么时候明确告诉模型这个场景不适合调用都会直接影响模型的选择准确率。我自己踩过的坑是某个工具叫查询商品信息描述里没有说明它不包含库存状态。结果模型在处理还有货吗这类问题时频繁错误调用它返回的商品页里根本没有库存字段后面的判断全部建立在幻觉上。后来我调整了工具描述写法每条描述故意带上三个信息功能边界做什么、触发场景什么时候用、反面约束什么时候别用。比如查询商品信息用于获取商品标题、价格、规格当用户询问库存数量或发货状态时请使用 inventory 工具不要使用本工具。改完之后工具选择的准确率几乎翻倍。这里有一个通用原则工具描述是写给模型看的操作手册不是写给别人看的代码注释。每条工具描述都应该采用人话边界触发条件反例的结构必要时给出调用示例。别怕描述长模型处理文字的带宽远比处理混乱语义的成本低。2.2 上下文一长就翻车剪枝、摘要与记忆分层另一个决定下限的问题是上下文管理。很多 Agent 第一版都是把整段对话历史原封不动塞进模型反正接口支持长上下文。结果系统 prompt 加工具定义再加七八轮历史之后关键信息稀释在几千 token 里模型开始把用户的旧问题当成当前任务或者被中间一段无关讨论带偏。本质问题不是模型记不住而是外壳没有帮它筛选该记什么。好的外壳会对上下文做分层。第一层是固定信息包括身份设定、任务目标、工具定义第二层是会话状态包括当前意图、正在处理的订单号、已完成的步骤第三层才是最近的原始对话用于理解即时话锋。在进入模型之前外壳会把第二层状态直接从业务数据里取出来拼成一个紧凑快照而不会让模型从历史对话里自己翻。这一步很像人处理工作你不会把一年前的邮件全部重读一遍再做今天的决策你只看今天这份简报。上下文较长时可以用摘要压缩而非直接截断。比如每轮对话结束后把前几轮总结成一句带关键字段的摘要保留在顶层。对长会话我通常会设定一个窗口超过某个轮数就把更老的原始对话折叠成结构化摘要。这个折叠动作还要特别注意数值型字段比如订单号、金额、日期——这些信息绝不能丢否则到了退款环节就是灾难。2.3 模型输出不听话时校验、重试、人工兜底三层保险不管 prompt 写得再好模型总有概率输出格式错误的内容比如 JSON 里套了 Markdown、顺手多打了一个字段、把refund写成了refuns。如果外壳不设防一次格式错误可能让整个流程直接崩掉。处理这个问题要有三层兜底第一层是 schema 校验第二层是自动纠正加重试第三层是人工确认兜底。第一层出现的问题最多。我遇到的极其常见的情况是模型多轮对话中间偶尔输出了一段思考过程作为 JSON 值schema 解析直接失败。简单重试效果有限因为模型在同样的上下文里大概率会重复同样的坏习惯。真正有效的做法是捕获校验失败的具体错误把它回传给模型并附上修正提示。比如告诉它你刚才输出的字段status枚举值不合法可接受值为 processing/succeeded/failed请重新输出 JSON。这相当于给模型一个明确的纠错信号。第三层兜底其实很实用对于高影响操作退款、删除、外发消息不要允许模型直接执行外壳把模型生成的参数留待人确认后才能真正调用。这是把 Agent 的信任边界从模型可信转移到流程可信的关键一步也是把下限兜住最稳妥的做法。3. 外壳的上半身编排架构和并发承载决定能力上限3.1 编排架构的演进路线从直连到图式工作流外壳的另一个大头是编排。模型在单轮对话中能力再强也不可能在一个 call 里完成非常复杂的多步任务。这时候外壳就要负责把任务拆解成多轮模型调用并控制它们之间的衔接。不同的编排方式直接决定 Agent 能处理的任务复杂度的上限。最基础的形态是单轮直调用户问一句模型答一句最多带一次工具。这种形态适合翻译、摘要、分类这类单步任务优点是简单可控缺点是完全不具备处理多步骤事务的能力。高一阶是 ReAct 循环模型思考下一步该做什么调用工具看到结果后继续思考下一轮循环直到任务完成。这是很多 LangChain 初版应用的样子。它能处理多步任务但有两个隐患——一是容易陷入无意义循环反复调用同一个工具拿一样的结果二是中间状态全存在对话历史里环境一复杂就混乱。高阶形态是图式工作流用 LangGraph 这类工具把任务显式建模成节点和有向边。比如把客服流程建成一个图意图识别 - 订单查询 - 售后判断 - 退款执行每个节点是明确的模型调用或工具调用节点之间用类型化的状态对象传递数据而不是靠把历史对话塞给模型去猜当前在哪一步。这种架构把流程控制和模型语义能力解耦了模型只负责节点内的决策和生成流程转移由编码逻辑决定。实测下来图式工作流在处理长流程任务时的稳定性和可维护性明显高于 ReAct 这类自由循环。再往上还有多 Agent 协作架构不同角色 Agent 分别处理不同子领域通过消息相互协作。这种模式适用于问题面很宽的场景但代价是语义损耗和调试复杂度同步上升小团队慎用。3.2 状态管理是编排的命门丢状态等于失忆任何编排架构只要涉及多轮调用就一定绕不开状态管理。很多 Agent 项目做大了以后开始频繁出现上一步结果下一步就忘了的问题多数不是模型健忘而是外壳没做好状态传递。在 ReAct 架构里状态往往隐式存在对话历史里靠模型自己从上下文中感知当前进度。这在短链路里没问题但链路一长模型的注意力会漂就会忘记自己已经完成了哪些步骤。这本质上是因为状态不是结构化的而是散落在语义里。正确的做法是把状态显式建模。外壳里维护一个 state 对象包含当前节点、已完成步骤、已获取的关键数据、待处理分支列表。每次进入节点前外壳把 state 中的关键字段注入到上下文中节点执行完毕后把新产出回写到 state。这样模型永远只负责当前这一步不用从几千 token 历史里去倒推我现在到底进行到哪了。状态集中在代码里还能顺便解决可观测性问题——任何时候想知道 Agent 在干嘛查一下 state 就行。如果你要设计一个通用一点的参考可以沿用这个思路在代码里就是循环里挂状态机state { current_node: intent_classify, order_id: None, intent: None, steps_done: [], needs_human_confirm: False, } while state[current_node] ! finished: node workflow[state[current_node]] state node.run(state, llm)3.3 并发问题“模型繁忙”时外壳该怎么扛再往上走参与一次真实的 Agent 项目遇到的一定是并发问题。热搜里有人问AI Agent 怎么扛并发说到底是重建一套共识大多数 Agent 应用线上大面积超时瓶颈往往不在模型服务本身而在外壳层完全没有做并发相关的治理措施。模型服务第一次“升级”时会收到 模型繁忙 的错误码实际上就是在告诉你“上游打满了”。如果外壳不做处理只是把错误直接抛给用户这个 Agent 在业务上就是脆弱的。我自己做线上 Agent 应用时总结了三个手段缓存、排队、降级。缓存是最容易被忽略的一个。同一个用户的同一个问题在短周期内被反复问到其实是很常见的现象。外壳层可以对输入问题 用户身份做一个短时缓存命中就直接返回结果不消耗模型调用。一次缓存命中可能在高峰期帮你省下 30% 的模型压力。排队针对的是模型服务限流的场景。外壳层可以对模型调用封装一层请求队列并用令牌桶限制对上游的调用速率避免一瞬间把所有任务都打到模型上。队列缓冲之后配合合理的轮询状态可以有效减轻“模型繁忙”触发。降级是针对模型能力的策略。高峰期把复杂推理降级为通用回答或者降级到更便宜的模型。注意降级的时候一定要把任务上下文压缩得更紧再发给小模型因为小模型的指令遵循能力弱更需要显式约束。这个技巧结合后面要说的“本地模型”话题一起看特别有感触。4. 从零搭一个可用的外壳选型与最小实践4.1 技术栈怎么选框架不是越重越好聊完原理落到选型。现在市面上的 Agent 框架五花八门我见过用 Spring AI Agent 的、用 LangChain 的、用 LangGraph 的、用 Rust 自研的甚至还有直接用 FastAPI 手搓循环的。每个方案都有它的位置但选型之前必须先想清楚一个问题你的业务是高频短任务还是低频长流程如果是高频短任务比如客服问答、内容分类、信息抽取这类任务的核心诉求是低延迟、高并发、易维护。结论往往是别上太重的工作流框架直接把模型调用做一层薄封装配上缓存、限流和工具协议反而比套大框架更稳。我曾见过团队为了一个单轮信息抽取应用引进了整套多 Agent 编排框架结果主要体现在调度复杂、改一个 prompt 要翻三层抽象线上问题不好定位。如果是低频长流程例如工单处理、项目方案生成、竞品调研报告这类任务的核心诉求是状态持久化、任务分解、分支处理。LangGraph、Temporal 这类带显式状态和工作流语义的方案更合适。框架能帮你处理好任务恢复、中间状态保存、人工审批节点这些看似不起眼但实际决定成败的机制。下面是一个简化版对照表仅供参考方案适合场景维护成本可控性FastAPI 手写循环高频短任务、团队熟悉 Python低高LangChain快速原型验证中版本变动大中LangGraph复杂工作流、显式状态中高中高Spring AI AgentJava 技术栈、企业集成中中高Rust 自研极高压并发、资源敏感高极高4.2 最小可用外壳的骨架代码不管用哪个框架一个最小的可用外壳基本都包含这几块模型接口、工具注册表、状态对象、编排循环、人工确认机制。用一个很朴素的 Python 伪代码来呈现这套骨架最好理解它不依赖任何框架方便你迁到 LangGraph 或者其它平台上class AgentRuntime: def __init__(self, llm): self.llm llm self.tools {} # 工具注册表 self.state {steps: []} # 显式状态 def register_tool(self, name, description, schema, handler): self.tools[name] { description: description, schema: schema, handler: handler, } def run(self, task): # 1. 组织上下文把状态摘要 工具手册 任务描述拼起来 context self._build_context(task) # 2. 调用模型要求输出严格 JSON 工具调用 response self.llm.chat(context, response_formatjson) action, args self._parse_action(response) # 3. 执行工具 校验 回写状态 if action in self.tools: result self.tools[action][handler](**args) self.state[steps].append(action) return result def _build_context(self, task): tool_manual self._tool_manual() state_snapshot json.dumps(self.state) return f 当前进度: {state_snapshot} 你的任务: {task} 可用工具: {tool_manual} 只输出 JSON 工具调用。骨架之所以强调“工具注册表 状态快照 严格输出格式”就是因为这三样就足以把前面聊到的几类问题全部兜住模型不会在模糊的工具描述里乱选状态不会丢在历史里输出格式有校验基础。把这个跑通之后再在循环外面加缓存、限流、人工确认节点就是一个能上生产的壳了。4.3 给外壳装刹车护栏设计的几个细节外壳里最容易被忽略的是护栏但护栏恰恰是最体现工程经验的部分。做 Agent 不能只考虑让它干活还要考虑它不能干什么。我常用的几个设计第一不要把系统 prompt 里的限制当成安全边界。真正需要禁止模型操作的动作比如删除数据、修改核心配置、外向发送消息应该是模型根本看不到这些工具而不是模型被提示不要调用。我们需要通过工具可见性来控制能力边界而依赖模型的自觉不靠谱。第二高影响操作一定要加入人工确认节点。有些人觉得加人工确认影响体验但真实业务里机器确认 人确认 审计日志是保命符。一次漏掉审批可能比一百次体验折损更致命。第三给所有工具调用加上审计日志。每次模型调用了什么工具、传了什么参数、拿到什么结果、经过了哪一层校验全部落日志。Agent 的项目调试非常依赖完整链路回放日志有时比功能本身还要重要。日志记录做好了你才能知道模型在哪一步开始偏离、是哪一段上下文误导了它。5. 什么时候别硬套外壳减法思维是真功夫5.1 简单任务用重框架反而会把模型变蠢讲了很多外壳设计之后我想说一个反方向的观点外壳不是越复杂越好。给简单任务套上复杂的编排架构效果往往适得其反。比如单轮问答任务硬上了多 Agent 协作、多轮反思机制结果是模型每句话都要被反思一遍把原本干净的回答改得冗长别扭还把本来应该保留的话改了意思。复杂度本身就是成本会带来更多的故障点。判断过度设计有一个简单标准任务能不能用一个模型调用完成如果能就别上多轮编排用一次调用加一次工具调用能完成就别上多 Agent。外壳的设计目标是恰好够用不是看起来高级。5.2 模型实力不够时外壳能补多少还有一个避免不了的问题模型本身能力不够外壳能帮忙到什么程度我经常看到有人说只要提示词写得好小模型也能干大活这话对一半。外壳确实能把小模型的下限大幅度抬起来——通过严格 schema、显式状态、窄化任务边界、给足示例小模型可以在特定窄场景下干得有模有样。但模型的知识储备和推理底盘是壳补不了的。比如用强外壳 小模型去处理长文档归纳和新知识联想小模型会频繁出现事实偏差和逻辑跳跃外壳只能让它在规定格式里犯错不能让它不犯错。本地模型和云端模型的差距在复杂语义场景下就是存在。外壳的意义在于让模型能力得到充分释放而不是凭空创造能力。所以对模型能力有限的场景务实的做法是把任务切得更细、范围收得更窄、由外壳承担更多工程逻辑而不是把希望全押在模型自己会理解上。这也是为什么同一个模型的本地版接外壳后能解决很多实际问题但离通用多面手还很远。5.3 我自己的几条实操心得最后分享几条干活积攒下来的体会不成体系但都很实用。第一条永远先跑通最小链路再上框架。任何 Agent 项目我都建议先用一个脚本把输入-模型调用-工具执行-输出跑通确认业务逻辑本身成立再换到正式框架里去补状态管理和并发治理。直接上框架的结果往往是你搞不清楚是业务设计的问题还是框架配置的问题。第二条每次调外壳只盯两个指标任务成功率或完成率、人工干预率。理想情况下外壳改动应该让成功率上升、干预率下降。如果改了 prompt 结构发现任务完成率没变而 token 消耗涨了说明这个改动没有意义。用指标说话而不是凭感觉评价外壳好坏。第三条对外壳做改动时一次只改一个变量。模型的语义系统高度非线性你又是在外围调整同时换工具描述、改状态结构、调 prompt失败了根本不知道是哪一步影响的。一次改动跑一批测试样例对比看效果再动下一处。第四条可观测性永远要优先于功能开发。Agent 和普通程序最大的不同是它的“逻辑仓”存在于黑盒模型里。你做不到正常 debug只能像侦探一样靠日志和状态快照还原现场。所以项目第一天就把工具调用的全链路日志建好你会感谢这个决定。至于那些看起来系统性的工作流细节比如人类确认节点和防幻觉机制怎么设计、LangGraph 里状态恢复怎么做实操里多盘几轮自然会有感觉。我自己的体会是不要把模型当成队友把它当成一个能力很强但极不稳定的实习生外壳就是你给他配的工作流程和审批流。一切流程、约束和检查都是为了让他稳定发挥出能力同时犯不了大错。想清楚这个定位很多设计决策就豁然开朗了。