@@ -65,6 +65,7 @@ tRPC-Agent 评测模块是一套**自动化 Agent 质量检验工具**。它让
6565| 知识召回评估 | 评估 RAG 场景下检索到的知识是否足以支撑回答 | 验证知识库检索结果覆盖了问题中的关键事实 |
6666| 多轮运行与统计 | 同一用例跑多次,计算 pass@k 等稳定性指标 | 评估 Agent 在多次尝试中的通过率 |
6767| Trace 回放 | 跳过推理,直接用录制好的对话轨迹打分 | 用线上日志做离线评估,不消耗推理资源 |
68+ | 外部 Agent 评测 | 通过 ` call_agent ` 评测非本框架创建的 Agent(HTTP 服务、CLI、其他框架) | 对已有 Claude Code CLI 或远程 API 做回归评测 |
6869| 回调钩子 | 在推理/打分的 8 个生命周期节点挂载自定义逻辑 | 打点、日志、采样、上报 |
6970
7071#### 评测整体流程
@@ -283,6 +284,7 @@ pytest test_quickstart.py -v --tb=short -s
283284- ** 有用例未达阈值** :框架会抛出 ` AssertionError ` ,失败摘要以 JSON 形式包含在错误信息中。
284285- ** 结果落盘** :若调用时传入 ` eval_result_output_dir ` ,当次评测结果会写入该目录下的 ` .evalset_result.json ` 文件(详见[ 评测结果] ( #评测结果 ) 一节)。
285286
287+
286288---
287289
288290### 核心概念
@@ -296,7 +298,7 @@ pytest test_quickstart.py -v --tb=short -s
296298| ** AgentEvaluator** | 对用户暴露的入口,提供 ` evaluate() ` 与 ` get_executer() ` | 在 pytest 测试中调用它 |
297299| ** 评测集(EvalSet)** | 描述"测什么"——场景、用户输入、预期输出 | 编写 ` .evalset.json ` 文件 |
298300| ** 评测配置(EvalConfig)** | 描述"怎么判"——用哪些指标、阈值、匹配规则 | 编写 ` test_config.json ` 文件 |
299- | ** 评估服务(LocalEvalService)** | 执行推理与打分的引擎 | 框架自动创建,通常无需关心 |
301+ | ** 评估服务(LocalEvalService / RemoteEvalService )** | 执行推理与打分的引擎(本地 Agent 或 ` call_agent ` ) | 框架自动创建,通常无需关心 |
300302| ** 评估器(Evaluator)** | 按指标计算分数的具体实现 | 选择内置评估器,或注册自定义 |
301303| ** 评估器注册表(EvaluatorRegistry)** | 维护 ` metric_name ` → 评估器类型的映射 | 需要自定义评估器时注册 |
302304| ** 评测结果(EvaluateResult)** | 承载评测的结构化结果 | 通过 ` get_result() ` 获取并分析 |
@@ -305,12 +307,31 @@ pytest test_quickstart.py -v --tb=short -s
305307
306308AgentEvaluator 是整个评测流程的入口和编排者:
307309
308- 1 . ** 加载阶段** :AgentEvaluator 从评测集文件(` .evalset.json ` / ` .test.json ` )加载 EvalSet,从同目录的 ` test_config.json ` 加载 EvalConfig,按 ` agent_module ` 加载 Agent(若整集为 [ Trace 模式] ( #trace-模式 ) ,此步可省略)。
309- 2 . ** 构建评估服务** :AgentEvaluator 将 EvalSet 写入 InMemoryEvalSetsManager,创建 LocalEvalService(依赖该 Manager、UserSimulatorProvider、可选 EvalSetResultsManager、Runner、Callbacks)。默认使用 StaticUserSimulator,按 conversation 的 user_content 驱动推理。可选注入 LocalEvalSetResultsManager 将运行结果写入目录 。
310- 3 . ** 推理阶段** :评估服务按 EvalSet 中的用例与 conversation 驱动 Runner 推理 ,得到实际 Invocation 列表(实际工具调用、实际回复) 。
310+ 1 . ** 加载阶段** :AgentEvaluator 从评测集文件(` .evalset.json ` / ` .test.json ` )加载 EvalSet,从同目录的 ` test_config.json ` 加载 EvalConfig;若走本地 Agent 路径 ,按 ` agent_module ` 加载 Agent(使用 ` call_agent ` 或整集为 [ Trace 模式] ( #trace-模式 ) 时 ,此步可省略)。
311+ 2 . ** 构建评估服务** :AgentEvaluator 将 EvalSet 写入 InMemoryEvalSetsManager;传 ` call_agent ` 时创建 RemoteEvalService,否则创建 LocalEvalService(依赖 Manager、UserSimulatorProvider、可选 EvalSetResultsManager、Runner、Callbacks)。
312+ 3 . ** 推理阶段** :评估服务按 EvalSet 的用例与 conversation 逐轮推理:LocalEvalService 通过 Runner 调 Agent;RemoteEvalService 通过 ` call_agent(query) ` 获取每轮实际回复 ,得到实际 Invocation 列表。
3113134 . ** 打分阶段** :评估服务根据 EvalConfig 中的 EvalMetric 列表,从 EvaluatorRegistry 获取各评估器,对实际与预期逐项打分并汇总为 EvalCaseResult。
3123145 . ** 结果汇总** :AgentEvaluator 根据结果判定通过/失败,有用例未达阈值时抛出 ` AssertionError ` ,可选将结果落盘为 ` .evalset_result.json ` 。
313315
316+ #### AgentEvaluator 参数列表
317+
318+ ` evaluate() ` 与 ` get_executer() ` 接受相同的参数(` evaluate() ` 内部调用 ` get_executer() ` ):
319+
320+ | 参数 | 类型 | 说明 |
321+ | --- | --- | --- |
322+ | eval_dataset_file_path_or_dir | str | 评测集文件或目录路径(递归扫描 ` .evalset.json ` / ` .test.json ` ) |
323+ | agent_module | str \| None | 本框架 Agent 所在 Python 模块路径;与 ` call_agent ` 互斥。传 ` call_agent ` 时不需要;全部 case 为 Trace 模式时也不需要 |
324+ | call_agent | CallAgent \| None | 非本框架 Agent 的异步可调用对象(` async def(str)->str ` );与 ` agent_module ` / ` runner ` 互斥 |
325+ | num_runs | int | 每个评测集运行次数,默认 1 |
326+ | agent_name | str \| None | Agent 显示名称 |
327+ | print_detailed_results | bool | 是否打印每个用例的详细对比信息,默认 True |
328+ | eval_result_output_dir | str \| None | 结果落盘目录;不传则仅内存聚合 |
329+ | runner | Runner \| None | 自定义 Runner 实例;与 ` call_agent ` 互斥 |
330+ | case_parallelism | int \| None | 推理阶段最大并发用例数 |
331+ | case_eval_parallelism | int \| None | 打分阶段最大并发用例数 |
332+ | callbacks | Callbacks \| None | 生命周期回调 |
333+ | eval_metrics_file_path_or_dir | str \| None | 共享评测配置文件路径(覆盖同目录 ` test_config.json ` ) |
334+
314335---
315336
316337### 评测集(EvalSet)编写指南
@@ -455,6 +476,8 @@ Trace 模式的配置详见[高级功能 - Trace 模式](#trace-模式)。
455476| ` llm_rubric_response ` | LLMRubricResponseEvaluator | LLM 裁判按评估细则逐项打分 | 需要从多个维度(正确性、相关性、合规性等)评估回复质量 |
456477| ` llm_rubric_knowledge_recall ` | LLMRubricKnowledgeRecallEvaluator | LLM 裁判评估知识检索结果是否足以支撑回答 | RAG 场景,需验证检索到的知识覆盖了关键事实 |
457478
479+ > 注意:` call_agent ` 模式不支持 ` tool_trajectory_avg_score ` 。评测外部黑盒 Agent 时,建议优先使用 ` final_response_avg_score ` 或 LLM Judge 类指标。
480+
458481** Rubric** 指评估细则:在配置中以 ` rubrics ` 数组列出多条可独立判定的条款(如「回答须包含结论」「须与问题相关」),LLM 裁判对每条细则给出通过与否,再汇总为该项指标的得分。
459482
460483#### 如何选择指标
@@ -816,70 +839,7 @@ LLM 最终响应评判(仅需 judge_model):
816839
817840建议 ` api_key ` 、` base_url ` 用环境变量占位(如 ` ${TRPC_AGENT_API_KEY} ` ),由执行环境替换,避免明文写入配置文件。
818841
819- ** 多裁判模型(跨模型聚合)**
820-
821- 同一个 LLM 裁判指标可以同时使用多个裁判模型,并通过 ` models_aggregator ` 聚合各模型的判定结果。此时改用 ` judge_models ` 而非 ` judge_model ` ,两字段互斥。每个裁判模型的明细会输出到 ` PerInvocationResult.per_model_scores ` (` NamedScoreResult ` 列表)。
822-
823- 内置聚合器:
824-
825- | 名称 | 通过规则 | 总分 |
826- | --- | --- | --- |
827- | ` all_pass ` (默认) | 所有模型都通过 | 各模型得分的最小值 |
828- | ` any_pass ` | 任一模型通过 | 各模型得分的最大值 |
829- | ` majority_pass ` | 严格多数通过(` passed*2 > total ` ) | ` passed_count / total ` |
830- | ` avg ` | 平均分 ≥ threshold | 各模型得分的平均值 |
831- | ` weighted_avg ` | 加权平均 ≥ threshold | ` sum(w*s) / sum(w) ` |
832- | ` weighted_majority ` | 通过模型的权重占比 ≥ 0.5 | ` sum(w where passed) / sum(w) ` |
833-
834- 若某个裁判模型执行抛异常,则该模型视为一张反对票;若所有模型都抛异常,该轮结果记为 ` NOT_EVALUATED ` 。
835-
836- ``` json
837- {
838- "metrics" : [
839- {
840- "metric_name" : " llm_final_response" ,
841- "threshold" : 1 ,
842- "criterion" : {
843- "llm_judge" : {
844- "judge_models" : [
845- {
846- "model_name" : " glm-4.7" ,
847- "api_key" : " ${TRPC_AGENT_API_KEY}" ,
848- "base_url" : " ${TRPC_AGENT_BASE_URL}" ,
849- "weight" : 2.0
850- },
851- {
852- "model_name" : " gpt-4o" ,
853- "api_key" : " ${TRPC_AGENT_API_KEY}" ,
854- "base_url" : " ${TRPC_AGENT_BASE_URL}" ,
855- "weight" : 1.0
856- }
857- ],
858- "models_aggregator" : " weighted_avg" ,
859- "parallel" : true
860- }
861- }
862- }
863- ]
864- }
865- ```
866-
867- ` parallel ` 控制多个裁判模型之间的执行方式:` true ` (默认)并发调用,耗时取决于最慢的模型;` false ` 按声明顺序串行调用。仅在 ` judge_models ` 有多个模型时生效。
868-
869- 若裁判模型默认开启思考链,建议在对应 ` JudgeModelOptions ` 上显式设 ` "think": false ` :judge 输出本身是结构化 JSON,思考链对最终判分无价值,关闭可显著降低 token 消耗与延时。每个裁判模型的 ` think ` 独立设置。
870-
871- 也可以在运行时注册自定义聚合器,其优先级高于 criterion 中写的 ` models_aggregator ` 名:
872-
873- ``` python
874- from trpc_agent_sdk.evaluation import LLM_EVALUATOR_REGISTRY , ScoreResult
875-
876- def my_aggregator (per_model , threshold , weights ):
877- # per_model: list[ScoreResult];weights: list[float]
878- score = sum (s.score or 0.0 for s in per_model) / len (per_model)
879- return ScoreResult(score = score, reason = " custom aggregation" )
880-
881- LLM_EVALUATOR_REGISTRY .register_models_aggregator(" llm_final_response" , my_aggregator)
882- ```
842+ > 同一个 LLM 裁判指标还可以同时使用多个裁判模型并聚合结果,详见[ 高级功能 - 多裁判模型(跨模型聚合)] ( #多裁判模型跨模型聚合 ) 。
883843
884844#### 自定义准则
885845
@@ -1819,10 +1779,155 @@ async def test_pass_at_k():
18191779
18201780完整示例见 [ examples/evaluation/pass_at_k/] ( ../../../examples/evaluation/pass_at_k/ ) 。
18211781
1782+ #### 评测非本框架创建的 Agent(call_agent)
1783+
1784+ 若被测 Agent 不是通过本框架创建和管理的(例如部署在 HTTP/RPC 服务后面、通过 CLI 调用、或使用其他框架封装),无法提供 ` agent_module ` 或 ` runner ` ,可改用 ** ` call_agent ` ** 参数:传入一个异步函数,evaluator 会在每轮对话中调用它获取实际回复,其余打分流程不变。
1785+
1786+ ** 配置方式**
1787+
1788+ 在 ** AgentEvaluator.evaluate()** 或 ** get_executer()** 中传入 ` call_agent=your_async_fn ` ,不传 ` agent_module ` 和 ` runner ` 。` call_agent ` 的签名必须是 ` async def call_agent(query: str) -> str ` 。
1789+
1790+ ** 适用场景**
1791+
1792+ 评测任何无法实例化为本框架 ` BaseAgent ` 的可调用对象:HTTP/RPC 远程服务、CLI Agent、其他框架(LangChain / AutoGen / 自研)封装的黑盒 Agent 等。
1793+
1794+ ** 约束**
1795+
1796+ - ` call_agent ` 必须是异步函数(传入同步函数会报 ` ValueError ` )
1797+ - ` call_agent ` 与 ` agent_module ` / ` runner ` 互斥(同时传入会报 ` ValueError ` )
1798+ - ` call_agent ` 模式与 Trace 模式互斥(evalset 含 trace case 会报 ` ValueError ` )
1799+ - ` call_agent ` 模式不支持 ` tool_trajectory_avg_score ` (会报 ` ValueError ` );建议使用 ` final_response_avg_score ` 、` llm_final_response ` 或 ` llm_rubric_response `
1800+ - 多轮 case 会按轮次依次调用 ` call_agent ` ;每次调用对应一个 ` Invocation `
1801+
1802+ ** 示例** :以 Claude Code CLI 为例,将其封装为 ` call_agent ` 并接入评测
1803+
1804+ ``` python
1805+ import asyncio
1806+ import os
1807+ from asyncio.subprocess import PIPE
1808+
1809+ from trpc_agent_sdk.evaluation import AgentEvaluator
1810+
1811+
1812+ async def call_agent (query : str ) -> str :
1813+ """ 调用 Claude Code CLI,返回其文本输出。"""
1814+ cli_bin = os.getenv(" CLAUDE_CODE_BIN" , " claude" )
1815+ cli_args = [cli_bin, " -p" , query]
1816+
1817+ model_name = os.getenv(" CLAUDE_CODE_MODEL" )
1818+ if model_name:
1819+ cli_args.extend([" --model" , model_name])
1820+
1821+ proc = await asyncio.create_subprocess_exec(* cli_args, stdout = PIPE , stderr = PIPE )
1822+ stdout, stderr = await proc.communicate()
1823+
1824+ if proc.returncode != 0 :
1825+ raise RuntimeError (stderr.decode(" utf-8" , errors = " ignore" ).strip())
1826+
1827+ output_text = stdout.decode(" utf-8" , errors = " ignore" ).strip()
1828+ for line in output_text.splitlines():
1829+ if line.strip():
1830+ return line.strip()
1831+ return " "
1832+
1833+
1834+ # 方式 A:只关心 pass/fail
1835+ await AgentEvaluator.evaluate(
1836+ eval_dataset_file_path_or_dir = " agent/my_evalset.evalset.json" ,
1837+ call_agent = call_agent,
1838+ )
1839+
1840+ # 方式 B:需要结构化结果
1841+ executer = AgentEvaluator.get_executer(
1842+ eval_dataset_file_path_or_dir = " agent/my_evalset.evalset.json" ,
1843+ call_agent = call_agent,
1844+ )
1845+ await executer.evaluate()
1846+ result = executer.get_result() # EvaluateResult
1847+ ```
1848+
1849+ > 示例中默认命令是 ` claude ` 。如果你环境里的可执行文件名不同(例如 ` trpc-claudecode ` 或自定义命令),将 ` CLAUDE_CODE_BIN ` 环境变量改为对应命令即可。对于 HTTP 服务场景,只需把 ` call_agent ` 函数体改为 ` aiohttp ` / ` httpx ` 调用,签名保持 ` async def call_agent(query: str) -> str ` 不变。
1850+
1851+ #### 多裁判模型(跨模型聚合)
1852+
1853+ 同一个 LLM 裁判指标可以同时使用多个裁判模型,并通过 ` models_aggregator ` 聚合各模型的判定结果,降低单模型裁判的波动。此时改用 ` judge_models ` 而非 ` judge_model ` ,两字段互斥。每个裁判模型的明细会输出到 ` PerInvocationResult.per_model_scores ` (` NamedScoreResult ` 列表)。
1854+
1855+ ** 配置方式**
1856+
1857+ 在 ` test_config.json ` 的 LLM 裁判类指标 ` criterion.llm_judge ` 中,将 ` judge_model ` 替换为 ` judge_models ` (数组),并设置 ` models_aggregator ` 选择聚合策略。` parallel ` 控制多个裁判模型之间的执行方式:` true ` (默认)并发调用,` false ` 串行调用。
1858+
1859+ ** 适用场景**
1860+
1861+ 对评判结果要求更高置信度(如安全合规、医疗场景),或希望对比不同裁判模型的判定差异。
1862+
1863+ ** 内置聚合器**
1864+
1865+ | 名称 | 通过规则 | 总分 |
1866+ | --- | --- | --- |
1867+ | ` all_pass ` (默认) | 所有模型都通过 | 各模型得分的最小值 |
1868+ | ` any_pass ` | 任一模型通过 | 各模型得分的最大值 |
1869+ | ` majority_pass ` | 严格多数通过(` passed*2 > total ` ) | ` passed_count / total ` |
1870+ | ` avg ` | 平均分 ≥ threshold | 各模型得分的平均值 |
1871+ | ` weighted_avg ` | 加权平均 ≥ threshold | ` sum(w*s) / sum(w) ` |
1872+ | ` weighted_majority ` | 通过模型的权重占比 ≥ 0.5 | ` sum(w where passed) / sum(w) ` |
1873+
1874+ 若某个裁判模型执行抛异常,则该模型视为一张反对票;若所有模型都抛异常,该轮结果记为 ` NOT_EVALUATED ` 。
1875+
1876+ ** 示例** :两个裁判模型按加权平均聚合
1877+
1878+ ``` json
1879+ {
1880+ "metrics" : [
1881+ {
1882+ "metric_name" : " llm_final_response" ,
1883+ "threshold" : 1 ,
1884+ "criterion" : {
1885+ "llm_judge" : {
1886+ "judge_models" : [
1887+ {
1888+ "model_name" : " glm-4.7" ,
1889+ "api_key" : " ${TRPC_AGENT_API_KEY}" ,
1890+ "base_url" : " ${TRPC_AGENT_BASE_URL}" ,
1891+ "weight" : 2.0
1892+ },
1893+ {
1894+ "model_name" : " gpt-4o" ,
1895+ "api_key" : " ${TRPC_AGENT_API_KEY}" ,
1896+ "base_url" : " ${TRPC_AGENT_BASE_URL}" ,
1897+ "weight" : 1.0
1898+ }
1899+ ],
1900+ "models_aggregator" : " weighted_avg" ,
1901+ "parallel" : true
1902+ }
1903+ }
1904+ }
1905+ ]
1906+ }
1907+ ```
1908+
1909+ 若裁判模型默认开启思考链,建议在对应 ` JudgeModelOptions ` 上显式设 ` "think": false ` :judge 输出本身是结构化 JSON,思考链对最终判分无价值,关闭可显著降低 token 消耗与延时。
1910+
1911+ ** 自定义聚合器**
1912+
1913+ 也可以在运行时注册自定义聚合器,其优先级高于 criterion 中写的 ` models_aggregator ` 名:
1914+
1915+ ``` python
1916+ from trpc_agent_sdk.evaluation import LLM_EVALUATOR_REGISTRY , ScoreResult
1917+
1918+ def my_aggregator (per_model , threshold , weights ):
1919+ score = sum (s.score or 0.0 for s in per_model) / len (per_model)
1920+ return ScoreResult(score = score, reason = " custom aggregation" )
1921+
1922+ LLM_EVALUATOR_REGISTRY .register_models_aggregator(" llm_final_response" , my_aggregator)
1923+ ```
1924+
18221925#### Trace 模式
18231926
18241927默认模式下,评估服务会真实调用 Agent 做推理。若你已有录制好的对话轨迹(如线上日志、历史会话),希望只做「打分」、不重复推理,可使用 ** Trace 模式** :在用例上设置 ** eval_mode: "trace"** 并提供 ** actual_conversation** ,评估服务会跳过推理,直接使用该轨迹参与打分。
18251928
1929+ > 注意:Trace 模式与 ` call_agent ` 模式互斥;传入 ` call_agent ` 且评测集中包含 trace case 时,框架会在启动期抛出 ` ValueError ` 。
1930+
18261931** 配置方式**
18271932
18281933- 在 ** EvalCase** 上设置 ** eval_mode** : ` "trace" ` 。
0 commit comments