7.1 引导填写:工具执行中途的反问 本节摘要:本节开始全书最有意思的部分——交互式能力。到目前为止,我们的工具都是「模型一发请求,服务端一口气算完返回」。但有一类真实需求让这个模式失效:工具执行到一半,需要问用户一个问题才能继续。比如转账要确认收款人、删除要二次确认、配置要让用户从选项里挑。本节讲引导填写(Elicitation)的动机,以及它为什么不能简单地用「预先在工具参数里要求」替代。读完本节,你理解了「中途反问」这个独特需求,为后续机制学习铺好认知。 一、一个纯函数工具搞不定的场景 先看一个真实痛点。假设你写个转账工具: 模型调 。但 10000 是个大额转账,业务规则要求用户必须在中途确认才能执行。
本节摘要:本节开始全书最有意思的部分——交互式能力。到目前为止,我们的工具都是「模型一发请求,服务端一口气算完返回」。但有一类真实需求让这个模式失效:工具执行到一半,需要问用户一个问题才能继续。比如转账要确认收款人、删除要二次确认、配置要让用户从选项里挑。本节讲引导填写(Elicitation)的动机,以及它为什么不能简单地用「预先在工具参数里要求」替代。读完本节,你理解了「中途反问」这个独特需求,为后续机制学习铺好认知。
先看一个真实痛点。假设你写个转账工具:
@mcp.tool() def transfer(from_id: str, to_id: str, amount: float) -> bool: """Transfer money.""" return do_transfer(from_id, to_id, amount)
模型调 transfer(from_id="u1", to_id="u2", amount=10000)。但 10000 是个大额转账,业务规则要求用户必须在中途确认才能执行。问题来了:
纯函数工具的困境: 模型调 transfer(...) │ ▼ 服务端开始执行 执行到一半:「金额超 1 万,需要确认」 │ ▼ 怎么问用户? 纯函数没有「暂停等用户回答」的能力 → 要么硬执行(违反业务规则) → 要么直接失败(用户体验差,要重新调)
这就是「中途反问」的需求——工具执行到一半,需要向用户问一个问题,拿到答案再继续。传统纯函数工具做不到,因为它是「一口气跑完」的。
你可能会想:「让模型在调用前就问用户确认,不就行了?」这是个直觉方案,但有几个问题:
方案:让模型预先问用户 模型:「转账 1 万,你确认吗?」(对话里问) 用户:「确认」 模型:调 transfer(...) 问题一:模型可能不问 模型可能直接调 transfer,跳过确认 → 业务规则被绕过 问题二:问题依赖执行中的信息 有些问题要到执行中才知道(如「余额只够转 8000,转吗?」) → 模型预先问不出来 问题三:多轮交互复杂 确认 → 选收款方式 → 输验证码 → ... → 让模型全程在对话里转述,效率低、易错
核心问题:有些信息只在执行到特定阶段才该问,预先问不出来、也问不对。引导填写就是为这种「执行中的、有上下文的、需要用户输入」的场景设计的。
哪些场景需要引导填写?共同特征是「执行中需要用户输入才能继续」:
| 场景 | 中途要问什么 | 为什么不能预先问 |
|---|---|---|
| 大额转账 | 「确认转 1 万给 u2?」 | 金额阈值在执行中判断 |
| 删除操作 | 「确认删除这 10 个文件?」 | 文件数执行中才统计 |
| 配置选择 | 「选哪个环境:dev/prod?」 | 选项依赖执行上下文 |
| 验证码 | 「输入短信验证码」 | 验证码执行中发送 |
| 缺失信息补全 | 「邮箱没填,补一下?」 | 缺失在执行中发现 |
| 二选一冲突 | 「名字冲突,覆盖还是改名?」 | 冲突执行中检测 |
注意共同点:问题依赖执行中的信息,或必须由人在环节中确认。这类场景用「预先问」或「直接失败」都处理不好。
引导填写让工具从「一问一答的纯函数」升级为「执行中能反问的对话单元」:
纯函数工具: 模型问 → 工具一口气答 (单向,无中途交互) 支持引导填写的工具: 模型问 → 工具执行 → 工具反问 → 用户答 → 工具继续 → 工具答 (双向,执行中可交互)
这个升级让工具能处理「需要人在环节中」的复杂流程——这在真实业务里极其常见(任何涉及确认、审批、补全的操作)。
引导填写的实现分两种模式(第 7.2 节详讲):
| 模式 | 怎么问 | 适合 |
|---|---|---|
| 表单模式 | 宿主弹结构化表单(带 Schema) | 简单确认、填几个字段 |
| URL 模式 | 宿主跳转外部页面 | OAuth、富交互、复用 Web 流程 |
表单模式适合「确认一下」「填个验证码」这种简单交互;URL 模式适合「要走 OAuth」「要复用现有 Web 表单」这种复杂交互。两者都是「把用户引导到一个界面,拿到回答再继续」。
这里要先点破一个 v2 的关键变化(第 7.3 节详讲)——引导填写的实现方式,在 2026-07-28 现代协议下变了。
legacy 协议(旧): 服务端可以直接发起请求(ctx.elicit) → 服务端执行中,主动向客户端要输入 现代协议(2026-07-28): 服务端不能直接发起请求 → 引导填写靠 Resolve 返回 Elicit + 多轮往返(MRTR) → 工具执行中「暂停」,客户端下次带答案继续
这个变化是 v2 最微妙的部分——协议简化了(移除服务端发起请求),但引导填写的实现变复杂了(要靠多轮往返)。第 7.3 节会专门讲透这个机制,本节先建立「为什么需要引导填写」的认知。
⚠️ 注意:别被「现代协议实现复杂」吓到。从写代码角度,SDK 把这套机制封装得很好——你用
Resolve返回Elicit或用ctx.elicit,SDK 替你处理多轮往返的细节。复杂在协议层,不在你的代码层。
最后,把引导填写与几种「相关但不相同」的交互区分开:
| 交互 | 谁问谁 | 何时 | 例子 |
|---|---|---|---|
| 引导填写 | 服务端问用户 | 工具执行中 | 「确认转账?」 |
| 提示词 | 用户触发服务端 | 对话开始 | 用户选「summarize」 |
| 工具调用 | 模型触发服务端 | 模型推理中 | 模型调「search」 |
| 补全 | 服务端建议用户 | 用户填参数时 | 弹出用户 ID 候选 |
引导填写的独特之处:它发生在工具执行中途,由服务端主动发起,目标是获取用户输入以继续执行。其他交互要么不是中途,要么不是服务端发起,要么不是为获取输入。这个区分让你在遇到需求时,准确判断「这是不是引导填写场景」。
动机清楚了,下一节讲两种模式(表单与 URL)的细节。