aeval-framework 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. aeval_framework-0.1.0.dist-info/METADATA +42 -0
  2. aeval_framework-0.1.0.dist-info/RECORD +63 -0
  3. aeval_framework-0.1.0.dist-info/WHEEL +4 -0
  4. aeval_framework-0.1.0.dist-info/entry_points.txt +2 -0
  5. agent_eval/__init__.py +14 -0
  6. agent_eval/api/__init__.py +14 -0
  7. agent_eval/api/app.py +82 -0
  8. agent_eval/api/events.py +96 -0
  9. agent_eval/api/routes/__init__.py +0 -0
  10. agent_eval/api/routes/datasets.py +441 -0
  11. agent_eval/api/routes/graders.py +19 -0
  12. agent_eval/api/routes/metrics.py +49 -0
  13. agent_eval/api/routes/runs.py +573 -0
  14. agent_eval/api/routes/suites.py +84 -0
  15. agent_eval/api/routes/tasks.py +114 -0
  16. agent_eval/api/standalone.py +105 -0
  17. agent_eval/cli.py +455 -0
  18. agent_eval/core/__init__.py +48 -0
  19. agent_eval/core/contract.py +296 -0
  20. agent_eval/core/metrics.py +184 -0
  21. agent_eval/core/runner.py +868 -0
  22. agent_eval/core/suite.py +60 -0
  23. agent_eval/core/types.py +227 -0
  24. agent_eval/dataset/__init__.py +31 -0
  25. agent_eval/dataset/models.py +199 -0
  26. agent_eval/dataset/quality.py +194 -0
  27. agent_eval/dataset/sources/__init__.py +45 -0
  28. agent_eval/dataset/sources/llm_generator.py +219 -0
  29. agent_eval/dataset/sources/manual.py +172 -0
  30. agent_eval/dataset/sources/regression.py +201 -0
  31. agent_eval/dataset/sources/trace_mining.py +277 -0
  32. agent_eval/dataset/storage.py +342 -0
  33. agent_eval/dataset/version.py +72 -0
  34. agent_eval/examples/__init__.py +0 -0
  35. agent_eval/examples/basic_usage.py +175 -0
  36. agent_eval/examples/mock_runner.py +195 -0
  37. agent_eval/graders/__init__.py +91 -0
  38. agent_eval/graders/artifact_check.py +114 -0
  39. agent_eval/graders/code_based.py +101 -0
  40. agent_eval/graders/human.py +77 -0
  41. agent_eval/graders/metric.py +142 -0
  42. agent_eval/graders/model_based.py +179 -0
  43. agent_eval/graders/state_check.py +106 -0
  44. agent_eval/graders/step_level.py +116 -0
  45. agent_eval/graders/tool_calls.py +102 -0
  46. agent_eval/graders/transcript.py +86 -0
  47. agent_eval/metrics/__init__.py +110 -0
  48. agent_eval/metrics/answer_relevancy.py +57 -0
  49. agent_eval/metrics/base.py +155 -0
  50. agent_eval/metrics/batch_evaluation.py +267 -0
  51. agent_eval/metrics/context_precision.py +62 -0
  52. agent_eval/metrics/context_recall.py +71 -0
  53. agent_eval/metrics/faithfulness.py +72 -0
  54. agent_eval/metrics/llm_judge.py +100 -0
  55. agent_eval/metrics/prompt_metric.py +150 -0
  56. agent_eval/metrics/pytest_plugin.py +308 -0
  57. agent_eval/metrics/report.py +149 -0
  58. agent_eval/metrics/synthetic_data.py +203 -0
  59. agent_eval/storage/__init__.py +17 -0
  60. agent_eval/storage/memory.py +95 -0
  61. agent_eval/storage/sqlite.py +240 -0
  62. agent_eval/trace/__init__.py +16 -0
  63. agent_eval/trace/phoenix.py +144 -0
@@ -0,0 +1,60 @@
1
+ """
2
+ Suite loading from YAML with strict validation.
3
+
4
+ All validation rules (semver version, unique task ids, grader name format,
5
+ weight >= 0, sample_count 1-10, name <= 128 chars, at least one task) live on
6
+ the Pydantic models in core/types.py so both YAML loading and API JSON
7
+ creation validate identically. This module wraps load/validation failures
8
+ with file-path context.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+
15
+ import yaml
16
+ from pydantic import ValidationError
17
+
18
+ from agent_eval.core.types import EvalSuite
19
+
20
+
21
+ class SuiteLoadError(Exception):
22
+ """Suite YAML 加载或校验失败 (错误信息包含文件路径上下文)。"""
23
+
24
+
25
+ def load_suite(path: str | Path) -> EvalSuite:
26
+ """
27
+ 从 YAML 文件加载评测套件。
28
+
29
+ Args:
30
+ path: YAML 文件路径
31
+
32
+ Returns:
33
+ 校验通过的 EvalSuite
34
+
35
+ Raises:
36
+ SuiteLoadError: 文件不存在 / YAML 语法错误 / 校验失败 (均含文件路径)
37
+ """
38
+ suite_path = Path(path)
39
+
40
+ if not suite_path.exists():
41
+ raise SuiteLoadError(f"Suite file not found: {suite_path}")
42
+
43
+ try:
44
+ with open(suite_path, encoding="utf-8") as f:
45
+ data = yaml.safe_load(f)
46
+ except OSError as e:
47
+ raise SuiteLoadError(f"Cannot read suite file '{suite_path}': {e}") from e
48
+ except yaml.YAMLError as e:
49
+ raise SuiteLoadError(f"Invalid YAML in suite file '{suite_path}': {e}") from e
50
+
51
+ if not isinstance(data, dict):
52
+ raise SuiteLoadError(
53
+ f"Suite file '{suite_path}' must contain a YAML mapping "
54
+ f"(got {type(data).__name__})"
55
+ )
56
+
57
+ try:
58
+ return EvalSuite(**data)
59
+ except ValidationError as e:
60
+ raise SuiteLoadError(f"Suite validation failed for '{suite_path}':\n{e}") from e
@@ -0,0 +1,227 @@
1
+ """
2
+ Core data types for the Aeval evaluation framework.
3
+
4
+ This module defines all the data models used throughout the framework:
5
+ - Task definition layer: EvalTask, EvalSuite, GraderConfig
6
+ - Run result layer: TrialResult, GraderResult, TaskSummary, RunSummary, RunResult
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import time
12
+ import uuid
13
+ from enum import Enum
14
+ from typing import Any, Literal
15
+
16
+ from pydantic import BaseModel, Field, field_validator, model_validator
17
+
18
+ # ─── Task Definition Layer ───────────────────────────────────────────────────
19
+
20
+
21
+ class GraderType(str, Enum):
22
+ """评分器类型枚举"""
23
+
24
+ CODE = "code" # 确定性评分 (字符串匹配/正则/静态分析)
25
+ MODEL = "model" # LLM Judge
26
+ STATE = "state" # 环境状态检查
27
+ TOOL_CALLS = "tool_calls" # 工具调用验证
28
+ TRANSCRIPT = "transcript" # 转录记录分析
29
+ ARTIFACT = "artifact" # 产物检查
30
+ METRIC = "metric" # LLM 输出质量指标 (AnswerRelevancy/Faithfulness/...)
31
+ CUSTOM = "custom" # 自定义
32
+
33
+
34
+ class ScoreStrategy(str, Enum):
35
+ """评分聚合策略"""
36
+
37
+ ALL_PASS = "all_pass" # 所有 grader 必须通过
38
+ WEIGHTED = "weighted" # 加权平均
39
+ HYBRID = "hybrid" # required 必须通过 + 非 required 加权
40
+
41
+
42
+ class GraderConfig(BaseModel):
43
+ """单个评分器的配置"""
44
+
45
+ type: GraderType
46
+ name: str = Field(
47
+ ...,
48
+ min_length=1,
49
+ pattern=r"^[a-zA-Z][a-zA-Z0-9_-]*$",
50
+ description="评分器名称 (用于注册/查找)",
51
+ )
52
+ weight: float = Field(1.0, ge=0.0, description="权重 (用于加权评分)")
53
+ required: bool = Field(False, description="是否必须通过")
54
+ sample_count: int = Field(
55
+ 1, ge=1, le=10, description="采样次数 (LLM Judge 多采样计算 confidence)"
56
+ )
57
+ dependencies: list[str] = Field(
58
+ default_factory=list,
59
+ description="依赖的其他 grader 名称 (拓扑排序, 依赖未通过则跳过)",
60
+ )
61
+ config: dict[str, Any] = Field(default_factory=dict, description="类型特定的配置")
62
+
63
+
64
+ class EvalTask(BaseModel):
65
+ """单个评测任务"""
66
+
67
+ id: str = Field(..., min_length=1, description="唯一标识")
68
+ description: str = Field("", description="人类可读描述")
69
+ prompt: str = Field(..., description="给 Agent 的输入")
70
+ graders: list[GraderConfig] = Field(..., min_length=1, description="评分器列表")
71
+ env: dict[str, Any] = Field(default_factory=dict, description="环境参数 (透传给 AgentRunner)")
72
+ max_trials: int = Field(3, ge=1, description="默认 trial 数")
73
+ score_strategy: ScoreStrategy = Field(ScoreStrategy.HYBRID, description="评分聚合策略")
74
+ score_threshold: float = Field(0.7, ge=0.0, le=1.0, description="通过阈值 (用于 WEIGHTED/HYBRID)")
75
+ tracked_metrics: list[str] = Field(
76
+ default_factory=lambda: [
77
+ "n_turns",
78
+ "n_toolcalls",
79
+ "n_total_tokens",
80
+ "latency_ms",
81
+ ],
82
+ description="从 trace 提取的过程指标",
83
+ )
84
+
85
+ def get_grader_config(self, name: str) -> dict[str, Any]:
86
+ """获取指定名称的评分器配置"""
87
+ for g in self.graders:
88
+ if g.name == name:
89
+ return g.config
90
+ return {}
91
+
92
+
93
+ class EvalSuite(BaseModel):
94
+ """评测套件 (一组任务)"""
95
+
96
+ name: str = Field(..., description="套件名称")
97
+ description: str = Field("", description="套件描述")
98
+ version: str = Field(
99
+ "1.0.0",
100
+ pattern=r"^\d+\.\d+\.\d+$",
101
+ description="语义化版本 (semver)",
102
+ )
103
+ tasks: list[EvalTask] = Field(..., min_length=1, description="任务列表")
104
+ metadata: dict[str, Any] = Field(default_factory=dict, description="自定义元数据")
105
+
106
+ @field_validator("name")
107
+ @classmethod
108
+ def _validate_name(cls, v: str) -> str:
109
+ if not v or not v.strip():
110
+ raise ValueError("Suite name cannot be empty")
111
+ if len(v) > 128:
112
+ raise ValueError("Suite name too long (max 128 chars)")
113
+ return v
114
+
115
+ @model_validator(mode="after")
116
+ def _validate_task_ids_unique(self) -> EvalSuite:
117
+ ids = [t.id for t in self.tasks]
118
+ if len(ids) != len(set(ids)):
119
+ duplicates = sorted({x for x in ids if ids.count(x) > 1})
120
+ raise ValueError(f"Duplicate task IDs: {duplicates}")
121
+ return self
122
+
123
+ @classmethod
124
+ def from_yaml(cls, path: str) -> EvalSuite:
125
+ """从 YAML 文件加载评测套件 (含严格校验, 错误带文件路径上下文)"""
126
+ from agent_eval.core.suite import load_suite
127
+
128
+ return load_suite(path)
129
+
130
+
131
+ # ─── Run Result Layer ─────────────────────────────────────────────────────────
132
+
133
+
134
+ class GraderResult(BaseModel):
135
+ """单个评分器的评分结果"""
136
+
137
+ grader_name: str
138
+ grader_type: GraderType
139
+ score: float = Field(..., ge=0.0, le=1.0, description="评分 0.0-1.0")
140
+ passed: bool = Field(..., description="是否通过")
141
+ explanation: str = Field("", description="评分理由")
142
+ details: dict[str, Any] = Field(default_factory=dict, description="类型特定的详情")
143
+ confidence: float = Field(
144
+ 1.0, ge=0.0, le=1.0, description="置信度 (多采样时 = 1 - 不确定性)"
145
+ )
146
+ uncertainty: float = Field(
147
+ 0.0, ge=0.0, le=1.0, description="不确定性 (多采样极差的一半)"
148
+ )
149
+ sample_count: int = Field(1, ge=1, description="评分采样次数")
150
+ duration_ms: float = Field(0.0, description="评分耗时 (毫秒)")
151
+
152
+
153
+ class TrialResult(BaseModel):
154
+ """单次 trial 的完整结果"""
155
+
156
+ trial_index: int = Field(..., ge=0, description="第几次 trial (0-based)")
157
+ trace_id: str = Field("", description="OTel trace ID")
158
+ success: bool = Field(True, description="最终是否成功")
159
+ grader_results: list[GraderResult] = Field(default_factory=list, description="各 grader 的评分")
160
+ metrics: dict[str, float] = Field(default_factory=dict, description="过程指标")
161
+ transcript: list[dict[str, Any]] = Field(default_factory=list, description="完整对话记录")
162
+ outcome: dict[str, Any] = Field(default_factory=dict, description="环境最终状态")
163
+ duration_ms: float = Field(0.0, description="总耗时 (毫秒)")
164
+ error: str | None = Field(None, description="错误信息 (如果失败)")
165
+
166
+ def avg_score(self) -> float:
167
+ """计算所有 grader 的平均分"""
168
+ if not self.grader_results:
169
+ return 0.0
170
+ return sum(r.score for r in self.grader_results) / len(self.grader_results)
171
+
172
+
173
+ class TaskSummary(BaseModel):
174
+ """单个任务的汇总 (跨 trials)"""
175
+
176
+ task_id: str
177
+ task_description: str = ""
178
+ total_trials: int
179
+ pass_at_k: dict[int, float] = Field(default_factory=dict, description="{k: rate}")
180
+ pass_power_k: dict[int, float] = Field(default_factory=dict, description="{k: rate}")
181
+ avg_score: float = Field(0.0, description="所有 trial 的平均分")
182
+ avg_metrics: dict[str, float] = Field(default_factory=dict, description="平均过程指标")
183
+ failures: list[int] = Field(default_factory=list, description="失败的 trial 索引")
184
+ pending_trials: list[int] = Field(
185
+ default_factory=list, description="等待人工评分的 trial 索引 (不计入通过率)"
186
+ )
187
+ consistent: bool = Field(True, description="trial 间分数是否一致 (std < 0.2)")
188
+ score_std_dev: float = Field(0.0, description="trial 间分数标准差")
189
+
190
+
191
+ class RunSummary(BaseModel):
192
+ """一次 suite 运行的汇总"""
193
+
194
+ total_tasks: int
195
+ total_trials: int
196
+ pass_at_k: dict[int, float] = Field(default_factory=dict, description="全局 pass@k")
197
+ pass_power_k: dict[int, float] = Field(default_factory=dict, description="全局 pass^k")
198
+ avg_score: float = Field(0.0, description="全局平均分")
199
+ avg_metrics: dict[str, float] = Field(default_factory=dict, description="全局平均指标")
200
+ task_summaries: list[TaskSummary] = Field(default_factory=list, description="每个任务的汇总")
201
+ failures: list[str] = Field(default_factory=list, description="未通过的任务 ID")
202
+ saturation: dict[str, Any] = Field(
203
+ default_factory=dict,
204
+ description="饱和度检测结果 (is_saturated/saturation_ratio/recommendation)",
205
+ )
206
+
207
+
208
+ class RunResult(BaseModel):
209
+ """一次 suite 运行的完整结果"""
210
+
211
+ run_id: str = Field(default_factory=lambda: f"run_{uuid.uuid4().hex[:12]}")
212
+ suite_name: str = ""
213
+ status: Literal["pending", "running", "completed", "failed", "cancelled"] = "pending"
214
+ started_at: float = Field(default_factory=lambda: time.time() * 1000)
215
+ completed_at: float | None = None
216
+ trials: dict[str, list[TrialResult]] = Field(
217
+ default_factory=dict, description="task_id → trials"
218
+ )
219
+ summary: RunSummary | None = None
220
+ error: str | None = None
221
+
222
+ @property
223
+ def duration_ms(self) -> float | None:
224
+ """运行总耗时 (毫秒)"""
225
+ if self.completed_at is not None:
226
+ return self.completed_at - self.started_at
227
+ return None
@@ -0,0 +1,31 @@
1
+ """Dataset construction and management for the Aeval evaluation framework.
2
+
3
+ Modules:
4
+ models — EvalDataset / EvalDatasetItem with provenance + to_suite()
5
+ storage — DatasetStorage protocol + SQLite / Memory implementations
6
+ quality — DatasetQualityChecker + CoverageAnalyzer
7
+ version — DatasetVersionManager (semver bump + change log)
8
+ sources — manual import / trace mining / LLM generation / regression extract
9
+ """
10
+
11
+ from agent_eval.dataset.models import (
12
+ DatasetError,
13
+ EvalDataset,
14
+ EvalDatasetItem,
15
+ SourceType,
16
+ )
17
+ from agent_eval.dataset.storage import (
18
+ DatasetStorage,
19
+ MemoryDatasetStorage,
20
+ SqliteDatasetStorage,
21
+ )
22
+
23
+ __all__ = [
24
+ "DatasetError",
25
+ "EvalDataset",
26
+ "EvalDatasetItem",
27
+ "SourceType",
28
+ "DatasetStorage",
29
+ "MemoryDatasetStorage",
30
+ "SqliteDatasetStorage",
31
+ ]
@@ -0,0 +1,199 @@
1
+ """
2
+ Dataset data models — EvalDatasetItem / EvalDataset with provenance.
3
+
4
+ A dataset is a curated pool of evaluation items. Each item carries provenance
5
+ (source_type / source_ref) and capability metadata; `to_suite()` converts a
6
+ dataset into an executable EvalSuite reusing the change-① Suite validation
7
+ (single source of truth for validation rules).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import time
13
+ import uuid
14
+ from enum import Enum
15
+ from typing import Any
16
+
17
+ from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator
18
+
19
+ from agent_eval.core.types import EvalSuite, EvalTask, GraderConfig
20
+
21
+
22
+ def now_ms() -> float:
23
+ """当前时间 (epoch 毫秒)"""
24
+ return time.time() * 1000
25
+
26
+
27
+ def new_dataset_id() -> str:
28
+ return f"ds_{uuid.uuid4().hex[:12]}"
29
+
30
+
31
+ class SourceType(str, Enum):
32
+ """数据集条目来源类型"""
33
+
34
+ MANUAL = "manual" # 手动编写
35
+ TRACE_MINING = "trace_mining" # 从真实 trace 挖掘
36
+ LLM_GENERATED = "llm_generated" # LLM 辅助生成 (含合成数据)
37
+ ADVERSARIAL = "adversarial" # 对抗样本 (手工构造, 手动的子类)
38
+ REGRESSION = "regression" # 从 run 失败 trial 提取
39
+
40
+
41
+ class DatasetError(Exception):
42
+ """数据集操作失败 (导入/转换/升版等)。"""
43
+
44
+
45
+ class EvalDatasetItem(BaseModel):
46
+ """评测数据集中的单个条目 (含溯源)"""
47
+
48
+ id: str = Field(..., min_length=1, description="条目唯一标识 (数据集内)")
49
+ prompt: str = Field(..., description="给 Agent 的输入")
50
+ description: str = Field("", description="人类可读描述")
51
+ graders: list[GraderConfig] = Field(
52
+ default_factory=list,
53
+ description="评分器配置 (空 = 待补; to_suite 与质量检查会标出)",
54
+ )
55
+ env: dict[str, Any] = Field(default_factory=dict, description="环境参数 (透传)")
56
+ metadata: dict[str, Any] = Field(
57
+ default_factory=dict,
58
+ description="自定义元数据 (能力维度标签放 metadata.capabilities: list[str])",
59
+ )
60
+ source_type: SourceType = Field(SourceType.MANUAL, description="来源类型")
61
+ source_ref: str = Field("", description="来源引用 (trace_id / run_id / 场景等)")
62
+ created_at: float = Field(default_factory=now_ms, description="创建时间 (epoch ms)")
63
+
64
+
65
+ class EvalDataset(BaseModel):
66
+ """评测数据集 — 一组相关的评测条目"""
67
+
68
+ id: str = Field(default_factory=new_dataset_id, description="唯一标识")
69
+ name: str = Field(..., min_length=1, description="数据集名称")
70
+ description: str = Field("", description="描述")
71
+ version: str = Field(
72
+ "1.0.0",
73
+ pattern=r"^\d+\.\d+\.\d+$",
74
+ description="语义化版本 (semver)",
75
+ )
76
+ tags: list[str] = Field(default_factory=list, description="标签 (分类/筛选)")
77
+ capability_map: dict[str, float] = Field(
78
+ default_factory=dict,
79
+ description="能力维度 → 覆盖度 (0-1), 由 CoverageAnalyzer 更新",
80
+ )
81
+ items: list[EvalDatasetItem] = Field(default_factory=list, description="条目列表")
82
+ metadata: dict[str, Any] = Field(default_factory=dict, description="自定义元数据")
83
+ change_log: list[dict[str, Any]] = Field(
84
+ default_factory=list,
85
+ description="版本变更记录 [{version, change_type, note, at, item_count}]",
86
+ )
87
+ created_at: float = Field(default_factory=now_ms, description="创建时间 (epoch ms)")
88
+ updated_at: float = Field(default_factory=now_ms, description="更新时间 (epoch ms)")
89
+
90
+ @field_validator("name")
91
+ @classmethod
92
+ def _validate_name(cls, v: str) -> str:
93
+ if not v or not v.strip():
94
+ raise ValueError("Dataset name cannot be empty")
95
+ if len(v) > 128:
96
+ raise ValueError("Dataset name too long (max 128 chars)")
97
+ return v
98
+
99
+ @model_validator(mode="after")
100
+ def _validate_item_ids_unique(self) -> EvalDataset:
101
+ ids = [i.id for i in self.items]
102
+ if len(ids) != len(set(ids)):
103
+ duplicates = sorted({x for x in ids if ids.count(x) > 1})
104
+ raise ValueError(f"Duplicate item IDs: {duplicates}")
105
+ return self
106
+
107
+ def get_item(self, item_id: str) -> EvalDatasetItem | None:
108
+ """按 ID 获取条目"""
109
+ for item in self.items:
110
+ if item.id == item_id:
111
+ return item
112
+ return None
113
+
114
+ def to_suite(self, name: str | None = None) -> EvalSuite:
115
+ """
116
+ 转换为可执行的评测 Suite。
117
+
118
+ 条目 → 任务 (prompt/graders/env 直传); suite 元数据记录数据集
119
+ ID 与版本, 使 run 结果可关联回数据集版本。复用 EvalSuite 的
120
+ 校验器 (任务 ID 唯一 / 至少一个任务 / grader 配置合法),
121
+ 非法条目 (缺 prompt/graders、ID 重复等) 拒绝转换并给出明确错误。
122
+ """
123
+ try:
124
+ tasks = [
125
+ EvalTask(
126
+ id=item.id,
127
+ description=item.description,
128
+ prompt=item.prompt,
129
+ graders=item.graders,
130
+ env=item.env,
131
+ )
132
+ for item in self.items
133
+ ]
134
+ return EvalSuite(
135
+ name=name or self.name,
136
+ description=self.description,
137
+ tasks=tasks,
138
+ metadata={
139
+ **self.metadata,
140
+ "dataset_id": self.id,
141
+ "dataset_version": self.version,
142
+ },
143
+ )
144
+ except ValidationError as e:
145
+ raise DatasetError(
146
+ f"Dataset '{self.name}' (v{self.version}) → Suite conversion "
147
+ f"failed — fix the items below and retry:\n{e}"
148
+ ) from e
149
+
150
+
151
+ # grader 类型 → 内置注册名默认值 (LLM 生成/导入缺 name 时保证可被 runner 解析)
152
+ _TYPE_DEFAULT_NAMES: dict[str, str] = {
153
+ "code": "code_based",
154
+ "model": "model_based",
155
+ "state": "state_check",
156
+ "tool_calls": "tool_calls",
157
+ "transcript": "transcript",
158
+ "artifact": "artifact_check",
159
+ "human": "human",
160
+ }
161
+
162
+
163
+ def make_grader_config(
164
+ grader_type: str,
165
+ name: str | None = None,
166
+ **config: Any,
167
+ ) -> GraderConfig:
168
+ """
169
+ 便捷构造 GraderConfig (数据源/生成器使用)。
170
+
171
+ Args:
172
+ grader_type: grader 类型字符串 (如 "model" / "metric" / "tool_calls")
173
+ name: grader 名称; 缺省时 metric 用 metric_name (无则 "metric"),
174
+ 其余类型映射到内置注册名 (model → model_based 等)
175
+ **config: 类型特定配置 (如 metric_name/threshold/rubric)
176
+
177
+ Raises:
178
+ DatasetError: 类型不合法或名称不合法 (含具体字段信息)
179
+ """
180
+ from agent_eval.core.types import GraderType
181
+
182
+ try:
183
+ grader_t = GraderType(grader_type)
184
+ except ValueError:
185
+ valid = ", ".join(t.value for t in GraderType)
186
+ raise DatasetError(
187
+ f"Invalid grader type '{grader_type}' (valid: {valid})"
188
+ ) from None
189
+
190
+ if name is None:
191
+ if grader_t == GraderType.METRIC:
192
+ name = config.get("metric_name", "metric")
193
+ else:
194
+ name = _TYPE_DEFAULT_NAMES.get(grader_t.value, grader_t.value)
195
+
196
+ try:
197
+ return GraderConfig(type=grader_t, name=name, config=config)
198
+ except ValidationError as e:
199
+ raise DatasetError(f"Invalid grader config (name={name!r}):\n{e}") from e