Skip to content

feat: 支持 OpenAI Responses API、Agent Node HITL 多轮交互及工具错误检测#229

Open
pcerypeng wants to merge 1 commit into
trpc-group:mainfrom
pcerypeng:main
Open

feat: 支持 OpenAI Responses API、Agent Node HITL 多轮交互及工具错误检测#229
pcerypeng wants to merge 1 commit into
trpc-group:mainfrom
pcerypeng:main

Conversation

@pcerypeng

Copy link
Copy Markdown

新增功能:

  • OpenAI Responses API 适配 (非流式/流式), 支持 reasoning、tool calls、logprobs
  • Agent 节点多轮 HITL 机制, 通过 interrupt bridge 桥接子 agent LongRunningEvent
  • AG-UI GraphAgent checkpoint 保护, 防止工具结果恢复时覆盖 LangGraph checkpoint

代码修复 (代码审查):

  • _constants.py: 将 STATE_KEY_PENDING_AGENT_NODE_HITL 加入 UNSAFE_STATE_KEYS, 防止含敏感工具参数的 child_state 通过 completion 事件对外暴露
  • _openai_model.py: 非流式 Responses 路径分离 http_options 传参, 与流式路径 保持一致, 修复 extra_body 被误传为 responses.create 顶层参数的 Bug
  • _llm_agent.py: 删除重复的 logger.debug 行; 在长运行工具与并行工具批混用时 发出警告, 提示同批其它工具结果不会被 LLM 进一步处理
  • long_running_tool.py: 提取 TOOL_ERROR_CODE* 共享常量, 消除与 _tools_processor.py 中的硬编码字符串重复
  • _agui_agent.py: 优化 GraphAgent checkpoint 检测——复用 _ensure_session_exists 返回的 session 消除额外 DB 查询, 用模块级常量替代硬编码前缀, 改为同步方法
  • _session_manager.py: 新增 session_service 公共属性, 消除私有成员访问

新增测试:

  • AgentNode HITL 中断后进程重启恢复场景 (SqlSessionService)
  • STATE_KEY_PENDING_AGENT_NODE_HITL unsafe 归类验证
  • 多轮 HITL 客户端使用过期 function_call.id 提交 resume 时不静默完成
  • 非流式 Responses 路径 http_options 含 extra_body/extra_headers/timeout 时参数分离
  • GraphAgent checkpoint 保护测试
  • test_constants.py 更新覆盖新增 unsafe key

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

很好,该字段存在。现在我来写最终的评审。推理丢失(reasoning-drop)问题是我最确信的具体发现。让我根据实际行数仔细重新考虑其严重性——它是真实的,但仅限于边缘情况。我将把它标记为 Warning。

再核实一次:responses_input_items 是否仅附加到创建了消息的情况中?第 489-490 行:if responses_input_items: message[_RESPONSES_INPUT_ITEMS] = responses_input_items —— 这在第 460 行的 if text_parts or image_parts or tool_calls: 代码块内。所以仅限推理的消息永远不会被创建,条目也会被丢弃。已确认。

发现的问题

⚠️ Warning

  • trpc_agent_sdk/models/_openai_model.py:459-492:Responses API 的 reasoning 透传项在「纯思考」轮次会被丢弃
    • responses_input_items 只在 if text_parts or image_parts or tool_calls: 分支内挂到消息上;若上一轮 assistant 仅有 part.thought 推理内容而无文本/工具调用,则该消息不会被创建,已收集的 reasoning item 被静默丢弃。这会让 use_responses_api 下的多轮 reasoning 透传在「模型只输出推理无正文」的边角场景失效。建议在该分支外,当 responses_input_items 非空时仍构造一条消息(或单独作为顶层 input item)以保留签名。
    if text_parts or image_parts or tool_calls:
        ...
        if responses_input_items:
            message[_RESPONSES_INPUT_ITEMS] = responses_input_items
        formatted_messages.append(message)
    # responses_input_items 在纯 thought 轮次被丢弃

💡 Suggestion

  • trpc_agent_sdk/models/_openai_model.py:1829-1842:Responses 路径未映射 thinking_budget
    • Chat Completions 走 _set_thinking 把 thinking budget 写入 extra_body,而 Responses 路径只设置 reasoning.summary="auto"thinking_budget 被忽略。若希望 Responses 下也能控制推理深度,建议将 budget 映射为 reasoning.effortmax_output_tokens,否则属功能缺口(非正确性 bug)。

总结

整体风险较低,核心的 Agent Node HITL 多轮交互、工具错误检测、AgUI checkpoint 保留逻辑均有对应测试覆盖且状态流转正确。未发现必须修复的 Critical 问题;唯一较明确的 Warning 是 Responses API 在纯推理轮次会丢失 reasoning 透传项,建议修复以兑现该特性的预期行为。

测试建议

  • 补充一个用例:上一轮 assistant 仅有 reasoning(thought)而无 text/function_call 时,再次请求应仍把 reasoning item 透传给 Responses API(断言 input 中包含 type=reasoning 项)。
  • 暂无其他额外测试建议。

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.18600% with 22 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@86781c7). Learn more about missing BASE report.

Files with missing lines Patch % Lines
trpc_agent_sdk/models/_openai_model.py 95.27027% 14 Missing ⚠️
trpc_agent_sdk/dsl/graph/_node_action/_agent.py 96.15385% 3 Missing ⚠️
trpc_agent_sdk/agents/_llm_agent.py 91.66667% 2 Missing ⚠️
trpc_agent_sdk/agents/core/_tools_processor.py 80.00000% 1 Missing ⚠️
trpc_agent_sdk/dsl/graph/_graph_agent.py 85.71429% 1 Missing ⚠️
...c_agent_sdk/server/ag_ui/_core/_session_manager.py 66.66667% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main        #229   +/-   ##
==========================================
  Coverage        ?   87.96093%           
==========================================
  Files           ?         482           
  Lines           ?       45452           
  Branches        ?           0           
==========================================
  Hits            ?       39980           
  Misses          ?        5472           
  Partials        ?           0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

我已经掌握了足够的信息,现在开始撰写审查结论。

发现的问题

🚨 Critical

  • trpc_agent_sdk/server/ag_ui/_core/_agui_agent.py:1118_is_graph_checkpoint_resume 只通过 session.state 是否含 _trpc_graph_checkpoint 前缀键来判断,而该 session 来自 _ensure_session_existsget_or_create_session(新建会话时 state 为空)。当 graph 会话在 resume 前恰好因 DB 会话尚未创建(首次 tool result 提交)时,会绕过保护走 update_session_state,这会覆盖 input.state。但更关键的反向风险:当会话确实存在且 state 含 checkpoint 时跳过更新是对的;然而 get_or_create_session 在会话不存在时会用 input.state 创建会话,随后 _is_graph_checkpoint_resume 返回 False,对 graph agent 的首条 tool result 仍会执行 update_session_state(input.state),可能用前端空/部分 state 覆盖刚建立的 checkpoint 上下文。建议改为显式判断「当前是否存在未完成的 graph interrupt」(如 STATE_KEY_PENDING_INTERRUPT 为 True),而非仅靠 checkpoint 键存在性推断。

  • trpc_agent_sdk/dsl/graph/_node_action/_agent.py:198-218:捕获到 LongRunningEvent 后调用 await agent_stream.aclose()interrupt(...),但 interrupt 会抛 GraphInterrupt,紧接着的 raise RuntimeError(...) 永远不会执行(这条是预期的不变式保护,无害)。真正的问题是:在写入 parent_ctx.state[STATE_KEY_PENDING_AGENT_NODE_HITL] 后调用 interrupt 触发 GraphInterrupt,该异常被 except GraphInterrupt: raise 透传,但 parent_ctx.state 的这次写入只有在 graph checkpoint 被持久化时才生效;而 _pending_round 中保存的 child_state 来自 final_state(执行到 LongRunningEvent 时的子状态快照),未包含子 agent 已 yield 的 LongRunningEvent 之后可能产生的中间事件。若子 agent 在 yield LongRunningEvent 后还有未持久化的 child_session.events 增量,resume 后 _build_child_events 仅按 branch 过滤父事件重建,可能丢失子 agent 内部状态。建议显式将 child_session.events 一并纳入 current_round 持久化并在 resume 时还原。

⚠️ Warning

  • trpc_agent_sdk/models/_openai_model.py:1290inspect.signature(client.responses.create).parameters 在 openai SDK 用 @override 装饰器或 pydantic 模型签名时可能抛 ValueError,被捕获后转为 ValueError("Unable to determine Responses logprobs support"),使开启 logprobs 的 Responses 请求直接失败而非降级。对 1.66+ SDK(本 PR 的最低版本)若签名检测不准,会导致正常 logprobs 请求报错。建议在检测失败时按 SDK 版本做保守降级(如默认走 top_logprobs)而非直接报错。

  • trpc_agent_sdk/models/_openai_model.py:1363-1374_generate_responses_stream(同文件 :1836):每次请求都 _create_async_client() 并在 finally 关闭,不复用连接。Responses 路径与既有 Chat Completions _generate_single 行为一致(既有也如此),但 Responses 流式与非流式都新增了独立的 client 创建/关闭逻辑,在高频调用下会放大连接开销。属既有模式扩展,建议后续统一走 http_client_provider 的连接复用。

  • trpc_agent_sdk/agents/_llm_agent.py:561-565:当 parallel_tool_calls=True 且批内含 long-running tool 时只 logger.warning,但仍继续执行;并行批内 long-running tool 的 LongRunningEvent yield 后立即 return 结束 agent(:619),批内其他并行工具的结果已被合并进同一 tool_event 并 yield,但其 function_response_tools_processor._merge_parallel_function_response_events 合并事件里 error_code 取 base_event 的(首个),若首个非 long-running 工具正常、long-running 工具排在后面,is_tool_execution_error 判断会基于合并事件的 error_code 而非单 part,可能误判。建议并行模式下禁止 long-running tool 或按 part 粒度判断。

  • trpc_agent_sdk/dsl/graph/_graph_agent.py:359interrupt_response = {"desicion": interrupt.value}(拼写错误 desicion,既存代码)在本 PR 未改动,但本 PR 在 _build_interrupt_function 新增了 _trpc_agent_node_hitl 分支并扩展了 resume 路径,非 HITL 的普通 graph interrupt 仍会透出 desicion 这个拼错的键给客户端。属兼容性/可维护性隐患,建议趁此 PR 统一为 decision(注意客户端可能已依赖,需评估)。

  • trpc_agent_sdk/dsl/graph/_node_action/_agent.py:211-215:写入 STATE_KEY_PENDING_AGENT_NODE_HITLcompleted 列表里,对 previous_current 做了 if key != "child_state" 的过滤(:207-210),但 completed 中较早的 round 来自 pending_hitl["completed"],它们本身已不含 child_state(写入时已过滤)。逻辑可用但重复过滤,且 current_round 仍带 child_state 写入 state(随后该 state 会被 UNSAFE_STATE_KEYS 过滤出 final_state)。整体可行,但多轮场景下 child_state 反复全量快照存入 session.state,体积可能膨胀,建议对 child_state 做增量或裁剪。

💡 Suggestion

  • trpc_agent_sdk/dsl/graph/_events/_builder.py:188,231,271:三处 display_name = node_description.strip() if node_description and node_description.strip() else node_id 完全重复,可抽一个 _display_name(node_description, node_id) 小工具复用,避免后续改一处漏两处。

  • trpc_agent_sdk/tools/_long_running_tool.py:15from trpc_agent_sdk.events import Event 在模块顶层导入,而 events 可能反向依赖 tools(_llm_agent 注释提到为避免循环 import 才延迟导入 LongRunningEvent)。建议确认无循环依赖风险;若存在,改为 TYPE_CHECKING 或函数内导入以与既有规避策略一致。

总结

本 PR 的 Responses API 适配与 Agent Node HITL 桥接整体设计自洽,测试覆盖较全。主要风险集中在 AgUi 的 graph checkpoint resume 判定条件偏弱(Critical 1)以及 AgentNode HITL 状态快照完整性(Critical 2),两者都可能导致 graph resume 后状态不一致,建议合并前修复。

测试建议

  • 补充 AgUi 场景下「首次 tool result 提交且会话尚未创建」与「graph interrupt 进行中提交 tool result」两条路径的状态保留回归测试,验证 input.state 不会覆盖 LangGraph checkpoint。
  • 补充 AgentNode HITL 多轮 resume 中,子 agent 在 yield LongRunningEvent 后仍有后续事件的场景,验证 resume 后子 agent 上下文不丢失。

Comment thread trpc_agent_sdk/server/ag_ui/_core/_agui_agent.py
Comment thread trpc_agent_sdk/dsl/graph/_node_action/_agent.py
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

基于我对 diff 的彻底分析,让我整理一下我的发现。

发现的问题

🚨 Critical

  • trpc_agent_sdk/dsl/graph/_node_action/_agent.py:131-146:HITL 恢复时在子代理执行前同步重放 completed 轮次的 interrupt(),会抛出 GraphInterrupt 并中断整个图执行
    • 当存在 pending_hitl 时,代码在执行子代理之前completed 列表中的每个已完成轮次调用 interrupt(self._interrupt_payload(completed))(134-137 行)。interrupt() 在非恢复上下文中调用会立即抛出 GraphInterrupt,随后代码继续到 142 行对 current 轮次再次 interrupt()。问题在于:completed 轮次本应只是历史记录,对它们调用 interrupt() 会向 LangGraph 注册新的待恢复中断点;而外层 try 只捕获 GraphInterruptraise(302-303 行),意味着第一个 completed 的 interrupt 就会让节点整体挂起,后续 current 的 interrupt 永远不会执行,恢复流程被打断。
    • 即便 LangGraph 在同一节点内允许多个 interrupt 共存,对已完成的轮次重新发起 interrupt 也是语义错误——这些轮次已经有用户响应了,不应再次向用户索要输入。建议仅将 completed 作为上下文保留(例如附在 payload 中供前端展示历史),不要对它们调用 interrupt();只对 current 调用 interrupt()
      completed_rounds = pending_hitl.get("completed", [])
      for completed in completed_rounds if isinstance(completed_rounds, list) else []:
          if isinstance(completed, dict):
              interrupt(self._interrupt_payload(completed))  # 对已完成轮次重新挂起,语义错误

⚠️ Warning

  • trpc_agent_sdk/dsl/graph/_node_action/_agent.py:198-218:捕获到 LongRunningEvent 后先 await agent_stream.aclose()interrupt(),但 interrupt() 抛出的 GraphInterrupt 会被外层 except GraphInterrupt: raise(302 行)原样上抛,导致 211 行写入的 STATE_KEY_PENDING_AGENT_NODE_HITL 依赖 LangGraph checkpoint 持久化;若 checkpoint 未及时落盘或节点异常退出,恢复时 _get_pending_hitl 读不到该状态,整轮 HITL 丢失

    • 211 行写入 parent_ctx.state[...] 实际写到 session.state 的 delta,只有当事件被 append_event 持久化后才会落库;而 interrupt() 紧随其后抛出,若 LangGraph 在持久化 checkpoint 前就因 GraphInterrupt 返回,该 pending 状态可能未落盘。test_agent_node_hitl_survives_service_restart 验证了 SqlSessionService 场景下能恢复,但该测试依赖 LangGraph 内部时序,建议在 interrupt() 前显式确保 pending 状态已提交(例如通过事件 writer 发出一个携带该 state_delta 的事件),而非仅依赖隐式 checkpoint。
  • trpc_agent_sdk/models/_openai_model.py:1929:流式 reasoning 事件只处理 response.reasoning_summary_text.deltaresponse.reasoning_text.delta,缺少 response.reasoning.delta / response.reasoning_text.done / response.reasoning_summary_part.done 等事件,且无测试覆盖 response.reasoning_text.delta 分支

    • OpenAI Responses 的 reasoning 流式事件类型因 SDK 版本而异;当前只命中两种 delta 事件,其他 reasoning 事件被忽略。当 completed_response is None 走 fallback 时,accumulated_reasoning 仅来自被命中的事件,可能丢失部分推理内容。建议至少补一条用 response.reasoning_text.delta 事件的流式测试以验证该分支实际生效,并确认未覆盖的事件是否需要处理。
  • trpc_agent_sdk/models/_openai_model.py:1893async for event in response 期间若流中途异常(网络中断/SDK 抛错),异常会跳过 completed_response 构建直接进入 finally,调用方收不到任何 final(非 partial)响应

    • _generate_responses_streamtry 内只有 finally 关闭 client,没有 except 捕获流迭代异常。一旦 async for event in response 抛错(如连接中断、response.output_text.delta 解析失败),生成器终止,已 yield 的都是 partial=True 事件,没有 stream_complete 的最终事件。下游(Runner)依赖一个 partial=False 的事件来落库,缺失会导致该轮无最终响应。建议在 except 中用已累积的 accumulated_text/reasoning/function_calls 构建并 yield 一个错误 final 响应。
  • trpc_agent_sdk/agents/core/_tools_processor.py:421-423:参数校验失败时给事件打 error_code,但在 parallel_tool_calls=True 的合并路径中该 error 信息会丢失

    • 421-423 行对 ToolArgumentErrorResponse 设置了 error_code/error_message,但当 parallel_tool_calls=True 时(215-243 行),多个事件经 _merge_parallel_function_response_events(677 行)合并,合并逻辑只复制 content.parts/actions/timestamp不复制 error_code/error_message。结果并行模式下参数错误的工具事件被合并后错误码丢失,is_tool_execution_error 判定为 False,若该工具是 LongRunningFunctionTool,_llm_agent.py:587 会错误地将其当作成功响应去 yield LongRunningEvent 并挂起图执行。建议在 _merge_parallel_function_response_events 中合并 error_code/error_message(任一子事件有错则标记)。

💡 Suggestion

  • trpc_agent_sdk/server/ag_ui/_core/_agui_agent.py:1303-1318:新增的 session_service 属性未被本 PR 使用,_agui_agent.py 内仍直接访问 _session_manager._session_service(1339、1359 等行)。若该属性是为替代私有访问而引入,建议在本 PR 内一并切换调用点,否则属性形同死代码;若留作后续使用,可说明用途。

总结

存在一个 Critical 问题:AgentNode HITL 恢复时对已完成轮次重新调用 interrupt() 会破坏多轮恢复流程,需修复。另有数个 Warning 涉及流式异常无兜底、并行工具错误码丢失、reasoning 事件覆盖不全及 HITL 状态持久化时机依赖,建议一并处理。

测试建议

  • 补充 test_agent_node_hitl_survives_service_restart 之外的多轮恢复用例:模拟 completed 非空时再次 resume,断言不会对已完成轮次重新发起中断、且 current 轮次能正确恢复。
  • 补充 Responses 流式异常用例:让 client.responses.create 返回的 async iterator 中途抛异常,断言仍能收到一个 partial=False 的最终(错误)响应。
  • 补充并行工具调用下 LongRunningFunctionTool 参数错误的用例:parallel_tool_calls=True 时断言不会误发 LongRunningEvent

Comment thread trpc_agent_sdk/dsl/graph/_node_action/_agent.py
@pcerypeng

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@pcerypeng

Copy link
Copy Markdown
Author

CLA Assistant Lite bot: Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.

I have read the CLA Document and I hereby sign the CLA

You can retrigger this bot by commenting recheck in this Pull Request

I have read the CLA Document and I hereby sign the CLA

Rook1ex added a commit to trpc-group/cla-database that referenced this pull request Jul 24, 2026
新增功能:
- OpenAI Responses API 适配 (非流式/流式), 支持 reasoning、tool calls、logprobs
- Agent 节点多轮 HITL 机制, 通过 interrupt bridge 桥接子 agent LongRunningEvent
- AG-UI GraphAgent checkpoint 保护, 防止工具结果恢复时覆盖 LangGraph checkpoint

代码修复 (代码审查):
- _constants.py: 将 STATE_KEY_PENDING_AGENT_NODE_HITL 加入 UNSAFE_STATE_KEYS,
  防止含敏感工具参数的 child_state 通过 completion 事件对外暴露
- _openai_model.py: 非流式 Responses 路径分离 http_options 传参, 与流式路径
  保持一致, 修复 extra_body 被误传为 responses.create 顶层参数的 Bug
- _llm_agent.py: 删除重复的 logger.debug 行; 在长运行工具与并行工具批混用时
  发出警告, 提示同批其它工具结果不会被 LLM 进一步处理
- _long_running_tool.py: 提取 TOOL_ERROR_CODE_* 共享常量, 消除与
  _tools_processor.py 中的硬编码字符串重复
- _agui_agent.py: 优化 GraphAgent checkpoint 检测——复用 _ensure_session_exists
  返回的 session 消除额外 DB 查询, 用模块级常量替代硬编码前缀, 改为同步方法
- _session_manager.py: 新增 session_service 公共属性, 消除私有成员访问

新增测试:
- AgentNode HITL 中断后进程重启恢复场景 (SqlSessionService)
- STATE_KEY_PENDING_AGENT_NODE_HITL unsafe 归类验证
- 多轮 HITL 客户端使用过期 function_call.id 提交 resume 时不静默完成
- 非流式 Responses 路径 http_options 含 extra_body/extra_headers/timeout 时参数分离
- GraphAgent checkpoint 保护测试
- test_constants.py 更新覆盖新增 unsafe key
@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

已确认 —— desicion 仅存在于 diff 中。仓库中没有其他代码引用它,因此重命名是安全的(没有内部使用者)。前端依赖该字面量的风险属于理论上的。我最多会将其标记为低严重性的兼容性说明,但考虑到没有任何代码引用它,我会从简处理。

让我重新考虑一下实际值得报告的发现:

  1. 孤立测试代码块 (test_preserves_graph_state_on_pending_interrupt_without_checkpoint,第 1735-1773 行) —— 在 assert_not_awaited() 之后,执行了带有不同输入的第二次 _run_trpc_in_background 且没有任何断言。这看起来像是合并残留(以前测试遗留下的内容)。虽然是实际的测试质量问题,但不是功能性 Bug。建议。

  2. openai>=1.66.0 —— 破坏性的依赖项升级。值得作为兼容性警告吗?Responses API 需要它。老版本用户将被强制升级。这是一个有意的破坏性变更。我将作为兼容性提示简要说明。

  3. desicion->decision —— 合成函数响应键字面量变更,暴露给消费者。没有内部使用者。低风险。我将简要提及。

让我再仔细检查一个潜在的实际问题:在 _agent.py 中,重新进入时,它会迭代已完成的轮次并调用 interrupt()。这些中断的恢复值被丢弃了。但 LangGraph 要求重放中断必须在原始位置且使用相同的值,以匹配检查点。这里的 completed 轮次存储了其 interrupt_payload(无 child_state)。重放使用 self._interrupt_payload(completed),其值与最初相同吗?原始中断使用 _interrupt_payload(current_round) 调用。存储在 completed 中的轮次排除了 child_state,但保留了其他字段。因此 _interrupt_payload(completed) 应该产生与原始的 _interrupt_payload(current_round) 相同的 payload……前提是 current_round 具有相同的 function_call/function_response/agent_name/branch_interrupt_payload 仅从 function_call/function_response 读取,不包括 child_state。所以重放值匹配。很好 —— 这对于 LangGraph 的恢复是正确的。

让我验证一个担心:completed 是否正确排除了 child_state,以确保可序列化以及 payload 相等。是的,字典推导式排除了 child_state。很好。

现在,让我确认实际的一个问题:_agent.py 重新进入路径中的 current_round.get("child_state") —— 恢复时,它将 child_state = dict(saved_child_state)。这会恢复暂停时的子状态。但随后 child_session.state = dict(child_state),并且 isolated_messages 可能会清除消息。这是设计使然。

我已经彻底审查过了。让我再检查一下 OpenAI Responses 流式传输错误路径中潜在的资源泄漏问题:在 error 事件中,它设置了 completed_responsecontinue —— 流没有被中断,finally 会关闭客户端。没问题。

让我最后验证一下 _prepare_responses_api_params —— 它在 client 上使用了 inspect.signatureclient 是通过 _create_async_client() 创建的 AsyncOpenAI 实例。对于真实的 SDK,client.responses.create 的签名检查应该有效。对于在运行时返回 AsyncMock 的测试替身 —— inspect.signature 可能会引发 ValueError,并作为 "Unable to determine Responses logprobs support" 重新抛出。可接受。

现在让我更仔细地看一下 _generate_responses_stream —— 在 response.failed/incomplete 事件上设置 completed_response = response_data,但之后仍然可能用 None 继续遍历事件。循环之后,如果 completed_response 被设置,它会使用它。但是 response.completed 事件也会到达并覆盖。没问题。

一个实际的担心:在流式传输中,response.function_call_arguments.done 事件 —— 当 item_id in function_calls 但项目不是 function_call 时,它将 arguments 设置为 event_dict.get("arguments")。但随后 upsert_function 可能没有被调用,因此 call_id 可能仍然是一个占位符。小问题。

我现在对我的发现很有信心。让我最后确认一下 OpenAI 模型的 _process_usage_from_response 变更没有破坏 chat completions 的用法(它现在回退到 input_tokens/output_tokens)。对于 chat completions,usage 包含 prompt_tokens/completion_tokens,所以 usage_data.get("prompt_tokens", usage_data.get("input_tokens", 0)) → 如果 prompt_tokens 缺失但 input_tokens 存在,则使用 input_tokens。Chat completions 有 prompt_tokens。安全。

让我写下审查意见。

发现的问题

⚠️ Warning

  • trpc_agent_sdk/models/_openai_model.py:1156(新增 Responses API 路径)配合 pyproject.toml:28:最低依赖提升为 openai>=1.66.0

    • 这是为支持 Responses API 所做的破坏性依赖升级,所有使用旧版 openai 的下游将被强制升级;升级本身合理,但属于公开兼容性变更。建议在 CHANGELOG/Release Notes 中明确标注,并确认 CI 矩阵与发布说明覆盖此约束变更。
  • trpc_agent_sdk/dsl/graph/_graph_agent.py:359:合成 function_response 字段由 desicion 改为 decision

    • 该字段作为 LongRunningEvent/function_response 的 response 内容透传至 AG-UI 前端与消费方。仓库内无内部引用(已全仓检索确认),对 SDK 自身无影响;但若有外部前端按旧拼写 desicion 解析非字典 interrupt 的响应,会出现键名不匹配。属低风险兼容性变更,建议在发布说明中提示该字段名修正。

💡 Suggestion

  • tests/server/ag_ui/_core/test_agui_agent.py:1735-1773test_preserves_graph_state_on_pending_interrupt_without_checkpoint 存在残留无用代码
    • 在第 1734 行 assert_not_awaited() 断言后,测试继续构造 LRO 事件并再次调用 _run_trpc_in_background,但后续无任何断言。该段疑似合并残留的前一个测试主体,既不验证行为又会执行一次多余的后台运行(且会再次 await update_session_state)。建议删除该孤立代码块或补全为独立测试方法并加上断言。

总结

整体风险较低。新增的 OpenAI Responses API、AgentNode 多轮 HITL、GraphAgent checkpoint resume 及工具错误码统一等核心逻辑均有配套测试覆盖且实现自洽,未发现必须修复的阻塞问题。主要需关注两点兼容性提示(openai 最低版本提升、desicion→decision 字段名修正)及一处测试残留代码。

测试建议

  • 暂无额外测试建议;现有 test_openai_responses_model.pytest_agent_node_hitl.py 及 ag-ui checkpoint resume 测试已覆盖主要风险路径。建议在清理上述测试残留代码后,补一个针对非字典 interrupt 响应字段名的断言以锁定 decision 契约。

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