Repository navigation
fix(model): one same-model rule for baseline, self-judge and repo compare #273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e159659
2dc7fa4
58eafba
020b53e
1752b92
1e27fcd
19c701f
d48334f
8a2f952
795dcb2
82c42ad
7140e6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| ### Fixed | ||
|
|
||
| - **"Same model" means one thing in `baseline compare`, the self-judge warning and `repo compare`** — they | ||
| used two rules that disagreed. A dated id (`claude-sonnet-4-5-20250929`) and its undated alias, a Bedrock | ||
| regional profile (`eu.anthropic.…`) and its bare id, and the same model behind a route | ||
| (`anthropic/`, `openrouter/anthropic/`, `bedrock/`, `vertex_ai/`) or a `-v1:0` revision are now one model | ||
| everywhere; two different dated snapshots are still two models, and case no longer matters. Effects: the | ||
| self-judge warning fires when the answer and the judge are the same model on another provider too; `baseline | ||
| compare` no longer reports or refuses (`--strict`) a judge or answer that only changed its route, region or | ||
| snapshot alias; `repo compare` pairs arms by the same rule. Scores are not recomputed, but a comparison that | ||
| used to refuse or warn about such a pair may now pass. | ||
| `dokimi run` stores `run_metadata.evaluator_independence_warning` by this rule too, as `dokimi eval` does (it | ||
| was an exact provider-and-model match, so the printed warning and the stored flag disagreed). | ||
| A route is a provider or gateway prefix; the organization of an `org/name` id is part of the id, so | ||
| `meta-llama/X` and `myorg/X` are two models. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ | |
| ) | ||
| from dokimi.eval.judge import JUDGE_PROVIDERS | ||
| from dokimi.eval.judge_validity import DEFAULT_MIN_ACCURACY, NoUsablePairs | ||
| from dokimi.utils import normalize_model_ref, write_text_or_exit | ||
| from dokimi.utils import same_model, write_text_or_exit | ||
|
|
||
|
|
||
| def _warn_judge_independence( | ||
|
|
@@ -30,8 +30,8 @@ def _warn_judge_independence( | |
| judge_model: str, | ||
| context: str = "run", | ||
| ) -> bool: | ||
| """Emit a non-fatal warning when answer and judge use the same model/provider.""" | ||
| same = normalize_model_ref(answer_provider, answer_model) == normalize_model_ref(judge_provider, judge_model) | ||
| """Emit a non-fatal warning when answer and judge are the same model (on any provider route).""" | ||
| same = same_model(answer_model, judge_model) | ||
|
napetrov marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if same: | ||
| print( | ||
| "⚠ Evaluator independence warning " | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,10 +3,11 @@ | |||||||||||||
|
|
||||||||||||||
| import json | ||||||||||||||
| import os | ||||||||||||||
| import re | ||||||||||||||
| from pathlib import Path | ||||||||||||||
| from typing import Tuple, Dict, Any | ||||||||||||||
|
|
||||||||||||||
| from dokimi.defaults import BEDROCK_MODEL_IDS, bedrock_profile | ||||||||||||||
| from dokimi.defaults import bedrock_profile | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def parse_frontmatter(text: str) -> "Tuple[Dict[str, Any], str]": | ||||||||||||||
|
|
@@ -113,15 +114,64 @@ def retry_writable(func: Any, path: str, _exc: Any) -> None: | |||||||||||||
| pass | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def normalize_model_ref(provider: str, model: str) -> str: | ||||||||||||||
| """Normalize provider and model names for comparison. | ||||||||||||||
| # Bedrock cross-region inference profiles: the same model served from another region. They are dropped | ||||||||||||||
| # from any provider's name, not only Bedrock's: no non-Bedrock id is known to start with one of them. | ||||||||||||||
| _REGION_PROFILES = ("us-gov.", "global.", "apac.", "us.", "eu.", "jp.", "au.", "ca.") | ||||||||||||||
|
|
||||||||||||||
| On Bedrock a bare known name and its inference profile are the same model. | ||||||||||||||
|
|
||||||||||||||
| # Leading path segments that name how a model is reached, not which model it is: a provider or a gateway | ||||||||||||||
| # (``anthropic/``, ``openrouter/``, ``bedrock/converse/``, ``azure/``). Any other segment (a Hugging Face | ||||||||||||||
| # organization such as ``meta-llama/``) is part of the id. Every provider of ``llm.PROVIDERS`` and its aliases is | ||||||||||||||
| # one (pinned by a test); the rest are litellm route prefixes. | ||||||||||||||
|
Comment on lines
+122
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion:
Suggested change
|
||||||||||||||
| _ROUTES = frozenset({ | ||||||||||||||
| "openai", "openai-codex", "anthropic", "google", "gemini", "google-vertex", "openrouter", "amazon-bedrock", | ||||||||||||||
| "bedrock", "converse", "vertex_ai", "azure", "azure_ai", "models", "groq", "ollama", "mistral", "deepseek", | ||||||||||||||
| "together_ai", "fireworks_ai", "xai", "cohere", "huggingface", | ||||||||||||||
| }) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def _model_core(model: str) -> tuple[str, str | None]: | ||||||||||||||
| """A model name reduced to ``(id, snapshot date or None)``. A snapshot is ``-YYYYMMDD`` or OpenAI's ``-YYYY-MM-DD``. | ||||||||||||||
|
|
||||||||||||||
| The route goes (``openrouter/anthropic/``, ``bedrock/``, ``vertex_ai/``, an ARN's | ||||||||||||||
| ``inference-profile/``), and so do the case, the region profile (``eu.``) and a Bedrock | ||||||||||||||
| revision (``-v1:0`` or ``-1:0``). Vertex's ``@20250929`` is the snapshot ``-20250929``. The organization of an | ||||||||||||||
| ``org/name`` id stays, so a re-upload under another organization is another model. | ||||||||||||||
| """ | ||||||||||||||
| name = (model or "").strip().lower() | ||||||||||||||
| for marker in ("inference-profile/", "foundation-model/"): # the tail of a Bedrock ARN | ||||||||||||||
| if marker in name: | ||||||||||||||
| name = name.rsplit(marker, 1)[-1] | ||||||||||||||
| parts = name.split("/") | ||||||||||||||
| while len(parts) > 1 and parts[0] in _ROUTES: | ||||||||||||||
| parts.pop(0) | ||||||||||||||
| name = "/".join(parts) | ||||||||||||||
| name = re.sub(r"-v?\d+:\d+$", "", name) | ||||||||||||||
| for prefix in _REGION_PROFILES: | ||||||||||||||
| if name.startswith(prefix): | ||||||||||||||
| name = name[len(prefix):] | ||||||||||||||
| break | ||||||||||||||
| name = re.sub(r"@(?=\d{8}$)", "-", name) | ||||||||||||||
| snapshot = re.search(r"-(\d{8}|\d{4}-\d{2}-\d{2})$", name) | ||||||||||||||
| return (name[:snapshot.start()], snapshot.group(1).replace("-", "")) if snapshot else (name, None) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def same_model(a: str, b: str) -> bool: | ||||||||||||||
| """Whether two model names are one model. | ||||||||||||||
|
|
||||||||||||||
| The same id matches, and so does a dated snapshot and its undated alias (the alias runs | ||||||||||||||
| some snapshot of it), or the same id behind another route or region profile. Two dated | ||||||||||||||
| ids match only on the same date, and a bare family name (``haiku``) is not a full id. | ||||||||||||||
|
|
||||||||||||||
| The provider is not part of the identity: the same weights on Bedrock and on the direct | ||||||||||||||
| API are still one model for the self-judge warning and for a baseline's model check. | ||||||||||||||
| """ | ||||||||||||||
| prov, name = (provider or "").strip().lower(), (model or "").strip() | ||||||||||||||
| if prov == "amazon-bedrock": | ||||||||||||||
| name = BEDROCK_MODEL_IDS.get(name, name) | ||||||||||||||
| return f"{prov}/{name.lower()}" | ||||||||||||||
| (base_a, date_a), (base_b, date_b) = _model_core(a), _model_core(b) | ||||||||||||||
| if not (base_a and base_b) or (date_a and date_b and date_a != date_b): | ||||||||||||||
| return False | ||||||||||||||
| # a lowercase word before the first dot is taken as a vendor prefix (``anthropic.``, ``amazon.``) and | ||||||||||||||
| # dropped; a dot after a digit or a dash (``gpt-4.1``, ``qwen2.5``) is part of the name | ||||||||||||||
| return re.sub(r"^[a-z]+\.", "", base_a) == re.sub(r"^[a-z]+\.", "", base_b) | ||||||||||||||
|
Comment on lines
+163
to
+174
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggestion: keep the alias-matches-snapshot rule for the warning and
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Waiting on a decision before I change this: the undated alias matching its dated snapshot was a deliberate part of this PR, but you are right that before it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (b), as you recommend. The panel pairing does not have to go. Compare in two passes: first by exact normalized id and date (for a panel: the same multiset). If that matches, it is a match. If it fails but |
||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def _read_key(value: str) -> str: | ||||||||||||||
|
|
||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.