Agent工具调用与预览链路实战:从Function Calling到文件渲染 📅 发布时间:2026/9/16 2:26:15 👁 浏览次数: 如果你最近动手折腾过 AI Agent 方向的东西大概率对这句话很熟悉模型很聪明但不会干活。你让它分析一张表格它给你一段代码你让它做一份报告它给你一份 Markdown 大纲。OpenCowork 这个项目做的工作就是把这种“能说不能做”的 Agent 变成“说一句话任务右侧自动出结果”的工具台在输入框里敲一个自然语言任务Agent 自动规划并调用工具处理文件、清洗数据、生成图表右侧面板实时预览 HTML、表格和 PDF最后把最终产物交付给你。这篇文章不聊 PPT 概念直接拆我自己在 OpenCowork 里做“任务输入 → Agent 工具调用 → 右侧预览 → 最终输出”这条链路时踩过的坑和总结出的实现方案。适合正在做 Agent 开发、function calling、文件类工具集成、或者纠结“结果到底怎么呈现给用户”的朋友。里面的代码和架构思路不是唯一解但都是我实测下来能跑通、好维护、出问题也好定位的路线。1. “能说不能做”不是小事OpenCowork 想解决的痛点1.1 对话式 AI 的尴尬会分析、不会干活传统聊天式 AI 的最大问题是它和“执行环境”之间隔着一层玻璃。无论模型说得多好回到现实世界它碰不到文件、碰不到数据库、碰不到代码执行器。于是大多数普通用户拿到 AI 工具之后做的事情依然是复制粘贴把模型生成的代码复制到编辑器里把模型给的表格公式复制到 Excel 里把那段 Python 脚本保存下来自己跑。OpenCowork 选择把执行环境直接内置进来这个决策本身就回答了“为什么要做一个带工具调用的工作台而不是普通对话机器人”。用户要的不是“建议”是“结果”。当模型可以自己调用 read_csv、自己执行 groupby、自己生成图表并渲染成 PDF 时它的价值就从“参谋”变成了“执行者”。这一步跨过去Agent 才真正落地。我在实际开发中遇到过不少只把 API 接上就号称 Agent 的产品效果一言难尽。原因在于它们只解决了“模型能生成文本”没解决“模型生成之后的动作如何被信任、被验证、被展示”。这就是 OpenCowork 在做任务链路设计时第一个确认的原则每一次工具调用都必须有可见的中间产物用户不需要盲信模型输出而是直接看到右侧预览面板里真的生成了一个文件、一张表、一份 PDF。1.2 预览优先的交互把“执行过程”变成“可视化产物”“预览优先”听起来像 UI 层面的小事实际上它改变了整个 Agent 系统的反馈回路。普通对话里模型输出文本后任务就结束了用户只能靠文字描述脑补结果。预览优先的交互则要求工具执行完毕系统立即把产物以可视化形态挂到右侧面板比如 HTML 页面直接渲染、CSV 转成表格控件、PDF 用 pdf.js 逐页展示。这让原本不可见的中间状态全部暴露出来用户可以随时中止、追问、让 Agent 重跑某个环节。更重要的是右侧预览面板成了模型与用户之间的“公共显示屏”。我之前单独调试 Agent 时经常遇到模型说“我已经生成了报告”但你根本不知道它在哪个路径生成了什么也不知道内容对不对。在 OpenCowork 里右侧预览面板直接提供一个受控的文件浏览与渲染环境Agent 产生任何文件都会自动进入文件树预览服务按 MIME 类型决定该渲染成网页、表格还是 PDF。用户看到的永远是文件和产物本身而不是模型的自我描述。这个设计还顺带解决了一个信任问题当模型在右侧渲染出了它声称的图表和 PDF用户对它的信任会快速建立。如果渲染失败或者产物是空的用户也能第一时间发现而不是被一段漂亮话带过去。对 Agent 开发来说这种“可见性”不只是一个体验优化它是调试器、验证器和纠错机制三合一。2. 一次任务的完整生命周期从自然语言到最终产物2.1 整体流转链路拆解OpenCowork 里一次任务从输入到输出通常走完整条链路用户输入自然语言任务 → Agent 规划器将任务拆成若干可执行步骤 → 调度器按步骤调用外部工具 → 工具执行结果写入本地工作区 → 预览服务把工作区中的产物转换成可访问的 URL → 前端右侧面板加载这个 URL → 用户确认无误后Agent 汇总输出最终产物。这个过程听起来很顺滑真正实现时每一环都有不少细节。链路的第一环是任务理解模型需要把“帮我把这个 CSV 的销售额按月统计一下生成折线图”这种口语化描述转换成具体工具调用序列。这一步如果只丢给模型一个工具列表效果通常很差。我在 OpenCowork 里做了两层约束第一层是系统提示词里明确“你必须先规划再执行”要求模型输出 JSON 格式的 plan第二层是调度器对 plan 做结构校验发现格式不合法就拒绝执行并请求模型重新规划。这样虽然会增加一次模型交互但大幅降低了后续工具调用阶段的崩溃概率。中间环节是工具调用这层最重要的不是“模型聪明”而是“工具定义清晰”和“参数校验严格”。OpenCowork 的工具注册表里每个工具都有名称、描述、参数 schema、执行函数四件套调度器先把模型生成的 arguments 做 JSON 解析和 schema 校验再传给真正执行函数。最后一步就是把工具产出物交给预览服务由它生成可访问的渲染页面。这套链路的好处是每一层职责单一模型只负责“决策”调度器只负责“搬运和校验”预览服务只负责“展示”任何一层出问题都能快速定位。阶段核心模块关键产物常见失败点任务理解Agent 规划器结构化 plan模型输出非 JSON、步骤缺失工具调度调度器 执行器文件 / 数据对象arguments 解析失败、工具名不明确结果展示预览服务预览 URLMIME 判断错误、渲染跨域用户确认前端面板产物可视化大文件渲染卡死、编码不对最终输出汇总器下载链接 / 结论文本文件路径拼错、产物未落盘2.2 分层架构Agent 负责决策Harness 负责运行社区里经常讨论 harness 和 agent 的区别我自己的理解是Agent 是那个“思考”的部分负责看上下文、定计划、决定调哪个工具Harness 是那个“运行”的部分负责把 Agent 的决策翻译成真实的进程、文件操作和 API 调用。OpenCowork 的调度器本质上就是一个 Harness它不自己思考但负责循环、重试、超时、错误捕获保证 Agent 每一次“想法”都能被安全执行。为什么要把 Agent 和 Harness 分开而不是做成一个“全能体”因为我踩过一整块的坑所有逻辑都塞进一个循环里模型既要想下一步动作又要处理工具返回的错误还要维护对话状态。一旦工具执行失败了模型会开始胡编乱造甚至自己脑补一个成功结果继续往下走。分开之后Harness 层把工具的错误结构化比如“read_csv 失败文件不存在路径为 xxx”再把这个错误作为新消息交给 Agent强迫它基于事实重新规划。这个改动让整个系统的稳定性提升了一个量级。我在 Harness 层还加了一个重要机制最大调度步数限制。每个任务最多允许模型调用 10 次工具超出就强制终止并把已生成的产物展示给用户。很多 Agent 死在“模型自己绕圈出不来”上设置这个上限之后至少不会让整个任务无限循环白烧 token 也让用户干等。OpenCowork 现在的架构里Agent 只出“下一步”这个决策Harness 负责“这条路到底能不能走通”两者各司其职问题排查起来非常清楚。2.3 为什么要在右侧单独留一块预览面板右侧预览面板不是锦上添花它承担着四个核心职责。第一是消除“黑箱”用户能实时看到 Agent 干了什么而不是听模型转述。第二是区分“过程”和“结果”用户需要确认某个中间 CSV 长什么样才能在下一步让 Agent 继续处理。第三是给多模态产物留位置纯文本聊天根本无法容纳一个完整的 PDF、一张大表格独立的预览面板天然解决这个空间问题。第四是把预览服务变成统一入口文件路径、MIME 类型、渲染方式都通过它分发前端不需要为每种文件类型单独写逻辑。预览面板的连接方式也需要慎重设计。OpenCowork 没有采用“前端直接读取本地文件”的方案因为浏览器的安全限制会把本地文件访问堵死。正确做法是构建一个本地预览服务监听 localhost 端口通过 HTTP 流把工作区内容返回给前端。这样预览 URL 形如http://127.0.0.1:9527/preview/report.pdf前端 iframe 直接请求这个地址即可。选择 127.0.0.1 而不是 0.0.0.0 还有一个安全考虑避免同一局域网的其他设备访问到你的本地工作区文件。3. 工具调用层让 Agent 学会调用工具的工程细节3.1 Function Calling 机制与工具注册表设计Function Calling 说白了就是让模型输出一个结构化指令而不是一段自然语言。模型本身不执行任何工具它只负责在你给定的工具列表里做选择并生成符合参数要求的 JSON真正的执行由外层代码完成。OpenCowork 的工具注册表采用统一 schema 管理每个工具的定义基本长这样{ name: read_csv, description: 读取 CSV 文件返回表格数据适用于分析表格类任务, parameters: { type: object, properties: { path: { type: string, description: CSV 文件的相对路径 }, encoding: { type: string, description: 文件编码默认 utf-8, enum: [utf-8, gbk, gb2312] } }, required: [path] } }这段 schema 看着简单但有几个细节直接影响效果。description 必须写清楚“什么时候该用这个工具”以及“参数到底是什么意思”因为在模型眼里所有工具都是扁平列表它只能靠 description 做判断。如果 description 写得模棱两可比如“读取文件”模型面对 read_csv 和 read_excel 时就会频繁选错。我建议把所有高频工具的描述写成“当用户要求……并且满足……时使用”这样模型误调用的概率会明显下降。工具数量同样不能贪多。一开始我在 OpenCowork 里挂了 30 多个工具结果模型每轮都要在 30 个选项里挑三拣四不仅慢还经常挑错。后来我把低频工具收进子工具组模型先调用一个“数据分析工具组”再在组内做二次选择。这相当于给模型做了两级路由准确率提高了很多。工具注册表什么时候加新工具、加什么新工具应该取决于真实任务日志里反复出现的需求而不是提前把所有可能用到的功能全塞进去。3.2 嵌套 arguments 问题为什么会反复出现“嵌套 arguments 的问题反复”这条我在热搜里看到时心里咯噔一下这正是我调 OpenCowork 时被折磨最久的问题。典型场景是Agent 先调用一个工具拿到了结果紧接着想把整个结果作为另一个工具的输入参数。比如第一步 read_csv 返回了 DataFrame 的摘要第二步模型想调用一个自定义分析函数并把第一步的完整结果塞进分析函数的参数里。这时问题出现了模型生成的 arguments 经常长这样{ tool_calls: [ { function: { name: analysis, arguments: {\input\:\{\\\month\\\:\\\2025-03\\\,\\\source\\\:\\\data.csv\\\}\} } } ] }外层 arguments 是一个字符串里面又包了一层被转义过的 JSON 字符串再往里还有一层。这种嵌套把 parse 逻辑彻底搞乱你用json.loads(arguments)解析一次得到的 input 字段依然是字符串而不是对象再解析一次才拿到真正的数据。如果模型偶尔生成了一层没转义、另一层转义了或者某层用了单引号整个工具调度直接崩溃。我在调试日志里看到过不下十种嵌套写法每次都想骂人。根本原因在于模型在生成 JSON 字符串时是在做“字符级别的 token 预测”它对自己之前输出的转义斜杠没有全局感知能力。尤其在上下文很长、工具调用已经进行了四五轮之后模型对“当前应该传对象还是字符串”会逐渐迷失。这也是为什么纯文本提示词无论你怎么强调“参数必须是合法 JSON”都不完全管用因为模型不是被规则难住的是被序列化转义的表达方式难住的。3.3 处理嵌套参数的推荐实践针对嵌套 arguments我最后在 OpenCowork 里用了一套组合拳总算把这个问题压到了可接受范围。第一是返回给模型的工具结果尽量“摘要化”不要把巨大且结构复杂的数据原样丢回上下文而是转成简短的表格摘要或统计信息这样模型就没有机会在下一轮里把整个对象嵌套进新的参数中。第二是执行器在调用任何工具前对 arguments 做递归解析def safe_load_arguments(raw): if isinstance(raw, dict): return raw if not isinstance(raw, str): return raw data json.loads(raw) # 如果解析出来还是字符串继续递归展开 return safe_load_arguments(data)这段代码会递归地剥开所有“字符串里套对象”的结构直到拿到真正的 dict。它不能解决模型生成畸形 JSON 的问题但能解决“看起来畸形其实是多层转义”的问题。事实证明一大半嵌套错误都是因为没有做递归展开只做了一层json.loads就报错放弃了。第三个实践是尽量避免设计“参数里再包一个对象”的工具 schema。比如分析函数与其让模型传input: {month: ..., source: ...}这种复杂对象不如直接把参数拆成顶层字段month和source并列。扁平化的参数结构对模型友好得多生成转义嵌套 JSON 的概率会大幅下降。我在重写工具定义时反复检查每个参数只要能拍平就拍平能不传大数据结构就不传。这个原则看着笨但确实让执行失败率降了差不多一半。4. 右侧预览层的实现HTML、表格、PDF 一个都不能少4.1 本地文件预览为什么会被浏览器拦截做文件预览最先撞上的拦路虎不是渲染技术而是浏览器对本地文件的安全策略。当时我把 Agent 生成的 HTML 文件路径直接丢给前端 iframe结果右侧面板弹出一句让人血压飙升的提示“你尝试预览的文件可能对你的计算机有害。如果你信任此文件以及其来源请打开此文件。” 这其实是 Windows 的 Mark of the WebMOTW机制在起作用Windows 会给所有从网络下载、或者由本地代理写入的文件打上一个Zone.Identifier标记浏览器只要检测到 HTML 文件的 alternate data stream 里有ZoneId3就会判定为“来自 Internet 的文件”然后弹警告。理解这个机制之后解法就清晰了不要让浏览器直接访问工作区里的真实文件路径而是让 OpenCowork 启动一个本地预览服务由服务端读取文件内容并通过 HTTP 流返回。因为http://127.0.0.1:9527/preview/xxx.html的响应不携带 MOTW 标记浏览器就会把它当作普通网页正常渲染。这也顺带解释了为什么很多开发者在本地双击自己生成的 HTML 没问题但用户下载后打开就会看到安全警告因为下载过程给文件贴上了“来自网络”的标签。如果你还是想保留“双击打开本地文件”的体验也有一个补救办法在生成文件后用 PowerShell 清理掉 Zone.Identifier命令是Unblock-File -Path 文件名。但我不推荐把这个作为主要方案因为每次生成都要清理一次而且杀毒软件可能把自动清理行为当恶意操作。OpenCowork 的稳妥做法就是把所有预览统一收敛到本地 HTTP 服务对用户来说只是“在工具里看”完全绕开浏览器对本地文件的那套审查逻辑。4.2 HTML 结果预览与沙箱隔离当 Agent 生成的 HTML 需要直接在右侧面板渲染时必须用 iframe 做沙箱隔离。这个不是可选项是安全底线。Agent 的 HTML 可能包含脚本脚本可能读取页面数据、发起网络请求、甚至通过同源漏洞接触父页面。OpenCowork 给预览 iframe 设置了严格属性iframe sandboxallow-scripts srchttp://127.0.0.1:9527/preview/demo.html?t1710000000000 /iframesandboxallow-scripts允许脚本执行但不允许allow-same-origin这样脚本就被困在独立起源里拿不到父页面 DOM。如果业务确实需要让预览页面和主应用通信可以用postMessage做白名单消息通道而不是放开同源限制。我见过有团队图省事直接去掉 sandbox 属性结果预览里一个恶意脚本就能把整个工作台搞瘫真的别抱侥幸心理。HTML 预览还有一个容易被忽略的细节缓存。Agent 可能反复覆盖同一个文件而浏览器对相同 URL 的 iframe 会自动缓存用户看到的永远是旧版本。解决办法是在预览 URL 后面拼时间戳参数也就是上面代码里的?t1710000000000每次预览都生成新查询串强制浏览器重新拉取。这个问题的表现形式很迷惑——明明文件内容已经更新右侧面板毫无反应排查了半天才发现是缓存。4.3 表格数据预览与大数据量处理表格预览在 OpenCowork 里走的是“解析 → 清洗 → 渲染”三件套。Agent 处理后生成的 CSV 或 Excel 文件进入预览服务预览服务调用解析器把数据转成标准 JSON再交给前端渲染成可排序的表格控件。CSV 解析我推荐用 Papa ParseExcel 可以用 SheetJSxlsx两个库都成熟稳定能处理大部分常见格式。这里要特别注意编码问题中文环境下 CSV 最常见的三个编码是 UTF-8、带 BOM 的 UTF-8 和 GBK解析器需要自动探测编码否则表格里会全是乱码用户第一眼就会觉得系统坏了。大数据量表格不能无脑渲染。一个 10 万行的 CSV如果前端一次性插入 10 万行 DOM 节点浏览器必定卡死。OpenCowork 目前的策略是分页加截断默认只渲染前 500 行同时显示“共 10 万行”的总行数提示需要继续查看时提供简单的上一页和下一页按钮每次只渲染 500 行。这个方案虽然朴素但效果很稳定。如果你对性能有更高追求可以上虚拟滚动只渲染可视区域内的几十行但开发成本和调试难度会明显上升我建议先从分页截断做起。渲染之前还要做一步数据类型识别。我踩过一个坑所有列都被解析成字符串用户在表格里没法按照销售额排序因为“1000”和“999”按字符串排序时是 “1000” 排在 “999” 前面。后来我在预览服务里加入类型推断数字列转成 number、日期列转成标准格式、纯文本列保持原样。这样表格控件才能正确排序、过滤和统计。类型识别不需要很复杂试着把值转成 float 和 Date成功率超过一定比例就算识别成功。4.4 PDF 解析、渲染与中文乱码排查PDF 在右侧面板的预览绕不开 pdf.js。它是 Mozilla 开源的 PDF 渲染库能在浏览器里把 PDF 解析成 canvas 渲染出来。基本用法是const pdfjsLib window[pdfjs-dist/build/pdf]; pdfjsLib.GlobalWorkerOptions.workerSrc /static/pdf.worker.min.js; const task pdfjsLib.getDocument({ url: previewUrl }); task.promise.then((pdf) { pdf.getPage(1).then((page) { const viewport page.getViewport({ scale: 1.5 }); const canvas document.getElementById(pdf-canvas); canvas.width viewport.width; canvas.height viewport.height; page.render({ canvasContext: canvas.getContext(2d), viewport }); }); });这里最容易翻车的是 workerSrc 路径。很多人本地演示时用相对路径没问题一部署到子路径就白屏因为 worker 脚本没找到。我的建议是 workerSrc 用完整 URL并且和实际部署路径保持一致或者直接用 CDN 上的官方 worker 文件。PDF 中文乱码是另一个高频问题。如果 PDF 里的中文字体没有被嵌入只是引用了系统字体名pdf.js 渲染时找不到对应字体就会显示成乱码方块或者干脆空白。这个问题在“生成 PDF”的场景里更常见用 reportlab 这类库生成 PDF 时默认字体不支持中文必须显式注册一个中文字体文件比如STSong-Light或者下载一个 Noto Sans CJK 的 TTF。如果 PDF 来源是用户上传的扫描件乱码只能通过 OCR 解决那已经超出预览范畴了。我的经验是预览层不要试图“修复”字体问题正确做法是在生成端就强制嵌入字体让 PDF 从一开始就是自包含的。因为预览层的修复手段非常有限而且很容易把性能和显示效果都搞崩。5. 端到端实操输入一个数据分析任务跑通全流程5.1 定义一个可复现的示范任务为了把上面的理论串起来我准备了一个仿真任务你可以在 OpenCowork 里照着试。任务描述就一句话“读取工作区的 sales.csv按品类统计销售额和订单量生成柱状图输出一份 PDF 分析报告并在预览面板里展示。” 这个任务覆盖了文件读取、分组聚合、图表生成、PDF 导出、最终预览五个环节基本就是标题里“输入任务 → Agent 调用工具 → 预览结果 → 最终输出”的标准流程。实际执行时OpenCowork 会在工作区准备一份 sales.csv字段大概是date, category, amount, quantity。用户不需要知道文件长什么样Agent 需要自己去探测列名、数据量和是否存在缺失值。这个“探测”过程对 Agent 很重要因为模型并不预先知道 CSV 的具体结构必须通过工具调用获取真实信息而不是凭空假设列名是category就往下走。我在调试中见过模型跳过探测直接拿假列名写代码然后整个分析全错。5.2 Agent 规划与工具执行过程实录当任务被提交后Agent 首先输出一段规划 JSONOpenCowork 的 Harness 层校验通过后开始逐项执行。规划结果大致是{ plan: [ { step: 1, tool: read_csv, arguments: { path: sales.csv } }, { step: 2, tool: inspect_data, arguments: { rows: 5 } }, { step: 3, tool: group_by_category, arguments: { metric: sum } }, { step: 4, tool: render_bar_chart, arguments: { x: category, y: total_sales } }, { step: 5, tool: build_pdf_report, arguments: { chart_path: output/chart.png } } ] }这个规划是理想状态实际运行时几乎都会在中途加步骤或者回退。比如 read_csv 之后Agent 发现category列有缺失值需要在 group_by 之前加一步“剔除缺失行”。这就体现了我前面说的 Agent 与 Harness 分离的好处Harness 把工具返回的错误和警告结构化回传给 AgentAgent 基于真实数据调整下一步而不是死板地执行原计划。OpenCowork 在日志面板里会记录每一步的状态是成功、警告、失败还是被跳过遇到失败项还能直接查看异常堆栈。前面提到的“工具返回摘要化”在这一步意义重大。read_csv 工具不会把整个 DataFrame 的 10 万行数据塞给模型而是返回“列名数组、数据类型、前 5 行示例、总行数”这些信息足够让模型决定下一步怎么做又不会把上下文窗口撑爆。真正需要完整数据的是图表生成工具和 PDF 报告工具它们直接读取工作区文件不经过模型上下文。这种“给模型看摘要给工具传文件路径”的模式是 Agent 处理大体量数据的关键。5.3 从预览到最终输出产物如何收敛工具执行完之后先进入右侧预览面板的是柱状图。OpenCowork 的预览服务把output/chart.png转成图片 URL前端在右侧面板里直接展示。紧接着 build_pdf_report 工具完成预览服务同时生成一个 PDF 的预览链接用户可以在右侧切换到 PDF 页签查看效果。这个阶段最关键的是“预览状态”与“最终产物”区分左侧面板的柱状图只是过程产物右侧的 PDF 报告才是最终交付物所以在最终输出区域Agent 会把 PDF 链接标记为主要结果并且附上一段汇总说明。最终输出不只是一个链接。OpenCowork 的汇总器会把整个任务的执行路径、关键统计数字和产物清单整合起来形成一个结构化的交付面板左边是“任务结论”比如各品类销售额的排序右边是“产物列表”点击即可预览或下载。这样用户既能看到结论又能验证结论的来源避免“模型说它对但其实给了一个空文件”的尴尬情况。整个链路跑完后用户对中间每一步都有把握因为右侧预览面板已经把过程全都摊开在眼前。6. 高频问题排查与避坑记录6.1 常见问题速查表现象可能原因推荐排查方法Agent 生成畸形 arguments长上下文下 JSON 转义混乱使用原生 function calling、递归解析参数、扁平化参数结构HTML 文件无法预览或空白iframe sandbox 过严或 MIME 错误检查是否设置 allow-scripts、确认服务返回 text/html出现“文件可能对你的计算机有害”Windows MOTW 标记通过本地预览服务走 HTTP 流避免 file:// 直开PDF 预览白屏pdf.js worker 未加载或跨域检查 workerSrc 路径、跨域配置PDF 中文乱码生成端未嵌入中文字体在生成工具里注册 Noto Sans CJK 并强制嵌入字体大表格预览卡死一次性渲染整份数据截断前 500 行 分页 / 虚拟滚动Agent 执行中途报错终止未捕获异常导致链路中断在 Harness 层添加全局 try/catch、错误结构化回传预览内容不刷新浏览器缓存了旧 URLURL 末尾添加时间戳参数这张表基本覆盖了我做 OpenCowork 预览与工具调用时遇到的所有典型问题。每次遇到类似现象先按表格顺序排查基本能在十分钟内定位。对比多个现象会发现很多问题不是 Agent 本身傻而是外层架构没给足安全感和容错空间参数不规范就解析失败文件有 MOTW 就预览失败字体不嵌入就渲染乱码。6.2 三条亲手踩出来的经验第一条经验工具返回给模型的内容一定要摘要化给前端展示的内容一定要完整化。这看着像两个需求其实是同一个问题的两面。模型不需要看到 10 万行原始数据也能做出正确的分析决策它只需要知道列名、行数、示例和前几条统计信息但用户在预览面板里希望看到完整可交互的表格。把这两条链路分开处理模型上下文清爽前端性能也稳否则两边都会出问题。第二条经验不要信任模型第一次生成的参数。哪怕是最强的模型在工具调用时也可能给出一个“看起来合理但根本不存在”的文件路径或者把参数类型搞错。OpenCowork 的调度器里强制加了一层 schema 校验和一次参数修正机会校验失败时把错误信息回传给模型让它看一眼自己的错再重新生成。这一步会多消耗一次模型调用但相比直接执行一个错误参数导致整条链路崩掉成本低得多。第三条经验预览面板不只是给用户看的它在开发调试阶段的价值可能更大。每当我怀疑 Agent 是不是在某一步出错时第一件事就是看预览面板里的中间产物。工具逻辑对不对、文件生成没生成、渲染踩了什么错全都能在预览里直观看到。可以说如果没有这个右侧预览面板我对 OpenCowork 的调试时间至少要多出一倍。所以如果你在做一个带工具调用的 Agent请务必把“每个中间产物都可视化”当成第一优先级来做。