atlas-agent-evaluation 1.0.1__tar.gz

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 (30) hide show
  1. atlas_agent_evaluation-1.0.1/.gitignore +35 -0
  2. atlas_agent_evaluation-1.0.1/LICENSE +21 -0
  3. atlas_agent_evaluation-1.0.1/PKG-INFO +29 -0
  4. atlas_agent_evaluation-1.0.1/README.md +7 -0
  5. atlas_agent_evaluation-1.0.1/pyproject.toml +43 -0
  6. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/__init__.py +132 -0
  7. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/_models.py +39 -0
  8. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/_version.py +3 -0
  9. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/case.py +79 -0
  10. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/dataset.py +45 -0
  11. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/errors.py +49 -0
  12. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluator.py +125 -0
  13. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluators/__init__.py +19 -0
  14. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluators/_common.py +84 -0
  15. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluators/citation.py +62 -0
  16. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluators/contains.py +54 -0
  17. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluators/exact_match.py +56 -0
  18. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluators/guardrail.py +68 -0
  19. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluators/judge.py +74 -0
  20. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluators/status.py +48 -0
  21. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/evaluators/tool_usage.py +70 -0
  22. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/executor.py +72 -0
  23. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/judge.py +82 -0
  24. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/metric.py +76 -0
  25. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/observation.py +319 -0
  26. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/py.typed +1 -0
  27. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/report.py +185 -0
  28. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/result.py +138 -0
  29. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/runner.py +310 -0
  30. atlas_agent_evaluation-1.0.1/src/atlas_agents/evaluation/summary.py +92 -0
@@ -0,0 +1,35 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .coverage
5
+ coverage.xml
6
+ reports/release/*coverage.xml
7
+ reports/release/junit.xml
8
+ reports/release/bandit.json
9
+ reports/release/dependency-audit.json
10
+ reports/release/requirements-audit.txt
11
+ .mypy_cache/
12
+ .pytest_cache/
13
+ .tmp-pytest-*/
14
+ .tmp-pytest/
15
+ .ruff_cache/
16
+ .uv-cache/
17
+ .venv/
18
+ build/
19
+ dist/
20
+ /release/
21
+ bin/
22
+ obj/
23
+ htmlcov/
24
+ .env
25
+ .env.*
26
+ !.env.example
27
+ *.key
28
+ *.log
29
+ *.pem
30
+ *.tmp
31
+ *.swp
32
+ .DS_Store
33
+ Thumbs.db
34
+ .idea/
35
+ .vscode/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jorge Medina
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.5
2
+ Name: atlas-agent-evaluation
3
+ Version: 1.0.1
4
+ Summary: Avaliação provider-neutral para o Atlas Agent Framework
5
+ Project-URL: Repository, https://github.com/Medicode/ai-agent-framework
6
+ Project-URL: Documentation, https://github.com/Medicode/ai-agent-framework/tree/main/docs/reference
7
+ Project-URL: Issues, https://github.com/Medicode/ai-agent-framework/issues
8
+ Author: Jorge Medina
9
+ Maintainer: Jorge Medina
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: atlas-agent-core~=1.0.1
20
+ Requires-Dist: pydantic<3,>=2.13
21
+ Description-Content-Type: text/markdown
22
+
23
+ # atlas-agent-evaluation
24
+
25
+ Framework de avaliação provider-neutral.
26
+
27
+ Este pacote integra o ecossistema modular Atlas Agent Framework. Consulte a
28
+ [documentação oficial](https://github.com/Medicode/ai-agent-framework/tree/main/docs)
29
+ para instalação, compatibilidade e APIs públicas.
@@ -0,0 +1,7 @@
1
+ # atlas-agent-evaluation
2
+
3
+ Framework de avaliação provider-neutral.
4
+
5
+ Este pacote integra o ecossistema modular Atlas Agent Framework. Consulte a
6
+ [documentação oficial](https://github.com/Medicode/ai-agent-framework/tree/main/docs)
7
+ para instalação, compatibilidade e APIs públicas.
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "atlas-agent-evaluation"
7
+ dynamic = ["version"]
8
+ description = "Avaliação provider-neutral para o Atlas Agent Framework"
9
+ requires-python = ">=3.12"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ readme = "README.md"
13
+ authors = [{ name = "Jorge Medina" }]
14
+ maintainers = [{ name = "Jorge Medina" }]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ "atlas-agent-core~=1.0.1",
25
+ "pydantic>=2.13,<3",
26
+ ]
27
+
28
+ [project.urls]
29
+ Repository = "https://github.com/Medicode/ai-agent-framework"
30
+ Documentation = "https://github.com/Medicode/ai-agent-framework/tree/main/docs/reference"
31
+ Issues = "https://github.com/Medicode/ai-agent-framework/issues"
32
+
33
+ [tool.hatch.version]
34
+ path = "src/atlas_agents/evaluation/_version.py"
35
+
36
+ [tool.hatch.build]
37
+ dev-mode-dirs = ["src"]
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/atlas_agents"]
41
+
42
+ [tool.hatch.build.targets.sdist]
43
+ include = ["src/atlas_agents/evaluation", "LICENSE", "README.md", "pyproject.toml"]
@@ -0,0 +1,132 @@
1
+ """Public runtime-independent evaluation framework."""
2
+
3
+ from atlas_agents.evaluation._version import __version__
4
+ from atlas_agents.evaluation.case import (
5
+ EvaluationCase,
6
+ EvaluationExpectation,
7
+ EvaluationInput,
8
+ )
9
+ from atlas_agents.evaluation.dataset import EvaluationDataset
10
+ from atlas_agents.evaluation.errors import (
11
+ DuplicateEvaluationCaseError,
12
+ DuplicateEvaluatorError,
13
+ DuplicateExpectationError,
14
+ EvaluationConfigurationError,
15
+ EvaluationDatasetError,
16
+ EvaluationError,
17
+ EvaluationExecutionError,
18
+ EvaluationMetricError,
19
+ EvaluationProtocolError,
20
+ EvaluatorNotRegisteredError,
21
+ EvaluatorRegistryError,
22
+ IncompatibleEvaluationMetricError,
23
+ )
24
+ from atlas_agents.evaluation.evaluator import (
25
+ EvaluationContext,
26
+ EvaluationContextFactory,
27
+ EvaluationExecutor,
28
+ Evaluator,
29
+ EvaluatorRegistry,
30
+ )
31
+ from atlas_agents.evaluation.evaluators import (
32
+ CitationEvaluator,
33
+ ContainsEvaluator,
34
+ ExactMatchEvaluator,
35
+ ExecutionStatusEvaluator,
36
+ GuardrailOutcomeEvaluator,
37
+ JudgeEvaluator,
38
+ ToolUsageEvaluator,
39
+ )
40
+ from atlas_agents.evaluation.executor import AgentRuntimeEvaluationExecutor
41
+ from atlas_agents.evaluation.judge import (
42
+ EvaluationJudge,
43
+ JudgeRequest,
44
+ JudgeResponse,
45
+ )
46
+ from atlas_agents.evaluation.metric import EvaluationMetric, MetricDirection
47
+ from atlas_agents.evaluation.observation import (
48
+ EvaluationCapturePolicy,
49
+ EvaluationObservation,
50
+ EvaluationObservationSummary,
51
+ ObservedToolCall,
52
+ )
53
+ from atlas_agents.evaluation.report import (
54
+ EvaluationCaseOutcome,
55
+ EvaluationCaseResult,
56
+ EvaluationMetricSummary,
57
+ EvaluationReport,
58
+ EvaluationReportOutcome,
59
+ EvaluationSummary,
60
+ )
61
+ from atlas_agents.evaluation.result import (
62
+ EvaluationErrorInfo,
63
+ EvaluationFinding,
64
+ EvaluationFindingSeverity,
65
+ EvaluationResult,
66
+ EvaluationResultStatus,
67
+ EvaluationScore,
68
+ )
69
+ from atlas_agents.evaluation.runner import (
70
+ EvaluationClock,
71
+ EvaluationIdFactory,
72
+ EvaluationRunner,
73
+ )
74
+ from atlas_agents.evaluation.summary import build_summary
75
+
76
+ __all__ = [
77
+ "AgentRuntimeEvaluationExecutor",
78
+ "CitationEvaluator",
79
+ "ContainsEvaluator",
80
+ "DuplicateEvaluationCaseError",
81
+ "DuplicateEvaluatorError",
82
+ "DuplicateExpectationError",
83
+ "EvaluationCapturePolicy",
84
+ "EvaluationCase",
85
+ "EvaluationCaseOutcome",
86
+ "EvaluationCaseResult",
87
+ "EvaluationClock",
88
+ "EvaluationConfigurationError",
89
+ "EvaluationContext",
90
+ "EvaluationContextFactory",
91
+ "EvaluationDataset",
92
+ "EvaluationDatasetError",
93
+ "EvaluationError",
94
+ "EvaluationErrorInfo",
95
+ "EvaluationExecutionError",
96
+ "EvaluationExecutor",
97
+ "EvaluationExpectation",
98
+ "EvaluationFinding",
99
+ "EvaluationFindingSeverity",
100
+ "EvaluationIdFactory",
101
+ "EvaluationInput",
102
+ "EvaluationJudge",
103
+ "EvaluationMetric",
104
+ "EvaluationMetricError",
105
+ "EvaluationMetricSummary",
106
+ "EvaluationObservation",
107
+ "EvaluationObservationSummary",
108
+ "EvaluationProtocolError",
109
+ "EvaluationReport",
110
+ "EvaluationReportOutcome",
111
+ "EvaluationResult",
112
+ "EvaluationResultStatus",
113
+ "EvaluationRunner",
114
+ "EvaluationScore",
115
+ "EvaluationSummary",
116
+ "Evaluator",
117
+ "EvaluatorNotRegisteredError",
118
+ "EvaluatorRegistry",
119
+ "EvaluatorRegistryError",
120
+ "ExactMatchEvaluator",
121
+ "ExecutionStatusEvaluator",
122
+ "GuardrailOutcomeEvaluator",
123
+ "IncompatibleEvaluationMetricError",
124
+ "JudgeEvaluator",
125
+ "JudgeRequest",
126
+ "JudgeResponse",
127
+ "MetricDirection",
128
+ "ObservedToolCall",
129
+ "ToolUsageEvaluator",
130
+ "__version__",
131
+ "build_summary",
132
+ ]
@@ -0,0 +1,39 @@
1
+ """Shared immutable and JSON-safe evaluation model helpers."""
2
+
3
+ from collections.abc import Mapping
4
+ from copy import deepcopy
5
+ from datetime import datetime
6
+
7
+ from pydantic import BaseModel, ConfigDict, JsonValue, TypeAdapter
8
+
9
+ _JSON_MAPPING = TypeAdapter(dict[str, JsonValue])
10
+
11
+
12
+ class FrozenEvaluationModel(BaseModel):
13
+ """Provide strict immutable value semantics to evaluation contracts."""
14
+
15
+ model_config = ConfigDict(
16
+ frozen=True,
17
+ extra="forbid",
18
+ arbitrary_types_allowed=True,
19
+ )
20
+
21
+
22
+ def non_empty(value: str) -> str:
23
+ """Normalize outer whitespace and reject blank public identifiers."""
24
+ normalized = value.strip()
25
+ if not normalized:
26
+ raise ValueError("O valor não pode estar vazio")
27
+ return normalized
28
+
29
+
30
+ def json_mapping(value: Mapping[str, JsonValue]) -> dict[str, JsonValue]:
31
+ """Validate and isolate a JSON-compatible mapping."""
32
+ return deepcopy(_JSON_MAPPING.validate_python(dict(value)))
33
+
34
+
35
+ def timezone_aware(value: datetime) -> datetime:
36
+ """Reject timestamps without a usable timezone offset."""
37
+ if value.tzinfo is None or value.utcoffset() is None:
38
+ raise ValueError("O timestamp deve possuir fuso horário")
39
+ return value
@@ -0,0 +1,3 @@
1
+ """Package version generated from the repository VERSION file."""
2
+
3
+ __version__ = "1.0.1"
@@ -0,0 +1,79 @@
1
+ """Immutable evaluation inputs, expectations, and cases."""
2
+
3
+ from typing import Self
4
+
5
+ from pydantic import Field, JsonValue, field_validator, model_validator
6
+
7
+ from atlas_agents import AgentInput
8
+ from atlas_agents.evaluation._models import (
9
+ FrozenEvaluationModel,
10
+ json_mapping,
11
+ non_empty,
12
+ )
13
+ from atlas_agents.evaluation.errors import DuplicateExpectationError
14
+
15
+
16
+ class EvaluationInput(FrozenEvaluationModel):
17
+ """Describe optional production input used when executing an evaluation case."""
18
+
19
+ agent_input: AgentInput | None = None
20
+ metadata: dict[str, JsonValue] = Field(default_factory=dict)
21
+
22
+ @field_validator("metadata")
23
+ @classmethod
24
+ def validate_metadata(cls, value: dict[str, JsonValue]) -> dict[str, JsonValue]:
25
+ """Keep metadata JSON-compatible and isolated."""
26
+ return json_mapping(value)
27
+
28
+
29
+ class EvaluationExpectation(FrozenEvaluationModel):
30
+ """Bind an explicit expected value to one configured evaluator."""
31
+
32
+ expectation_id: str
33
+ evaluator_id: str
34
+ expected: JsonValue
35
+ metadata: dict[str, JsonValue] = Field(default_factory=dict)
36
+
37
+ @field_validator("expectation_id", "evaluator_id")
38
+ @classmethod
39
+ def validate_ids(cls, value: str) -> str:
40
+ """Reject blank stable identifiers."""
41
+ return non_empty(value)
42
+
43
+ @field_validator("metadata")
44
+ @classmethod
45
+ def validate_metadata(cls, value: dict[str, JsonValue]) -> dict[str, JsonValue]:
46
+ """Keep metadata JSON-compatible and isolated."""
47
+ return json_mapping(value)
48
+
49
+
50
+ class EvaluationCase(FrozenEvaluationModel):
51
+ """Describe one reproducible input and its ordered expectations."""
52
+
53
+ case_id: str
54
+ name: str
55
+ input: EvaluationInput = EvaluationInput()
56
+ expectations: tuple[EvaluationExpectation, ...] = ()
57
+ metadata: dict[str, JsonValue] = Field(default_factory=dict)
58
+
59
+ @field_validator("case_id", "name")
60
+ @classmethod
61
+ def validate_text(cls, value: str) -> str:
62
+ """Reject blank case identifiers and names."""
63
+ return non_empty(value)
64
+
65
+ @field_validator("metadata")
66
+ @classmethod
67
+ def validate_metadata(cls, value: dict[str, JsonValue]) -> dict[str, JsonValue]:
68
+ """Keep metadata JSON-compatible and isolated."""
69
+ return json_mapping(value)
70
+
71
+ @model_validator(mode="after")
72
+ def validate_expectation_ids(self) -> Self:
73
+ """Require unique expectation IDs while preserving order."""
74
+ identifiers = tuple(item.expectation_id for item in self.expectations)
75
+ if len(set(identifiers)) != len(identifiers):
76
+ raise DuplicateExpectationError(
77
+ "Os identificadores de expectativa não podem se repetir."
78
+ )
79
+ return self
@@ -0,0 +1,45 @@
1
+ """Versioned ordered evaluation dataset contracts."""
2
+
3
+ from typing import Self
4
+
5
+ from pydantic import Field, JsonValue, field_validator, model_validator
6
+
7
+ from atlas_agents.evaluation._models import (
8
+ FrozenEvaluationModel,
9
+ json_mapping,
10
+ non_empty,
11
+ )
12
+ from atlas_agents.evaluation.case import EvaluationCase
13
+ from atlas_agents.evaluation.errors import DuplicateEvaluationCaseError
14
+
15
+
16
+ class EvaluationDataset(FrozenEvaluationModel):
17
+ """Group ordered cases under a stable dataset identity and version."""
18
+
19
+ dataset_id: str
20
+ name: str
21
+ version: str
22
+ cases: tuple[EvaluationCase, ...] = ()
23
+ metadata: dict[str, JsonValue] = Field(default_factory=dict)
24
+
25
+ @field_validator("dataset_id", "name", "version")
26
+ @classmethod
27
+ def validate_text(cls, value: str) -> str:
28
+ """Reject blank dataset identity fields."""
29
+ return non_empty(value)
30
+
31
+ @field_validator("metadata")
32
+ @classmethod
33
+ def validate_metadata(cls, value: dict[str, JsonValue]) -> dict[str, JsonValue]:
34
+ """Keep metadata JSON-compatible and isolated."""
35
+ return json_mapping(value)
36
+
37
+ @model_validator(mode="after")
38
+ def validate_case_ids(self) -> Self:
39
+ """Reject duplicate cases while allowing an empty dataset."""
40
+ identifiers = tuple(case.case_id for case in self.cases)
41
+ if len(set(identifiers)) != len(identifiers):
42
+ raise DuplicateEvaluationCaseError(
43
+ "Os identificadores de casos não podem se repetir."
44
+ )
45
+ return self
@@ -0,0 +1,49 @@
1
+ """Typed failures for evaluation configuration and orchestration."""
2
+
3
+
4
+ class EvaluationError(Exception):
5
+ """Base class for expected evaluation framework failures."""
6
+
7
+
8
+ class EvaluationConfigurationError(EvaluationError):
9
+ """Report invalid evaluation configuration."""
10
+
11
+
12
+ class EvaluationDatasetError(EvaluationConfigurationError):
13
+ """Report an invalid dataset contract."""
14
+
15
+
16
+ class DuplicateEvaluationCaseError(EvaluationDatasetError):
17
+ """Report a repeated case identifier."""
18
+
19
+
20
+ class DuplicateExpectationError(EvaluationDatasetError):
21
+ """Report a repeated expectation identifier within one case."""
22
+
23
+
24
+ class EvaluatorRegistryError(EvaluationConfigurationError):
25
+ """Base class for evaluator registry failures."""
26
+
27
+
28
+ class DuplicateEvaluatorError(EvaluatorRegistryError):
29
+ """Report a repeated evaluator identifier."""
30
+
31
+
32
+ class EvaluatorNotRegisteredError(EvaluatorRegistryError):
33
+ """Report an expectation referencing an unknown evaluator."""
34
+
35
+
36
+ class EvaluationProtocolError(EvaluationError):
37
+ """Report an evaluator or observation protocol violation."""
38
+
39
+
40
+ class EvaluationMetricError(EvaluationError):
41
+ """Report an invalid metric, score, threshold, or bound."""
42
+
43
+
44
+ class IncompatibleEvaluationMetricError(EvaluationMetricError):
45
+ """Report conflicting definitions sharing a metric identifier."""
46
+
47
+
48
+ class EvaluationExecutionError(EvaluationError):
49
+ """Report failure to produce an observation for one case."""
@@ -0,0 +1,125 @@
1
+ """Async evaluator, executor, registry, and context contracts."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import Protocol
5
+
6
+ from pydantic import Field, JsonValue, field_validator
7
+
8
+ from atlas_agents import AgentContext
9
+ from atlas_agents.evaluation._models import (
10
+ FrozenEvaluationModel,
11
+ json_mapping,
12
+ non_empty,
13
+ )
14
+ from atlas_agents.evaluation.case import EvaluationCase, EvaluationExpectation
15
+ from atlas_agents.evaluation.errors import (
16
+ DuplicateEvaluatorError,
17
+ EvaluatorNotRegisteredError,
18
+ )
19
+ from atlas_agents.evaluation.metric import EvaluationMetric
20
+ from atlas_agents.evaluation.observation import EvaluationObservation
21
+ from atlas_agents.evaluation.result import EvaluationResult
22
+
23
+
24
+ class EvaluationContext(FrozenEvaluationModel):
25
+ """Carry immutable correlation facts for one evaluator invocation."""
26
+
27
+ evaluation_run_id: str
28
+ dataset_id: str
29
+ case_id: str
30
+ metadata: dict[str, JsonValue] = Field(default_factory=dict)
31
+
32
+ @field_validator("evaluation_run_id", "dataset_id", "case_id")
33
+ @classmethod
34
+ def validate_ids(cls, value: str) -> str:
35
+ """Reject blank evaluation correlation identifiers."""
36
+ return non_empty(value)
37
+
38
+ @field_validator("metadata")
39
+ @classmethod
40
+ def validate_metadata(cls, value: dict[str, JsonValue]) -> dict[str, JsonValue]:
41
+ """Keep context metadata JSON-compatible and isolated."""
42
+ return json_mapping(value)
43
+
44
+
45
+ class Evaluator(Protocol):
46
+ """Evaluate one explicit expectation against an immutable observation."""
47
+
48
+ @property
49
+ def evaluator_id(self) -> str:
50
+ """Return the stable evaluator identifier."""
51
+ ...
52
+
53
+ @property
54
+ def metric(self) -> EvaluationMetric:
55
+ """Return the metric definition emitted by this evaluator."""
56
+ ...
57
+
58
+ async def evaluate(
59
+ self,
60
+ case: EvaluationCase,
61
+ expectation: EvaluationExpectation,
62
+ observation: EvaluationObservation,
63
+ context: EvaluationContext,
64
+ ) -> EvaluationResult:
65
+ """Measure the observation without mutating production artifacts."""
66
+ ...
67
+
68
+
69
+ class EvaluationExecutor(Protocol):
70
+ """Produce an immutable observation through an explicitly configured path."""
71
+
72
+ async def execute(self, case: EvaluationCase) -> EvaluationObservation:
73
+ """Execute or retrieve one case observation."""
74
+ ...
75
+
76
+
77
+ type EvaluationContextFactory = Callable[[EvaluationCase], AgentContext]
78
+
79
+
80
+ class EvaluatorRegistry:
81
+ """Store evaluators by exact identifier in deterministic registration order."""
82
+
83
+ def __init__(self, evaluators: tuple[Evaluator, ...] = ()) -> None:
84
+ """Initialize an isolated registry and register initial evaluators."""
85
+ self._evaluators: dict[str, Evaluator] = {}
86
+ for evaluator in evaluators:
87
+ self.register(evaluator)
88
+
89
+ @property
90
+ def evaluators(self) -> tuple[Evaluator, ...]:
91
+ """Return evaluators in registration order."""
92
+ return tuple(self._evaluators.values())
93
+
94
+ def register(self, evaluator: Evaluator) -> None:
95
+ """Register one evaluator and reject duplicate IDs."""
96
+ identifier = non_empty(evaluator.evaluator_id)
97
+ if identifier in self._evaluators:
98
+ raise DuplicateEvaluatorError(
99
+ f"O evaluator '{identifier}' já está registrado."
100
+ )
101
+ self._evaluators[identifier] = evaluator
102
+
103
+ def unregister(self, evaluator_id: str) -> Evaluator:
104
+ """Remove and return an evaluator by exact identifier."""
105
+ identifier = non_empty(evaluator_id)
106
+ evaluator = self._evaluators.pop(identifier, None)
107
+ if evaluator is None:
108
+ raise EvaluatorNotRegisteredError(
109
+ f"O evaluator '{identifier}' não está registrado."
110
+ )
111
+ return evaluator
112
+
113
+ def get(self, evaluator_id: str) -> Evaluator:
114
+ """Resolve an evaluator or raise a typed error."""
115
+ identifier = non_empty(evaluator_id)
116
+ evaluator = self._evaluators.get(identifier)
117
+ if evaluator is None:
118
+ raise EvaluatorNotRegisteredError(
119
+ f"O evaluator '{identifier}' não está registrado."
120
+ )
121
+ return evaluator
122
+
123
+ def try_get(self, evaluator_id: str) -> Evaluator | None:
124
+ """Resolve an evaluator without raising when it is absent."""
125
+ return self._evaluators.get(non_empty(evaluator_id))
@@ -0,0 +1,19 @@
1
+ """Built-in deterministic and opt-in evaluation implementations."""
2
+
3
+ from atlas_agents.evaluation.evaluators.citation import CitationEvaluator
4
+ from atlas_agents.evaluation.evaluators.contains import ContainsEvaluator
5
+ from atlas_agents.evaluation.evaluators.exact_match import ExactMatchEvaluator
6
+ from atlas_agents.evaluation.evaluators.guardrail import GuardrailOutcomeEvaluator
7
+ from atlas_agents.evaluation.evaluators.judge import JudgeEvaluator
8
+ from atlas_agents.evaluation.evaluators.status import ExecutionStatusEvaluator
9
+ from atlas_agents.evaluation.evaluators.tool_usage import ToolUsageEvaluator
10
+
11
+ __all__ = [
12
+ "CitationEvaluator",
13
+ "ContainsEvaluator",
14
+ "ExactMatchEvaluator",
15
+ "ExecutionStatusEvaluator",
16
+ "GuardrailOutcomeEvaluator",
17
+ "JudgeEvaluator",
18
+ "ToolUsageEvaluator",
19
+ ]