06. Tool Calling、Agent 与 MCP
六、Tool Calling、Agent 与 MCP¶
导读:把工具调用看成一条受控执行链,而不是“模型会调函数”¶
Tool Calling、Agent、MCP 这三个词现在经常被混着讲,很多人学到最后会产生一种错觉:只要模型能输出工具名和参数,系统就已经有了“智能体能力”;只要接上 MCP,工具治理也就差不多完成了。真实工程里并不是这样。更稳的理解顺序是,先把 Tool Calling 讲清楚,再把 Agent runtime 讲清楚,最后再把 MCP 放回它真正负责的那一层。
Tool Calling 的核心,不是让模型“自己去执行函数”,而是让模型在自然语言和系统能力之间做翻译。用户说的是“帮我查一下昨天那张报销单为什么还没过审”,模型要把它翻译成结构化的调用建议,例如建议调用 get_expense_claim_status,并从上下文里抽出单号、时间或用户意图。可到了真正执行这一步,决定权依然必须留在服务端。服务端要检查 schema 是否满足、当前用户有没有权限、这个工具当前是否允许在这个场景下使用、风险级别是否需要审批、超时和重试怎么配。只有这些都通过后,工具调用才真正发生。
你只要把这条边界先牢牢记住,很多后续问题都会顺理成章。为什么工具输入输出都应该结构化?因为这样服务端才容易校验、记录、回放和评测。为什么工具要分读写风险等级?因为查询失败多数只是体验问题,退款、发信、执行 shell 失败就可能是事故。为什么 trace 和审计在这部分特别重要?因为一旦模型开始驱动多步执行,错误就不再是一个点,而是一条链。
Tool Calling 真正考的是什么¶
面试里最常见的失分点,是把 Tool Calling 讲成“让模型调用函数”。这样说太像 demo,不像工程。更成熟的说法应该是:模型负责提出下一步动作建议,服务端负责决定能不能做、怎么做、做到哪一步为止。这个说法为什么更稳?因为它天然把 schema、鉴权、超时、幂等、审批、风控、审计、trace 都带了进来。
schema 在这里不是装饰品,而是协议。模型输出天然带概率性,没有 schema 时,它可能把 claim_id 写成别的字段名,可能把时间写成自然语言,可能把本该是枚举的字段写成一大段解释文本。人类看着大概知道它想说什么,程序却接不稳。schema 的意义,就是把“人勉强能猜懂”变成“机器能明确接受或拒绝”。同样的道理也适用于工具输出。只要工具结果还停留在自由文本,后面的模型汇总、前端展示、日志审计和离线回放都会很难做。
为什么工具要分层治理,也是在这部分必须讲清楚的。只读工具和写工具根本不是同一类问题。查状态、查制度、查组织架构,重点是权限和返回结构;保存草稿、更新中间状态,重点是幂等和可重试语义;退款、发消息、关闭工单、控制浏览器,这类高风险非幂等动作通常必须要审批、确认和更严格的审计。很多人以为 Tool Calling 难在“怎么把函数暴露给模型”,其实真正难的是“什么时候必须停下来让人接手”。
Agent 到底比 Tool Calling 多了什么¶
如果说 Tool Calling 解决的是“模型怎样提出调用意图,以及服务端怎样安全执行这次调用”,那么 Agent 解决的就是“怎样把模型、工具、状态、循环和停止条件组织成一个能持续完成任务的系统”。也就是说,Tool Calling 更像一跳,Agent 则是把多跳串成了一条受控执行链。
这也是为什么 Agent 和普通 ChatBot 的差别,不在于“谁更像人在聊天”,而在于“谁更像一个运行时”。普通聊天机器人很多时候是一轮输入对应一轮输出,系统把上下文拼好后调用一次模型,流程就结束了。Agent 则可能先做判断,决定要不要查知识库;查完之后发现还缺业务数据,再去调业务工具;工具返回后又要决定是否继续、是否进入审批、是否需要人工确认。这条链里一旦多出循环,你就必须面对状态如何保存、何时停止、失败如何恢复、哪些动作必须进入审计这些问题。
ReAct 和 planning 也是在这一层最容易被问到。ReAct 可以先被理解成“根据当前观察决定下一步动作,再根据新观察继续决定下一步”。planning 则是在真正执行之前先做更高层的任务拆解。它们不是热词本身多重要,而是它们背后代表了两种不同的执行风格。路径不完全固定、每一步都依赖上一跳结果的任务,很适合 ReAct;长链路、返工成本高、跨多个系统的任务,则更适合先做计划再执行。但 planning 也会增加 token 和复杂度,所以简单任务并不值得先做一大段计划。
多 Agent 协作系统应该怎样理解¶
“如何实现多 Agent 协作系统”这类题看起来很高级,其实本质仍然是工作拆分和治理边界。多 Agent 真正值得上的前提,不是“听起来更智能”,而是角色职责天然分离,而且拆开以后确实能带来上下文隔离、工具隔离或风险隔离。比如一个 agent 专门负责资料检索,一个专门负责业务执行,一个专门负责合规复核,这类拆分就有明确意义。
如果角色边界本来就不清晰,或者团队连单 Agent 的 trace、评测和审批都还没跑稳,上多 Agent 往往只会让排障更痛苦。因为系统一旦出错,你不仅要知道“哪一步错了”,还要知道“是哪一个 agent 在什么上下文下做错了什么决定”。所以多 Agent 协作系统更考验协调者设计、共享状态管理、handoff 语义、冲突处理和人工接管点,而不是“并行跑几个模型”这么简单。
更成熟的多 Agent 设计,通常会先定义一个清晰的协调者。它负责下发任务、收敛中间结果、控制最大步数、判断是否进入人工审批。角色 agent 则各自拥有更窄的上下文和更小的工具集合。这样做的核心目的,是缩小每个 agent 的认知边界和风险边界,而不是一味追求数量。
MCP 到底解决了什么,没解决什么¶
MCP 值得关注,但一定不要把它讲成“企业工具接入的一站式答案”。更准确的理解是:它解决的是工具、资源和提示模板如何用更统一的协议对外暴露、被客户端发现和调用。换句话说,它在做接入标准化,而不是业务治理自动化。
这层区分非常关键。只要一个工具开始接私有数据、内部系统或高风险动作,真正决定系统是否安全的仍然是服务端自己的策略层,而不是协议本身。哪怕工具是通过 MCP 暴露的,你也一样要做身份映射、租户隔离、参数校验、风险分级和审计。协议能让接入更统一,但不能替你承担业务规则。
如果再把协议对象讲具体一点,MCP 至少可以先记住三类核心内容。tools 更像“可执行能力”,例如查工单、查订单、发起审批;resources 更像“可读取材料”,例如文档、配置、静态知识或上下文资源;prompts 则更像“可复用的提示模板”。再往外一层,是 client 和 server 的关系。server 负责把能力标准化暴露出来,client 负责发现和调用这些能力,二者之间再通过 transport、认证和会话机制把连接真正跑起来。你不一定要一口气把规范细节背完,但至少要知道:MCP 不只是“再包一层函数调用”,它在协议层明确区分了可执行能力、可读取资源和提示模板这几类对象。
所以如果面试官问“为什么要关注 MCP”,比较稳的回答通常是:当工具和数据源越来越多、接入方越来越杂时,标准化协议能降低集成成本,也更适合做平台化治理;但安全、权限和审批依然要由服务端策略层承担。这个边界讲清楚了,答案就不会飘。
LLM API、SDK、Prompt 管理和观测,为什么最后都会连到一起¶
很多看起来分散的问题,最后都会在这一章合流。比如“LLM API 如何设计接口”“Java 调用 OpenAI API 如何设计 SDK”“Prompt 和 Response 怎么记录”“如何设计 Prompt 管理系统”。这些题表面形式不同,实质都在考你会不会把模型能力收敛成可维护的工程资产。
比较稳的 SDK 设计,通常会把供应商接口、统一能力抽象和业务服务层分开。最底层负责 HTTP 连接复用、认证、重试、流式事件解码和错误解析;中间层把普通响应、结构化输出、工具 schema、流式事件、embedding 这些能力整理成比较稳定的请求响应对象;最上层才是业务自己的 service,用来注入 Prompt 模板、工具注册、trace、限流和评测钩子。这样做的价值,是让供应商接口变化不会直接污染所有业务代码,同时又不会把自己锁死在过度抽象里。
Prompt 管理和日志观测也是同一条主线。Prompt 一旦不再是实验文本,而是正式业务的一部分,就必须有版本、发布、回滚、评测绑定和负责人。Prompt 与 Response 的记录,也不能简单地把所有原文直接打进普通日志,因为里面往往会包含用户隐私、企业文档和内部规则。更成熟的做法通常是分层记录:普通结构化日志保留 trace ID、模型名、token 用量、工具名、耗时、租户、错误类型;需要深度回放时,再在受控存储里按采样保留原始 Prompt、工具输入输出摘要和模型响应。这套设计决定了系统出了问题时,你是能真正回放排查,还是只能靠猜。
0. 先把 Tool Calling 想成“模型给建议,服务端做决定”¶
第一次学 Tool Calling 时,最容易产生一个误解:既然模型已经能输出工具名和参数,那它是不是就等于“自己会调用工具”了。不是。真正可靠的 Tool Calling,从来不是把执行权交给模型,而是让模型参与决策,让服务端保留控制权。
你可以把整件事想成这样一条链路。用户先用自然语言表达需求,模型负责理解这句话大概想做什么,并给出一个结构化建议,例如“我需要调用 get_expense_claim_status,参数是 claim_id=EXP-123”;到了这一步,服务端才开始接手,检查当前用户有没有权限、参数是否合法、是否命中了风控规则、这个工具是否在当前场景可用;通过检查以后,服务端才真的去调用下游系统;拿到工具结果后,再把结果回给模型,让它继续汇总回答。
只要你把这条边界牢牢记住,后面的 schema、幂等、审批、MCP、Agent 都会变得顺理成章。因为所有这些机制,本质上都在回答同一个问题:模型可以参与判断,但不能独占执行权。
你去看 openai-go 的工具 schema、OpenAI Agents 的 tools / approvals、Eino 的 tool node 和 MaxStep,会发现这些框架虽然长相不同,但都默认一个前提:模型输出的是建议,执行层需要继续做校验和治理。也正因为这个前提一致,Tool Calling 才能被真正放进企业系统,而不是只停留在 demo 里。
1. 为什么 AI 应用需要 Tool Calling¶
模型擅长理解自然语言、归纳信息、在不完整信息下给出下一步建议,但它并不天然拥有业务系统的实时数据和执行能力。它不能自己去查你的工单库,不能自己去访问 CRM,也不能自己去改一张报销单状态。真实业务动作最终还是要落在后端系统里。
Tool Calling 的价值,就在于把这两种能力拼起来。模型负责把自然语言需求翻译成“应该查什么、应该做什么”;服务端负责把这个意图变成真实、受控的系统调用。没有 Tool Calling,模型就只能在静态上下文里猜,很多场景只能做“看起来像回答”的事情,做不了真正接实时数据的业务助手。有了 Tool Calling,它才能从“会说”进一步走向“会查、会办、会串联流程”。
不过这件事的代价也非常明确。系统一旦允许模型驱动工具,问题就不再只是回答对不对,还会变成权限对不对、参数对不对、重复执行会不会出事、超时后能不能停、失败后能不能回放。也正因为如此,Tool Calling 是 AI 应用里最有后端味的一块内容,它真正考验的是工程控制力,而不是提示词技巧。
2. schema 到底是什么,为什么是硬约束¶
schema 不是给模型看的备注,而是模型和服务端之间的正式契约。它至少要回答六件事:这个工具叫什么、输入有哪些字段、字段类型是什么、哪些字段必填、哪些值合法、输出和失败语义长什么样。没有 schema,模型返回的不是“灵活”,而是程序难以稳定接住的半结构化文本。
Tool Calling 里,schema 的作用有三层。第一层是让模型知道自己到底要填什么,减少字段名乱写、类型混用、枚举漂移。第二层是让服务端能做机器校验,而不是靠人猜“这大概是在传什么”。第三层是让 trace、缓存、回放和评测有稳定边界。字段今天叫 claim_id,明天改成 expense_id,受影响的不只是一个解析器,而是整条执行链路。
设计 schema 时,第一件事不是写 JSON Schema,而是先拆字段归属。能从用户语义里推断出来的字段,例如 claim_id、time_range、reason,可以交给模型抽取。服务端可信字段,例如 tenant_id、user_id、权限范围、审批人、风控标签,必须由服务端补。只要这条边界没立住,后面的 schema 再完整,也只是把不可信输入包装得更正式。
比较稳的设计步骤通常是这样的:
- 先拆字段归属。模型字段和服务端字段分开设计,不要混在同一层里讨论。
- 再收紧类型。能用枚举、数字、日期、布尔,就不要留成自由文本。
- 定义必填和可空条件。不是所有可选字段都是真可选,要写清什么时候必须有。
- 定义输出契约。工具最好返回结构化结果,而不是一句自由文本说明。
- 设计失败语义。校验失败、权限不足、下游超时、幂等冲突,返回结构要稳定。
- 规划演进方式。字段新增、弃用、重命名都要考虑兼容期和回放样本。
拿 get_expense_claim_status 举例,模型可能只需要填 claim_id;服务端再补 user_id、tenant_id 和权限范围。工具输出则最好是 status、pending_node、pending_reason、last_action_time 这类字段,而不是一段“你的单据还在审批中”。这样模型后续汇总、前端展示、日志统计和离线评测都能复用同一份事实。
这部分最常见的错误有三类。第一,把本来可控的字段留成自由文本,后面全靠 repair 和 if/else 收场。第二,只定义输入,不定义输出,工具一多就很难做 trace 和评测。第三,字段一上线就频繁改名,导致缓存键、日志字段和回放样本一起失效。schema 的价值,不在于你写了一份格式说明,而在于它从第一天起就在替执行链路立规矩。
3. 为什么不能把模型输出当成最终命令¶
Tool Calling 最危险的失败方式,不是模型不会调工具,而是后端把模型的建议直接当成命令执行。只要这样做,系统很快就会被各种问题击穿。模型可能选错工具,可能把别人的工单号当成当前用户的工单号,可能在不需要写操作的时候发起写操作,也可能因为前文理解偏差而重复调用同一个工具。
所以成熟系统一定会在执行层加护栏。最基本的护栏包括工具白名单、参数校验、用户鉴权、单工具超时、单请求最大调用次数、重复调用识别和完整审计。你可以把这些理解成传统后端里的路由校验、权限控制和风控规则,只不过现在被放到了“模型建议 -> 真实执行”这条链路中间。
这也是为什么面试里一旦聊到 Tool Calling,你不能停留在“让模型调用函数”这句话上。真正有工程味的回答应该是:模型只负责提出调用意图,执行层仍然要经过契约校验、权限约束、风险判断和副作用控制。否则系统只是把一段不确定输出接到了高风险入口上。
最典型的事故并不神秘。比如助手本来应该先查一张报销单状态,却因为上下文理解偏差直接调了 send_reminder_email;又比如它本来只该查看当前用户自己的工单,却把同名同姓同事的工单号带进去。如果服务端只是“既然模型这么说了那就执行”,这些错误都会直接变成真实业务动作。
4. 幂等、重试和审批,为什么会在这里集中出现¶
一旦工具开始具有副作用,你就会频繁碰到幂等、重试和审批这几个词。幂等的意思是“同一个请求执行多次,结果不会额外扩大副作用”。比如“保存草稿”“写回摘要”这类操作,如果做得好,重复执行不会出大事;但“创建退款”“给客户发邮件”“关闭事故单”这类动作,如果被重复执行一次,可能马上就变成业务事故。
所以工具不能一概而论。最稳的做法,是至少把工具分成三层来看。第一层是只读工具,例如查工单状态、查制度文档、查客户信息,这类工具的重点是权限过滤、查询超时和结果结构稳定。第二层是低风险或幂等写工具,例如保存草稿、更新一个可覆盖的中间状态,这类工具的重点是幂等键、可重试语义和状态回查。第三层是高风险非幂等写工具,例如退款、发信、关闭工单、执行 shell、控制浏览器,这类工具就不能只靠“模型谨慎一点”,而要引入更严格的权限、人工确认和更完整的审计。
为什么审批会跟 Tool Calling 放在一起?因为很多真实业务动作不应该在一次模型判断后直接执行。报销助手如果只是查制度和查状态,问题还相对简单;一旦它能“催办审批”“撤回申请”“发送客户通知”,你就必须明确哪些动作要停下来等人确认,哪些动作可以自动完成。成熟 Agent 系统的难点,常常不是不会调工具,而是不知道在哪一步该让人接手。
你在前面调研的 Agent 框架里会看到,人机审批、tool approval、interrupt / resume 会被反复强调,原因就在这里。高风险写工具的难点从来不在“怎么把函数暴露给模型”,而在“什么时候必须停下来让人看一眼”。只要把这个问题想清楚,很多框架选型其实就不再神秘。
5. 用一个业务例子把执行循环讲透¶
以报销助手为例,用户问:“帮我查一下昨天提交的报销单为什么还没过审。” 这类问题真正的主链不是“模型想一想,然后调函数”,而是一条受控执行循环。只要把这条循环讲清楚,Tool Calling、审批和 Agent 的边界就都清楚了。
一条最小但完整的执行链,通常有八步:
- 用户输入请求。服务端先补当前登录用户、租户、会话状态和本轮可用工具集合。
- 模型给出下一步建议。它可能选择调用
get_expense_claim_status,也可能先要求澄清单号,或者直接回答“不足以判断”。 - 服务端校验建议。这里要做 schema 校验、参数补全、权限判断、风控检查和超时设置。
- 判断是否需要审批。如果是只读查询,通常直接执行;如果是催办、发信、撤回申请这类高风险动作,这一步就要停下来等人确认。
- 执行工具。调用报销系统、知识检索系统或其他下游服务。
- 规范化工具结果。把下游返回整理成结构化对象,写入 trace 和当前状态,不要把原始自由文本直接丢回模型。
- 模型决定下一步。它可能发现状态已经够回答了,也可能还需要再查一次制度条款,解释“为什么缺附件不能过审”。
- 满足停止条件后收口。停止条件通常包括拿到最终答案、进入人工审批、触发最大步数、遇到权限错误或下游失败。
如果把这条链放回例子里看,就很清楚。模型第一步建议查单据状态,服务端确认当前用户确实有权查看这张单,再去报销系统查到“当前停在部门主管审批,缺少发票附件”。模型拿到这份结构化结果后,可能已经能回答“为什么没过审”,也可能还要再调一次 search_policy_documents,查制度里关于附件要求的条款。到这里,它依然没有执行任何高风险写操作,只是在受控范围内查事实、补解释。
真正容易出事故的是循环中间那几步。模型可能选错工具,可能连续三次查同一张单,可能把别人的单号带进去,也可能在“是否催办”这类节点越过审批直接发消息。所以执行层至少要有四个硬边界:最大步数、重复调用识别、权限和风控校验、完整 trace。没有这四样,系统出错时你连“哪一步开始偏的”都说不清。
这也是为什么“模型能输出工具名和参数”还远远不够。真正的工程含量在于:哪些参数允许模型给,哪些参数必须服务端补;什么时候可以自动继续,什么时候必须停下来等人确认;工具失败后是直接报错、降级回答,还是进入人工接管。把这些判断放进执行循环里,Tool Calling 才是一个可控系统,而不是一段演示代码。
6. Tool Calling、Agent 和 MCP,三者到底是什么关系¶
这三个词很容易被混用,但它们并不是一回事。Tool Calling 说的是“模型怎样提出工具调用意图,以及服务端怎样执行这个意图”;Agent 说的是“怎样把模型、工具、状态、循环和停止条件组织成一个能持续完成任务的执行系统”;MCP 说的则是“工具和资源怎样以更统一的协议接入进来”。换成更直白的话,Tool Calling 解决的是调用这一步,Agent 解决的是整个执行过程怎么组织,MCP 解决的是工具从哪来、怎么接。
把这三者分清很重要。你完全可以做一个没有 MCP 的 Tool Calling 系统,只要在自己后端里定义几个工具接口,模型照样能调用;你也可以接入很多 MCP server,但如果没有状态管理、循环控制、审批边界和观测体系,它依然不是一个成熟 Agent。面试里最容易出错的答法,就是把“接了 MCP”说成“已经有 Agent 平台”,或者把“模型能调工具”说成“已经完成智能体建设”。真正稳的讲法,是把它们放回各自负责的那一层。
如果还是觉得抽象,最好的办法是把三者放进同一个业务例子里。还是拿报销助手来说。用户问“我的报销单为什么还没过审”。模型先建议调用 get_expense_claim_status,服务端校验参数和权限后执行,这一小段就叫 Tool Calling。执行完以后,系统发现还需要补制度解释,于是再调 search_policy_documents,再判断是否进入“催办审批”节点,还要限制最大步数、记录 trace、必要时插入人工确认,这整条可循环、可中断、可观测的执行过程才叫 Agent。至于 get_expense_claim_status 和 search_policy_documents 这些工具到底是你自己在服务里直接写的,还是由财务系统、知识库系统通过统一协议暴露出来,那是 MCP 在解决的接入问题。
把前面调研的项目一一放回这个框架里看,就很清楚了。openai-go 更多是在 Tool Calling 这一层给你统一的模型接口和 schema 能力;OpenAI Agents、Eino、LangGraph 处理的是 Agent 运行时这一层的状态、循环、审批和 trace;MCP Go SDK、TypeScript SDK、FastMCP 则主要处理工具怎样被标准化暴露和接入。这样拆层以后,你就不会再把“接了 MCP”误认为“已经把 Agent 平台做完了”。
7. MCP 值得怎么理解,才不会讲歪¶
MCP 值得学,但不要把它讲成万能答案。它更适合被理解成一种标准化接入协议,让模型客户端或 Agent runtime 能用更统一的方式发现和调用工具、读取资源、协商能力。它解决的是“接入边界统一”这个问题,而不是“权限、安全、审计自动完成”这个问题。
这层边界一定要讲清。因为很多人一听到“标准协议”,就下意识觉得安全也顺便解决了。现实里不是这样。即使某个工具通过 MCP 暴露出来,企业系统依然要自己做身份映射、租户隔离、权限裁剪、参数校验和危险动作确认。协议只能让接入更标准,不能替你做业务治理。
所以如果面试官问你“为什么要关注 MCP”,比较成熟的回答是:当工具和数据源越来越多、接入方越来越复杂时,标准化协议能降低集成成本,也更方便平台化治理;但真正决定系统是否安全可靠的,仍然是服务端自己的策略层,而不是协议名称本身。
FastMCP 这类高层框架的价值,更多是让你更快把一个工具 server 组织出来;官方 Go 和 TypeScript SDK 的价值,则是让你真正看到 transport、auth、client / server 这些协议层对象。无论你选哪一类,企业系统自己的权限和审计都还必须自己承担。
8. 工具结果为什么最好保持结构化¶
很多初学者在定义工具时,只盯着输入 schema,却忽略了输出契约。实际上,工具结果的形状会直接影响整个系统后续的稳定性。如果工具返回的是自由文本,看起来很灵活,实际上后面任何环节都不好复用。模型二次汇总时不容易抓字段,前端引用展示时难以对齐,日志和审计里也不方便做聚合统计,离线评测更难把一次工具调用拆开分析。
结构化结果的好处恰恰相反。假设 get_expense_claim_status 返回的是 status、approver、last_action_time、pending_reason 这些字段,那么模型只需要负责把这些字段讲成人话;而系统本身仍然能保留原始事实,方便后续做缓存、回放、排障和评测。这一点和结构化输出的理念其实是同一条线:越靠近系统边界,越应该把自由文本压缩成可校验、可复用的数据结构。
9. Tool Calling 出问题时,应该怎么排查¶
当一次工具调用流程出错时,排查顺序也很有讲究。先看模型是否选对了工具,再看参数是否满足 schema,然后看服务端是否因为权限或风控拒绝执行,再看工具执行本身是否成功,最后才看模型是不是在拿到结果后汇总错了。这个顺序很重要,因为它能让你快速判断问题属于“决策错误”“契约错误”“执行错误”还是“表达错误”。
比如用户问“帮我查一下张三的工单状态”,系统返回了别人的工单。如果你不拆层,很容易一句“模型理解错了”带过去。真正排查时,你应该先看当前用户是否本来就没有查张三工单的权限;如果没有权限,却仍然执行成功了,那问题根本不在模型,而在服务端没有做访问控制。再比如模型重复调用三次同一个查询工具,问题也未必在工具实现,可能是循环停止条件没设计好,或者模型没有从第一次结果里得到足够结构化的信息。
成熟团队之所以强调 trace 和审计,就是因为 Tool Calling 的错误不是一个点,而是一条链路。没有逐步记录,你会很难知道到底是哪里开始偏的。
所以一次好用的 trace 往往至少会记下这些东西:用户问题是什么,模型第几步选择了哪个工具,传了什么结构化参数,服务端为什么放行或拒绝,工具返回了哪些关键字段,最终答案引用了哪些结果。没有这些记录,Tool Calling 一旦出错就很容易沦为“猜哪一层出了问题”。
10. 用面试口径把 Tool Calling 讲完整¶
面试里,如果对方问“Tool Calling 最核心的点是什么”,比较成熟的回答不是“让模型调函数”,而是:模型负责把自然语言需求转换成结构化的工具调用建议,服务端负责用 schema、鉴权、超时、幂等、风险分级和审计来控制真实执行。只读工具、低风险写工具和高风险写工具要区别治理;工具结果最好保持结构化,方便模型汇总、前端展示、日志审计和离线评测;如果工具越来越多,再考虑用 MCP 这类协议标准化接入,但协议不会替代业务治理。
这样回答的好处是,你把 Tool Calling 放回了后端工程语境里。它不再只是一个模型特性,而是一条有清晰边界、有副作用控制、有观测和回放能力的执行流程。这正是企业面试最想听到的东西。
11. ReAct、planning 和 workflow 的边界,到底该怎么讲¶
很多面试题会把 ReAct、planning、workflow 混在一起问,好像它们只是不同框架里的不同叫法。其实更稳的理解是:workflow 代表确定性流程,ReAct 代表边推理边行动的循环,planning 代表先显式拆解任务再执行。三者不是互斥关系,而是在解决不同程度的不确定性。
如果任务路径基本固定,例如“上传文档 -> 解析 -> 切块 -> embedding -> 入库”,那就是工作流问题。你完全知道每一步该怎么走,失败语义也相对清楚,最值得追求的是稳定、可测和可观测。ReAct 适合的是那种下一步要不要查资料、查哪个工具、查完之后还要不要继续查,必须根据中间结果动态决定的任务。planning 再往上走一步,它适合长任务和多目标任务,例如“整理本周事故周报,先拉监控告警,再查工单,再找对应知识库修复记录,最后输出一份管理层摘要”。这种任务如果没有显式规划,很容易在中途丢步骤、重复查、或者把高价值子任务和低价值子任务混在一起。
所以面试里如果被问“什么是 ReAct Agent”,比较成熟的回答通常不是背论文名,而是说:ReAct 的核心是让模型在推理、行动、观察结果之间循环,每拿到一次新信息,就重新决定下一步。它的价值在于面对不确定环境时更灵活,但代价是步数更多、延迟更高、错误面也更广。planning 也是类似。它能让长任务更像项目管理,而不是即兴发挥,但同时会引入计划错误、执行偏差和状态同步成本。只有把这些代价一起讲出来,答案才像工程判断,而不是概念翻译。
一个很实用的判断标准是:短任务优先工作流,信息不确定但步骤不多的任务可以考虑 ReAct,长任务和多阶段任务再考虑 planning。换句话说,不要因为“会规划”听起来更高级,就默认所有 Agent 都应该先列一个计划。真正成熟的系统,会先让任务复杂度和治理能力匹配,而不是让框架能力决定系统形状。
12. ChatMemory、Agent memory 和对话历史,到底该怎样落地¶
讲 memory 时,最容易混在一起的是三件事:数据库里保存的完整历史、模型这轮真正能看到的上下文、以及跨轮保留的业务状态。把这三者分清,memory 才会落到工程上。ChatMemory 说的是“这轮推理前,要给模型看哪些消息和状态”;Agent memory 则更宽,除了当前会话上下文,还包含跨任务的知识、偏好、执行记录和恢复信息。
真正落地时,至少可以把 memory 分成四层。第一层是短期消息,也就是最近几轮和当前任务直接相关的对话、工具返回和系统提醒。第二层是摘要记忆,用来保留已经确认过的事实、未完成事项和上一轮的关键结论。第三层是结构化状态,例如当前单据号、权限范围、最近一次工具结果、当前审批节点。第四层是长期记忆,例如用户偏好、术语映射、历史案例和团队知识,这一层通常不该常驻上下文,而是按需检索。
每轮装配上下文时,比较稳的步骤通常是这样的:
- 从数据库取完整消息、工具结果和任务状态。
- 只保留最近几轮里与当前任务直接相关的消息。
- 把更早但仍重要的内容压成摘要,而不是整段历史回放。
- 把当前任务最关键的事实单独整理成结构化状态。
- 需要长期知识时,再按需检索,不要长期挂在上下文里。
- 模型返回后,再决定这轮新增内容该进入短期消息、摘要,还是只保留在回放日志里。
拿报销助手举例更清楚。用户前面聊过很多寒暄、试探和重复确认,这些内容大多不该每轮都送回模型。真正应该保住的,通常是“当前在看哪张报销单”“最近一次状态查询返回了什么”“制度检索命中了哪份文档”“用户刚刚已经确认过是否需要催办”。把这些事实做成结构化状态,比把十几轮自然语言原样回放更稳,也更便宜。
memory 最常见的错误也有规律。第一,把数据库里的全部聊天记录按时间顺序原样送回模型,成本和噪声一起上涨。第二,只存文本,不存结构化状态,结果模型每轮都要从历史对话里重新猜当前单据号和权限范围。第三,把长期知识硬塞进会话上下文,导致上下文越来越长,却没有真正提高命中率。第四,摘要只压缩字数,不保关键约束,最后把真正重要的状态压没了。
所以 memory 优化不等于“再压一点 token”,而是状态建模。能做结构化状态的,就不要埋进自然语言;能按需检索的,就不要长期常驻;能做摘要的,就不要重复整段历史。评估时也不要只看上下文是否变短,还要看多轮一致性、错误恢复能力、中断后续接能力、每轮 token 成本和过期状态导致的错误率。只有这几项一起看,ChatMemory 才不是“存聊天记录”,而是真正的运行时状态管理。