Skip to content

fix: 修复skill_exec 工具调用中产物丢失的问题 (#350) - #356

Merged
weimch merged 1 commit into
mainfrom
fix/skill-exec-output-collection
Oct 10, 2026
Merged

weimch merged 1 commit into
mainfrom
fix/skill-exec-output-collection

Conversation

@raychen911

Copy link
Copy Markdown
Contributor

No description provided.

@helloopenworld

Copy link
Copy Markdown
Contributor

AI Code Review

审查结论

通过

审查范围: base 02509b2..HEAD ee29597,共 12 个文件(1 处 SDK 源码修改、1 处测试修改、10 个示例文件新增)。核心变更是将 BaseWorkspaceRuntime 通过 _ExecSession/_start_session 传入 _collect_final_result,使 _prepare_outputs(ctx, ws, workspace_runtime, input) 四参签名调用正确,修复了 skill_exec 产物收集时因缺少 workspace_runtime 参数导致 TypeError 被吞、产物(含 artifact)全部丢失的缺陷。

计划符合性: 修复方向正确,变更最小且聚焦——workspace_runtime 来自与创建会话相同的 repository.get_workspace_runtime(tool_context),与 skill_run 路径一致;finalized 幂等保护、_attach_artifacts_if_requested/_merge_manifest_artifact_refs 的异常降级均保留。新增测试验证了 runtime 传递与产物返回的主路径。示例目录 API 兼容性(LlmAgent、SkillToolSet、Runner、dotenv 依赖)均已核实可用。

主要风险: ① skill_exec 返回路径不应用 omit_inline_content 后处理(skill_run 在 _skill_run.py:742-745 会清空 content),传入该参数时 skill_exec 仍返回完整内联内容,参数静默失效;② force_save_artifacts=True 构造的 SkillRunTool 经 create_exec_tools 配对的 skill_exec 路径绕过该覆盖,不按配置强制保存产物——两者均因本次修复让产物收集真正生效而成为可观察缺陷(修复前所有收集失败,参数本无效果)。均为中等级别的配置契约偏差,不影响本次修复的主目标。

测试充分性: 新测试覆盖主路径(runtime 传递、output_files/primary_output 断言、finalized 置位),但未覆盖 _prepare_outputs 抛异常的降级分支(797-799 行)——该分支正是修复前产物丢失的故障点,建议补充。示例为演示代码,README 的 stdin 行为描述经核实准确。

门禁结论: 无阻断性(SEVERE)缺陷,修复达成了预期目标,3 条均为中等/低级别可改进项,评审通过。

发现的问题

中等

trpc_agent_sdk/skills/tools/_skill_exec.py:777-796

问题: _collect_final_result 把 omit_inline_content 传入 fake_run_input(第 785 行)并交给 _prepare_outputs/_attach_artifacts_if_requested,但这些调用方都不消费该字段(_skill_run.py:844-874 只读取 outputs/output_files),本函数也从未像 SkillRunTool._run_async_impl(_skill_run.py:742-745)那样在返回前清空 output_files/primary_output 的 content。ExecInput(_skill_exec.py:125)声明该字段为 "Omit inline file content",但在 skill_exec 路径上全程无效。

触发条件: 调用方以 omit_inline_content=true 调用 skill_exec(或 skill_write_stdin/skill_poll_session 触发 _collect_final_result)并指定 output_files,进程退出后收集产物。

实际影响: 返回的 ExecOutput.result.output_files 与 primary_output 仍携带完整内联内容(含大文件全文),与 skill_run 的行为不一致,omit_inline_content 参数静默失效,依赖该参数控制上下文体积的调用方会收到超出预期的内容。本修复使产物收集真正生效前该参数本无效果(收集全部失败),因此修复后此缺陷首次可观察。

修正方向: 在 _collect_final_result 构造 SkillRunOutput 之后、exec_session.finalized = True 之前,参照 _skill_run.py:742-745 增加同样的 omit_inline_content 后处理(清空 output_files 与 primary_output 的 content),或将该后处理抽取为两个工具共享的辅助函数统一调用。

中等

trpc_agent_sdk/skills/tools/_skill_exec.py:790-796

问题: _collect_final_result 用 ExecInput 的原始 in_data.save_as_artifacts 构造 fake_run_input(第 784 行),绕过了 SkillRunTool._run_async_impl 中 _force_save_artifacts 的覆盖逻辑(_skill_run.py:664-669 会把 save_as_artifacts 强制置 True),_skill_exec.py 全文也无任何 _force_save_artifacts 引用。

触发条件: 用户以 SkillRunTool(force_save_artifacts=True) 构造 run_tool,经 create_exec_tools 或 SkillToolSet 配对的 skill_exec 调用未显式传 save_as_artifacts=true 时。

实际影响: 同一 run_tool 下 skill_run 会无条件保存产物,而 skill_exec 路径的最终 result.artifact_files 为空、不做任何保存,与构造参数声明的 "always attempt to persist collected output files via the artifact service" 契约不符;同样因本次修复让收集生效,该配置绕过才首次表现为可见缺陷。

修正方向: 在 _collect_final_result 构造 fake_run_input 时读取 run_tool._force_save_artifacts(为真时置 save_as_artifacts=True,outputs 非空时同步置 outputs.save=True),或把 _run_async_impl 中的覆盖逻辑抽成共享辅助函数供两条路径复用。

较低

trpc_agent_sdk/skills/tools/_skill_exec.py:791-799

问题: 新增测试 test_collect_final_result_passes_workspace_runtime_and_returns_files(tests/skills/tools/test_skill_exec.py:117-168)用 mock 的 _prepare_outputs 只覆盖成功路径,未覆盖 _collect_final_result 中 _prepare_outputs 抛异常后被吞掉并置空 files 的降级分支(_skill_exec.py:797-799)——该分支正是本修复前产物丢失的故障点(缺参 TypeError 在此被 except 吞掉、文件静默丢失)。

触发条件: 回归测试运行期无法发现此分支的行为变化;真实运行时 fs.collect/fs.collect_outputs 抛错时该分支无测试保障。

实际影响: 若未来再次引入 _prepare_outputs 签名或调用错误(本次修复的对象),mock 会绕过真实 fs 使测试继续通过,无法防止同类"产物静默丢失"回归;异常降级路径(返回空输出而非崩溃)与 manifest 合并分支、run_result 抛错回退 proc.log 的分支均无覆盖。

修正方向: 增加用例:run_tool._prepare_outputs 抛异常时断言 _collect_final_result 返回的 result 的 output_files 为空且 finalized=True(验证降级不阻塞轮询);并可补充 manifest 非空(_merge_manifest_artifact_refs)与 run_result 失败回退 proc.log 两个分支。

Comment on lines 790 to +796
try:
files, manifest = await run_tool._prepare_outputs(ctx, exec_session.ws, fake_run_input)
files, manifest = await run_tool._prepare_outputs(
ctx,
exec_session.ws,
exec_session.workspace_runtime,
fake_run_input,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: _collect_final_result 用 ExecInput 的原始 in_data.save_as_artifacts 构造 fake_run_input(第 784 行),绕过了 SkillRunTool._run_async_impl 中 _force_save_artifacts 的覆盖逻辑(_skill_run.py:664-669 会把 save_as_artifacts 强制置 True),_skill_exec.py 全文也无任何 _force_save_artifacts 引用。

触发条件: 用户以 SkillRunTool(force_save_artifacts=True) 构造 run_tool,经 create_exec_tools 或 SkillToolSet 配对的 skill_exec 调用未显式传 save_as_artifacts=true 时。

实际影响: 同一 run_tool 下 skill_run 会无条件保存产物,而 skill_exec 路径的最终 result.artifact_files 为空、不做任何保存,与构造参数声明的 "always attempt to persist collected output files via the artifact service" 契约不符;同样因本次修复让收集生效,该配置绕过才首次表现为可见缺陷。

修正方向: 在 _collect_final_result 构造 fake_run_input 时读取 run_tool._force_save_artifacts(为真时置 save_as_artifacts=True,outputs 非空时同步置 outputs.save=True),或把 _run_async_impl 中的覆盖逻辑抽成共享辅助函数供两条路径复用。

Comment on lines +791 to 799
files, manifest = await run_tool._prepare_outputs(
ctx,
exec_session.ws,
exec_session.workspace_runtime,
fake_run_input,
)
except Exception as ex: # pylint: disable=broad-except
logger.warning("skill_exec: collect outputs failed: %s", ex)
files, manifest = [], None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

问题: 新增测试 test_collect_final_result_passes_workspace_runtime_and_returns_files(tests/skills/tools/test_skill_exec.py:117-168)用 mock 的 _prepare_outputs 只覆盖成功路径,未覆盖 _collect_final_result 中 _prepare_outputs 抛异常后被吞掉并置空 files 的降级分支(_skill_exec.py:797-799)——该分支正是本修复前产物丢失的故障点(缺参 TypeError 在此被 except 吞掉、文件静默丢失)。

触发条件: 回归测试运行期无法发现此分支的行为变化;真实运行时 fs.collect/fs.collect_outputs 抛错时该分支无测试保障。

实际影响: 若未来再次引入 _prepare_outputs 签名或调用错误(本次修复的对象),mock 会绕过真实 fs 使测试继续通过,无法防止同类"产物静默丢失"回归;异常降级路径(返回空输出而非崩溃)与 manifest 合并分支、run_result 抛错回退 proc.log 的分支均无覆盖。

修正方向: 增加用例:run_tool._prepare_outputs 抛异常时断言 _collect_final_result 返回的 result 的 output_files 为空且 finalized=True(验证降级不阻塞轮询);并可补充 manifest 非空(_merge_manifest_artifact_refs)与 run_result 失败回退 proc.log 两个分支。

@weimch weimch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve

@weimch
weimch merged commit ac0cda4 into main Oct 10, 2026
5 of 8 checks passed
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.

3 participants