Skip to content

fix(api): 过滤历史空内容消息并完善工具返回消息的函数名称自动推断 - #30

Merged
Mag1cFall merged 2 commits into
Mag1cFall:mainfrom
anon0v0:fix/filter-empty-content-parts
Sep 22, 2026
Merged

Mag1cFall merged 2 commits into
Mag1cFall:mainfrom
anon0v0:fix/filter-empty-content-parts

Conversation

@anon0v0

@anon0v0 anon0v0 commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

🎯 解决的问题

  1. 多轮历史消息中空内容触发 400 报错
    在长上下文会话(如 SillyTavern、Cursor、NextChat 等客户端或带有世界书/占位扩展的应用)中,历史消息可能包含空字符串 ""、null、[] 或因撤回/重试留下的空 Assistant 占位符。
    在当前的 internal/api/openai.go 和 internal/api/anthropic.go 中,转换这部分消息后得到的 content.Parts 为空切片(nil 或 len == 0),但在 toGenerateRequest 时无条件将其加入了 contents。
    当序列化打包请求时,internal/aistudio/request.go:encodeContent 会严格校验 if len(content.Parts) == 0 并抛出错误:
    「AI Studio 请求参数无效: 编码 content X: content parts 不能为空」,导致长对话因一条空消息永久报废 400。
    (注:在 internal/api/gemini.go 中其实已存在 if len(parts) == 0 { continue },本 PR 将该过滤补齐至 OpenAI 与 Anthropic 兼容层)

  2. 工具返回消息缺少名称或 ID 不一致时触发 400 报错
    在 AstrBot、LangChain、某些中转代理或标准 OpenAI 客户端中,发送 role: tool 消息时依据 OpenAI 规范通常仅携带 tool_call_id 而没有包含 name 字段。
    若经过客户端格式转换、滑动窗口裁剪、或中转代理重编后导致 tool_call_id 与历史 assistant tool_calls 中的 ID 不一致,原本的 internal/aistudio/request.go 会直接抛出:
    「AI Studio 请求参数无效: 编码 content X: 编码 part 0: function result 缺少名称且无法按 call ID 解析」 400 报错,阻断对话。
    事实上,Google AI Studio 上游对 FunctionResult.Name 具备宽容性,只要有合法名称(甚至未知兜底名称)均可正常响应 200,在本地抛错属于非必要硬阻断。

  3. 普通文本被误截断为空
    在 internal/aistudio/youtube.go:attachYouTubeMedia 中,原本对所有 User 文本均执行了 strings.TrimSpace 且若为空则 continue。这会导致仅包含空白字符的普通文本在未匹配任何 YouTube URL 的情况下被直接丢弃,同样导致 content.Parts 变为 0 触发报错。


🛠️ 修复方案

  1. 协议转换层过滤空 Parts 消息

    • 在 internal/api/openai.go 的 toGenerateRequest 中,若 len(content.Parts) == 0 则直接 continue 跳过。
    • 在 internal/api/anthropic.go 的 toGenerateRequest 中,若 len(parts) == 0 则直接 continue 跳过。
    • 在 openAIContentParts、openAIContentPart 和 anthropicParts 中,将纯空白字符文本视为空文本优雅返回。
  2. 完善工具返回名称推断与安全兜底

    • 在 internal/api/openai.go 中,自动记录前置 assistant 消息中的工具调用列表;若 tool 消息缺少 name,先按 tool_call_id 匹配,若未匹配但上一轮仅有单个工具调用,自动推断关联其函数名称。
    • 在 internal/aistudio/request.go:encodeContents 序列化中维护最近函数调用名称,当 FunctionResult.Name 为空且无法按 ID 检索时,依次尝试 ID 本身、最近调用的函数名,最后安全兜底为 unknown_function。
    • 在 encodePart 中移除对 Name 为空的硬报错拦截,采用安全兜底保证请求顺利送达上游。
  3. 修正 YouTube 媒体附加逻辑

    • 在 internal/aistudio/youtube.go 中,仅在真正匹配并抽取出 YouTube URL 后才执行文本替换与去空处理;无 YouTube 链接的常规文本保持原样。
  4. 底包序列化增加防御性兜底

    • 在 internal/aistudio/request.go:encodeContents 循环中,对 len(content.Parts) == 0 的节点增加防御性跳过,避免异常节点中断整个请求。
  5. 单测覆盖

    • 补充 TestEncodeContentsEmptyPartsFilter、TestAttachYouTubeMediaPreservesText 与 TestFunctionResultNameInference。
    • 补充 TestOpenAIEmptyMessagesFiltered、TestAnthropicEmptyMessagesFiltered 与 TestOpenAIToolMessageNameInference。
    • go test -count=1 ./... 全量回归测试通过。

1. 在 OpenAI 与 Anthropic 兼容协议中跳过 parts 为空的消息,避免长会话因空占位符报错'content parts 不能为空'
2. 在 openAIContentParts 和 anthropicParts 中兼容空字符串及空白字符输入
3. 在 youtube.go 中仅在匹配到 YouTube URL 并剥离后才做空文本检查,避免普通文本被误截断为空
4. 在 encodeContents 序列化时对空 parts 增加跳过兜底保护,并补充单测覆盖
1. 在 OpenAI 协议转换层自动关联前置 assistant tool_calls 中的函数名称,解决客户端或中转代理未传 name 或 tool_call_id 不一致导致的名称缺失问题
2. 在底层 encodeContents 中维护最近函数调用名称并在缺少 Name 时自动回退推断
3. 在 encodePart 序列化时针对仍无法推断的异常孤立 tool 消息补充 unknown_function 安全兜底,避免 400 抛错阻断会话
@anon0v0 anon0v0 changed the title fix(api): 过滤历史空内容消息并修复非视频文本被截断为空的问题 fix(api): 过滤历史空内容消息并完善工具返回消息的函数名称自动推断 Sep 21, 2026
@Mag1cFall
Mag1cFall merged commit 217259e into Mag1cFall:main Sep 22, 2026
2 checks passed
@Mag1cFall

Copy link
Copy Markdown
Owner

非常感谢!复现后进行了更多修复,已合入。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants