> ## Documentation Index
> Fetch the complete documentation index at: https://opencompass-docs-preview-pr-335-0.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 结果与聚合

Benchmark 通过 `MetricContract` 声明 attempt 观测，将观测写入 `RunResult.metrics`，并由 `aggregate_metrics()` 返回经过校验的 `MetricReport`。执行状态与指标观测是两套独立契约。

## 声明 Metric Contract

标准的二元或标量结构可使用 `make_metric_contract()`：

```python theme={"system"}
from agentcompass.runtime.metrics import make_metric_contract


class ExampleBenchmark(BaseBenchmark):
    metric_contract = make_metric_contract(
        primary="correct",
        binary=("correct", "format_valid"),
        scalar=("reward",),
        labels={"correct": "Accuracy", "reward": "Reward"},
    )
```

每个 Contract 必须且只能包含一个主观测：

| 主观测 | 类型 | 多次尝试支持 |
| - | - | - |
| `correct` | `binary_success` | `avg` 和 `pass` |
| `score` | `scalar` | 仅 `avg` |

辅助观测可以是二元值或标量。未声明的观测、非布尔的二元值以及非有限标量都会校验失败。标量主观测不能使用 `pass` 策略。

## 写入评测观测

使用 `dataclasses.replace()` 保留 Harness 已生成的结果，并且只把 Benchmark 观测写入 `metrics`：

```python theme={"system"}
from dataclasses import replace

from agentcompass.runtime import ExecutionIssue, RunResult, TaskStatus


def apply_verifier_result(result: RunResult, verifier) -> RunResult:
    if verifier.timed_out or verifier.returncode not in {0, 1}:
        detail = (verifier.stderr or verifier.stdout).strip()
        status = (
            TaskStatus.ERROR
            if result.status == TaskStatus.RUN_ERROR
            else TaskStatus.EVAL_ERROR
        )
        return replace(
            result,
            status=status,
            issues=[*result.issues, ExecutionIssue("error", "evaluate", "verifier_failed", detail or "verifier failed")],
            metrics={},
        )

    passed = verifier.returncode == 0
    return replace(
        result,
        metrics={
            "correct": passed,
            "format_valid": verifier.format_valid,
            "reward": verifier.reward,
        },
    )
```

合法的错误答案携带有效的零分观测。评分时保留输入中的 issues、轨迹、产物和用量；ERROR 和 WARNING 可以与有效得分共存，完整诊断保存在 `artifacts.execution_diagnostics`。

在实际调用边界分类异常。`exception_issue()` 接收明确的 severity、phase 和 code，生成脱敏摘要并保留已有的 `StageFailure.issue`；`failed_result()` 接收已分类的 issue，保留部分结果与 traceback。不要通过 `source` 字符串让外层推断原因。同一 `run` 阶段中，模型超时为 ERROR，模型鉴权、配额和服务端故障为 FATAL；模型 API 适配器可复用 `runtime.llm.errors.model_exception_issue()`。Judge 调用失败在评分边界显式标为 FATAL，清理失败在释放边界显式标为 WARNING。

详情序列化器不会读取或写入旧的顶层 `correct` 和 `score` 字段。它始终保留 `status`、`metrics`、`final_answer`、`trajectory`、`issues`、`artifacts`、`analysis_result` 以及 `benchmark`、`harness` 两个 meta 命名空间，确保任务详情结构一致。

## 组织评分逻辑

Benchmark 负责声明指标，并在 `evaluate()` 中组织评测、将观测写入 `RunResult`。具体判分逻辑可按评测方式组织：

* 答案型评测：简单判分可直接写在 `evaluate()` 中，也可按需将提示词、答案解析和判分规则封装为独立 scorer，例如 [BrowseComp](https://github.com/open-compass/AgentCompass/blob/main/src/agentcompass/benchmarks/browsecomp.py)。scorer 只是可选的内部代码组织方式，不是需要单独注册的组件。
* 测试或 verifier 型评测：如 SWE-bench、TerminalBench，可直接执行测试或调用 verifier，并将结果转换为已声明的指标，无需额外封装 scorer。

## 理解两级聚合

聚合分为两个相互独立的阶段：

1. runtime 按选定的 attempt strategy 和 metric reducer，合并同一任务的 `k` 次观测。`k=1` 生成 `native@1`；`k>1` 时，`avg` 要求所有 attempt 完整，`pass` 可以在第一次二元成功后停止。
2. `Benchmark.aggregate_metrics()` 再按 Benchmark 的官方跨任务公式聚合任务级结果。

`BaseBenchmark.aggregate_metrics()` 会对 Contract 中的每个观测执行任务均值、类别均值或类别层级聚合。官方指标就是均值的 Benchmark 可以直接继承：

```python theme={"system"}
class ExampleBenchmark(BaseBenchmark):
    metric_contract = make_metric_contract(
        primary="score",
        scalar=("score",),
    )

    # load_tasks()、prepare_task()、evaluate() ...
```

默认实现和自定义 hook 都返回 `MetricReport`。每个 `MetricSeries` 会记录 `metric_id`、reducer、`k`、数值、聚合方法、相互独立的 `total`/`evaluated`/`error`/`unavailable` 计数，以及可选的类别和层级明细。

## 保留官方语料级公式

只有官方结果不是任务级数值均值时，才需要覆盖 `aggregate_metrics()`。应先取得共享报告，再通过 `BenchmarkAggregationContext` 保留统一的 attempt reduction、覆盖计数、类别处理和报告校验：

```python theme={"system"}
from agentcompass.runtime.metrics import BenchmarkAggregationContext


def aggregate_metrics(self, results, req, config):
    report = super().aggregate_metrics(results, req, config)
    context = BenchmarkAggregationContext(
        results,
        contract=self.metric_contract,
        report=report,
        category_hierarchy=getattr(config, "category_hierarchy", None),
    )
    context.replace_with_ratio_of_sums(
        output_metric="score",
        numerator_metric="points_earned",
        denominator_metric="points_available",
        label="Score",
    )
    return context.build()
```

`BenchmarkAggregationContext` 还提供 `replace_with_sum()`、`append_ratio_series()`、`append_benchmark_series()` 和 `update_extra()`，用于总和及 Benchmark 派生指标。不要在该 hook 中重新实现 attempt 选择，也不要修改 runtime 传入的持久化详情。

## 扩展 Strategy 与 Reducer

多次尝试的执行和指标计算分别由两个基于注册表的扩展点负责：

* `AttemptStrategy` 选择 reducer、提供调度策略，并判断某次 attempt 是否可以停止后续执行。
* `MetricReducer` 合并同一任务的精确观测，并声明自身支持的指标类型。

新实现分别通过 `register_attempt_strategy()` 和 `register_metric_reducer()` 注册。调度策略留在 strategy，数值语义留在 reducer，Benchmark 专属的跨任务公式仍由 `aggregate_metrics()` 负责。

只有 Benchmark 的每次 attempt 状态互相隔离，并且所选 Harness 也支持并行 attempt 时，才能设置 `parallel_attempts_safe = True`。否则 runtime 会串行执行同一任务的 attempts，同时仍遵循请求级 task concurrency。

## 聚合前检查

* Contract 声明了所有输出指标，并且只有一个 `correct` 或 `score` 主观测。
* 不根据 status 补齐缺失观察。最终 FATAL 使整题失效；普通 verifier reward 异常由组件显式报告 WARNING 和 fail／0。
* `avg@k` 拥有完整的 `k` 个有效观测；值为正的 `pass@k` 至少有一次有效成功。
* 自定义语料级公式保留官方分母，并返回 `context.build()`。
* 每个任务都有稳定、非空的 `task_id`，供 attempts、resume、reuse 和指标覆盖计数使用。

落盘结构见[任务结果](/zh/user_guide/other_features/results/task_results)，用户侧的指标行为见[指标与聚合](/zh/user_guide/other_features/results/metrics_aggregation)。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.