Skip to content

Commit 1b5d226

Browse files
taoisnieraychen911
authored andcommitted
feat: eval模块支持不同大模型评估同一metric
TAPD: --story=134052565
1 parent 5558813 commit 1b5d226

21 files changed

Lines changed: 2398 additions & 82 deletions

docs/mkdocs/en/evaluation.md

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ The eval configuration describes "how to judge." This section teaches you how to
425425

426426
`test_config.json` must be placed in the **same directory** as the eval set file (`.evalset.json` / `.test.json`); the framework loads it automatically.
427427

428+
> **Advanced**: If you want **multiple eval sets to share a single configuration** (e.g., centralizing all metric definitions in one JSON), pass `eval_metrics_file_path_or_dir` at call time to bypass the same-directory convention. See [Shared Configuration: `eval_metrics_file_path_or_dir`](#shared-configuration-eval_metrics_file_path_or_dir).
429+
428430
#### Structure Definition
429431

430432
**EvalConfig** (parsed from `test_config.json`)
@@ -718,7 +720,10 @@ Compare using text "contains" with case-insensitivity (common when the final res
718720

719721
| Field | Type | Description |
720722
| --- | --- | --- |
721-
| judge_model | object | Judge model configuration (JudgeModelOptions); required |
723+
| judge_model | object | Judge model configuration (JudgeModelOptions); required when `judge_models` is not set |
724+
| judge_models | array | Multi-model judge list (JudgeModelOptions items); mutually exclusive with `judge_model`. Cross-model results are combined by `models_aggregator` |
725+
| models_aggregator | string | Cross-model aggregation strategy. Built-in: `all_pass` (default) / `any_pass` / `majority_pass` / `avg` / `weighted_avg` / `weighted_majority`. Custom names must be registered via `LLM_EVALUATOR_REGISTRY.register_models_aggregator` before evaluation |
726+
| parallel | boolean | Whether to run the multiple judge models concurrently; default `true` |
722727
| rubrics | array | Rubric list; required for llm_rubric_response and llm_rubric_knowledge_recall |
723728
| knowledge_tool_names | array | List of knowledge retrieval tool names; used by llm_rubric_knowledge_recall, default `["knowledge_search"]` |
724729

@@ -730,7 +735,9 @@ Compare using text "contains" with case-insensitivity (common when the final res
730735
| api_key | string | API key |
731736
| base_url | string | Optional, custom endpoint |
732737
| num_samples | number | Number of judge samples per turn; default 1 |
733-
| generation_config | object | Generation parameters (max_tokens, temperature, etc.) |
738+
| weight | number | Per-model weight used by `weighted_avg` / `weighted_majority` aggregators; default 1.0 |
739+
| think | boolean | Controls the judge model's thinking mode. `false`: disable thinking (sets both `thinking_config.thinking_budget=0` and `chat_template_kwargs.enable_thinking=false`). `true`: enable thinking with automatic budget (`include_thoughts=true`). Unset (default): keep the model default. Recommended `false` for judge models to save tokens and latency |
740+
| generation_config | object | Generation parameters (max_tokens, temperature, etc.; may also explicitly set `thinking_config` / `http_options`; the `think` field overrides them) |
734741

735742
**Rubric** (items in the rubrics array)
736743

@@ -812,6 +819,71 @@ LLM response quality with rubrics (llm_rubric_response or llm_rubric_knowledge_r
812819

813820
It is recommended to use environment variable placeholders for `api_key` and `base_url` (e.g., `${TRPC_AGENT_API_KEY}`), which are replaced by the execution environment, to avoid writing plaintext in configuration files.
814821

822+
**Multi-model judge (cross-model aggregation)**
823+
824+
A single LLM-judge metric may use multiple judge models simultaneously and combine their verdicts via `models_aggregator`. Use `judge_models` instead of `judge_model`; the two fields are mutually exclusive. Per-model details are available on `PerInvocationResult.per_model_scores` (a list of `NamedScoreResult`).
825+
826+
Built-in aggregators:
827+
828+
| Name | Pass rule | Overall score |
829+
| --- | --- | --- |
830+
| `all_pass` (default) | all models pass | min of per-model scores |
831+
| `any_pass` | any model passes | max of per-model scores |
832+
| `majority_pass` | strict majority passes (`passed*2 > total`) | `passed_count / total` |
833+
| `avg` | mean ≥ threshold | mean of per-model scores |
834+
| `weighted_avg` | weighted mean ≥ threshold | `sum(w*s) / sum(w)` |
835+
| `weighted_majority` | weighted-passed share ≥ 0.5 | `sum(w where passed) / sum(w)` |
836+
837+
If a single judge model raises during execution, that model is counted as a non-passing vote; if every model raises, the invocation is reported as `NOT_EVALUATED`.
838+
839+
```json
840+
{
841+
"metrics": [
842+
{
843+
"metric_name": "llm_final_response",
844+
"threshold": 1,
845+
"criterion": {
846+
"llm_judge": {
847+
"judge_models": [
848+
{
849+
"model_name": "glm-4.7",
850+
"api_key": "${TRPC_AGENT_API_KEY}",
851+
"base_url": "${TRPC_AGENT_BASE_URL}",
852+
"weight": 2.0
853+
},
854+
{
855+
"model_name": "gpt-4o",
856+
"api_key": "${TRPC_AGENT_API_KEY}",
857+
"base_url": "${TRPC_AGENT_BASE_URL}",
858+
"weight": 1.0
859+
}
860+
],
861+
"models_aggregator": "weighted_avg",
862+
"parallel": true
863+
}
864+
}
865+
}
866+
]
867+
}
868+
```
869+
870+
`parallel` controls how multiple judge models are executed: `true` (default) calls all models concurrently, with latency bounded by the slowest model; `false` calls them sequentially in the declared order. Only takes effect when `judge_models` contains more than one model.
871+
872+
If a judge model has thinking enabled by default, consider setting `"think": false` on its `JudgeModelOptions`: the judge output is a structured JSON, thinking traces add no value to the final verdict, and disabling thinking significantly reduces token cost and latency. Each judge model has its own independent `think` flag.
873+
874+
Custom aggregators can be registered at runtime and take precedence over the `models_aggregator` name written in the criterion:
875+
876+
```python
877+
from trpc_agent_sdk.evaluation import LLM_EVALUATOR_REGISTRY, ScoreResult
878+
879+
def my_aggregator(per_model, threshold, weights):
880+
# per_model: list[ScoreResult]; weights: list[float]
881+
score = sum(s.score or 0.0 for s in per_model) / len(per_model)
882+
return ScoreResult(score=score, reason="custom aggregation")
883+
884+
LLM_EVALUATOR_REGISTRY.register_models_aggregator("llm_final_response", my_aggregator)
885+
```
886+
815887
#### Custom Criteria
816888

817889
To fully customize the "whether it matches" logic in code, you can register a matching function with `CRITERION_REGISTRY` before running the evaluation. Supported types for registration are `TOOL_TRAJECTORY` and `FINAL_RESPONSE`; once registered, comparisons of that type will invoke your provided function `(actual, expected) -> bool`, bypassing the built-in criteria from the configuration file.
@@ -1764,6 +1836,16 @@ In default mode, the eval service actually calls the Agent for inference. If you
17641836

17651837
Replaying existing conversations, offline evaluation, or avoiding repeated Agent and model calls when debugging evaluation flows.
17661838

1839+
**`agent_module` is optional**
1840+
1841+
`agent_module` tells the framework where to load the Agent from, so it can call the Agent for inference during evaluation. Trace mode no longer calls the Agent, so when **every case in the eval set is in trace mode**, `AgentEvaluator.evaluate()` / `get_executer()` no longer needs `agent_module` and you can simply omit it:
1842+
1843+
```python
1844+
await AgentEvaluator.evaluate(
1845+
eval_dataset_file_path_or_dir=trace_only_eval_set_path,
1846+
)
1847+
```
1848+
17671849
**Example**: A Trace mode case in the eval set
17681850

17691851
```json
@@ -2160,6 +2242,53 @@ async def test_evaluate_with_custom_runner():
21602242

21612243
For the complete example, see [examples/evaluation/custom_runner/](../../../examples/evaluation/custom_runner/).
21622244

2245+
#### Shared Configuration (eval_metrics_file_path_or_dir)
2246+
2247+
By default, every eval set needs a `test_config.json` placed in its **own directory**, which the framework loads automatically. When multiple eval sets need to use the same metrics and thresholds, copying `test_config.json` into every directory is redundant and prone to drift. You can extract the config into a single shared location and point to it via `eval_metrics_file_path_or_dir` at call time. The framework will then **ignore the same-directory convention** and apply this shared config to every eval set.
2248+
2249+
**Comparison**: in the default layout each eval set needs its own `test_config.json`; in the shared layout there is only one.
2250+
2251+
```
2252+
## Default (same-directory) ## Shared (eval_metrics_file_path_or_dir)
2253+
project/ project/
2254+
└── eval_data/ ├── shared_metrics.json ← shared config
2255+
├── weather/ └── eval_data/
2256+
│ ├── weather.evalset.json ├── weather/weather.evalset.json
2257+
│ └── test_config.json ├── booking/booking.evalset.json
2258+
└── booking/ └── search/search.evalset.json
2259+
├── booking.evalset.json
2260+
└── test_config.json
2261+
```
2262+
2263+
**Configuration**
2264+
2265+
Pass `eval_metrics_file_path_or_dir` to `AgentEvaluator.evaluate()` / `get_executer()`:
2266+
2267+
- A **file path** (`.json`): loaded directly as the shared configuration;
2268+
- A **directory path**: the framework looks up `*.json` in that directory **non-recursively**, and exactly one must be present; otherwise it raises `FileNotFoundError` (zero matches) or `ValueError` (more than one);
2269+
- Omitted or `None`: keep the default behavior—load `test_config.json` from each eval set's own directory.
2270+
2271+
**Applicable Scenarios**
2272+
2273+
Multiple eval sets sharing the same metrics and thresholds; switching thresholds per environment (dev / staging / prod) in CI; eval sets generated by other tools (WebUI, log replayers, etc.) where maintaining a per-directory `test_config.json` is inconvenient.
2274+
2275+
**Example**: point all eval sets in the right-hand layout above to `shared_metrics.json`
2276+
2277+
```python
2278+
import os
2279+
import pytest
2280+
from trpc_agent_sdk.evaluation import AgentEvaluator
2281+
2282+
@pytest.mark.asyncio
2283+
async def test_with_shared_metrics():
2284+
project_dir = os.path.dirname(os.path.abspath(__file__))
2285+
await AgentEvaluator.evaluate(
2286+
agent_module="agent",
2287+
eval_dataset_file_path_or_dir=os.path.join(project_dir, "eval_data"),
2288+
eval_metrics_file_path_or_dir=os.path.join(project_dir, "shared_metrics.json"),
2289+
)
2290+
```
2291+
21632292

21642293
## Using WebUI for Agent Evaluation
21652294

0 commit comments

Comments
 (0)