Skip to content

fix(agui): defer RunErrorEvent emission to prevent premature disconnect - #292

Closed
weimch wants to merge 1 commit into
mainfrom
bugfix/agui-error-event-premature-disconnect
Closed

fix(agui): defer RunErrorEvent emission to prevent premature disconnect#292
weimch wants to merge 1 commit into
mainfrom
bugfix/agui-error-event-premature-disconnect

Conversation

@weimch

@weimch weimch commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

问题

PCG-应用宝 pcerypeng 反馈:使用 GrafAgent + AGUI 服务部署时,如果增加一个 AgentNode,该 AgentNode 返回一个带 error 的 Event,客户端与服务的连接会被断开,但此时 Agent 并没有结束,仍在持续执行。

根因

EventTranslator 在遇到 is_error() and not function_responses 的 Event 时,立即翻译为 RunErrorEvent。根据 AG-UI 协议规范,RunErrorEvent 是终止事件,合规客户端收到后会关闭连接。但服务端 SSE stream 实际保持开着——Translator 是 per-event 调用的,return 只结束单个 event 的翻译,外层循环继续消费后续事件。

核心矛盾:AgentNode 级别的可恢复错误与真正的致命系统错误在 Translator 层无法区分,都满足 is_error() and not function_responses

修复方案(方案 D:延迟决策到 run 结束)

不在 error Event 出现时立即发 RunErrorEvent,等 run 结束后检查最后一个 Event 是否为 error,再决定发 RunErrorEvent 还是 RunFinishedEvent。

改动点

  1. _event_translator.py:error Event 改为发 CustomEvent(name="trpc_error", value={code, message}),不再发 RunErrorEvent,移除 return 让翻译继续
  2. _agui_agent.py:在 _run_trpc_in_backgroundrun_async 循环中跟踪 last_event_is_error;循环结束后如果为 True,发 RunErrorEvent(用最后一个 event 的 error_code/error_message)

正确性

最后一个 event 是否为 error 完美捕捉了 run 是否因错误终止的运行时状态:

  • LlmAgent yield error → return → 最后 event 是 error → RunErrorEvent ✅
  • GraphAgent node yield error → 图继续 → 后续 node 事件 → 最后 event 非 error → RunFinishedEvent ✅
  • ChainAgent 最后 sub=LlmAgent yield error → 最后 event 是 error → RunErrorEvent ✅
  • ChainAgent 中间 sub error → 后续 sub 事件 → RunFinishedEvent ✅

测试

  • test_event_translator.py + test_agui_agent.py:165 tests passed
  • 完整 tests/server/ag_ui 套件:all passed, exit code 0

关联

…premature disconnect

- EventTranslator: replace immediate RunErrorEvent with CustomEvent(name='trpc_error') on error events mid-stream
- AGUI agent: track last_event_is_error in _run_trpc_in_background loop, emit RunErrorEvent only when run ends with error as last event
- This prevents premature connection close when sub-agents (GraphAgent, ChainAgent, etc.) yield error events but continue executing

Fixes #TAPD-1020419452137024294
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

现在我已经全面了解了上下文。让我来汇总一下审查结果。

发现的问题

⚠️ Warning

  • trpc_agent_sdk/server/ag_ui/_core/_agui_agent.py:1296-1306last_event_is_error 判定会误报可恢复的工具错误

    • is_error() 的实现是 error_code is not Noneevents/_event.py:258),而工具执行失败时 _tools_processor.py:783 创建的事件同时设置了 error_code function_response(这是可恢复的、已回传给 LLM 的错误)。_event_translator.py 已用 and not function_responses 正确区分了可恢复/致命错误,但这里的延迟判定只用 is_error()、不再检查 function_responses,导致“最后一条事件恰好是带 function_response 的工具错误”时会被误判为致命错误并发出 RunErrorEvent,把本应正常结束的运行错误地终止。建议在循环中沿用 translator 的判定逻辑(如 trpc_event.is_error() and not trpc_event.get_function_responses())来置位,或显式记录致命错误标志。
  • trpc_agent_sdk/server/ag_ui/_core/_agui_agent.py:1251:异步生成器空转时 trpc_event 未定义,循环后引用会抛 UnboundLocalError

    • 注释假设 trpc_event 在循环后仍指向最后一个事件,但若 runner.run_async(...) 一次也不 yield(空运行成功完成),trpc_event 未定义。此时 last_event_is_error 仍为 False,本来不会进入 if 分支,因此当前不会触发;但这是脆弱的隐式依赖——一旦后续在 if 内或其上方增加对 trpc_event 的引用即会崩溃。建议显式保存最后事件引用并在空转时置 None,避免依赖循环变量泄漏。
  • trpc_agent_sdk/server/ag_ui/_core/_agui_agent.py:1296CustomEvent 的错误信息不会在 RunErrorEvent 中复用

    • translator 现在把致命错误以 name="trpc_error"CustomEvent 投递给客户端,随后该逻辑在循环结束时又发一条 RunErrorEvent。符合 AG-UI 协议的客户端会在收到 RunErrorEvent 时关闭连接,但此前已经收到了内容重复的 trpc_error CustomEvent;对协议严格断开连接的客户端而言存在重复告警。建议明确二者择一或文档化该先后顺序的预期。
  • trpc_agent_sdk/server/ag_ui/_core/_agui_agent.py / tests/server/ag_ui/_core/test_agui_agent.py:缺少对延迟 RunErrorEvent 路径的测试

    • 新增的“run 以致命错误结束→发 RunErrorEvent”以及“run 正常结束→不发 RunErrorEvent、发 RunFinishedEvent”是本次变更的核心行为,但现有 _run_trpc_in_background 测试(1453–1670 行附近)均未构造“最后一个 trpc_event 为 is_error()”的场景,test_event_translator.py 也未覆盖新增 trpc_error CustomEvent。建议补充:最后事件为致命错误时队列末尾出现 RunErrorEvent;最后事件为带 function_response 的可恢复工具错误时不出现 RunErrorEvent

💡 Suggestion

总结

核心变更是将 RunErrorEvent 从 translator 提前发出改为延迟到 run 结束时发出,方向合理,避免了子 agent 错误导致连接过早断开。但延迟判定仅用 is_error() 而未排除带 function_response 的可恢复工具错误,存在误报风险,且关键路径缺少测试覆盖,属于建议修复而非确定阻塞。

测试建议

  • 补充 _run_trpc_in_background 用例:mock run_async 最后一个事件为 is_error() function_response,断言队列中出现 RunErrorEventcode 取自 error_code,缺省 MODEL_ERROR)。
  • 补充对照用例:最后一个事件为 is_error() function_response,断言不出现 RunErrorEvent(验证上述 Warning 的修复)。
  • test_event_translator.py 增加致命错误(is_error=True、无 function_response)翻译为 name="trpc_error"CustomEvent 的断言。

@weimch weimch closed this Aug 11, 2026
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