Skip to content

fix: agentic 流式路径在并行工具调用时因 provider 交错文本帧聚合失败,误报 empty_model_output #285

Description

@oierxjn

现象

使用某些模型网关(实测:Cloudflare Workers AI,经 new-api 中转)时,Eino 各 agentic 模式(单代理 / Deep / Supervisor / Plan-Execute)在模型发起并行工具调用的轮次必现失败:

  • 工具卡片先正常出现(如 skill (1/2)exec (2/2)),随后整轮报 执行失败
  • 错误文案:[NodeRunError] exceeds max retries: last error: model output rejected by ShouldRetry at attempt 5,UI 显示「模型调用重试已耗尽:模型未返回原始错误;输出被重试策略拒绝」
  • 单工具调用场景一切正常;失败几乎总出现在第一次并行调用时

根因

  1. Provider 行为:该类网关在并行 tool_calls 的 SSE 流中,会在两个工具调用之间插入纯空白文本帧。实测原始流(节选):

    data: {"choices":[{"delta":{"tool_calls":[{"id":"call_a","index":0,...,"function":{"name":"exec","arguments":""}}]}}]}
    data: {"choices":[{"delta":{"tool_calls":[{"id":null,"index":0,...,"function":{"arguments":"{\"command\":\"whoami\"}"}}]}}]}
    data: {"choices":[{"delta":{"content":"\n"}}]}            <-- 插在两个工具调用之间的文本帧
    data: {"choices":[{"delta":{"tool_calls":[{"id":"call_b","index":1,...,"function":{"name":"exec","arguments":""}}]}}]}
    
  2. eino-ext 缺陷(已报上游 cloudwego/eino-ext#975,修复 PR cloudwego/eino-ext#976):acl/openaichunkConverter.advanceContent 在工具调用开始后,给交错文本块分配了与工具调用块相同的 StreamingMeta.Index

  3. 错误被掩盖schema.ConcatAgenticMessages 按 index 分组时类型冲突报错(content block type mismatch: expected 'function_tool_call', but got 'assistant_gen_text');eino ADK 的 typedConsumeStream 吞掉该错误返回 nil 消息,CyberStrikeAI 的 ShouldRetry 空输出判定(internal/multiagent/eino_model_resilience.go isRetryableEmptyAgenticModelOutput)将其判为 empty_model_output,重试 5 次后耗尽——真实错误完全被掩盖。

最小复现:

block := func(idx int, b *schema.ContentBlock) *schema.AgenticMessage {
	b.StreamingMeta = &schema.StreamingMeta{Index: idx}
	return &schema.AgenticMessage{Role: schema.AgenticRoleTypeAssistant, ContentBlocks: []*schema.ContentBlock{b}}
}
frames := []*schema.AgenticMessage{
	block(0, schema.NewContentBlock(&schema.AssistantGenText{Text: "\n\n"})),
	block(1, schema.NewContentBlock(&schema.FunctionToolCall{CallID: "call_a", Name: "exec"})),
	block(1, schema.NewContentBlock(&schema.FunctionToolCall{Arguments: `{"command":"whoami"}`})),
	block(1, schema.NewContentBlock(&schema.AssistantGenText{Text: "\n"})), // 与工具调用块同 index → 冲突
	block(2, schema.NewContentBlock(&schema.FunctionToolCall{CallID: "call_b", Name: "exec"})),
	block(2, schema.NewContentBlock(&schema.FunctionToolCall{Arguments: `{"command":"id"}`})),
}
_, err := schema.ConcatAgenticMessages(frames)
// err: content block type mismatch: expected 'function_tool_call', but got 'assistant_gen_text'

修复方案

newEinoAgenticChatModelFactory 的模型实例外包一层修复包装器(与既有 streamToolCallIndexRepairModel 同一防御思路):检测流式帧中跨块类型的 StreamingMeta.Index 冲突,把冲突块改编到负数 index(不与 converter 后续 index 相撞),内容完整保留。覆盖 OpenAI 兼容与 Claude 两条 agentic 路径,eino 单代理 / Deep / Supervisor / Plan-Execute 均受益。

该补丁为框架边界防御,对正常流零影响;待 eino-ext 上游修复发版、依赖升级后可移除。

随附 PR 实现 + 单元测试。

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions