
把同一个高能力 LLM 的 API 同时交给两位开发者一位做出了上线后稳定服务用户的智能体另一位做出来的却是一个“问答玩具”。问题往往不在模型本身而在模型之外那一整套系统工程。Dont credit the LLM——这句话并不是否定大模型的价值而是提醒技术团队现代 LLM 应用的成功更多来自模型之外的提示词策略、上下文工程、工具调用、编排框架、评测体系与安全护栏。模型是必要条件但远不是充分条件。这篇文章会从一个反直觉的观察出发拆解“模型之外”到底有哪些关键环节然后给出一个最小可运行的 Agent 示例证明同一个模型在不同工程条件下效果可以差出几个量级。读完你会得到两样东西一是知道一份 AI 应用的成绩单应该怎么归因二是获得一套可复用的 LLM 应用建设框架。1. 同一个模型为什么你的 Agent 比别人笨先看一个非常常见的场景。很多开发者在本地用同一个模型 API 搭建 Agent跑起来之后发现表现和网上 Demo 差得很远。Demo 里的助手能准确查询订单、调用接口、分步骤完成任务你的助手却经常自说自话给出看起来合理但完全编造的答案。更让人困惑的是你在聊天窗口里单独测试同一个模型它的回答明明很聪明一旦放进 Agent 流程就立刻“降智”。问题出在哪答案是模型没有变变的是模型周围的环境。在真实应用中模型只是整个系统里的一个“推理内核”。它接收到什么指令、能看到哪些工具、上下文里塞了哪些数据、调用结果如何被处理、失败后如何重试这些因素叠加在一起才最终决定用户看到的效果。任何一个环节设计得粗糙模型再强也会被拉低到“平庸”的水平。很多团队把效果不好归因于“模型不行”然后花大量精力去换更大、更新的模型结果升级之后效果提升有限。真正的杠杆往往藏在模型之外。这也正是本文想强调的核心判断在模型能力已经普遍够用的今天LLM 应用竞争的主战场正在从“选模型”转向“搭系统”。谁能在模型之外把数据、工具、编排、评测和安全做得更扎实谁就能做出真正可用的产品。2. LLM 只是引擎不是整台车用汽车来类比可能更直观。发动机决定了一台车的动力上限但没有人会靠发动机直接上路。变速箱、底盘、悬挂、轮胎、电子系统共同决定了这台车在实际道路上的表现。你坐在驾驶座上感受到的操控感和舒适度是整车工程的结果而不是某个零部件的功劳。LLM 在一个 AI 应用系统中就相当于发动机。如果把一个典型的 LLM 应用拆开来看大致有这么几层层级作用典型工作模型层提供推理和生成能力选型、版本管理、运行环境提示词层定义模型的行为边界和输出格式系统提示词、输出约束、拒识策略上下文层决定模型“看得到什么”上下文管理、检索增强、记忆策略工具层扩展模型获取外部信息的能力function calling、API 接入、MCP编排层把单次调用变成可持续执行的流程Agent 循环、状态管理、重试机制质量层度量输出是否达标评测集、指标、回归测试安全层防止模型和 Agent 越权或滥用权限控制、审计、护栏很多团队在搭建应用时把所有注意力都放在第一层觉得“只要模型够聪明一切问题都能解决”。但真正影响用户体验的是从第二层到第七层的组合质量。理解这层关系是理解“Dont credit the LLM”的前提。不是模型不重要而是它的重要性需要通过周围系统的放大才能体现出来。一个平庸模型配上优秀工程往往比一个优秀模型配上粗糙工程在真实业务中表现更好。3. 模型之外决定效果的五件事如果把模型之外的关键工作再做一次压缩可以归纳成五件事。这五件事几乎覆盖了 LLM 应用从开发到上线的全部核心环节。3.1 提示词与输出约束先让模型学会“好好说话”同一个模型在“请回答用户问题”和“你是订单客服只根据工具返回结果回答不要编造如果信息不足请说明”这两套系统提示词下表现会完全不同。这里的重点不是把提示词写得花哨而是做两件事一是明确角色的行为边界二是对输出施加结构化约束。比如要求模型以固定的 JSON 结构返回结果、规定模型哪些情况必须拒绝回答、哪些情况必须调用工具而不是凭记忆发挥。在模型 API 普遍支持 function calling 的今天提示词的另一项重要工作是让模型理解“什么时候该调用工具什么时候该直接回答”。这个决策边界如果写不清楚模型就会出现两种典型问题该调用工具时硬编答案不该调用时反而乱调。这类问题在后文排查部分会具体展开。3.2 上下文工程放什么进去比模型本身更影响结果上下文窗口越来越大于是很多开发者的第一反应是“把材料全部塞进去”。这个思路在简单场景下可以跑通在复杂业务里很快会遇到问题上下文过长导致响应变慢无关信息干扰回答质量关键信息被淹没在噪声里。真正值得投入的是上下文工程判断哪些信息必须进入上下文哪些信息应该按需检索检索结果如何排序和裁剪历史对话如何压缩和遗忘。检索增强生成RAG之所以成为 LLM 应用的主流模式就是因为它让系统不再依赖模型“记住一切”而是把知识存储和检索放到模型之外。在这个架构里文档切分、向量检索、重排序、上下文拼装的工程质量对最终回答质量的影响常常高过模型选型。3.3 工具调用与 MCP让模型从“猜”变成“查”没有工具的 LLM 只能根据训练数据回答遇到实时数据就靠猜。给模型接入工具之后它才真正具备与外部系统交互的能力。工具调用层的关键工作包括工具如何描述、参数如何定义、返回值如何解析、调用失败如何处理。工具描述写得好不好直接影响模型能否在正确的时机调用正确的工具。参数定义是否严谨直接影响模型是否能按照系统要求的格式生成参数。返回值解析是否健壮直接影响后续流程的稳定性。近年来MCPModel Context Protocol这类开放协议的出现把工具接入做成了标准化操作。它让模型应用可以像插 USB 一样接入各类外部工具和数据源解决了过去“每个工具都要单独开发一套适配层”的重复劳动。MCP 的价值恰恰在模型之外它统一的是工具接入和调用协议而不是模型本身的推理能力。3.4 编排与 Agent 循环把单次调用变成可持续流程把一次模型调用包装成一个函数是实现 LLM 应用最基础的方式。但真实业务很少是“一问一答”就能完成的。一个稍复杂的任务往往需要多个步骤先理解用户意图再决定调用哪个工具拿到工具结果后判断是否完成没完成就继续下一步直到最终汇总答案。这就是 Agent 循环的基本形态。编排层要解决的问题正是如何让这些步骤可控地跑起来状态如何保存、循环如何终止、工具调用结果如何回填、异常如何捕获、执行过程如何被观测和追溯。这些问题和模型智商无关但决定了你的智能体是“能用的工具”还是“一时好看的 Demo”。3.5 评测与护栏没有度量就无法归因没有评测集就没有办法回答一个灵魂问题这周的效果比上周是变好了还是变差了评测体系的意义不仅仅在于把关质量更在于为归因提供依据。当你发现一个 Agent 的回答质量下降时评测数据能帮你判断是模型版本变了还是提示词被改坏了还是工具返回的数据格式变了还是检索召回的文档质量下降了。护栏则是在评测之外加一层保护。比如限制 Agent 工具调用的最大次数防止陷入死循环比如对模型输出做格式校验防止结构解析失败比如在敏感操作前加入人工确认防止不可逆行为。4. 为什么 LLM 应用需要编排框架搜索热词里有一个高频问题“LLM 应用为什么需要编排框架”。这个问题背后是很多开发者的真实困惑直接用模型 SDK 写代码几行就能完成一次调用为什么要引入一个框架答案在于模型 SDK 解决的是“调用一次模型”的问题框架解决的是“构建一个完整的 LLM 应用”的问题。维度直接用模型 SDK使用编排框架工具接入每个工具手写参数处理和返回解析统一的工具注册与调用机制多轮状态手动管理消息列表和上下文框架提供的状态管理能力Agent 循环自己写 while 循环和终止条件内置的 Agent 执行器可观测性需要自行埋点自动记录执行链路和 token 消耗可测试性测试脚本零散有统一的测试和模拟机制回滚与版本管理依赖自研方案有清晰的工作流定义这不是说框架是万能的。相反框架的价值在于把“工程通用部分”沉淀下来让你把精力放在业务差异上。Spring AI、LangChain、LlamaIndex 等框架虽然技术栈不同但核心思路一致把模型调用从“点调用”变成“可编排流程”。如果你只是做一个简单的问答二次封装完全不需要框架。但一旦涉及多工具调用、多轮状态、复杂分支和团队协作框架带来的工程约束和可观测性会大大降低维护成本。框架不是智商放大器而是工程化容器。它不会让模型变聪明但它能让你的应用变得可维护、可测试、可回滚。5. 最小示例把“模型之外”的作用放大给你看为了让上面的讨论落到代码层面这一节用一个订单客服场景做最小示例。为了让任何读者都能在本地跑通示例中的模型调用层用模拟函数代替但 Agent 的核心结构是真实的。5.1 第一版没有工具的纯模型调用# 文件路径examples/agent_v1_direct_llm.py # 说明第一版不带任何外部工具和编排逻辑 # 用户提问之后把问题直接交给模型。 def llm_complete(system_prompt, user_message): # 真实项目中这里会调用你的 LLM SDK。 # 为了便于本地演示这里用模拟返回替代效果等同于让模型“凭记忆回答”。 return 订单已发货请耐心等待。 def answer_user_query(query): return llm_complete(你是一个订单客服助手。, query) if __name__ __main__: print(answer_user_query(我的订单 A1001 现在到哪了))这个版本的问题是显而易见的模型没有查询订单系统的能力只能根据训练数据中的“常见经验”给出假设性回答。在你的场景里这与“编造”没有本质区别而且它并不知道 A1001 这个订单是否真实存在。5.2 第二版给模型一把“工具”真正要做的是给模型提供一个订单查询工具并告诉它遇到订单问题请调用工具而不是自己回答。# 文件路径examples/agent_v2_tool_call.py # 说明第二版定义了一个订单查询工具 # 并在系统提示词与工具描述中告诉模型遇到订单问题应该调用工具。 # 真实项目中工具描述会通过模型的 tools 参数传给模型。 def get_order_status(order_id): # 真实项目中这里会查询订单数据库或订单服务 API。 if order_id A1001: return 订单已于 2025-06-01 从上海仓发出当前运输中。 return 未查询到该订单请核对订单号。 TOOL_REGISTRY { get_order_status: get_order_status, } def model_chat(messages, tools): # 真实项目中这里会把 messages 和 tools 一起发给模型 # 模型会选择是否发起工具调用。 # 为了便于本地演示这里用一个简化的关键词匹配来模拟“模型决定调用工具”。 last_user messages[-1][content] if 订单 in last_user and 哪 in last_user: return { content: None, tool_calls: [ { name: get_order_status, arguments: {order_id: A1001}, } ], } return {content: 我不知道如何回答这个问题。, tool_calls: []} def agent_run(user_query): messages [{role: user, content: user_query}] # 限制最大循环次数避免模型反复调用工具导致死循环。 max_steps 5 for _ in range(max_steps): response model_chat(messages, toolsNone) if not response[tool_calls]: return response[content] for call in response[tool_calls]: # 通过注册表调用工具而不是动态执行任意函数。 result TOOL_REGISTRY[call[name]](**call[arguments]) messages.append( { role: tool, name: call[name], content: result, } ) return 超过最大工具调用次数已停止。 if __name__ __main__: print(agent_run(我的订单 A1001 现在到哪了))这个版本的关键变化是模型从“凭记忆回答”变成了“查询后回答”。工具调用的执行结果会被追加到消息列表模型在下一轮就能基于真实数据组织回答。在真实模型 API 中工具定义通常通过类似下面的 JSON 结构传给模型{ model: your-llm-model-name, messages: [ { role: user, content: 我的订单 A1001 现在到哪了 } ], tools: [ { type: function, function: { name: get_order_status, description: 根据订单号查询订单的最新物流状态。当用户询问订单位置或物流进度时必须调用该工具。, parameters: { type: object, properties: { order_id: { type: string, description: 订单号例如 A1001 } }, required: [order_id] } } } ] }工具描述写得越清晰模型就越容易在正确场景触发正确工具这也对应了前面提到的“工具层”工程。5.3 MCP 化接入工具管理的下一个阶段当工具数量变多手工编写每个工具的接入逻辑会变得非常繁琐。MCP 协议的出现改变了这种局面。下面是一个 MCP 客户端的常见配置示意具体字段以你使用的 MCP SDK 文档为准{ mcpServers: { order-service: { command: docker, args: [run, --rm, -i, order-mcp-server], env: { ORDER_SERVICE_URL: http://localhost:8080 } } } }配置好之后MCP 客户端会负责与 order-service 通信把工具能力暴露给 LLM 应用层。这样订单服务团队只需要维护一个 MCP Server所有接入该服务的 LLM 应用都可以复用不必为每个项目单独写一套适配代码。MCP 的意义不在于让模型变强而在于让工具接入从“定制开发”变成“标准接入”。这正是“模型之外”的系统工程价值它降低的是应用层的集成成本而不是推理成本。6. 运行结果与效果验证在本地依次运行两个版本可以直观看到对比。python examples/agent_v1_direct_llm.py第一版输出订单已发货请耐心等待。回答看上去礼貌但完全不可验证因为它根本没有查询订单系统。如果用户追问“从哪里发货”“物流单号是什么”模型大概率只能继续编。python examples/agent_v2_tool_call.py第二版输出订单已于 2025-06-01 从上海仓发出当前运输中。这个结果来自工具真实返回的数据而不是模型猜测。对比两个版本模型本身的“智商”没有变化变化的只是周围系统。判断运行是否成功可以从三个信号看程序是否按预期调用了工具工具返回值是否被正确追加到消息列表最终回答是否包含工具返回的关键信息。如果某一步失败优先检查工具注册表的函数名与模型返回的 name 是否一致以及参数键名与函数形参是否匹配。7. 常见问题与排查方法在模型之外的工程建设中很多问题看起来像“模型变笨了”实际是系统配置或者流程结构出了问题。下面整理了一份高频排查表。问题现象可能原因排查方式解决方案模型不调用工具直接返回编造答案工具描述不清或当前模型版本不支持 function calling检查 tools 参数是否真正传入打印模型原始响应优化工具描述补充调用场景示例更换支持 function calling 的模型模型拒绝执行工具调用拒识系统提示词的拒绝策略过严或用户请求触发安全边界查看完整消息日志和拒识具体阶段区分“明确该拒绝的请求”和“需要先确认再执行的请求”调整提示词边界上下文被撑爆回答开始偏离主题检索结果噪声太多把无关内容塞进上下文查看单次请求的上下文明细评估召回质量限制检索条数增加重排序压缩历史记录Agent 循环陷入死循环工具结果没有正确追加或模型反复调用同一个工具增加最大循环次数打印每一步的 messages设置循环上限、超时和工具调用去重工具调用报参数错误模型生成的参数与函数签名不匹配打印模型返回的 arguments与函数形参比对在工具定义中写清参数格式必要时增加参数校验线上效果波动找不到原因模型版本、提示词、工具返回格式或检索数据发生变化回看每个环节的变更记录建立模型外变更日志做版本回归“拒识”这个词在 LLM 应用开发中越来越常见它指的是模型因为安全策略、指令约束或输入内容判断而拒绝执行某项输出或工具调用。正确处理拒识不是简单地把所有拒绝都放开而是要让系统在“安全”和“可用”之间找到平衡比如对高风险操作明确要求人工确认而不是完全禁止。8. 模型之外的工程红线安全边界与最小授权模型之外的工作不仅影响效果更决定了系统的安全边界。一个容易被忽视的风险是 Agent 的“过度授权”。在网络安全的演练场景中已经出现了专门针对 LLM API 过度授权问题的攻击实验。它的核心逻辑是如果给 Agent 的工具调用权限过大攻击者可以通过精心构造的输入诱导 Agent 调用它不应该调的接口造成数据泄露或未授权操作。比如订单客服 Agent 本来应该只提供订单状态查询能力但如果它不小心获得了修改订单、退款、导出用户列表的接口权限那么一次成功的提示词注入就可能让系统执行超出预期的操作。在实际项目中几个原则值得反复强调最小权限原则Agent 能调用的工具只限于完成业务目标所必需的接口。变更操作二次确认查询类操作可以放行但修改、删除、转账等操作必须走人工确认流程。凭证分离Agent 服务的账号权限应该与开发人员个人账号隔离单独分配。测试环境先行所有工具接入和策略调整先在测试环境验证再上生产。变更可回滚工具定义、提示词和编排流程都应有版本记录异常时可以快速回滚。安全不是模型的属性而是系统的属性。把安全策略全部寄托在“模型很聪明不会乱来”上是 LLM 应用建设中最危险的想法。9. 最佳实践别急着换模型先做这九件事基于前面提到的工程维度这里给出一份可直接落地的检查清单。当你觉得自己的 LLM 应用效果不好时先按这个顺序自查而不是立刻换一个更大的模型。建立最小评测集。准备 20 到 50 条覆盖核心场景的测试问题每次改动后跑一遍记录合格率。检查工具描述质量。把工具描述当成接口文档来写写清楚“什么时候调用、参数是什么、返回值长什么样”。控制上下文长度。检索出的文档不是越多越好先精炼到模型真正需要的内容。给 Agent 加循环上限。无论模型多聪明都要设定最大工具调用次数和超时时间。记录模型之外的所有变更。提示词、工具、上下文策略、检索配置任何一个变化都要有日志记录。把模型版本锁进配置文件。模型升级应该是显式的动作而不是悄悄发生的变量。输出校验不可少。对模型返回的结构化内容做格式校验解析失败时要有兜底处理。生产环境限制 Agent 权限。按最小权限分配工具敏感操作加人工确认。建立回滚机制。改动系统提示词或工具定义前先保存上一版本出现问题时能一键回退。把这份清单落地你会发现很多“模型表现不行”的问题实际上是工程问题。而工程问题是可以通过流程和工具解决的。10. 总结与后续学习方向“Dont credit the LLM”这句话真正想说的不是否定模型而是提醒我们不要把模型当成魔法忽视它周围的决定性工程。模型能力是一个重要变量但应用系统的效果由提示词、上下文、工具、编排、评测与安全共同决定。与其在模型选型上反复摇摆不如先把你手上的系统工程做扎实。继续深入的方向可以沿着这几条线展开工具接入层可以研究 MCP让工具管理从定制开发走向标准协议上下文层可以深入研究 RAG 的切分、召回、重排和压缩策略编排层可以学习主流框架的 Agent 机制理解状态管理与异常恢复质量层可以搭建自己的评测集与回归体系让效果可度量、可归因。下一次当你看到某个 LLM 应用的惊艳效果时先别急着感慨“模型真强”不妨想想它背后那套模型之外的系统是怎么把一次普通的推理变成一项可靠服务的。