AI 应用交付物工程 2026:从工具结果到可验证产物
工具返回值不是交付物本身。本文把 AI 应用的交付物当作一等工程对象,给出取回窗口、内容寻址句柄、分层验证 ROI 三套可计算方法,附 MCP Apps 规范、容器过期语义与 CSP 权限面的一手事实来源。
约 38 分钟阅读11,266 字1 次阅读博主

AI 应用交付物工程 2026:从工具结果到可验证产物
一句话摘要:本文把「AI 应用交付物」当作一类独立的一等工程对象,论证从工具返回值到用户手中可下载、可验证、可撤销产物的完整链路应当显式建模,并给出取回窗口、内容寻址、分层验证三套可计算的设计方法与全部一手事实来源。
一、被长期忽略的一层:工具结果不等于交付物
做 AI 应用时,一个几乎所有人都会先撞上的直觉是:模型的输出就是应用的核心。但这个直觉在工程化阶段会失效,因为「模型输出」和「用户拿到的东西」是两个不同的对象。
本文所说的交付物(deliverable),指的是应用最终交给用户的那份东西:一份生成的报告、一个可下载的表格文件、一段能直接落到代码库的补丁、一张能展开筛选的图表、一份能复核的引用清单。它的存在形式可以是文本、文件、可交互组件,甚至是一段由宿主代理执行的动作。这些东西有一个共同特征:它们的生命周期不由模型决定,而由应用自己的存储、取回与撤销策略决定。
这个区分之所以重要,是因为工具调用这一层已经把「返回」和「交付」混为一谈了。以 MCP 协议为例,传统工具返回文本、图片、资源或结构化数据,宿主把它显示在对话里就算完成。但当工具真的做了一件事——生成了 500 行的 CSV、跑了一段代码产出一张 PNG、改了用户的文件——返回的那个字符串只是这次交付的回执,不是交付本身。
OpenAI 的 Code Interpreter 文档把这个区分写得很直白:容器生成的图片和文件是作为 assistant 消息上的 annotation(注解)返回的,其中 container_file_citation 类型会带上 container_id、file_id 和 filename 三个字段,文档明确说「你可以解析这些注解来暴露下载链接,或者以其他方式处理这些文件」。也就是说,协议层面早就承认「结果」与「产物」是分离的:结果是一段注解,产物是容器里的一个文件。
这个分离在容器语义下会变成一个真实且不可回避的工程约束。同一份文档写着:容器在 20 分钟内未被使用就会过期,过期后容器里的一切数据「将被我们的系统丢弃且不可恢复」,而且「你不能把一个已过期的容器变回活跃状态」;文档的结论性建议是「把容器当作临时性的,把与该工具使用相关的所有数据存储在你自己的系统上」,并补充「你应当在容器活跃期间下载你可能需要的任何文件」。
于是「交付」这一步的真实工作内容是明确的:在生成的那一刻把产物从临时执行环境里取出来,落到应用自己的、有明确生命周期的存储上。这不是锦上添花,这是交付物能被用户取回的唯一前提。
二、第一道工程约束:取回窗口与思考时长的赛跑
上面那条 20 分钟的过期时钟,值得展开算一算,因为它揭示了一个反直觉的结论。
假设用户提出请求后,模型在容器里跑代码生成交付物,然后用户会花一段时间才想起来点下载。这个「思考时长」(D) 是个随机变量。取一个合理的工作假设:(D) 服从对数正态分布 (\mathrm{LogNormal}(m, \sigma)),(m) 是中位数(分钟),(\sigma) 是形状参数。那么交付物丢失的概率就是 (P(D > 20)),因为容器在闲置 20 分钟后过期,产物随之蒸发。
按这个模型算出来的一组数(全部由脚本现场计算,非经验值):
- 中位数 2 分钟、(\sigma=1.0) 时,丢失率 1.065%
- 中位数 5 分钟、(\sigma=1.0) 时,丢失率 8.283%
- 中位数 10 分钟、(\sigma=1.0) 时,丢失率 24.411%
- 中位数 10 分钟、(\sigma=1.5) 时,丢失率 32.201%
- 中位数 20 分钟、(\sigma=1.0) 时,丢失率恰好 50.000%
最后一行是个 sanity check:中位数恰好等于阈值时,超出的概率应该是 50%,算出来确实如此,说明模型和实现都对。
现在做一次决策翻转。如果采用「急切取回」策略——在生成后 0.5 分钟内就把产物同步落盘到自有存储——那么丢失率就是 0%,因为容器过期这件事根本还没来得及发生。在中位数 10 分钟、(\sigma=1.5) 的假设下,这两种策略的差距是 32.201% 对 0%。
反解一下更实用的版本:要让懒取回的丢失率压到 10% 以下,中位思考时长需要多小?在 (\sigma=1.5) 时需要 (m < 2.925) 分钟;(\sigma=2.0) 时需要 (m < 1.541) 分钟。换句话说,「等用户来取」这个策略在任何现实可感的思考时长下都不可靠,除非你把中位思考时长压到几分钟以内。
这个计算给出的工程结论非常干脆:取回动作必须由系统发起,不能由用户发起。用户点「下载」的时候,系统读的是一个早就落盘好的副本,而不是现场去临时容器里捞。后者是把可用性押在用户的注意力时延上,而注意力时延的分布尾部很重。
还有一个容易忽略的细节:容器的过期锚点是 last_active_at,且「任何容器操作——无论是获取容器、添加还是删除文件——都会自动刷新容器的 last_active_at 时间」。这意味着只要系统定期去碰一下容器,就能给它续命。但续命策略和落盘策略相比是次优的:它把可用性绑定在一个持续运行的保活任务上,一旦保活任务挂了,所有未取回的产物一起丢。落盘是把可用性从「服务必须持续活着」换成「一次写成功」,这个交换在任何可靠性口径下都是划算的。
三、第二道约束:交付物不该内联进上下文
交付物一旦变大,就会遇到第二个问题:它该以什么形式存在于对话里。
先把量级算清楚。假设一个交付物是 10,000 行、5 列的表格。按每字段平均 12 字符估算,CSV 约 660,000 字节;若用 JSON 表示(每字段带引号和键名),约 870,000 字节,比 CSV 大 1.318 倍。
如果把这些内容内联进模型上下文,用常见的 token 估算系数(CSV 约 0.42 token/字节、JSON 约 0.28 token/字节):
- CSV 内联约 277,200 token
- JSON 内联约 243,600 token
这两个数字都很吓人。更值得警惕的是编码方式带来的隐性膨胀:二进制文件走 Base64 时,字节数精确地变成原来的 4/3 倍,也就是 +33.33%,再加 76 列换行的 1.316% 开销,总共 +34.65%。一张 1 MB 的 PNG 内联进上下文大约要 349,517 token。
而如果改为句柄引用——上下文里只放一个约 40 token 的 handle(内容哈希加版本号),真正的内容放在应用自己的存储里,由客户端按需拉取——277,200 token 就压缩成了 40 token,6930 倍。
这个数字对比说明了交付物设计的第一原则:交付物在对话里应该是引用,不应该是内容。这与 MCP Apps 扩展的设计是高度一致的——该扩展引入 ui:// URI scheme,让工具通过 _meta.ui.resourceUri 字段指向一个声明好的 UI 资源,宿主在工具被调用之前就可以预加载这个资源。规范把这条链路分成四步:UI 预加载、资源获取、沙箱化渲染、双向通信。注意第一步是预加载——资源在工具还没被调用时就已经就位了,这同样是「系统先准备好,不等用户触发」的思想。
那么上下文里应该留什么?三类东西足够:
- 可读的描述——文件名、类型、大小、生成参数,用户能据此判断要不要点开
- 稳定的句柄——内容寻址的哈希加版本号,让引用在版本之间可追溯
- 校验信息——哈希本身,让客户端能验证下载完整性
这里的关键判断是:描述和内容必须分离。把描述内联、把内容外置,是同时优化了上下文预算、缓存命中率和用户信任三件事。描述稳定不变,因此可以被缓存;内容可大可小,按需取用。
四、UI 交付层:MCP Apps 把「渲染」也变成协议的一部分
交付物有两种形态:文件型和界面型。文件型就是上一节说的那些;界面型是「一个可以直接在对话里操作的组件」——一个能点开筛选的下拉列表,一个能拖动调整的图表,一个能实时刷新的仪表盘。
这类交付物过去是每个应用自己想办法的:聊天框里塞一段 HTML,或者让模型输出一段 React 代码再动态执行。这种做法的三个问题是显然的:安全边界靠自觉、样式不统一、跨宿主不通用。
MCP Apps 扩展(SEP-1865,状态为 Final,类型为 Extensions Track,创建于 2025-11-21)给出了标准化答案。它的扩展标识是 io.modelcontextprotocol/ui,核心内容有四项:UI 资源(用 ui:// URI scheme 声明)、工具-UI 关联(工具通过 metadata 引用 UI 资源)、双向通信(UI iframe 用标准 MCP JSON-RPC 协议与宿主通信)、以及安全模型(强制的 iframe 沙箱与可审计的通信)。
具体到工作流,规范描述得相当清楚。当 LLM 决定调用一个支持 MCP Apps 的工具时:
- UI 预加载:工具描述里的
_meta.ui.resourceUri指向一个ui://资源,宿主可以在工具被调用之前就预加载它,从而支持「把工具输入流式送进应用」这类特性 - 资源获取:宿主从服务器取回该 UI 资源,这是一段 HTML 页面,通常把 JS 和 CSS 打包在一起以简化实现;应用也可以从
_meta.ui.csp指定的源加载外部脚本和资源 - 沙箱化渲染:Web 宿主通常把这个 HTML 渲染进对话内的沙箱 iframe 里
- 双向通信:应用与宿主通过一个 JSON-RPC 协议通信,它形成了 MCP 自己的一个方言——部分请求与通知和核心 MCP 共享(如
tools/call),部分类似但不同(如ui/initialize),多数是新的ui/前缀方法名
这个方言设计里有个值得单独指出的细节,规范要求宿主与视图之间的双向方法(ui/initialize 请求与 ui/notifications/initialized 通知)都由 View 发送,且「宿主 MUST NOT 向视图发送任何以 ui/notifications/sandbox- 开头的方法」——方向性是被显式约束的,这样双向通道才不会退化成任意调用。
安全模型同样值得逐条看。规范写明:沙箱阻止应用访问宿主窗口的 DOM、读取宿主的 cookie 或 local storage、导航宿主页面,或在宿主上下文中执行脚本;所有应用与宿主之间的通信都走 postMessage API;宿主控制应用能访问哪些能力,例如宿主可以限制应用能调用哪些工具,或者禁用 sendOpenLink 能力。
「宿主控制」这四个字是整个扩展的支点。交付物的渲染权限不是由交付方自己声明并自我执行的,而是由宿主在运行时裁决的。这一点在权限模型上比 CSP 声明更进一步。
规范还定义了工具侧的可见性控制(_meta.ui.visibility),取值是 ["model", "app"] 的组合:"model" 表示工具对 agent 可见且可调用,"app" 表示只能由该服务器上的 app 调用。规范给的例子很能说明设计意图:一个 get_weather 工具对模型和 app 都可见,而一个 refresh_dashboard 工具只对 app 可见——它是一个纯 UI 侧的刷新动作,不该占用模型的决策预算,更不该出现在模型的工具列表里。这直接关系到成本与准确率:把 UI 内部动作从模型可见集里剔除,能减少无效的工具调用。
规范同时标注了弃用项:扁平写法 _meta["ui/resourceUri"] 已被弃用,应改用 _meta.ui.resourceUri,且「弃用格式将在 GA 之前移除」。这类演进细节在阅读旧示例代码时容易踩坑,值得注意。
从生态侧看,SEP-1865 明确写了两条来路:MCP-UI 社区项目「已展示 MCP app 的可行性与价值」并「发展为社区的试验场和 UI 规范与 SDK 的来源」;OpenAI 的 Apps SDK「在 2025 年 11 月推出,进一步验证了对话式 AI 界面中富 UI 体验的需求」。而 MCP Apps 的动机段落里,列出的正是缺标准化的后果:服务器无法可靠地期待宿主支持 UI、各宿主行为可能略有差异、安全与审计模式不一致、开发者必须为不同宿主维护多套实现。
这几个开源仓库的实时数据(本次通过 GitHub API 拉取):modelcontextprotocol/ext-apps 3030 star、最后推送 2026-10-29、TypeScript;MCP-UI-Org/mcp-ui 5246 star、Apache-2.0、最后推送 2026-09-16;openai/openai-apps-sdk-examples 2382 star、MIT、最后推送 2026-04-15;openai/apps-sdk-ui 973 star、MIT、最后推送 2026-05-20。
宿主支持矩阵截至本次核验时列出了 Claude、Claude Desktop、VS Code GitHub Copilot、Microsoft 365 Copilot、Goose、Postman、MCPJam 与 Archestra.AI 等,且官方明确提示宿主支持情况各异——「交付物能在你的目标宿主里渲染」是一个需要在选型时逐个核验的前置条件,而不是可以假设的能力。
五、CSP 与沙箱:可枚举的权限面
UI 型交付物的安全边界最终落在 CSP 上。MCP Apps 规范把 CSP 配置定义成四个正交维度,每一项都有明确的默认语义:
connectDomains(对应connect-src)——网络请求来源。省略或为空即表示不允许任何外部连接,这是安全默认值resourceDomains(对应img-src/script-src/style-src/font-src/media-src)——静态资源来源,同样默认为空,且支持https://*.example.com形式的通配子域frameDomains(对应frame-src)——嵌套 iframe 来源,默认为frame-src 'none',即不允许任何嵌套 iframebaseUriDomains(对应base-uri)——文档的基础 URI,省略时只有同源被允许
规范给的示例是:connect 两个(https://api.weather.com、wss://realtime.service.com)、resource 两个(https://cdn.jsdelivr.net、https://*.cloudflare.com)、frame 两个(https://www.youtube.com、https://player.vimeo.com)、baseUri 一个(https://cdn.example.com),合计 7 个显式源。
这里有一个从数字里读出来的结论:通配源不适合做静态审计基准。https://*.cloudflare.com 展开后的主机集合取决于 DNS,静态不可枚举——你没法写一个「检查所有可达源」的静态测试,因为集合本身是开放的。相比之下,四个维度的显式源加起来是可以枚举的、可以做成快照对比的。
因此官方构建教程给出的省事方案值得注意:教程明确说 UI 资源「最终会在带 deny-by-default CSP 配置的安全 iframe 中渲染」,如果你的应用有 CSS 和 JS 资源,要么去配置 CSP,要么「用 vite-plugin-singlefile 这样的工具把资源打包进 HTML 里」——教程选的就是后者。打包进单文件 HTML 意味着资源域列表可以完全为空,这不仅更安全,还消除了对 CDN 的运行时依赖。教程的依赖清单也印证了这个思路:@modelcontextprotocol/ext-apps、@modelcontextprotocol/sdk,加上一堆 dev 依赖。
除了 CSP,规范还定义了一组 UI 可以「请求」的能力:camera、microphone、geolocation、clipboardWrite,各自映射到对应的 Permission Policy 特性。规范在这里的措辞很谨慎:宿主 MAY 予以满足(通过设置 iframe 的 allow 属性),而应用 SHOULD NOT 假定权限会被授予,应当用 JS 特性检测作为回退。
这条 SHOULD 值得单独强调,因为它是安全工程里少见的、由规范直接给出的「防御性编程」要求:应用必须能在「权限被拒」的情况下正常降级,而不是崩溃或静默失败。
MDN 关于 iframe sandbox 的文档还提供了两个必须知道的判据。其一:「当嵌入文档与嵌入页面同源时,强烈不建议同时使用 allow-scripts 和 allow-same-origin,因为这会让嵌入文档移除 sandbox 属性——使其不比完全不使用 sandbox 属性更安全。」其二,也是更根本的一条:「如果攻击者能在沙箱 iframe 之外展示内容,沙箱就毫无用处——例如查看器在新标签页中打开了该框架;这类内容还应当从独立源提供服务,以限制潜在损害。」
第二条对交付物设计有直接推论:沙箱不是万能护栏,它的前提是内容永远不离开 iframe。任何「在新标签页打开」「导出为独立页面」「复制 HTML 源码」的路径,都是绕过沙箱的路径。设计交付物 UI 时,如果提供了「在独立页面打开」的功能,那份独立页面就必须自己承担沙箱的职责——从独立源提供服务、重新施加 CSP、重新评估权限。
六、第三道约束:分层验证与边际 ROI
前两节解决的是「能不能拿到」和「拿到什么」,这一节解决「拿到的东西对不对」。
交付物的验证有个内在困难:它可能很大、很贵、很慢。给一份 5000 行的生成 CSV 做全量业务校验,成本可能比生成它本身还高。但完全不验证,风险又直接落在用户身上——一份数字错位的报告被拿去汇报,后果比生成失败严重得多。
这里有一个可以量化的框架。把交付物按生成置信度分成若干层,每层有自己的缺陷率 (p)、验证检出率 (d)、误拒率 (f) 和单次验证成本 (c)。设总交付物数 (N=1000),验证预算 (B)(单位:验证成本)。
取一个具体的分层设定:
| 层 | 占比 | 缺陷率 p | 检出率 d | 误拒率 f |
|---|---|---|---|---|
| T1 低置信生成 | 20% | 12.0% | 90% | 3% |
| T2 中置信 | 50% | 3.0% | 85% | 2% |
| T3 高置信生成 | 30% | 0.5% | 80% | 1% |
在「逃逸缺陷的严重度是误拒返工的 10 倍」这个权重下,每单位预算的边际 ROI(净减损除以支出)算出来是:T1 = 1.054、T2 = 0.236、T3 = 0.030。
ROI 严格递减,这带来一个结构性结论:在预算受限时,最优解必然是角点解——把预算优先给 ROI 最高的一层,直到它饱和,多余的才流向下一层。T1 全量核查的成本是 200 单位(20% × 1000 × 1.0),也就是说预算超过 200 之后才开始轮到 T2。
用网格搜索验证这个结论(在预算约束下枚举各层抽样比例):
- 预算 B=150:分层最优抽样 75% / 0% / 0%,逃逸缺陷 24.30,总损失 246.96;均匀抽样 15% 时逃逸 35.17、损失 354.37;不验证逃逸 40.50、损失 405.00。分层比均匀降损 30.31%
- 预算 B=250:分层最优 100% / 10% / 0%,逃逸 17.73、损失 183.55;均匀 25% 时损失 320.62;降损 42.75%
- 预算 B=400:分层最优 100% / 40% / 0%,逃逸 13.91、损失 148.21;均匀 40% 时损失 269.99;降损 45.10%
这里有个值得注意的规律:在所有预算档位下,T3 的最优抽样率始终是 0%。因为 T3 的 ROI 只有 0.030,而 T2 有 0.236、T1 有 1.054——只要预算紧张,T3 就永远排不上。这直接给出一条实践规则:置信度分层不只是抽样依据,更应该是预算分配依据。对绝大多数应用,把验证预算平摊到所有交付物上是错误做法;正确做法是先按置信度分层,再优先验证最可疑的那一层。
同样值得指出的是,预算 B=150 时「不验证」的损失是 405.00,而分层最优只有 246.96——用 15% 的交付物接受率就换来了 39.02% 的损失下降。这说明在缺陷率高的层上做抽样验证,收益是显著的。
还有一个工程细节:验证本身有误拒(false reject)成本。误拒一个合格交付物不是零成本,它需要返工,代价通常是 (r=2) 个成本单位。这解释了为什么不能无脑全量验证——如果某层缺陷率太低而误拒率高,验证反而是净亏损。
七、撤销、幂等与扇出:交付物是副作用,不是消息
前面三层都假设交付物一旦生成就稳定存在。但交付物是副作用——它会被用户撤销、被上游数据变化作废、被合规要求强制删除。这让它在架构上更接近一次写操作,而不是一条消息。
撤销的工程难点在于扇出。一个交付物在被系统里留下多少个引用点?按本文的设计,至少是六处:工具结果消息体、容器内文件、widgetState 状态槽、CDN 对象、客户端缓存条目、审计日志行。撤销时必须让这六处同时失效。
如果一个交付物出现在一条会话的 (N) 个 turn 中,撤销的工作量就是 (N) 处会话引用加上 5 处全局引用。扇出不随时间收敛——会话越长,撤销越贵。这是「交付物一旦进入长对话就很难干净撤销」这个直觉的量化解释。
解法是内容寻址 + 版本号作为幂等键。当 handle 由内容哈希加版本号构成时:
- 重复撤销天然收敛到同一个 handle,撤销操作本身是幂等的
- 重复交付也收敛到同一个 handle,不会产生重复对象
- 版本递增给出了「这份交付物是第几版」的确定答案,撤销可以精确到版本而非整个对象
这条设计与 MCP Apps 规范里的一个机制天然呼应:OpenAI Apps SDK 的示例仓库里,_meta["widgetSessionId"] 正是用来跨轮次保持 widgetState 的——README 写得很清楚,「widget 把工具响应与先前的 widgetState 合并,UI 动作(比如增减数量)反馈回共享状态,因此 assistant 始终看到最新的购物车」。会话 ID + 状态槽 + 内容寻址的句柄,这三个东西组合起来,正好是扇出可收敛的前提。
另一个可借鉴的一手事实来自 LangChain 的 ToolNode 文档,它把「工具调用与工具结果必须配对」这条不变量写成了硬约束:「消息历史中的每个工具调用都必须有对应的 ToolMessage」,并且「ToolNode 强制这条要求:如果更新中没有与该工具调用匹配的 ToolMessage,它会抛出一个 ValueError」。文档还说明并行工具调用的语义:「当模型在一步中调用多个工具时,它们会全部先执行完」,并且只有当这一批里每一个工具都是 return_direct=True 时,agent 才路由到 END;混合调用时它会带着所有 ToolMessage 回到模型,让模型能基于全部结果继续推理。
这三条对交付物设计的含义是直接的:交付物的 ID 与结果消息必须严格配对(这正是扇出图的边),并行产生的多个交付物要有确定的关联(否则撤销时不知道该撤销哪一个),以及当部分交付成功、部分失败时,必须把成功的那些一并回传(否则失败路径会丢掉已生成的产物,产物就泄漏在系统里了)。
八、落地清单:把交付物当成一等对象
综合前面七节,一份可以直接拿去用的工程清单:
存储与取回
- 交付物在生成后立即落盘到自有存储,不依赖执行环境的存活
- 记录执行环境的过期时钟(本文场景是 20 分钟),把「过期前必须取回」写进设计约束
- 不把保活任务当作取回策略的替代品——落盘优先于续命
上下文与传输 4. 上下文里只放描述、句柄、校验信息,不放内容 5. 句柄用内容哈希加版本号构造,天然幂等 6. 内联内容前先算 token 账单:本文场景下 10k×5 的表格内联约 27.7 万 token,句柄引用是 40 token
呈现与权限 7. UI 型交付物优先打包成单文件 HTML,使 CSP 的资源域列表可以为空 8. 渲染权限由宿主裁决,应用对权限被拒必须能降级而不是崩溃 9. 「在新标签页打开」这类逃逸路径必须重新施加沙箱与独立源
验证与撤销 10. 按生成置信度分层,验证预算优先给 ROI 最高的那一层(本文场景 ROI 是 1.054 / 0.236 / 0.030) 11. 交付物 ID 与结果消息严格配对,缺失即抛错而不是静默 12. 并行产生的多个交付物要有确定的关联关系 13. 撤销时清点全部扇出点(本设计是六处),并让重复撤销幂等
可观测 14. 记录每个交付物的完整生命周期:生成、内容哈希、版本、取回时刻、验证结果、撤销时刻
第 14 条常被漏掉,但它是后面所有能力的基础:只有把生命周期记全,才能在事故发生后回答「用户当时拿到的到底是哪一版」。
九、这套方法与相邻工作的边界
有必要说明本文与几个相邻话题的边界。
与 RAG 工程的关系:RAG 解决的是「检索什么进上下文」,交付物工程解决的是「生成出来的东西怎么交到用户手上」。两者在引用溯源上有交集(交付物也要能被溯源),但重心不同。
与结构化输出工程的关系:结构化输出关心的是「模型能不能吐出合规 JSON」,交付物工程关心的是「吐出来的东西怎么变成一个可下载、可撤销、可验证的对象」。前者是格式契约,后者是生命周期。
与生成式 UI 的关系:AI SDK 文档对 generative UI 的定义是「允许大语言模型超越文本并『生成 UI』的过程」,其核心是「把工具调用的结果连接到 React 组件上」。这与本文的 UI 交付层是同一件事的两种实现路径——一种由框架帮你做类型推导(AI SDK 提供 InferUITool 这类类型辅助工具,以及推荐用 parts 属性而非 content 属性来渲染消息的做法),一种由协议标准化(MCP Apps 的 ui:// + _meta.ui.resourceUri)。两条路都可行,但它们都解决不了本文前面四节讨论的存储、取回与验证问题——那部分与 UI 怎么渲染无关,是所有路径的公共下半段。
与 Agent 内核机制的关系:工具调用的重试语义、终止条件、规划与反思,属于 agent 内核,本文一概不涉及。本文只关心当内核跑完之后,产物怎么落地。
参考文献
- Model Context Protocol. SEP-1865: MCP Apps - Interactive User Interfaces for MCP. Extensions Track, Status Final, Created 2025-11-21. https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp.md
- Model Context Protocol. MCP Apps specification, version 2026-01-26. https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx
- Model Context Protocol. MCP Apps Overview. https://modelcontextprotocol.io/extensions/apps/overview.md
- Model Context Protocol. Build an MCP App. https://modelcontextprotocol.io/extensions/apps/build.md
- OpenAI. Code Interpreter guide (containers, container_file_citation annotations, expiration semantics). https://developers.openai.com/api/docs/guides/tools-code-interpreter
- OpenAI. Containers API reference (expires_after, anchor, memory_limit, network_policy). https://developers.openai.com/api/reference/resources/containers
- OpenAI. Tools guide (function calling, tool_choice, strict schema). https://developers.openai.com/api/docs/guides/tools
- OpenAI. Apps SDK developer documentation. https://developers.openai.com/apps-sdk
- Anthropic. Building effective agents. Published Dec 19, 2024. https://www.anthropic.com/engineering/building-effective-agents
- Vercel. AI SDK - Generative user interfaces. https://ai-sdk.dev/docs/ai-sdk-ui/generative-user-interfaces
- Vercel. AI SDK UI - Chatbot (message parts, useChat, InferUITool). https://ai-sdk.dev/docs/ai-sdk-ui/chatbot
- LangChain. Tools (ToolNode, ToolMessage pairing, parallel tool calls, ToolRuntime). https://docs.langchain.com/oss/python/langchain/tools
- MDN Web Docs. The Inline Frame element (iframe sandbox attribute, allow-scripts/allow-same-origin). https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe
- Model Context Protocol. ext-apps repository (spec and SDK for MCP Apps). https://github.com/modelcontextprotocol/ext-apps
- MCP-UI-Org. mcp-ui (UI over MCP). https://github.com/MCP-UI-Org/mcp-ui
- OpenAI. openai-apps-sdk-examples (widgetState and widgetSessionId usage). https://github.com/openai/openai-apps-sdk-examples