Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 172 additions & 68 deletions docs/mkdocs/en/evaluation.md

Large diffs are not rendered by default.

241 changes: 173 additions & 68 deletions docs/mkdocs/zh/evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ tRPC-Agent 评测模块是一套**自动化 Agent 质量检验工具**。它让
| 知识召回评估 | 评估 RAG 场景下检索到的知识是否足以支撑回答 | 验证知识库检索结果覆盖了问题中的关键事实 |
| 多轮运行与统计 | 同一用例跑多次,计算 pass@k 等稳定性指标 | 评估 Agent 在多次尝试中的通过率 |
| Trace 回放 | 跳过推理,直接用录制好的对话轨迹打分 | 用线上日志做离线评估,不消耗推理资源 |
| 外部 Agent 评测 | 通过 `call_agent` 评测非本框架创建的 Agent(HTTP 服务、CLI、其他框架) | 对已有 Claude Code CLI 或远程 API 做回归评测 |
| 回调钩子 | 在推理/打分的 8 个生命周期节点挂载自定义逻辑 | 打点、日志、采样、上报 |

#### 评测整体流程
Expand Down Expand Up @@ -283,6 +284,7 @@ pytest test_quickstart.py -v --tb=short -s
- **有用例未达阈值**:框架会抛出 `AssertionError`,失败摘要以 JSON 形式包含在错误信息中。
- **结果落盘**:若调用时传入 `eval_result_output_dir`,当次评测结果会写入该目录下的 `.evalset_result.json` 文件(详见[评测结果](#评测结果)一节)。


---

### 核心概念
Expand All @@ -296,7 +298,7 @@ pytest test_quickstart.py -v --tb=short -s
| **AgentEvaluator** | 对用户暴露的入口,提供 `evaluate()` 与 `get_executer()` | 在 pytest 测试中调用它 |
| **评测集(EvalSet)** | 描述"测什么"——场景、用户输入、预期输出 | 编写 `.evalset.json` 文件 |
| **评测配置(EvalConfig)** | 描述"怎么判"——用哪些指标、阈值、匹配规则 | 编写 `test_config.json` 文件 |
| **评估服务(LocalEvalService)** | 执行推理与打分的引擎 | 框架自动创建,通常无需关心 |
| **评估服务(LocalEvalService / RemoteEvalService)** | 执行推理与打分的引擎(本地 Agent 或 `call_agent`) | 框架自动创建,通常无需关心 |
| **评估器(Evaluator)** | 按指标计算分数的具体实现 | 选择内置评估器,或注册自定义 |
| **评估器注册表(EvaluatorRegistry)** | 维护 `metric_name` → 评估器类型的映射 | 需要自定义评估器时注册 |
| **评测结果(EvaluateResult)** | 承载评测的结构化结果 | 通过 `get_result()` 获取并分析 |
Expand All @@ -305,12 +307,31 @@ pytest test_quickstart.py -v --tb=short -s

AgentEvaluator 是整个评测流程的入口和编排者:

1. **加载阶段**:AgentEvaluator 从评测集文件(`.evalset.json` / `.test.json`)加载 EvalSet,从同目录的 `test_config.json` 加载 EvalConfig,按 `agent_module` 加载 Agent(若整集为 [Trace 模式](#trace-模式),此步可省略)。
2. **构建评估服务**:AgentEvaluator 将 EvalSet 写入 InMemoryEvalSetsManager,创建 LocalEvalService(依赖该 Manager、UserSimulatorProvider、可选 EvalSetResultsManager、Runner、Callbacks)。默认使用 StaticUserSimulator,按 conversation 的 user_content 驱动推理。可选注入 LocalEvalSetResultsManager 将运行结果写入目录
3. **推理阶段**:评估服务按 EvalSet 中的用例与 conversation 驱动 Runner 推理,得到实际 Invocation 列表(实际工具调用、实际回复)
1. **加载阶段**:AgentEvaluator 从评测集文件(`.evalset.json` / `.test.json`)加载 EvalSet,从同目录的 `test_config.json` 加载 EvalConfig;若走本地 Agent 路径,按 `agent_module` 加载 Agent(使用 `call_agent` 或整集为 [Trace 模式](#trace-模式),此步可省略)。
2. **构建评估服务**:AgentEvaluator 将 EvalSet 写入 InMemoryEvalSetsManager;传 `call_agent` 时创建 RemoteEvalService,否则创建 LocalEvalService(依赖 Manager、UserSimulatorProvider、可选 EvalSetResultsManager、Runner、Callbacks)。
3. **推理阶段**:评估服务按 EvalSet 的用例与 conversation 逐轮推理:LocalEvalService 通过 Runner 调 Agent;RemoteEvalService 通过 `call_agent(query)` 获取每轮实际回复,得到实际 Invocation 列表。
4. **打分阶段**:评估服务根据 EvalConfig 中的 EvalMetric 列表,从 EvaluatorRegistry 获取各评估器,对实际与预期逐项打分并汇总为 EvalCaseResult。
5. **结果汇总**:AgentEvaluator 根据结果判定通过/失败,有用例未达阈值时抛出 `AssertionError`,可选将结果落盘为 `.evalset_result.json`。

#### AgentEvaluator 参数列表

`evaluate()` 与 `get_executer()` 接受相同的参数(`evaluate()` 内部调用 `get_executer()`):

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| eval_dataset_file_path_or_dir | str | 评测集文件或目录路径(递归扫描 `.evalset.json` / `.test.json`) |
| agent_module | str \| None | 本框架 Agent 所在 Python 模块路径;与 `call_agent` 互斥。传 `call_agent` 时不需要;全部 case 为 Trace 模式时也不需要 |
| call_agent | CallAgent \| None | 非本框架 Agent 的异步可调用对象(`async def(str)->str`);与 `agent_module` / `runner` 互斥 |
| num_runs | int | 每个评测集运行次数,默认 1 |
| agent_name | str \| None | Agent 显示名称 |
| print_detailed_results | bool | 是否打印每个用例的详细对比信息,默认 True |
| eval_result_output_dir | str \| None | 结果落盘目录;不传则仅内存聚合 |
| runner | Runner \| None | 自定义 Runner 实例;与 `call_agent` 互斥 |
| case_parallelism | int \| None | 推理阶段最大并发用例数 |
| case_eval_parallelism | int \| None | 打分阶段最大并发用例数 |
| callbacks | Callbacks \| None | 生命周期回调 |
| eval_metrics_file_path_or_dir | str \| None | 共享评测配置文件路径(覆盖同目录 `test_config.json`) |

---

### 评测集(EvalSet)编写指南
Expand Down Expand Up @@ -455,6 +476,8 @@ Trace 模式的配置详见[高级功能 - Trace 模式](#trace-模式)。
| `llm_rubric_response` | LLMRubricResponseEvaluator | LLM 裁判按评估细则逐项打分 | 需要从多个维度(正确性、相关性、合规性等)评估回复质量 |
| `llm_rubric_knowledge_recall` | LLMRubricKnowledgeRecallEvaluator | LLM 裁判评估知识检索结果是否足以支撑回答 | RAG 场景,需验证检索到的知识覆盖了关键事实 |

> 注意:`call_agent` 模式不支持 `tool_trajectory_avg_score`。评测外部黑盒 Agent 时,建议优先使用 `final_response_avg_score` 或 LLM Judge 类指标。

**Rubric** 指评估细则:在配置中以 `rubrics` 数组列出多条可独立判定的条款(如「回答须包含结论」「须与问题相关」),LLM 裁判对每条细则给出通过与否,再汇总为该项指标的得分。

#### 如何选择指标
Expand Down Expand Up @@ -816,70 +839,7 @@ LLM 最终响应评判(仅需 judge_model):

建议 `api_key`、`base_url` 用环境变量占位(如 `${TRPC_AGENT_API_KEY}`),由执行环境替换,避免明文写入配置文件。

**多裁判模型(跨模型聚合)**

同一个 LLM 裁判指标可以同时使用多个裁判模型,并通过 `models_aggregator` 聚合各模型的判定结果。此时改用 `judge_models` 而非 `judge_model`,两字段互斥。每个裁判模型的明细会输出到 `PerInvocationResult.per_model_scores`(`NamedScoreResult` 列表)。

内置聚合器:

| 名称 | 通过规则 | 总分 |
| --- | --- | --- |
| `all_pass`(默认) | 所有模型都通过 | 各模型得分的最小值 |
| `any_pass` | 任一模型通过 | 各模型得分的最大值 |
| `majority_pass` | 严格多数通过(`passed*2 > total`) | `passed_count / total` |
| `avg` | 平均分 ≥ threshold | 各模型得分的平均值 |
| `weighted_avg` | 加权平均 ≥ threshold | `sum(w*s) / sum(w)` |
| `weighted_majority` | 通过模型的权重占比 ≥ 0.5 | `sum(w where passed) / sum(w)` |

若某个裁判模型执行抛异常,则该模型视为一张反对票;若所有模型都抛异常,该轮结果记为 `NOT_EVALUATED`。

```json
{
"metrics": [
{
"metric_name": "llm_final_response",
"threshold": 1,
"criterion": {
"llm_judge": {
"judge_models": [
{
"model_name": "glm-4.7",
"api_key": "${TRPC_AGENT_API_KEY}",
"base_url": "${TRPC_AGENT_BASE_URL}",
"weight": 2.0
},
{
"model_name": "gpt-4o",
"api_key": "${TRPC_AGENT_API_KEY}",
"base_url": "${TRPC_AGENT_BASE_URL}",
"weight": 1.0
}
],
"models_aggregator": "weighted_avg",
"parallel": true
}
}
}
]
}
```

`parallel` 控制多个裁判模型之间的执行方式:`true`(默认)并发调用,耗时取决于最慢的模型;`false` 按声明顺序串行调用。仅在 `judge_models` 有多个模型时生效。

若裁判模型默认开启思考链,建议在对应 `JudgeModelOptions` 上显式设 `"think": false`:judge 输出本身是结构化 JSON,思考链对最终判分无价值,关闭可显著降低 token 消耗与延时。每个裁判模型的 `think` 独立设置。

也可以在运行时注册自定义聚合器,其优先级高于 criterion 中写的 `models_aggregator` 名:

```python
from trpc_agent_sdk.evaluation import LLM_EVALUATOR_REGISTRY, ScoreResult

def my_aggregator(per_model, threshold, weights):
# per_model: list[ScoreResult];weights: list[float]
score = sum(s.score or 0.0 for s in per_model) / len(per_model)
return ScoreResult(score=score, reason="custom aggregation")

LLM_EVALUATOR_REGISTRY.register_models_aggregator("llm_final_response", my_aggregator)
```
> 同一个 LLM 裁判指标还可以同时使用多个裁判模型并聚合结果,详见[高级功能 - 多裁判模型(跨模型聚合)](#多裁判模型跨模型聚合)。

#### 自定义准则

Expand Down Expand Up @@ -1819,10 +1779,155 @@ async def test_pass_at_k():

完整示例见 [examples/evaluation/pass_at_k/](../../../examples/evaluation/pass_at_k/)。

#### 评测非本框架创建的 Agent(call_agent)

若被测 Agent 不是通过本框架创建和管理的(例如部署在 HTTP/RPC 服务后面、通过 CLI 调用、或使用其他框架封装),无法提供 `agent_module` 或 `runner`,可改用 **`call_agent`** 参数:传入一个异步函数,evaluator 会在每轮对话中调用它获取实际回复,其余打分流程不变。

**配置方式**

在 **AgentEvaluator.evaluate()** 或 **get_executer()** 中传入 `call_agent=your_async_fn`,不传 `agent_module` 和 `runner`。`call_agent` 的签名必须是 `async def call_agent(query: str) -> str`。

**适用场景**

评测任何无法实例化为本框架 `BaseAgent` 的可调用对象:HTTP/RPC 远程服务、CLI Agent、其他框架(LangChain / AutoGen / 自研)封装的黑盒 Agent 等。

**约束**

- `call_agent` 必须是异步函数(传入同步函数会报 `ValueError`)
- `call_agent` 与 `agent_module` / `runner` 互斥(同时传入会报 `ValueError`)
- `call_agent` 模式与 Trace 模式互斥(evalset 含 trace case 会报 `ValueError`)
- `call_agent` 模式不支持 `tool_trajectory_avg_score`(会报 `ValueError`);建议使用 `final_response_avg_score`、`llm_final_response` 或 `llm_rubric_response`
- 多轮 case 会按轮次依次调用 `call_agent`;每次调用对应一个 `Invocation`

**示例**:以 Claude Code CLI 为例,将其封装为 `call_agent` 并接入评测

```python
import asyncio
import os
from asyncio.subprocess import PIPE

from trpc_agent_sdk.evaluation import AgentEvaluator


async def call_agent(query: str) -> str:
"""调用 Claude Code CLI,返回其文本输出。"""
cli_bin = os.getenv("CLAUDE_CODE_BIN", "claude")
cli_args = [cli_bin, "-p", query]

model_name = os.getenv("CLAUDE_CODE_MODEL")
if model_name:
cli_args.extend(["--model", model_name])

proc = await asyncio.create_subprocess_exec(*cli_args, stdout=PIPE, stderr=PIPE)
stdout, stderr = await proc.communicate()

if proc.returncode != 0:
raise RuntimeError(stderr.decode("utf-8", errors="ignore").strip())

output_text = stdout.decode("utf-8", errors="ignore").strip()
for line in output_text.splitlines():
if line.strip():
return line.strip()
return ""


# 方式 A:只关心 pass/fail
await AgentEvaluator.evaluate(
eval_dataset_file_path_or_dir="agent/my_evalset.evalset.json",
call_agent=call_agent,
)

# 方式 B:需要结构化结果
executer = AgentEvaluator.get_executer(
eval_dataset_file_path_or_dir="agent/my_evalset.evalset.json",
call_agent=call_agent,
)
await executer.evaluate()
result = executer.get_result() # EvaluateResult
```

> 示例中默认命令是 `claude`。如果你环境里的可执行文件名不同(例如 `trpc-claudecode` 或自定义命令),将 `CLAUDE_CODE_BIN` 环境变量改为对应命令即可。对于 HTTP 服务场景,只需把 `call_agent` 函数体改为 `aiohttp` / `httpx` 调用,签名保持 `async def call_agent(query: str) -> str` 不变。

#### 多裁判模型(跨模型聚合)

同一个 LLM 裁判指标可以同时使用多个裁判模型,并通过 `models_aggregator` 聚合各模型的判定结果,降低单模型裁判的波动。此时改用 `judge_models` 而非 `judge_model`,两字段互斥。每个裁判模型的明细会输出到 `PerInvocationResult.per_model_scores`(`NamedScoreResult` 列表)。

**配置方式**

在 `test_config.json` 的 LLM 裁判类指标 `criterion.llm_judge` 中,将 `judge_model` 替换为 `judge_models`(数组),并设置 `models_aggregator` 选择聚合策略。`parallel` 控制多个裁判模型之间的执行方式:`true`(默认)并发调用,`false` 串行调用。

**适用场景**

对评判结果要求更高置信度(如安全合规、医疗场景),或希望对比不同裁判模型的判定差异。

**内置聚合器**

| 名称 | 通过规则 | 总分 |
| --- | --- | --- |
| `all_pass`(默认) | 所有模型都通过 | 各模型得分的最小值 |
| `any_pass` | 任一模型通过 | 各模型得分的最大值 |
| `majority_pass` | 严格多数通过(`passed*2 > total`) | `passed_count / total` |
| `avg` | 平均分 ≥ threshold | 各模型得分的平均值 |
| `weighted_avg` | 加权平均 ≥ threshold | `sum(w*s) / sum(w)` |
| `weighted_majority` | 通过模型的权重占比 ≥ 0.5 | `sum(w where passed) / sum(w)` |

若某个裁判模型执行抛异常,则该模型视为一张反对票;若所有模型都抛异常,该轮结果记为 `NOT_EVALUATED`。

**示例**:两个裁判模型按加权平均聚合

```json
{
"metrics": [
{
"metric_name": "llm_final_response",
"threshold": 1,
"criterion": {
"llm_judge": {
"judge_models": [
{
"model_name": "glm-4.7",
"api_key": "${TRPC_AGENT_API_KEY}",
"base_url": "${TRPC_AGENT_BASE_URL}",
"weight": 2.0
},
{
"model_name": "gpt-4o",
"api_key": "${TRPC_AGENT_API_KEY}",
"base_url": "${TRPC_AGENT_BASE_URL}",
"weight": 1.0
}
],
"models_aggregator": "weighted_avg",
"parallel": true
}
}
}
]
}
```

若裁判模型默认开启思考链,建议在对应 `JudgeModelOptions` 上显式设 `"think": false`:judge 输出本身是结构化 JSON,思考链对最终判分无价值,关闭可显著降低 token 消耗与延时。

**自定义聚合器**

也可以在运行时注册自定义聚合器,其优先级高于 criterion 中写的 `models_aggregator` 名:

```python
from trpc_agent_sdk.evaluation import LLM_EVALUATOR_REGISTRY, ScoreResult

def my_aggregator(per_model, threshold, weights):
score = sum(s.score or 0.0 for s in per_model) / len(per_model)
return ScoreResult(score=score, reason="custom aggregation")

LLM_EVALUATOR_REGISTRY.register_models_aggregator("llm_final_response", my_aggregator)
```

#### Trace 模式

默认模式下,评估服务会真实调用 Agent 做推理。若你已有录制好的对话轨迹(如线上日志、历史会话),希望只做「打分」、不重复推理,可使用 **Trace 模式**:在用例上设置 **eval_mode: "trace"** 并提供 **actual_conversation**,评估服务会跳过推理,直接使用该轨迹参与打分。

> 注意:Trace 模式与 `call_agent` 模式互斥;传入 `call_agent` 且评测集中包含 trace case 时,框架会在启动期抛出 `ValueError`。

**配置方式**

- 在 **EvalCase** 上设置 **eval_mode**: `"trace"`。
Expand Down
Loading
Loading