Add judge_language config plumbing (settings + scenario + ScoreRequest)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
wangwei
2026-07-01 17:47:21 +08:00
co-authored by Copilot
parent 03e682b89c
commit 4a646b6b9c
6 changed files with 95 additions and 0 deletions
+1
View File
@@ -64,6 +64,7 @@ def load_scenario(path: str | Path) -> Scenario:
optimization_advisor=model.optimization_advisor, optimization_advisor=model.optimization_advisor,
metric_weights=dict(model.metric_weights), metric_weights=dict(model.metric_weights),
doc_weights=dict(model.doc_weights), doc_weights=dict(model.doc_weights),
judge_language=model.judge_language,
) )
# Run cross-field checks after all relative paths have been resolved. # Run cross-field checks after all relative paths have been resolved.
validate_scenario(scenario) validate_scenario(scenario)
+1
View File
@@ -57,6 +57,7 @@ class ScenarioModel(BaseModel):
optimization_advisor: bool = False optimization_advisor: bool = False
metric_weights: dict[str, float] = Field(default_factory=dict) metric_weights: dict[str, float] = Field(default_factory=dict)
doc_weights: dict[str, float] = Field(default_factory=dict) doc_weights: dict[str, float] = Field(default_factory=dict)
judge_language: Literal["en", "zh"] | None = None
@field_validator("metrics") @field_validator("metrics")
@classmethod @classmethod
+1
View File
@@ -22,6 +22,7 @@ class EvaluationSettings(BaseSettings):
openai_api_key: str | None = Field(default=None, alias="OPENAI_API_KEY") openai_api_key: str | None = Field(default=None, alias="OPENAI_API_KEY")
openai_base_url: str = Field(default="http://6.86.80.4:30080/v1", alias="OPENAI_BASE_URL") openai_base_url: str = Field(default="http://6.86.80.4:30080/v1", alias="OPENAI_BASE_URL")
ragas_judge_model: str = Field(default="gpt-5", alias="RAGAS_JUDGE_MODEL") ragas_judge_model: str = Field(default="gpt-5", alias="RAGAS_JUDGE_MODEL")
ragas_judge_language: str = Field(default="en", alias="RAGAS_JUDGE_LANGUAGE")
ragas_embedding_model: str = Field( ragas_embedding_model: str = Field(
default="text-embedding-3-small", default="text-embedding-3-small",
alias="RAGAS_EMBEDDING_MODEL", alias="RAGAS_EMBEDDING_MODEL",
+3
View File
@@ -79,6 +79,9 @@ class Scenario:
optimization_advisor: bool = False optimization_advisor: bool = False
metric_weights: dict[str, float] = field(default_factory=dict) metric_weights: dict[str, float] = field(default_factory=dict)
doc_weights: dict[str, float] = field(default_factory=dict) doc_weights: dict[str, float] = field(default_factory=dict)
# Language used for judge prompts: 'en' (default, English) or 'zh' (Chinese).
# None means "use the global settings default".
judge_language: str | None = None
def snapshot(self) -> dict[str, Any]: def snapshot(self) -> dict[str, Any]:
"""Serialize the scenario into a reporting-friendly dictionary snapshot.""" """Serialize the scenario into a reporting-friendly dictionary snapshot."""
+72
View File
@@ -0,0 +1,72 @@
"""Tests for judge_language plumbing across settings, scenario schema, and loader."""
from pathlib import Path
import pytest
from rag_eval.settings import EvaluationSettings
from rag_eval.config.loader import load_scenario
def test_settings_default_judge_language_is_en():
"""ragas_judge_language defaults to 'en' when the env var is absent."""
settings = EvaluationSettings(_env_file=None)
assert settings.ragas_judge_language == "en"
def _write_scenario(tmp_path: Path, extra: str) -> Path:
"""Write a minimal valid offline scenario YAML plus the given extra line(s)."""
dataset = tmp_path / "data.csv"
dataset.write_text("sample_id,question,answer,contexts,ground_truth\n", encoding="utf-8")
text = (
"scenario_name: t\n"
"mode: offline\n"
f"dataset: {dataset.name}\n"
"judge_model: gpt-5\n"
"embedding_model: text-embedding-3-small\n"
"metrics: [faithfulness]\n"
"output_dir: out\n"
f"{extra}"
)
path = tmp_path / "s.yaml"
path.write_text(text, encoding="utf-8")
return path
def test_scenario_loads_judge_language_zh(tmp_path):
"""A scenario may declare judge_language: zh and it lands on the dataclass."""
path = _write_scenario(tmp_path, "judge_language: zh\n")
scenario = load_scenario(path)
assert scenario.judge_language == "zh"
def test_scenario_defaults_judge_language_none(tmp_path):
"""Omitting judge_language leaves it None so the factory can apply the settings default."""
path = _write_scenario(tmp_path, "")
scenario = load_scenario(path)
assert scenario.judge_language is None
def test_scenario_rejects_invalid_judge_language(tmp_path):
"""An unsupported judge_language value is rejected at schema validation."""
path = _write_scenario(tmp_path, "judge_language: fr\n")
with pytest.raises(Exception):
load_scenario(path)
def test_score_request_judge_language_defaults_none():
"""ScoreRequest exposes an optional judge_language defaulting to None."""
from webapp.models import ScoreRequest
req = ScoreRequest(question="q", answer="a")
assert req.judge_language is None
req_zh = ScoreRequest(question="q", answer="a", judge_language="zh")
assert req_zh.judge_language == "zh"
def test_session_score_request_inherits_judge_language():
"""SessionScoreRequest inherits the judge_language field from ScoreRequest."""
from webapp.models import SessionScoreRequest
req = SessionScoreRequest(session_id="s1", question="q", answer="a", judge_language="zh")
assert req.judge_language == "zh"
+17
View File
@@ -48,6 +48,15 @@ class DistributionBin(BaseModel):
count: int count: int
class SampleHistoryEntry(BaseModel):
"""One past evaluation of the same question, for the history comparison table."""
run_id: str
scenario_name: str = ""
finished_at: str = ""
metrics: dict[str, float | None] = Field(default_factory=dict)
class SampleScore(BaseModel): class SampleScore(BaseModel):
"""Per-sample row used for the lowest-score review table.""" """Per-sample row used for the lowest-score review table."""
@@ -62,6 +71,10 @@ class SampleScore(BaseModel):
metrics: dict[str, float | None] = Field(default_factory=dict) metrics: dict[str, float | None] = Field(default_factory=dict)
mean_score: float | None = None mean_score: float | None = None
error: str = "" error: str = ""
history: list[SampleHistoryEntry] = Field(
default_factory=list,
description="同一问题在以往运行中的评分(按时间倒序),用于历史对比。",
)
class ReportData(BaseModel): class ReportData(BaseModel):
@@ -460,6 +473,10 @@ class ScoreRequest(BaseModel):
default=None, default=None,
description="Embedding 模型名称;为 null 时使用 .env 中的 RAGAS_EMBEDDING_MODEL。", description="Embedding 模型名称;为 null 时使用 .env 中的 RAGAS_EMBEDDING_MODEL。",
) )
judge_language: str | None = Field(
default=None,
description="评判 prompt 语言;'zh' 启用中文评判,为 null 时使用 RAGAS_JUDGE_LANGUAGE(默认 en)。",
)
@field_validator("metrics") @field_validator("metrics")
@classmethod @classmethod