feat(agent): gate advanced image models behind user confirmation
**PR:**feat(agent): gate advanced image models behind user confirmation (#4580)
source_automation: “voyager-merged-pr” run_date: “2026-07-10” anchor_pr_number: 4580 pr_number: 4580 pr_title: “feat(agent): gate advanced image models behind user confirmation” pr_url: “https://github.com/adastralab-ai/voyager/pull/4580 ↗” author: “Horcrux / magicismight” merged_at: “2026-07-09T17:18:42Z” modules: [“backend/workers/agent/src/tools”,“backend/workers/agent/src/prompts”,“backend/workers/agent/src/routes”,“agent-eval/cases/image”,“agent-eval/asserts”] files_changed: 15 learning_tags: [“agent-runtime”,“image-generation”,“user-confirmation”,“credit-control”,“tool-schema”,“terminal-result”,“agent-eval”] business_line: null related_prs: [4314,4344,4309,4516,4389] line_stage: null open_questions: [] feishu_doc_url: “https://my.feishu.cn/docx/VtmndMxIJoKioExqbN9chJWhndc ↗”#
正文来源:飞书学习文档 ↗。以下为通过个人 Feishu API 获取并转换后的完整 Markdown 正文。
Voyager Merged PR 学习 - 2026-07-10 - PR #4580#
今日选择#
**PR:**feat(agent): gate advanced image models behind user confirmation (#4580)
**作者:**Horcrux / magicismight
**Merge 时间:**2026-07-09T17:18:42Z(Australia/Melbourne 2026-07-10 03:18:42)
链接:https://github.com/adastralab-ai/voyager/pull/4580 ↗
**模块:**backend/workers/agent/src/tools, backend/workers/agent/src/prompts, backend/workers/agent/src/routes, agent-eval/cases/image, agent-eval/asserts
**学习标签:**agent-runtime, image-generation, user-confirmation, credit-control, tool-schema, terminal-result, agent-eval
为什么值得学#
- 它把“高级模型更适合”从 agent 自主决策改成用户授权协议:模型只能触发确认卡,不能在同一轮直接传 advanced modelId。
- 它没有只靠 prompt 约束,而是在 image_generation runtime 里加 guard:未授权的 2K/4K + Lite 组合终态失败,不再静默升级或静默降级。
- 它把失败结果的 message 透传给模型,避免把业务 guard 错误归因为“服务临时故障”。
- 它同步更新 eval harness:记录 toolSequence、暴露 choice card、引入 noAdvancedImageArgs 断言,测试的是协议行为而不是最终文本。
关键代码#
1. tool schema 把授权规则前置#
**文件:**backend/workers/agent/src/tools/toolDefs.ts,PR diff hunk @@ -262 和 @@ -273
.describe(
"Optional; unset renders 1K on the default model... Resolutions above 1K run only on advanced models, so they follow the same approval rule as modelId... without one the call fails."
)
.describe(
"Optional. NEVER set this yourself: advanced models ... are only ever the user's decision. Pass an advanced modelId only when the user pinned that model ... or answered a request_user_choice..."
)plaintext设计点:把 tool 参数说明从“模型自己挑合适模型”改成“模型只能执行已授权选择”。这类约束必须写在 schema/tool description 层,因为这里是模型决定参数的最近边界。
2. runtime guard 删除静默升级路径#
**文件:**backend/workers/agent/src/tools/imageGenerationTool.ts,PR diff hunk @@ -81
const resolvedModelId = modelId ?? imageModelId ?? DEFAULT_IMAGE_MODEL_ID;
if (resolution != null && resolution !== "1K" && resolvedModelId === NANO_BANANA_LITE_IMAGE_MODEL_ID) {
return {
status: "failed",
reason: "error",
errorCode: "resolution_requires_approved_model",
message: "The requested resolution needs an advanced image model, which only the user can approve...",
};
}plaintext设计点:之前 explicit 2K/4K 会把 fallback model 升到 gpt-image-2;现在如果没有 pin 或 card approval,就终态失败。这样 credit spending 的边界从 prompt 约束变成 runtime invariant。
3. failure message 作为恢复指令传给模型#
**文件:**backend/workers/agent/src/tools/imageTaskTool.ts,PR diff hunk @@ -47
value:
output.message ??
`The image service failed server-side (${output.errorCode ?? "unknown error"}) ...`,plaintext设计点:同样是 failed/error,业务 guard 和后端临时故障需要不同恢复策略。允许 tool result 携带 message 后,模型能把“去询问用户授权”当成下一步,而不是把它讲成服务失败。
4. eval 断言禁止未授权高级参数#
**文件:**agent-eval/asserts/noAdvancedImageArgs.ts,新增文件 @@ -0,0 +1,41
const offending = seq
.filter((step) => step.name === "image_generation")
.map((step) => ({ modelId: step.input?.modelId, resolution: step.input?.resolution }))
.filter((args) => ADVANCED_MODELS.has(args.modelId ?? "") || ADVANCED_RESOLUTIONS.has(args.resolution ?? ""));plaintext设计点:eval 不再只看最终回答是否“看起来合理”,而是读 trajectory metadata,确认 agent 没有偷偷把高级模型或高分辨率塞进 tool args。
和最近学习记录的关系#
有直接关系。最近几条学习记录围绕 image generation 的跨层契约展开:#4314 把 gpt-image-2 迁到 AI Gateway route;#4344 把附件图作为 referenceAssetIds 进入工具边界;#4309 把 Nano Banana Lite 作为跨层 model contract;#4516 把 terminal image failure 表达成结构化结果;#4389 把 home composer 的 creation directive 和 image model routing 接进 agent protocol。
#4580 是这条链路的 credit-control / approval-control 后续:当 composer 或对话能影响 image model 时,系统必须区分“模型觉得应该升级”和“用户已经授权升级”。它把这个区别落在 prompt、tool schema、runtime guard 和 eval 四层。
我会怎么吸收#
- 涉及成本、权限、外部副作用的 agent 工具,不要只靠 prompt。需要把“未授权不可执行”写成 runtime guard,并返回模型可恢复的结构化失败。
- 用户偏好或 pin 不是绝对锁。更好的模型是“强默认 + 明确条件下重新询问”,这样既尊重用户选择,也保留处理超出能力请求的空间。
- eval 应该抓 tool trajectory,而不是只评估 finalText。特别是 agent 协议类需求,关键回归通常藏在 tool args 里。
边界/风险#
- PR 自己说明单轮 harness 覆盖不了多轮状态:例如“本次对话都使用”后的后续轮、拒绝后不重复询问、对已生成结果不满意后重新打开选择,这些还需要线上或多轮 eval 覆盖。
- modelId description 很长,短期可控,但长期可能变成高熵 prompt 规则堆叠;后续如果确认协议稳定,可以考虑把 approval state 做成更显式的 runtime/session state。
- PR 还发现 place-generated-image eval 的旧断言和 main 上 skill 行为不一致,这不是本 PR 风险,但说明 image eval 套件仍有历史债。
候选说明#
今天(Australia/Melbourne 2026-07-10)窗口查到 6 个 merged PR;昨天窗口查到 15 个。最终选 #4580,因为它有明确的工程设计:用户授权、成本控制、tool/runtime 双边界和 eval 验证。#4581 与它同主题但更偏 composer UI/入口;#4582 是 media panel source tabs;#4592 是 2 行 focus 小改;#4544/#4424 偏 brand/settings polish。日志里已记录 #4389,因此跳过去重,没有重复讲。