Repository navigation
fix(agent): allow tool responses to pass image content to next-round vision input - #7923
lirenjie95 wants to merge 2 commits into
Conversation
…gent loop Address code review feedback on labring#7923: - convertToolResponseContentToPiContent accepts a useVision option and degrades image parts to [Image: url] placeholders when the model lacks vision support, matching the capability filtering applied by loadRequestMessages. - run.ts and build.ts derive useVision from the llm params/vision config and pass it to every conversion site; empty content arrays now fall back to the text response instead of being dropped. - promptToolCallMessageRewrite handles null tool content and preserves image-only tool messages as [Image: url] text instead of silently dropping. - extractAgentLoopCoreToolContent rejects image_url parts with empty urls. - Adds unit tests for the new behavior in piAgentMessage.test.ts, toolCall.test.ts, and workflowToolRunner.test.ts.
|
审查基于当前提交 本次兼容要求是:工具响应在原有字符串基础上增加显式的
另外,plugin #563 将原本任意业务输出中的 验证范围:主库 5 个相关测试文件共 167 项通过;plugin 2 个相关测试文件共 26 项通过。上述边界问题另用实际函数及 mock 依赖的内存实验验证;现有通过的测试尚未覆盖这些回归路径。未运行全量测试,未修改源码。 |
|
按意见都改了一遍:
测试都补了,本地 service/global 的 vitest 和 tsc 全过。 |
…gent loop Address code review feedback on labring#7923: - convertToolResponseContentToPiContent accepts a useVision option and degrades image parts to [Image: url] placeholders when the model lacks vision support, matching the capability filtering applied by loadRequestMessages. - run.ts and build.ts derive useVision from the llm params/vision config and pass it to every conversion site; empty content arrays now fall back to the text response instead of being dropped. - promptToolCallMessageRewrite handles null tool content and preserves image-only tool messages as [Image: url] text instead of silently dropping. - extractAgentLoopCoreToolContent rejects image_url parts with empty urls. - Adds unit tests for the new behavior in piAgentMessage.test.ts, toolCall.test.ts, and workflowToolRunner.test.ts.
3d9dcdf to
61287e3
Compare
61287e3 to
7c303c8
Compare
7c303c8 to
26b232e
Compare
c121914yu
left a comment
There was a problem hiding this comment.
Codex 自动审查:fix(agent): allow tool responses to pass image content to next-round vision input
重要发现
-
[P1] 图片 user 消息打断同轮多个 tool call 的结果回填。 loadRequestMessages:831–840 对每条 tool 消息分别追加 user 图片消息。同一个 assistant 发起 A、B 两个工具调用,且 A 返回图片时,实际出站顺序变成
assistant(A,B) → tool A → user(image A) → tool B;旧版纯文本结果会连续回填 A、B。图片 user 消息出现时 B 尚未响应,破坏工具调用协议,严格校验的模型接口会拒绝下一轮请求。临时用例已复现该顺序;后续mergeAssistantFieldMessages不会修复它。应先连续写回同轮全部 tool 结果,再统一追加图片消息;可对照当前 pi-ai 0.67.3 对连续 toolResult 的分组处理。 -
[P2] Agent V2 所选业务工具仍无法产出视觉输入。 新增提取仅在 workflowToolRunner:211 调用,覆盖 ToolCall 节点的 workflow 工具。Agent V2 的实际路径是
createWorkflowAgentToolProvider → getExecuteTool → dispatchTool / dispatchPlugin:插件返回的{ content: [image_url part] }仍在 dispatchTool:237 整体 JSON.stringify,且 getExecuteTool:251 未返回 content。临时用例确认插件输出成功、response 中保留了图片 JSON,但结果没有独立 content;fastAgent 和 piAgent 最终都只收到字符串。这是本次新增能力遗漏的入口,Sandbox 路径的修复不能覆盖它。请在 Agent V2 业务工具/子工作流结果进入统一 runtime 前提取并逐层透传 content,同时保留原 response。 -
[P2] 手写校验接受共享 schema 明确拒绝的图片 part。 extractAgentLoopCoreToolContent:123–135 只校验 type 和 URL,没有校验
image_url.detail,随后通过类型断言原样返回。例如detail: 'ultra'被共享ChatCompletionToolMessageContentPartSchema拒绝,却会被该函数接受;归一化又保留此字段,可能让下一轮请求被供应商拒绝。临时用例已确认这两种校验结果不一致。旧路径会把这类业务数据作为文本,本次识别后却将其提升为请求字段;应复用共享 schema 做 safeParse,失败时回退文本,并保留现有非空检查。
需求变更
为 #7827 补齐工具取图后的多模态输入:工具结果由仅有 response 字符串扩展为可选 text/image_url parts;Sandbox get_file_url 对图片额外生成 image_url。fastAgent 组合最终文本与图片,piAgent 下载图片并转换为 ImageContent;不支持视觉时过滤或降级。对外共享消息 schema 扩展,未修改 API 路由、数据库结构、权限判断或计费公式。
关键实现路径
- Sandbox:get_file_url → createToolRunner → runSandboxTools → fastAgent/piAgent → 下一轮模型输入。
- ToolCall:workflow 工具输出 → workflowToolRunner 提取 content → normalizeAgentLoopCoreToolRunResult → provider。fastAgent 出站时由 loadRequestMessages 将图片移到 user 消息;piAgent 将图片转为 base64。
- 展示与历史:GPTMessages2Chats 只回折 text parts;图片本身不通过该链路持久化。prompt tool 重写保留文本及图片占位;真实请求会先经过 loadRequestMessages。
- pro:指针未变;核查 workflowBuilder runner、CLI result 对 AgentLoopToolExecutionResult 的调用,现有仅返回 response 的调用保持兼容。
影响范围与越界判断
22 个文件的改动均围绕工具多模态结果、两种 provider、消息适配及局部测试,属于合理扩散。未发现无关依赖升级、部署配置修改、数据库迁移或权限/计费逻辑调整。主要风险是公共请求格式化引入的消息顺序回归,以及 Agent V2 入口覆盖不完整。
代码质量
结构化字段沿 domain/runtime 透传的方向清晰,Sandbox 下载失败与模型能力过滤有兜底。必须修复上述分组、入口覆盖与 schema 校验问题;现有新增测试主要验证单工具和 helper,未覆盖真实多工具消息序列及 Agent V2 插件入口。
代码风格一致性
新增类型使用 Zod 推导,核心 helper 有职责注释,测试位置与相邻实现一致。手写 content 校验应复用已有业务 schema,避免两套契约漂移。
非阻塞建议:type.ts:131–145 的注释把 image_url tool parts 描述为 OpenAI SDK 原生契约,与已安装 openai 6.34.0 的 string | ChatCompletionContentPartText[] 不符。建议明确这是 FastGPT 内部扩展、出站时转换为 user 图片消息;官方 SDK 定义 可供对照。
测试与验证缺口
- 在指定 worktree 安装锁定依赖、构建 SDK;11 个相关原有测试文件共 382 个用例通过。覆盖 chat adapt、LLM utils、prompt tool、fastAgent/base loop、piAgent message/provider、Sandbox tools/get_file_url、workflowToolRunner、Agent V2 dispatchTool。
- 首轮 workflowToolRunner 因跳过安装脚本而缺少 sandbox-adapter 构建产物,补齐 SDK 构建后该文件 17 个用例通过;该环境准备失败不计为 PR 缺陷。
- 三个临时回归用例均在预期断言处失败,分别证实上述三项发现;复现代码与原始日志已保留在任务目录,临时测试文件已清理。未修改业务代码。
- 完整阅读主仓库 diff;pro 固定版本可用、指针未变,核查相关消费者。未运行全量测试、全库类型检查、真实模型请求或完整 Sandbox 网络集成,因此供应商拒绝影响来自契约和调用链分析,并非实测线上返回。
- 当前 CI:build/test 均 SKIPPED;report-coverage、CLA 为 SUCCESS,不能据此认定运行时链路通过。
需要作者确认的问题
PR 描述仍提到 isExactImageUrl/getToolResponseContent 和“整串图片链接兜底”,但固定 head 没有这些函数,普通 response 字符串保持文本。请确认该兜底已从本次范围移除,并同步描述。该文案差异不改变上述已证实问题的结论。
审查结论
needs_changes。本次以 COMMENT 发布,不执行批准、请求修改或合并操作。
- 主仓库 head:
26b232ef8646d3913dbfd90dc2d1ac350c148e66 - base 分支:
main;固定 base / merge-base:749809070b75278e11a1576a8ef05ca90acb2d08 - pro merge-base / head:均为
b2a36080f37af4582c09f4ca1ea36bc85cf3c01d,指针未变。 - worktree:
/Users/yjl/.codex/experiments/pr-review-client-probe/worktrees/pr-7923-83b2fac2e66b(复用宿主目录并保留;主仓库/pro 干净)。
- 同一轮多个 tool call 的图片消息统一追加在全部 tool 结果之后, 避免 assistant(A,B) 变成 tool A -> user(image) -> tool B 打断回填顺序 - Agent V2 业务工具(插件/MCP/HTTP)输出的结构化 content 逐层透传到 agent-loop - content parts 提取复用共享 schema 校验,拒绝 detail 等非法字段并回退文本 - 修正 tool content part 注释:属于 FastGPT 内部扩展,出站转成 user 图片消息
|
三个问题都改了:
另外按非阻塞建议改了 本地 |
c121914yu
left a comment
There was a problem hiding this comment.
Codex 自动审查:fix(agent): allow tool responses to pass image content to next-round vision input
重要发现
-
[P2] MCP 原生图片结果没有转换,仍无法进入视觉链路。 dispatchTool:294 将 MCPClient 返回值直接交给只接受 text/image_url 的提取器;当前 MCP SDK 的图片格式为
{ type: 'image', data, mimeType }。合法图片会导致整组提取失败,回退为 JSON 文本。已用当前 SDK 的 CallToolResultSchema 校验并复现。应在 MCP 边界先转换为 data URI image_url,再按共享 schema 校验。 -
[P2] piAgent 在新增图片时丢失 response 中的其他业务信息。 executeOrdinaryTool:151–155 在 content 非空时直接采用 parts。对
{ jobId: 'task-123', content: [image_url part] }这类合法工具输出,旧路径会把完整 response 发给模型;新路径只剩图片,jobId 丢失,影响后续查询或编辑。已实际执行构建出的 AgentTool 复现:事件 response 包含 jobId,模型 content 中没有。应保留规范化 response 文本并追加图片,与 fastAgent 保持一致,交互恢复路径也需同步。 -
[P2] 内网沙箱预览取图被公共 SSRF 检查拦截。 inlineSandboxPreviewImage:330 使用的 getImageBase64 最终经过 shared axios。当
CHECK_INTERNAL_IP=true且预览代理为内网地址时,配置中的合法预览 URL 会被拒绝;fastAgent 丢弃图片,piAgent 降级文本,核心取图能力失效。已实际调用取图 helper,确认错误为Request to private network not allowed,并验证出站图片被删除。应提供基于当前 sandbox 或精确配置地址的受限读取通道,继续保护任意工具 URL。
需求变更
解决 #7827 中 Agent 通过工具取得图片后只能把链接作为文本传给下一轮模型的问题。工具执行结果新增可选 text/image_url parts,Sandbox get_file_url 对图片追加 image_url;fastAgent 出站将图片移到全部 tool 结果之后的 user 消息,piAgent 将图片下载为 ImageContent。普通 response 字符串继续按文本处理,共享消息 schema 支持内部图片扩展。
关键实现路径
- Sandbox:get_file_url → createToolRunner → runSandboxTools → provider → 下一轮模型请求。
- 工作流 ToolCall:workflowToolRunner 提取 content → normalizeAgentLoopCoreToolRunResult → agent-loop。
- Agent V2 业务工具:dispatchTool(插件/MCP/HTTP)→ getExecuteTool → WorkflowAgentToolProvider → 同一 runtime/provider。
- fastAgent:buildToolMessageContent 保留最终文本及图片 → createLLMResponse → loadRequestMessages 按模型能力过滤、归一化并分组追加图片。
- piAgent:buildPiAgentTools / 交互恢复 → convertToolResponseContentToPiContent → pi-agent-core;recordToolResult 保存标准 transcript。
- 展示与历史:GPTMessages2Chats 提取 tool 的 text parts;核查固定 pro 中 chatAgentHelper 的 formatAIResponse、workflowBuilder handler 等消费者。
影响范围与越界判断
28 个文件均围绕工具结果、多模态请求转换、两种 provider、展示适配和对应测试,属于合理扩散。没有无关依赖升级、部署配置、数据库迁移、API 路由、权限或计费公式改动。公共消息适配的影响范围较大,多工具分组已有覆盖;上述三个入口/数据边界问题仍需修复。
代码质量
共享 schema 校验和单独的 toolContent 模块减少了契约重复;结构化字段已穿过主要 runtime 层。上述问题来自协议边界未转换、provider 组合规则不一致,以及配置可信地址与公共下载策略未衔接。建议分别补充真实 MCP 图片、带额外业务字段的 piAgent 工具结果、内网预览代理用例。
代码风格一致性
新增类型沿用 Zod 推导,核心 helper 有职责和回退说明;未引入动态 i18n key。非阻塞建议:workflowToolRunner.ts:107 和 agentLoopCore/interface/index.ts:44 新增了转导,违反当前 .agents/code/syntax.md 的禁止 re-export 规则;消费者及测试应直接从 domain/toolContent 导入。
测试与验证缺口
- 在指定独立 worktree 安装锁定依赖并构建 3 个 SDK;所选 9 个测试文件、349 个用例全部通过。覆盖 global chat adapt、LLM utils/prompt tool、fastAgent、piAgent message/provider、get_file_url、Agent V2 utils 和 workflowToolRunner。
- 首次运行有 3 个文件因 sandbox-adapter 尚未构建而无法加载,补齐 SDK 构建后仅重跑这 3 个文件,54 个用例通过。环境准备问题已排除。
- 额外以真实函数调用复现上述三项问题;MCP 使用已安装 SDK 的结果 schema,piAgent 执行实际构建的工具,内网取图使用实际下载 helper 和 SSRF 检查。复现脚本及输出保留在任务目录,未修改业务代码。
- 完整阅读主仓库 diff;pro 固定提交可用、指针未变,检查相关共享消息消费者。没有执行全量测试、全库类型检查、真实模型请求或完整 Sandbox 网络集成。
- 当前 CI 的 build/test 为 SKIPPED,report-coverage 与 CLA 为 SUCCESS。
需要作者确认的问题
无额外需求歧义;三个问题均有代码和执行证据。
审查结论
needs_changes
- 主仓库 head:
75abcfab6c30bb2d84d3ade9ff17e3b40ac103ec - base 分支:
main;固定 base / merge-base:749809070b75278e11a1576a8ef05ca90acb2d08 - pro merge-base / head:均为
b2a36080f37af4582c09f4ca1ea36bc85cf3c01d,指针未变化,递归子模块状态已核对。 - worktree:
/Users/yjl/.codex/experiments/pr-review-client-probe/worktrees/pr-7923-e766002e6ffc,复用并保留宿主准备目录。 - 复现材料:
/Users/yjl/.codex/experiments/pr-review-client-probe/runs/7923-e766002e6ffc3998/tool-image-probe.mts及同名 .log。
| toolName, | ||
| params | ||
| }); | ||
| const content = extractAgentLoopCoreToolContent(result); |
There was a problem hiding this comment.
Codex 自动审查 · [P2] 先转换 MCP 原生图片格式。 MCPClient.toolCall 原样返回 SDK 的 CallToolResult,其图片 part 是 { type: 'image', data, mimeType }。这里直接交给只接受 text/image_url 的提取器,遇到标准 MCP 图片时会整组返回 undefined,最终仍把 base64 图片 JSON 当作文本,下一轮模型没有视觉输入。已用当前 SDK 的 CallToolResultSchema 校验合法图片结果并复现。请在 MCP 边界将 image 转为 image_url: { url: 'data:<mimeType>;base64,<data>' } 后再校验,并覆盖真实 MCP 返回格式。
| // 工具返回结构化 content 时优先透传;否则用规范化后的 response 字符串兜底 | ||
| const toolContent = result.content?.length | ||
| ? result.content | ||
| : normalizeToolResponseContent(result.response); | ||
| onToolResult({ call, response: normalizedResponse, assistantMessages, content: toolContent }); |
There was a problem hiding this comment.
Codex 自动审查 · [P2] 保留 response 中的业务字段。 有 content 时这里直接替换完整 response。例如 HTTP/插件输出 { jobId: 'task-123', content: [image_url part] },上游仍把整个对象序列化为 response,但本分支给 piAgent 的实际工具结果只剩图片,模型无法获知 jobId,后续查询/编辑操作因而缺少必要参数。实际调用 buildPiAgentTools(...)[0].execute(...) 已复现;工具事件仍持有 jobId,实际模型 content 却没有。请按 fastAgent 的组合规则保留规范化 response 文本并追加图片,同步处理交互恢复路径。
| } | ||
|
|
||
| try { | ||
| const { completeBase64: base64 } = await getImageBase64(part.image_url.url); |
There was a problem hiding this comment.
Codex 自动审查 · [P2] 让受信任的内网预览地址能够取图。 当部署开启 CHECK_INTERNAL_IP=true,且 AGENT_SANDBOX_PREVIEW_PROXY_URL 使用内网地址时,getImageBase64 内部的 shared axios 会在请求前报 Request to private network not allowed。这里捕获后删除图片,所以有效的 get_file_url 结果仍无法成为下一轮视觉输入;piAgent 调用同一 helper 也只能降级文本。已用配置前缀 http://10.23.45.67:3006 复现实际拒绝及图片被移除。应通过当前 sandbox 的受控读取能力,或为配置中的精确 origin/路径设计受限取图通道,同时保留普通工具 URL 的 SSRF 防护。
Fixes #7827
背景
Agent 工具(如沙盒
get_file_url、workflow 工具节点、Agent V2 业务工具)返回图片后,下一轮模型请求无法把图片作为视觉输入,只能拿到文本链接。改动内容
工具结果从「只有 response 字符串」扩展为「可选 text / image_url content parts」,并让整条工具链路支持主动产出图片:
ChatCompletionToolMessageParamSchema.content支持字符串或ChatCompletionToolMessageContentPart[](text / image_url)。这是 FastGPT 内部扩展契约:供应商的 tool message 只接受字符串或文本 parts,出站时由loadRequestMessages把图片拆成这一轮 tool 结果之后的 user 视觉消息,不会把图片直接发给供应商。AgentLoopToolExecutionResult/AgentLoopCoreToolRunResult/ sandbox 工具结果新增可选content字段,逐层透传。{ content: [{ type: 'image_url', image_url: { url } }] }时提取为 tool content;get_file_url对图片文件在完整文件列表文本之外额外返回 image_url parts。ChatCompletionToolMessageContentPartSchema校验,任一 part 不合法(如detail不是 auto/low/high、URL 为空)整体回退为普通文本。useVision做能力过滤。测试
本地
tsc -p tsconfig.json与相关 vitest 用例全部通过。兼容性
联动 PR
插件侧配套实现:labring/fastgpt-plugin#563