Agent Harness 技术笔记:从 Trajectory 到 Function Calling Loop

1. Agent 评测和「一问一答」评测有什么不同

传统 LLM Eval(例如 QA、翻译、选择题)通常是:

input → model → output → metric

Agent 场景则往往是 多步决策

用户任务 → 思考 → 选工具(Action) → 观察结果(Observation) → 再思考 → … → 最终答案

典型循环叫 ReAct(Reason + Act):

Thought: 我需要查北京天气
Action: get_weather(city="Beijing")
Observation: {"weather": "晴", "temperature": "25°C"}
Thought: 信息够了,可以回答
Final Answer: 北京今天晴,25°C

Agent Harness 的核心目标不是「最后一句话像不像标准答案」,而是:

给定 Task + Tool Set → 自动执行 → 记录完整轨迹 → 自动打分 → 可回归对比

因此需要记录的不只是 final_answer,而是 整条 decision chain


2. Trajectory:为什么要持久化、可回放

Agent 交互往往是 multi-step。Harness 需要把每一步结构化存下来,形成 trajectory

单步 schema 示例:

{
"step_id":1,
"thought":"需要查北京天气",
"tool_name":"get_weather",
"tool_args":{"city":"Beijing"},
"observation":"{\"weather\": \"晴\"}",
"latency_ms":420,
"prompt_tokens":180,
"completion_tokens":45
}

完整 trajectory 还包括:run_idtask_idmodelmax_stepsfinal_answersteps[] 等。

为什么要存盘

能力 没有 trajectory 有 trajectory
复盘 tool 是否调对
检测死循环 / 超步数
离线重算 metric
跨版本回归对比

持久化之后可以:

  1. 离线回放 —— 不必重调 API,直接读 JSON/JSONL
  2. 自动打分 —— 从 steps 算 success、misuse、loop、cost
  3. 回归测试 —— 对比两次 run 的轨迹与指标

Trajectory schema 是 Agent 评测的「通用语言」:先定义格式,再写 runner、metrics、report。


3. Tool 评测:expected vs actual

Agent 除了最终答案,还要评 工具调用是否正确

每步 tool 调用约定为:

{"name": "get_weather", "args": {"city": "Beijing"}}

比对逻辑是 结构化字段匹配,不是简单字符串相等:

字段 规则
name
工具名必须相同
args
参数字典必须相同(可按规则归一化,如 city 别名)

三个核心指标:

match_tool_sequence(expected, actual) # 逐步 [True/False]
tool_call_accuracy(expected, actual) # 匹配数 / max(期望步数, 实际步数)
tool_misuse_rate(expected, actual) # 错/多/漏 的比例

三种典型 Tool Misuse

类型 期望 实际 问题
错工具
get_weather
calculator
选错能力
错参数
city=Beijing
city=Shanghai
工具对、参数错
多余调用
1 次 weather
weather + search
多调了不需要的 tool

这是 rule-based 批改:name + args 精确匹配(加少量 normalize),不是语义理解。


4. Tool Schema 和 Tool 执行函数是两回事

Function Calling 体系里必须分清两层:

4.1 Tool Schema(给模型看的「菜单」)

{
"type":"function",
"function":{
"name":"get_weather",
"description":"获取指定城市的天气",
"parameters":{
"type":"object",
"properties":{
"city":{"type":"string"}
},
"required":["city"]
}
}
}

这是 JSON 元数据,传给 API 的 tools= 参数。
它 不能执行,只告诉模型:有哪些工具、各需要什么参数。

4.2 Tool 执行函数(本地真正干活的代码)

def get_weather(city: str) -> dict:
# 查 API / 数据库 / mock 数据
return{"city": city,"weather":"晴","temperature":"25°C"}

模型 永远不会直接调用 这个 Python 函数。流程是:

Schema → 模型生成 tool_call(name + arguments)
→ 你的代码 parse 后 execute()
→ 结果塞回 messages

模型决策,代码执行 —— 这是 FC 的安全与可控基础。


5. function_call vs tool_calls(OpenAI 兼容 API)

字段 状态 特点
function_call
Deprecated
老版;每次最多 1 个 function
tool_calls
当前标准
列表;可 多个 调用;带 id;统一 tools 框架

新版结构示例:

message.tool_calls = [
{
"id":"call_xxx",
"type":"function",
"function":{
"name":"get_weather",
"arguments":'{"city": "北京"}'
}
}
]

回传 tool 结果时必须对应 id:

{
"role":"tool",
"tool_call_id":"call_xxx",
"content":'{"weather": "晴", ...}'
}

新代码应统一使用 tools= + tool_calls,不再使用 function_call


6. Agent Runner:run_fc_loop 在做什么

Agent Runner 的职责:把 task + tools 跑成标准 trajectory,而不是追求最强 Agent 能力。

run_fc_loop 本质是 orchestration 循环(不是模型「自我调用」):

初始化 messages = [system, user]
 
 
for step in range(max_steps):
resp = LLM.chat(messages, tools=TOOL_SCHEMAS)

if resp.tool_calls:
for call in resp.tool_calls:
obs = execute(call.name, call.args)
messages.append(assistant_message)
messages.append(tool_message(obs))
记录 TrajectoryStep
if detect_loop(steps): break
else:
final_answer = resp.content
break

return trajectory

三个角色分工:

组件 职责
LLM
决策:调什么 tool、何时给最终答案
execute()
执行:跑本地 Python / 调外部 API
Runner
编排:循环、记日志、安全截断

7. 为什么 Function Calling 通常至少两轮 API

这是 FC 的 必要设计,不是实现冗余。

第 1 轮 API
输入: 用户问题
输出: tool_calls(模型说「我需要查天气」)
本地: execute(get_weather) → observation
 
第 2 轮 API
输入: 用户问题 + tool 结果
输出: final_answer(基于真实 observation 组织回答)

若只有一轮:

  • 不调 tool → 容易幻觉
  • 调了 tool 但看不到结果 → 无法 grounded 回答

因此 「一轮要 tool + 一轮给答案」 是标准两阶段。
复杂任务会更多轮:多次 tool 调用 + 最后一次生成答案。

Trajectory 里的 步数 通常统计的是 API 轮次 / 决策步,不等于「同一个 tool 被执行了几次」。


8. 死循环检测:detect_loop

Agent 常见故障之一是 重复调用相同工具

step1: get_weather(Beijing)
step2: get_weather(Beijing) ← 连续相同 → 判定死循环
step3: get_weather(Beijing)

最小检测规则:

def detect_loop(steps):
for i inrange(1,len(steps)):
if(steps[i-1].tool_name == steps[i].tool_name
and steps[i-1].tool_args == steps[i].tool_args):
returnTrue
returnFalse

Runner 中一旦发现 loop,应:

  • 设 loop_detected = True
  • 强制 break,避免无限烧 token

另一种失败模式:跑满 max_steps 仍无 final_answer,也可视为 timeout / loop 类失败。


9. Agent Harness 六层架构(串联)

agent_tasks/ 测什么(JSONL:单步 tool / 多步任务 / 回归集)
tool_registry/ 能用什么(schema + execute)
agent_runner/ 谁执行(FC loop / ReAct loop)
trajectory/ 怎么记(每步 thought / tool / observation / tokens)
agent_metrics/ 怎么算(success / tool_accuracy / loop / cost)
reports/ 怎么交付(metrics + trajectory 附件)

数据流:

Task → Runner → Trajectory → Metrics → Report
Tool Registry

10. 核心结论

  1. Agent ≠ Chatbot:评测对象是 multi-step 决策链,不是单句 output。
  2. Trajectory 是 Agent Eval 的增量:可审计、可回放、可回归。
  3. Tool 评测是结构化匹配name + args,外加 misuse / accuracy 指标。
  4. Schema ≠ 函数:模型看 menu,代码做 execution。
  5. FC 至少两轮 API:先 tool、再 answer,是协议设计而非浪费。
  6. Runner 是编排层:循环驱动 LLM ↔ Tool,并负责 loop 截断与日志。
  7. tool_calls 取代 function_call:多调用、可追踪、可扩展。

以上构成 Agent Harness 的最小技术栈:不依赖特定框架(LangGraph、MCP 等),用 schema + loop + trajectory + rule metrics 即可搭建可测试、可度量的 Agent 评测流水线。

posted @ 2026-06-22 11:34  若-飞  阅读(25)  评论(0)    收藏  举报