Prhub

#33898 [inkling] Render tool-result media instead of coercing content to str

原始 PR 作者 ekzhang 合并时间 2026-08-08 14:39 文件变更 2 提交数 3 评论 5 代码增减 +147 / -20

执行摘要

修复 Inkling tool 结果渲染图片报 400 错误

PR body 明确指出:Multimedia (image) tool results did not render correctly in Inkling's OSS renderer, leading to 400 Bad Request errors。提交信息进一步给出根因:render_inkling_messages 对 tool 角色特判,把 content 塞进 _expect_string_content,该函数对非 str 内容一律抛 TypeError;而 Anthropic 适配器的 _convert_tool_result_content 对任何不止一个文本块的 tool_result 都会产出 list,于是携带图片的 tool 结果(Claude Code 截图、读回渲染文件)让整个请求失败:TypeError: message content must be a string for this Inkling role, got list。

值得精读。核心设计点是"渲染器输出与 MM processor 媒体收割之间的占位符计数契约",这类隐式契约在推理服务里很容易被忽略,本 PR 用专门测试把它显式固化;同时展示了如何把特判分支收敛为复用统一解析逻辑。建议关注后续 Inkling 多模态链路(如 audio part)是否也有类似契约需要补齐。

讨论亮点

本次合并没有行内 review 评论,讨论集中在流程与验证:yhyang201 在评论中 @JustinTong0323 表示 LGTM,随后两位 reviewer 均 APPROVED;CI 曾触发重跑,ispobock 用 /rerun-test 单独跑 test/registered/unit/parser/test_inkling_renderer.py,github-actions 机器人回报 1 个测试全部通过。PR body 中的验证数据(106 条请求回放,19 条报错降到 0 条)是合入的重要依据。

实现拆解

  1. 变更入口:render_inkling_messages(python/sglang/srt/parser/inkling_renderer.py)主循环中的 if role == "tool" 分支。
  2. 核心逻辑改造:将 _expect_string_content(message.get("content", "")) 替换为 list(_iter_render_parts(...)) 遍历,逐 part 调用 _append_message,使 text、image、audio 都按自身类型渲染,并在 tool 分支内显式拒绝 thinking part,与通用分支语义对齐。
  3. 边界处理:对空 content("" / None / [])回退为 [("text", "")],避免已应答的 tool_call 悬空;tool_namestr() 包裹,保证 author_name 始终为字符串。
  4. 配套清理:删除不再被引用的 _expect_string_content 辅助函数。
  5. 测试配套:test/registered/unit/parser/test_inkling_renderer.py 新增 6 个用例,覆盖图片+文本混合渲染、占位符计数与图片数一致、多文本块逐一渲染、空结果仍产出块、author 回退到 tool_call_id、拒绝 thinking part。
  6. 验证配套:作者手工回放 106 条采集的 agent 请求(含 14 条图片、5 条多文本 tool 结果),修复前 19 条抛错,修复后 0 条,且无占位符/媒体数量错配。
文件 模块 状态 重要度
python/sglang/srt/parser/inkling_renderer.py 渲染器 modified 7.04
test/registered/unit/parser/test_inkling_renderer.py 渲染测试 modified 7.01

关键符号

render_inkling_messages _iter_render_parts _append_message _expect_string_content

关键源码片段

python/sglang/srt/parser/inkling_renderer.py core-logic

核心修复文件:tool 分支从字符串强转改为逐 part 渲染,删除 _expect_string_content,是本 PR 的行为变更主体。

# render_inkling_messages 主循环中处理 tool 角色的分支(合并后版本)
for message_index, message in enumerate(message_list):
    if message_index == leading_system_count:
        append_effort()
    role = _expect_role(message)
    if role == "tool":
        # 修复前这里用 _expect_string_content 把 content 强转成 str,
        # 而 Anthropic 适配器对非单文本块的 tool_result 产出 list,
        # 带图片的 tool 结果(agent 截图、读回 PNG)会抛 TypeError 导致请求 400。
        # 现在复用 _iter_render_parts 逐 part 渲染,MM processor 会按媒体项数量展开占位符,
        # 两者必须保持一致,否则渲染与收割两趟会错位(desync)。
        tool_name = str(
            message.get("name")
            or tool_call_id_to_name.get(message.get("tool_call_id") or "", "")
        )
        tool_parts = list(_iter_render_parts(message.get("content", "")))
        if not tool_parts:
            # 空 tool 结果也要产出一个空 text 块,否则它对应的 tool_call 会悬空
            tool_parts = [("text", "")]
        for kind, text in tool_parts:
            # thinking part 只允许出现在 assistant 角色,tool 角色出现直接报错
            if kind == "thinking":
                raise ValueError("Inkling thinking parts require role='assistant'")
            _append_message(
                input_ids,
                tokenizer,
                "tool",
                kind,
                text,
                author_name=tool_name,
            )
        continue

评论区精华

106 条真实 agent 请求回放验证 测试

PR body 记录:回放 106 条采集的 agent 请求(含 14 条图片、5 条多文本 tool 结果),经 Anthropic 转换、渲染与媒体收割全链路,修复前 19 条抛错,修复后 0 条,且无占位符与媒体数量错配。

结论:作为合入依据的验证数据,说明修复覆盖了真实场景,而非仅单元测试通过。 · 已解决

Review 审批与 LGTM other

yhyang201 在评论中 @JustinTong0323 表示 LGTM,随后两位 reviewer(yhyang201、JustinTong0323)均 APPROVED;无行内 review 评论。

结论:双 approve 后合入 main,无设计层面的争议。 · 已解决

CI 重跑与渲染器单测验证 other

维护者先后触发 /tag-and-rerun-ci,ispobock 用 /rerun-test test/registered/unit/parser/test_inkling_renderer.py 单独重跑渲染器单测,github-actions 机器人回报 ubuntu-latest 1 个测试全部通过。

结论:测试通过后 PR 合入,属于常规 CI 流程。 · 已解决

风险与影响

  1. 渲染路径行为变更:tool 内容从"要么 str 要么抛错"变为"逐 part 渲染",依赖旧报错语义的下游逻辑(概率极低)会受影响。
  2. 占位符计数隐式契约:渲染器与 MM processor 之间约定 IMAGE_TOKEN_ID 数量必须等于媒体收割数量,本 PR 用 test_tool_result_placeholder_count_matches_image_parts 锁定该契约,但 MM processor 侧收割逻辑后续若有改动,需同步回归。
  3. 手工回放验证未自动化:106 条请求重放是作者手工完成,仓库中没有对应的自动化端到端回归用例,后续回归只能靠单元测试部分覆盖。
  4. 影响范围有限:仅改 tool 分支,user/assistant 路径与推理 kernel 无改动,无性能影响。

修复 Inkling OSS 渲染器在 tool-use/agent 场景下的多模态渲染缺陷,携带图片的 tool 结果从 400/500 变为正常渲染,影响 Claude Code 风格 agent 工作流(截图、读回文件等)。影响范围为渲染器 tool 分支及其与 MM processor 的交互,对 user/assistant 消息处理无影响;团队侧收益是可复用的 _iter_render_parts 统一了 tool 与普通角色的 part 解析逻辑,删除的死代码降低了维护成本。

渲染分支行为变更 媒体占位符计数契约 手工回放验证未自动化

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论