open-compass / open-compass/AgentCompass
[Bug] 内置 ACTF 消费者无法一致解析非 OpenAI 工具调用 / Built-in ACTF consumers silently ignore non-OpenAI tool-call payloads
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 135
- Forks
- 28
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 37
Description
中文
问题概述
多个内置 Harness 会将工具调用保存在 ACTF 的 assistant_content.tool_calls 中,但这些 payload 并没有采用相同的内部结构。与此同时,多个内置 Analyzer、Parser 和 Viewer loader 分别假设工具调用使用 OpenAI 的 function.name / function.arguments 结构。
这会导致非 OpenAI 格式的工具调用被静默忽略,或者在下游处理中丢失名称、参数和动作信息。这里的主要问题不是原始事件没有保存,而是内置生产者和内置消费者之间缺少一致的解释契约。
最小示例
Codex Harness 会将下面这样的完成事件保存在 assistant_content.tool_calls 中:
{
"type": "command_execution",
"command": "pwd"
}
相关构造逻辑位于:
src/agentcompass/harnesses/codex.py:478-515
但是 BasicMetricAnalyzer 当前只从下面的字段读取工具名称:
tc["function"]["name"]
因此,上面的调用会返回 None,不会进入 total_tool_calls。
真实实验中的已确认现象
在一次真实的 Terminal-Bench 2.1 Codex replay 中(task:break-filter-js-from-html_security):
- 原始 Codex 记录包含 9 个已完成的
command_execution; - ACTF 的
assistant_content.tool_calls中保留了 9 个调用; BasicMetricAnalyzer报告total_tool_calls = 0;StepToolRepetitionAnalyzer报告total_tools = 0;CrossStepToolRepetitionAnalyzer报告total_tools = 0;ConsecutiveToolRepetitionAnalyzer无法生成可用的工具签名。
一个只读的最小复现不需要调用模型或重新运行 Benchmark,只需构造包含上述 payload 的 ACTF StepInfo,然后运行相关 Analyzer。
其他受影响的内置消费者
-
src/agentcompass/analyzers/basic/tool_repetition_analyzer.py_extract_tool_info()只读取function.name和function.arguments。Codex 调用会全部被过滤。这里不能只补充顶层type:重复检测还需要为顶层command等参数定义稳定签名,否则不同命令可能被错误地视为相同调用。 -
src/agentcompass/analyzers/qualitative/trajectory_parser.py_extract_tool_action()主要读取function.name和function.arguments。Codex 的 tool-only step 会产生空action,从而影响后续定性分析输入。 -
src/agentcompass/analyzers/trajectory_graph/io.pyGraph loader 只解析
function.name和function.arguments。Codex 调用的数量仍然存在,但会被转换成空名称和空参数。 -
src/agentcompass/analyzers/basic/network_op_analyzer.pyAnalyzer 会把非空
tool_calls计入total_tool_steps,但网络操作识别只读取function.name和function.arguments,因此看不到 Codex 顶层的command。这会使网络操作的分子漏计、比例偏低。上述真实 fixture 本身没有网络命令,所以它证明了结构不兼容,但没有形成网络正例漏报;该行为可以使用一个最小curlpayload 独立复现。
跨 Harness 的结构差异
当前内置 Harness 至少会产生以下几类结构:
OpenAI / OpenHands / Mini-SWE / Terminus2:
{id, type: "function", function: {name, arguments}}
Codex:
{type: "command_execution" | "mcp_tool_call" | "web_search" | "file_change", ...}
Claude Code:
{type: "tool_use", id, name, input}
OpenClaw:
{type: "toolCall", id, name, arguments | params}
ResearchHarness:
{name, arguments}
因此,简单使用 function.name -> type fallback 可以修复已确认的 Codex 计数问题,但并不是跨 Harness 的完整解决方案。例如 Claude Code 和 OpenClaw 的 type 只是通用事件类别,具体工具名称仍在顶层 name;ResearchHarness 则没有顶层 type。
历史背景
- PR #70 引入 Basic Analyzer 时,
_extract_tool_name()被明确实现为 OpenAI-format 解析; - PR #140 后来加入 Codex Harness,并直接将
command_execution等原生 item 保存到tool_calls; - 已有 Analyzer 没有随新的 payload 结构同步扩展;
- PR #161 后来加入的 trajectory graph loader 仍使用 OpenAI 结构解析。
这看起来更像是组件演进过程中形成的生产者—消费者兼容缺口,而不是原始轨迹丢失。
期望行为与设计问题
至少,内置消费者应当能够一致、安全地解释内置 Harness 产生的工具调用,不应在没有错误提示的情况下将真实调用统计为零或删除其语义字段。
希望确认 ACTF 对 assistant_content.tool_calls 的长期契约:
- 是否应由每个 Harness Adapter 输出统一的 canonical tool-call schema,例如稳定的
id、name和arguments,并在raw/extra中保留原始事件;或 - 是否应保留 Harness 原生 payload,但提供一个公共 normalization utility,由 Analyzer、定性 Parser 和 Viewer 统一消费。
本 Issue 不预设必须采用哪一种方案,但当前各消费者独立猜测 payload 格式会产生静默且不一致的指标。
与其他问题的边界
本 Issue 不包含以下问题:
- Issue #276:
reconstruct_run_result()没有恢复StepInfo.tools; - Codex tool-only step 是否应被文本 Analyzer 计为 LLM step;
StepInfo.tools在不同 Harness 中表示工具定义还是实际调用;- Result Browser 同时累加
assistant_content.tool_calls和StepInfo.tools可能造成的双计数。
这些问题的根因和验收标准不同,建议分别处理。
环境
- AgentCompass commit:
512af1cac726fb08389670d9e31a69b1524f69d4 - 复现不需要模型调用或外部 API
English
Summary
Several built-in harnesses store tool calls in ACTF under assistant_content.tool_calls, but their payloads do not share the same internal structure. At the same time, multiple built-in analyzers, parsers, and viewer loaders independently assume the OpenAI function.name / function.arguments shape.
As a result, non-OpenAI tool calls can be silently ignored, or lose their names, arguments, and action information during downstream processing. The main issue is not that the raw events are absent, but that built-in producers and built-in consumers do not share a consistent interpretation contract.
Minimal example
The Codex harness stores a completed event like the following in assistant_content.tool_calls:
{
"type": "command_execution",
"command": "pwd"
}
The relevant construction logic is located at:
src/agentcompass/harnesses/codex.py:478-515
However, BasicMetricAnalyzer currently reads the tool name only from:
tc["function"]["name"]
Therefore, the call above returns None and is not included in total_tool_calls.
Confirmed behavior in a real experiment
In a real Terminal-Bench 2.1 Codex replay (task: break-filter-js-from-html_security):
- the raw Codex record contains 9 completed
command_executionevents; - ACTF preserves 9 calls in
assistant_content.tool_calls; BasicMetricAnalyzerreportstotal_tool_calls = 0;StepToolRepetitionAnalyzerreportstotal_tools = 0;CrossStepToolRepetitionAnalyzerreportstotal_tools = 0;ConsecutiveToolRepetitionAnalyzercannot build usable tool signatures.
A read-only minimal reproduction does not require a model call or a complete benchmark rerun. It only needs an ACTF StepInfo containing the payload above, followed by running the relevant analyzer.
Other affected built-in consumers
-
src/agentcompass/analyzers/basic/tool_repetition_analyzer.py_extract_tool_info()only readsfunction.nameandfunction.arguments, so all Codex calls are filtered out. Adding only a top-leveltypefallback is insufficient here: repetition detection also needs a stable signature for top-level parameters such ascommand; otherwise, different commands could be incorrectly treated as identical calls. -
src/agentcompass/analyzers/qualitative/trajectory_parser.py_extract_tool_action()primarily readsfunction.nameandfunction.arguments. A Codex tool-only step therefore produces an emptyaction, affecting the input of downstream qualitative analysis. -
src/agentcompass/analyzers/trajectory_graph/io.pyThe graph loader only parses
function.nameandfunction.arguments. The number of Codex calls remains present, but they are converted into empty names and empty arguments. -
src/agentcompass/analyzers/basic/network_op_analyzer.pyThe analyzer includes non-empty
tool_callsintotal_tool_steps, but network-operation detection only readsfunction.nameandfunction.arguments, so it cannot see a Codex top-levelcommand. This can undercount the numerator and lower the reported network-operation ratio. The real fixture above contains no network commands, so it proves the structural incompatibility but does not contain an observed positive network-operation miss; that behavior can be reproduced independently with a minimalcurlpayload.
Payload differences across harnesses
Built-in harnesses currently produce at least the following shapes:
OpenAI / OpenHands / Mini-SWE / Terminus2:
{id, type: "function", function: {name, arguments}}
Codex:
{type: "command_execution" | "mcp_tool_call" | "web_search" | "file_change", ...}
Claude Code:
{type: "tool_use", id, name, input}
OpenClaw:
{type: "toolCall", id, name, arguments | params}
ResearchHarness:
{name, arguments}
Therefore, a simple function.name -> type fallback fixes the confirmed Codex counting symptom, but it is not a complete cross-harness solution. For example, type in Claude Code and OpenClaw is only a generic event category, while the concrete tool name remains in the top-level name; ResearchHarness has no top-level type at all.
Historical context
- PR #70 introduced the Basic Analyzers and explicitly implemented
_extract_tool_name()as an OpenAI-format parser; - PR #140 later introduced the Codex harness and stored raw items such as
command_executiondirectly intool_calls; - existing analyzers were not extended for the new payload shape;
- the trajectory graph loader introduced later in PR #161 still parsed only the OpenAI shape.
This appears to be a producer-consumer compatibility gap created during component evolution, rather than a loss of the raw trajectory.
Expected behavior and design question
At minimum, built-in consumers should consistently and safely interpret tool calls produced by built-in harnesses. Real calls should not be counted as zero or have their semantic fields removed without any error indication.
It would be helpful to clarify the long-term contract of ACTF assistant_content.tool_calls:
- Should every harness adapter emit a canonical tool-call schema with stable fields such as
id,name, andarguments, while preserving the original event inraw/extra; or - Should harness-native payloads remain unchanged, with a shared normalization utility consumed by analyzers, qualitative parsers, and viewers?
This issue does not assume that either approach must be chosen, but the current pattern of each consumer independently guessing the payload shape produces silent and inconsistent metrics.
Scope boundaries
This issue does not include:
- Issue #276, where
reconstruct_run_result()does not restoreStepInfo.tools; - whether Codex tool-only steps should be counted as LLM steps by text analyzers;
- whether
StepInfo.toolsrepresents tool definitions or actual calls across different harnesses; - possible double-counting in Result Browser when both
assistant_content.tool_callsandStepInfo.toolsare added together.
These findings have different root causes and acceptance criteria and should be handled separately.
Environment
- AgentCompass commit:
512af1cac726fb08389670d9e31a69b1524f69d4 - Reproduction requires no model calls or external APIs
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Codex construction logic in src/agentcompass/harnesses/codex.py:478-515 and run the described read-only StepInfo reproduction. Then inspect the four affected consumers: tool_repetition_analyzer.py, trajectory_parser.py, trajectory_graph/io.py, and network_op_analyzer.py. Done means built-in payload shapes are interpreted consistently without silently losing counts, names, arguments, actions, or stable repetition signatures.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, testing-qa, tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100