agentops-accelerator 0.3.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.
- agentops/__init__.py +10 -0
- agentops/__main__.py +6 -0
- agentops/agent/__init__.py +12 -0
- agentops/agent/_legacy_ids.py +92 -0
- agentops/agent/analyzer.py +207 -0
- agentops/agent/checks/__init__.py +1 -0
- agentops/agent/checks/catalog.py +880 -0
- agentops/agent/checks/errors.py +279 -0
- agentops/agent/checks/foundry_config.py +75 -0
- agentops/agent/checks/latency.py +84 -0
- agentops/agent/checks/opex.py +157 -0
- agentops/agent/checks/opex_workspace.py +874 -0
- agentops/agent/checks/posture.py +36 -0
- agentops/agent/checks/posture_rules/__init__.py +53 -0
- agentops/agent/checks/posture_rules/content_filter.py +59 -0
- agentops/agent/checks/posture_rules/diagnostics.py +74 -0
- agentops/agent/checks/posture_rules/local_auth.py +55 -0
- agentops/agent/checks/posture_rules/managed_identity.py +59 -0
- agentops/agent/checks/posture_rules/network.py +68 -0
- agentops/agent/checks/regression.py +78 -0
- agentops/agent/checks/release_readiness.py +182 -0
- agentops/agent/checks/safety.py +247 -0
- agentops/agent/checks/spec_conformance.py +375 -0
- agentops/agent/cockpit.py +5159 -0
- agentops/agent/config.py +240 -0
- agentops/agent/findings.py +113 -0
- agentops/agent/history.py +142 -0
- agentops/agent/knowledge/__init__.py +182 -0
- agentops/agent/knowledge/waf-checklist.csv +39 -0
- agentops/agent/llm_assist/__init__.py +16 -0
- agentops/agent/llm_assist/_base.py +124 -0
- agentops/agent/llm_assist/_bundle_rule.py +154 -0
- agentops/agent/llm_assist/_client.py +347 -0
- agentops/agent/llm_assist/_dataset_rules.py +191 -0
- agentops/agent/llm_assist/_engine.py +106 -0
- agentops/agent/llm_assist/_prompt_rules.py +291 -0
- agentops/agent/llm_assist/_spec_rules.py +235 -0
- agentops/agent/production_telemetry.py +430 -0
- agentops/agent/report.py +207 -0
- agentops/agent/server/__init__.py +1 -0
- agentops/agent/server/app.py +84 -0
- agentops/agent/server/auth.py +94 -0
- agentops/agent/server/chat.py +44 -0
- agentops/agent/server/protocol.py +72 -0
- agentops/agent/sources/__init__.py +1 -0
- agentops/agent/sources/azure_monitor.py +523 -0
- agentops/agent/sources/azure_resources.py +602 -0
- agentops/agent/sources/foundry_control.py +174 -0
- agentops/agent/sources/results_history.py +494 -0
- agentops/agent/sources/spec_detectors/__init__.py +42 -0
- agentops/agent/sources/spec_detectors/_base.py +58 -0
- agentops/agent/sources/spec_detectors/agents_md.py +75 -0
- agentops/agent/sources/spec_detectors/spec_kit.py +172 -0
- agentops/agent/time_range.py +117 -0
- agentops/cli/__init__.py +1 -0
- agentops/cli/app.py +4823 -0
- agentops/core/__init__.py +1 -0
- agentops/core/agentops_config.py +592 -0
- agentops/core/config_loader.py +22 -0
- agentops/core/evaluators.py +480 -0
- agentops/core/release_evidence.py +56 -0
- agentops/core/results.py +117 -0
- agentops/mcp/__init__.py +10 -0
- agentops/mcp/server.py +232 -0
- agentops/pipeline/__init__.py +8 -0
- agentops/pipeline/cloud_results.py +189 -0
- agentops/pipeline/cloud_runner.py +901 -0
- agentops/pipeline/comparison.py +108 -0
- agentops/pipeline/diagnostics.py +51 -0
- agentops/pipeline/invocations.py +535 -0
- agentops/pipeline/official_eval.py +414 -0
- agentops/pipeline/orchestrator.py +775 -0
- agentops/pipeline/prompt_deploy.py +377 -0
- agentops/pipeline/publisher.py +121 -0
- agentops/pipeline/reporter.py +202 -0
- agentops/pipeline/runtime.py +409 -0
- agentops/pipeline/thresholds.py +84 -0
- agentops/services/__init__.py +1 -0
- agentops/services/cicd.py +720 -0
- agentops/services/eval_analysis.py +848 -0
- agentops/services/evidence_pack.py +757 -0
- agentops/services/initializer.py +86 -0
- agentops/services/preflight.py +470 -0
- agentops/services/setup_wizard.py +709 -0
- agentops/services/skills.py +643 -0
- agentops/services/trace_promotion.py +300 -0
- agentops/services/workflow_analysis.py +1129 -0
- agentops/templates/.gitignore +15 -0
- agentops/templates/__init__.py +1 -0
- agentops/templates/agent-server/Dockerfile +23 -0
- agentops/templates/agent-server/README.md +61 -0
- agentops/templates/agent-server/main.bicep +94 -0
- agentops/templates/agent.yaml +87 -0
- agentops/templates/agentops.yaml +58 -0
- agentops/templates/foundry.svg +71 -0
- agentops/templates/icon.png +0 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-dev-azd.yml +118 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-dev.yml +73 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-prod-azd.yml +141 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-prod.yml +94 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-prompt-agent.yml +167 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-qa-azd.yml +118 -0
- agentops/templates/pipelines/azuredevops/agentops-deploy-qa.yml +68 -0
- agentops/templates/pipelines/azuredevops/agentops-pr-prompt-agent.yml +210 -0
- agentops/templates/pipelines/azuredevops/agentops-pr.yml +155 -0
- agentops/templates/pipelines/azuredevops/agentops-watchdog.yml +106 -0
- agentops/templates/project.gitignore +36 -0
- agentops/templates/sample-traces.jsonl +3 -0
- agentops/templates/skills/agentops-agent/SKILL.md +137 -0
- agentops/templates/skills/agentops-config/SKILL.md +113 -0
- agentops/templates/skills/agentops-dataset/SKILL.md +84 -0
- agentops/templates/skills/agentops-eval/SKILL.md +189 -0
- agentops/templates/skills/agentops-report/SKILL.md +71 -0
- agentops/templates/skills/agentops-workflow/SKILL.md +471 -0
- agentops/templates/smoke.jsonl +3 -0
- agentops/templates/waf-checklist.README.md +84 -0
- agentops/templates/waf-checklist.csv +22 -0
- agentops/templates/workflows/agentops-deploy-dev-azd.yml +166 -0
- agentops/templates/workflows/agentops-deploy-dev.yml +187 -0
- agentops/templates/workflows/agentops-deploy-prod-azd.yml +183 -0
- agentops/templates/workflows/agentops-deploy-prod.yml +171 -0
- agentops/templates/workflows/agentops-deploy-prompt-agent.yml +197 -0
- agentops/templates/workflows/agentops-deploy-qa-azd.yml +156 -0
- agentops/templates/workflows/agentops-deploy-qa.yml +145 -0
- agentops/templates/workflows/agentops-pr-prompt-agent.yml +210 -0
- agentops/templates/workflows/agentops-pr.yml +148 -0
- agentops/templates/workflows/agentops-watchdog.yml +122 -0
- agentops/utils/__init__.py +1 -0
- agentops/utils/azd_env.py +435 -0
- agentops/utils/azure_endpoints.py +62 -0
- agentops/utils/colors.py +47 -0
- agentops/utils/dotenv_loader.py +105 -0
- agentops/utils/foundry_discovery.py +229 -0
- agentops/utils/logging.py +59 -0
- agentops/utils/telemetry.py +554 -0
- agentops/utils/yaml.py +36 -0
- agentops_accelerator-0.3.0.dist-info/METADATA +278 -0
- agentops_accelerator-0.3.0.dist-info/RECORD +142 -0
- agentops_accelerator-0.3.0.dist-info/WHEEL +5 -0
- agentops_accelerator-0.3.0.dist-info/entry_points.txt +2 -0
- agentops_accelerator-0.3.0.dist-info/licenses/LICENSE +21 -0
- agentops_accelerator-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"""Evaluator catalog and auto-selection for AgentOps 1.0.
|
|
2
|
+
|
|
3
|
+
This module replaces the layered ``bundle.yaml`` system. There is no
|
|
4
|
+
user-facing ``scenario`` concept. Evaluators are picked from two inputs:
|
|
5
|
+
|
|
6
|
+
1. The resolved target kind (agent vs model). Model targets only get the
|
|
7
|
+
baseline quality evaluators - agent-specific evaluators are skipped even
|
|
8
|
+
if the dataset contains those fields.
|
|
9
|
+
2. The shape of the dataset rows:
|
|
10
|
+
|
|
11
|
+
* Always: baseline quality evaluators (Coherence, Fluency).
|
|
12
|
+
* Model-direct targets add exact/reference-answer checks (Similarity,
|
|
13
|
+
F1Score).
|
|
14
|
+
* Agent targets add answer-quality checks that tolerate free-form responses
|
|
15
|
+
(Similarity, ResponseCompleteness).
|
|
16
|
+
* If rows include ``context``: add RAG evaluators (Groundedness,
|
|
17
|
+
Retrieval, Relevance, ResponseCompleteness).
|
|
18
|
+
* If rows include ``tool_calls`` or ``tool_definitions``: add agent
|
|
19
|
+
evaluators (ToolCallAccuracy, IntentResolution, TaskAdherence).
|
|
20
|
+
|
|
21
|
+
The :func:`select_evaluators` function returns a list of resolved
|
|
22
|
+
:class:`EvaluatorPreset` objects. Each preset carries its class name, the
|
|
23
|
+
input mapping it requires, the score key it produces, and a default
|
|
24
|
+
threshold. The runner uses these presets to instantiate
|
|
25
|
+
``azure-ai-evaluation`` evaluator classes against each dataset row.
|
|
26
|
+
|
|
27
|
+
Power users can override the auto-selection by listing evaluator names in
|
|
28
|
+
``agentops.yaml`` under ``evaluators:``. When set, the override list is the
|
|
29
|
+
final word - no auto-detection runs.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import json
|
|
35
|
+
from dataclasses import dataclass, field
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Dict, FrozenSet, Iterable, List, Optional, Tuple
|
|
38
|
+
|
|
39
|
+
from agentops.core.agentops_config import TargetKind, TargetResolution, Threshold
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Catalog
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True)
|
|
48
|
+
class EvaluatorPreset:
|
|
49
|
+
"""Metadata for a single evaluator known to AgentOps.
|
|
50
|
+
|
|
51
|
+
``input_mapping`` keys are the parameter names the evaluator class
|
|
52
|
+
expects; values use the placeholder syntax ``$prompt``, ``$prediction``,
|
|
53
|
+
``$context``, ``$expected``, ``$tool_calls``, ``$tool_definitions``
|
|
54
|
+
which the runner resolves per row.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
name: str
|
|
58
|
+
class_name: str
|
|
59
|
+
score_key: str
|
|
60
|
+
input_mapping: Dict[str, str]
|
|
61
|
+
default_threshold: Optional[Threshold] = None
|
|
62
|
+
#: Categories that this evaluator belongs to. Used by the inference rules.
|
|
63
|
+
categories: FrozenSet[str] = field(default_factory=frozenset)
|
|
64
|
+
#: Set when this evaluator is not safe to run for raw model deployments.
|
|
65
|
+
agent_only: bool = False
|
|
66
|
+
#: When True and the row carries ``tool_calls``, the runner upgrades the
|
|
67
|
+
#: ``query`` and ``response`` kwargs from plain strings to conversation
|
|
68
|
+
#: message lists that include the agent's tool_call + tool_result trace.
|
|
69
|
+
#: This is required for evaluators that judge agent reasoning (e.g.
|
|
70
|
+
#: TaskAdherence, IntentResolution) - without the trace they only see a
|
|
71
|
+
#: short final answer and consistently score it as 1/5.
|
|
72
|
+
needs_conversation: bool = False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _t(metric: str, criteria: str, value: float) -> Threshold:
|
|
76
|
+
return Threshold(metric=metric, criteria=criteria, value=value) # type: ignore[arg-type]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
_QUALITY_BASELINE: Tuple[EvaluatorPreset, ...] = (
|
|
80
|
+
EvaluatorPreset(
|
|
81
|
+
name="CoherenceEvaluator",
|
|
82
|
+
class_name="CoherenceEvaluator",
|
|
83
|
+
score_key="coherence",
|
|
84
|
+
input_mapping={"query": "$prompt", "response": "$prediction"},
|
|
85
|
+
default_threshold=_t("coherence", ">=", 3.0),
|
|
86
|
+
categories=frozenset({"quality"}),
|
|
87
|
+
),
|
|
88
|
+
EvaluatorPreset(
|
|
89
|
+
name="FluencyEvaluator",
|
|
90
|
+
class_name="FluencyEvaluator",
|
|
91
|
+
score_key="fluency",
|
|
92
|
+
input_mapping={"response": "$prediction"},
|
|
93
|
+
default_threshold=_t("fluency", ">=", 3.0),
|
|
94
|
+
categories=frozenset({"quality"}),
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
_SIMILARITY = EvaluatorPreset(
|
|
99
|
+
name="SimilarityEvaluator",
|
|
100
|
+
class_name="SimilarityEvaluator",
|
|
101
|
+
score_key="similarity",
|
|
102
|
+
input_mapping={
|
|
103
|
+
"query": "$prompt",
|
|
104
|
+
"response": "$prediction",
|
|
105
|
+
"ground_truth": "$expected",
|
|
106
|
+
},
|
|
107
|
+
default_threshold=_t("similarity", ">=", 3.0),
|
|
108
|
+
categories=frozenset({"quality"}),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
_F1_SCORE = EvaluatorPreset(
|
|
112
|
+
name="F1ScoreEvaluator",
|
|
113
|
+
class_name="F1ScoreEvaluator",
|
|
114
|
+
score_key="f1_score",
|
|
115
|
+
input_mapping={
|
|
116
|
+
"response": "$prediction",
|
|
117
|
+
"ground_truth": "$expected",
|
|
118
|
+
},
|
|
119
|
+
default_threshold=_t("f1_score", ">=", 0.5),
|
|
120
|
+
categories=frozenset({"quality", "exact_reference"}),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
_RESPONSE_COMPLETENESS = EvaluatorPreset(
|
|
124
|
+
name="ResponseCompletenessEvaluator",
|
|
125
|
+
class_name="ResponseCompletenessEvaluator",
|
|
126
|
+
score_key="response_completeness",
|
|
127
|
+
input_mapping={
|
|
128
|
+
"query": "$prompt",
|
|
129
|
+
"response": "$prediction",
|
|
130
|
+
"ground_truth": "$expected",
|
|
131
|
+
},
|
|
132
|
+
default_threshold=_t("response_completeness", ">=", 3.0),
|
|
133
|
+
categories=frozenset({"quality", "rag"}),
|
|
134
|
+
agent_only=True,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
_MODEL_REFERENCE_EVALUATORS: Tuple[EvaluatorPreset, ...] = (
|
|
138
|
+
_SIMILARITY,
|
|
139
|
+
_F1_SCORE,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
_AGENT_ANSWER_EVALUATORS: Tuple[EvaluatorPreset, ...] = (
|
|
143
|
+
_SIMILARITY,
|
|
144
|
+
_RESPONSE_COMPLETENESS,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
_RAG_EVALUATORS: Tuple[EvaluatorPreset, ...] = (
|
|
149
|
+
EvaluatorPreset(
|
|
150
|
+
name="GroundednessEvaluator",
|
|
151
|
+
class_name="GroundednessEvaluator",
|
|
152
|
+
score_key="groundedness",
|
|
153
|
+
input_mapping={
|
|
154
|
+
"query": "$prompt",
|
|
155
|
+
"response": "$prediction",
|
|
156
|
+
"context": "$context",
|
|
157
|
+
},
|
|
158
|
+
default_threshold=_t("groundedness", ">=", 3.0),
|
|
159
|
+
categories=frozenset({"rag"}),
|
|
160
|
+
agent_only=True,
|
|
161
|
+
),
|
|
162
|
+
EvaluatorPreset(
|
|
163
|
+
name="RelevanceEvaluator",
|
|
164
|
+
class_name="RelevanceEvaluator",
|
|
165
|
+
score_key="relevance",
|
|
166
|
+
input_mapping={
|
|
167
|
+
"query": "$prompt",
|
|
168
|
+
"response": "$prediction",
|
|
169
|
+
"context": "$context",
|
|
170
|
+
},
|
|
171
|
+
default_threshold=_t("relevance", ">=", 3.0),
|
|
172
|
+
categories=frozenset({"rag"}),
|
|
173
|
+
agent_only=True,
|
|
174
|
+
),
|
|
175
|
+
EvaluatorPreset(
|
|
176
|
+
name="RetrievalEvaluator",
|
|
177
|
+
class_name="RetrievalEvaluator",
|
|
178
|
+
score_key="retrieval",
|
|
179
|
+
input_mapping={"query": "$prompt", "context": "$context"},
|
|
180
|
+
default_threshold=_t("retrieval", ">=", 3.0),
|
|
181
|
+
categories=frozenset({"rag"}),
|
|
182
|
+
agent_only=True,
|
|
183
|
+
),
|
|
184
|
+
_RESPONSE_COMPLETENESS,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
_TOOL_USE_EVALUATORS: Tuple[EvaluatorPreset, ...] = (
|
|
189
|
+
EvaluatorPreset(
|
|
190
|
+
name="ToolCallAccuracyEvaluator",
|
|
191
|
+
class_name="ToolCallAccuracyEvaluator",
|
|
192
|
+
score_key="tool_call_accuracy",
|
|
193
|
+
input_mapping={
|
|
194
|
+
"query": "$prompt",
|
|
195
|
+
"tool_calls": "$tool_calls",
|
|
196
|
+
"tool_definitions": "$tool_definitions",
|
|
197
|
+
},
|
|
198
|
+
default_threshold=_t("tool_call_accuracy", ">=", 0.7),
|
|
199
|
+
categories=frozenset({"agent"}),
|
|
200
|
+
agent_only=True,
|
|
201
|
+
),
|
|
202
|
+
EvaluatorPreset(
|
|
203
|
+
name="IntentResolutionEvaluator",
|
|
204
|
+
class_name="IntentResolutionEvaluator",
|
|
205
|
+
score_key="intent_resolution",
|
|
206
|
+
input_mapping={
|
|
207
|
+
"query": "$prompt",
|
|
208
|
+
"response": "$prediction",
|
|
209
|
+
"tool_definitions": "$tool_definitions",
|
|
210
|
+
},
|
|
211
|
+
default_threshold=_t("intent_resolution", ">=", 3.0),
|
|
212
|
+
categories=frozenset({"agent"}),
|
|
213
|
+
agent_only=True,
|
|
214
|
+
needs_conversation=True,
|
|
215
|
+
),
|
|
216
|
+
EvaluatorPreset(
|
|
217
|
+
name="TaskAdherenceEvaluator",
|
|
218
|
+
class_name="TaskAdherenceEvaluator",
|
|
219
|
+
score_key="task_adherence",
|
|
220
|
+
input_mapping={
|
|
221
|
+
"query": "$prompt",
|
|
222
|
+
"response": "$prediction",
|
|
223
|
+
"tool_definitions": "$tool_definitions",
|
|
224
|
+
},
|
|
225
|
+
# azure-ai-evaluation's TaskAdherenceEvaluator returns a binary
|
|
226
|
+
# 0/1 score (0 = flagged, 1 = adheres) - *not* a 1–5 Likert scale
|
|
227
|
+
# like IntentResolutionEvaluator. We default to >=0.5 so a score
|
|
228
|
+
# of 1.0 passes and 0.0 fails.
|
|
229
|
+
default_threshold=_t("task_adherence", ">=", 0.5),
|
|
230
|
+
categories=frozenset({"agent"}),
|
|
231
|
+
agent_only=True,
|
|
232
|
+
needs_conversation=True,
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
_LATENCY = EvaluatorPreset(
|
|
238
|
+
name="avg_latency_seconds",
|
|
239
|
+
class_name="_latency",
|
|
240
|
+
score_key="avg_latency_seconds",
|
|
241
|
+
input_mapping={},
|
|
242
|
+
# 30s default accommodates Foundry cold-start spikes on the first
|
|
243
|
+
# invocation after an agent is created or scaled to zero. Production
|
|
244
|
+
# users should tighten this in agentops.yaml once warmup is observed.
|
|
245
|
+
default_threshold=_t("avg_latency_seconds", "<=", 30.0),
|
|
246
|
+
categories=frozenset({"runtime"}),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
CATALOG: Dict[str, EvaluatorPreset] = {
|
|
251
|
+
preset.name: preset
|
|
252
|
+
for preset in (
|
|
253
|
+
*_QUALITY_BASELINE,
|
|
254
|
+
*_MODEL_REFERENCE_EVALUATORS,
|
|
255
|
+
*_AGENT_ANSWER_EVALUATORS,
|
|
256
|
+
*_RAG_EVALUATORS,
|
|
257
|
+
*_TOOL_USE_EVALUATORS,
|
|
258
|
+
_LATENCY,
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# Dataset shape detection
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@dataclass(frozen=True)
|
|
269
|
+
class DatasetShape:
|
|
270
|
+
"""Boolean flags summarising the columns present in a dataset."""
|
|
271
|
+
|
|
272
|
+
has_context: bool
|
|
273
|
+
has_tool_calls: bool
|
|
274
|
+
has_tool_definitions: bool
|
|
275
|
+
row_count: int
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def looks_rag(self) -> bool:
|
|
279
|
+
return self.has_context
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def looks_tool_use(self) -> bool:
|
|
283
|
+
return self.has_tool_calls or self.has_tool_definitions
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def detect_dataset_shape(dataset_path: Path, *, sample: int = 50) -> DatasetShape:
|
|
287
|
+
"""Inspect up to ``sample`` rows of ``dataset_path`` and report the shape.
|
|
288
|
+
|
|
289
|
+
Truthy values are required - empty strings, empty lists, and ``None`` do
|
|
290
|
+
not count as the field being present.
|
|
291
|
+
"""
|
|
292
|
+
if not dataset_path.exists():
|
|
293
|
+
raise FileNotFoundError(f"dataset file not found: {dataset_path}")
|
|
294
|
+
|
|
295
|
+
has_context = False
|
|
296
|
+
has_tool_calls = False
|
|
297
|
+
has_tool_definitions = False
|
|
298
|
+
count = 0
|
|
299
|
+
|
|
300
|
+
with dataset_path.open("r", encoding="utf-8") as handle:
|
|
301
|
+
for line in handle:
|
|
302
|
+
stripped = line.strip()
|
|
303
|
+
if not stripped:
|
|
304
|
+
continue
|
|
305
|
+
count += 1
|
|
306
|
+
try:
|
|
307
|
+
row = json.loads(stripped)
|
|
308
|
+
except json.JSONDecodeError as exc:
|
|
309
|
+
raise ValueError(
|
|
310
|
+
f"{dataset_path}: invalid JSON on line {count}: {exc}"
|
|
311
|
+
) from exc
|
|
312
|
+
if not isinstance(row, dict):
|
|
313
|
+
raise ValueError(
|
|
314
|
+
f"{dataset_path}: line {count} is not a JSON object"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
if not has_context and row.get("context"):
|
|
318
|
+
has_context = True
|
|
319
|
+
if not has_tool_calls and row.get("tool_calls"):
|
|
320
|
+
has_tool_calls = True
|
|
321
|
+
if not has_tool_definitions and row.get("tool_definitions"):
|
|
322
|
+
has_tool_definitions = True
|
|
323
|
+
|
|
324
|
+
if count >= sample and (
|
|
325
|
+
has_context and (has_tool_calls or has_tool_definitions)
|
|
326
|
+
):
|
|
327
|
+
# Already saw both signals; no need to keep reading.
|
|
328
|
+
break
|
|
329
|
+
|
|
330
|
+
if count == 0:
|
|
331
|
+
raise ValueError(f"{dataset_path}: dataset is empty")
|
|
332
|
+
|
|
333
|
+
return DatasetShape(
|
|
334
|
+
has_context=has_context,
|
|
335
|
+
has_tool_calls=has_tool_calls,
|
|
336
|
+
has_tool_definitions=has_tool_definitions,
|
|
337
|
+
row_count=count,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# ---------------------------------------------------------------------------
|
|
342
|
+
# Selection
|
|
343
|
+
# ---------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def select_evaluators(
|
|
347
|
+
target: TargetResolution,
|
|
348
|
+
shape: DatasetShape,
|
|
349
|
+
*,
|
|
350
|
+
overrides: Optional[List[str]] = None,
|
|
351
|
+
threshold_metrics: Optional[Iterable[str]] = None,
|
|
352
|
+
) -> List[EvaluatorPreset]:
|
|
353
|
+
"""Return the ordered list of evaluators to run.
|
|
354
|
+
|
|
355
|
+
When ``overrides`` is provided it wins outright - the inference rules are
|
|
356
|
+
bypassed. Each name must exist in :data:`CATALOG` or a ``ValueError`` is
|
|
357
|
+
raised.
|
|
358
|
+
|
|
359
|
+
Otherwise the rules are:
|
|
360
|
+
|
|
361
|
+
* Always include the baseline quality evaluators.
|
|
362
|
+
* If the target is a raw model, add exact/reference-answer evaluators and
|
|
363
|
+
stop here. Agent-specific evaluators are not meaningful (no tool calls,
|
|
364
|
+
no retrieved context).
|
|
365
|
+
* If the target is an agent without RAG/tool-use rows, add answer-quality
|
|
366
|
+
evaluators that tolerate free-form responses.
|
|
367
|
+
* If the dataset has ``context`` rows, add the RAG evaluators.
|
|
368
|
+
* If the dataset has ``tool_calls`` or ``tool_definitions``, add the agent
|
|
369
|
+
evaluators.
|
|
370
|
+
* Always append the runtime ``avg_latency_seconds`` evaluator.
|
|
371
|
+
"""
|
|
372
|
+
if overrides:
|
|
373
|
+
resolved: List[EvaluatorPreset] = []
|
|
374
|
+
for name in overrides:
|
|
375
|
+
preset = CATALOG.get(name)
|
|
376
|
+
if preset is None:
|
|
377
|
+
known = ", ".join(sorted(CATALOG.keys()))
|
|
378
|
+
raise ValueError(
|
|
379
|
+
f"unknown evaluator override {name!r}. "
|
|
380
|
+
f"Known evaluators: {known}"
|
|
381
|
+
)
|
|
382
|
+
resolved.append(preset)
|
|
383
|
+
return resolved
|
|
384
|
+
|
|
385
|
+
selected: List[EvaluatorPreset] = list(_QUALITY_BASELINE)
|
|
386
|
+
|
|
387
|
+
if target.kind == "model_direct":
|
|
388
|
+
selected.extend(_MODEL_REFERENCE_EVALUATORS)
|
|
389
|
+
elif _is_agent_target(target.kind):
|
|
390
|
+
if not shape.looks_rag and not shape.looks_tool_use:
|
|
391
|
+
selected.extend(_AGENT_ANSWER_EVALUATORS)
|
|
392
|
+
if shape.looks_rag:
|
|
393
|
+
selected.extend(_RAG_EVALUATORS)
|
|
394
|
+
if shape.looks_tool_use:
|
|
395
|
+
selected.extend(_TOOL_USE_EVALUATORS)
|
|
396
|
+
# F1ScoreEvaluator and SimilarityEvaluator compare the
|
|
397
|
+
# assistant's natural-language reply against ``expected``. In
|
|
398
|
+
# tool-using datasets ``expected`` is conventionally a behavior
|
|
399
|
+
# description (e.g. "Calls lookup_order with order_id='ORD-12345'")
|
|
400
|
+
# rather than the literal reply, so token overlap and semantic
|
|
401
|
+
# similarity are meaningless and gate well-behaved agents on a
|
|
402
|
+
# metric that does not apply. Drop both from the selection.
|
|
403
|
+
_drop = {"F1ScoreEvaluator", "SimilarityEvaluator"}
|
|
404
|
+
selected = [p for p in selected if p.name not in _drop]
|
|
405
|
+
|
|
406
|
+
if threshold_metrics:
|
|
407
|
+
selected = _include_thresholded_quality_presets(
|
|
408
|
+
selected,
|
|
409
|
+
target.kind,
|
|
410
|
+
threshold_metrics,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
selected.append(_LATENCY)
|
|
414
|
+
return selected
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _is_agent_target(kind: TargetKind) -> bool:
|
|
418
|
+
return kind in {"foundry_prompt", "foundry_hosted", "http_json"}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _include_thresholded_quality_presets(
|
|
422
|
+
selected: List[EvaluatorPreset],
|
|
423
|
+
target_kind: TargetKind,
|
|
424
|
+
threshold_metrics: Iterable[str],
|
|
425
|
+
) -> List[EvaluatorPreset]:
|
|
426
|
+
"""Honor explicit thresholds for optional quality/reference metrics.
|
|
427
|
+
|
|
428
|
+
A user who already set ``thresholds: {f1_score: ...}`` should not also need
|
|
429
|
+
to learn the evaluator override escape hatch. This keeps exact-reference F1
|
|
430
|
+
available as an opt-in gate without making it a default for free-form agent
|
|
431
|
+
responses.
|
|
432
|
+
"""
|
|
433
|
+
by_score_key = {preset.score_key: preset for preset in CATALOG.values()}
|
|
434
|
+
selected_names = {preset.name for preset in selected}
|
|
435
|
+
result = list(selected)
|
|
436
|
+
for metric in threshold_metrics:
|
|
437
|
+
preset = by_score_key.get(metric)
|
|
438
|
+
if preset is None or preset.name in selected_names:
|
|
439
|
+
continue
|
|
440
|
+
if "runtime" in preset.categories:
|
|
441
|
+
continue
|
|
442
|
+
if "quality" not in preset.categories and "exact_reference" not in preset.categories:
|
|
443
|
+
continue
|
|
444
|
+
if preset.agent_only and not _is_agent_target(target_kind):
|
|
445
|
+
continue
|
|
446
|
+
result.append(preset)
|
|
447
|
+
selected_names.add(preset.name)
|
|
448
|
+
return result
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def merge_thresholds(
|
|
452
|
+
presets: List[EvaluatorPreset],
|
|
453
|
+
user_thresholds: List[Threshold],
|
|
454
|
+
) -> List[Threshold]:
|
|
455
|
+
"""Combine evaluator default thresholds with user overrides.
|
|
456
|
+
|
|
457
|
+
User entries override the preset default for the same metric. Metrics
|
|
458
|
+
listed by the user that don't correspond to any selected preset are kept
|
|
459
|
+
as-is - the threshold engine will report them as unmet rather than
|
|
460
|
+
silently drop them.
|
|
461
|
+
"""
|
|
462
|
+
by_metric: Dict[str, Threshold] = {}
|
|
463
|
+
for preset in presets:
|
|
464
|
+
if preset.default_threshold is not None:
|
|
465
|
+
by_metric[preset.default_threshold.metric] = preset.default_threshold
|
|
466
|
+
for override in user_thresholds:
|
|
467
|
+
by_metric[override.metric] = override
|
|
468
|
+
# Preserve preset order, then append user-only metrics in original order.
|
|
469
|
+
ordered: List[Threshold] = []
|
|
470
|
+
seen: set[str] = set()
|
|
471
|
+
for preset in presets:
|
|
472
|
+
if preset.default_threshold is not None:
|
|
473
|
+
metric = preset.default_threshold.metric
|
|
474
|
+
ordered.append(by_metric[metric])
|
|
475
|
+
seen.add(metric)
|
|
476
|
+
for override in user_thresholds:
|
|
477
|
+
if override.metric not in seen:
|
|
478
|
+
ordered.append(override)
|
|
479
|
+
seen.add(override.metric)
|
|
480
|
+
return ordered
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Versioned schema for AgentOps release evidence artifacts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
ReadinessStatus = Literal["ready", "ready_with_warnings", "blocked"]
|
|
11
|
+
CheckStatus = Literal["ready", "warning", "blocked", "unknown"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReleaseEvidenceLink(BaseModel):
|
|
15
|
+
"""A navigable reference related to the release decision."""
|
|
16
|
+
|
|
17
|
+
label: str
|
|
18
|
+
url: str
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(extra="forbid")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ReleaseEvidenceCheck(BaseModel):
|
|
24
|
+
"""One production-readiness check in the evidence pack."""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
status: CheckStatus
|
|
28
|
+
summary: str
|
|
29
|
+
evidence: Dict[str, Any] = Field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(extra="forbid")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ReleaseEvidence(BaseModel):
|
|
35
|
+
"""Stable machine-readable production-readiness evidence contract."""
|
|
36
|
+
|
|
37
|
+
version: int = 1
|
|
38
|
+
generated_at: str
|
|
39
|
+
workspace: str
|
|
40
|
+
status: ReadinessStatus
|
|
41
|
+
target: Optional[str] = None
|
|
42
|
+
blockers: List[str] = Field(default_factory=list)
|
|
43
|
+
warnings: List[str] = Field(default_factory=list)
|
|
44
|
+
ready: List[str] = Field(default_factory=list)
|
|
45
|
+
checks: List[ReleaseEvidenceCheck] = Field(default_factory=list)
|
|
46
|
+
links: List[ReleaseEvidenceLink] = Field(default_factory=list)
|
|
47
|
+
latest_eval: Dict[str, Any] = Field(default_factory=dict)
|
|
48
|
+
official_eval: Dict[str, Any] = Field(default_factory=dict)
|
|
49
|
+
doctor: Dict[str, Any] = Field(default_factory=dict)
|
|
50
|
+
workflows: Dict[str, Any] = Field(default_factory=dict)
|
|
51
|
+
foundry: Dict[str, Any] = Field(default_factory=dict)
|
|
52
|
+
monitoring: Dict[str, Any] = Field(default_factory=dict)
|
|
53
|
+
trace_dataset: Dict[str, Any] = Field(default_factory=dict)
|
|
54
|
+
ailz: Dict[str, Any] = Field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(extra="forbid")
|
agentops/core/results.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Result dataclasses for the AgentOps 1.0 pipeline.
|
|
2
|
+
|
|
3
|
+
These shapes are written to ``results.json`` after every ``agentops eval`` run
|
|
4
|
+
and consumed by the reporter and comparison logic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RowMetric(BaseModel):
|
|
15
|
+
"""A single evaluator score for one dataset row."""
|
|
16
|
+
|
|
17
|
+
name: str
|
|
18
|
+
value: Optional[float] = None
|
|
19
|
+
error: Optional[str] = None
|
|
20
|
+
reason: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RowResult(BaseModel):
|
|
24
|
+
"""One evaluated dataset row."""
|
|
25
|
+
|
|
26
|
+
row_index: int
|
|
27
|
+
input: str
|
|
28
|
+
expected: Optional[str] = None
|
|
29
|
+
response: str = ""
|
|
30
|
+
context: Optional[str] = None
|
|
31
|
+
latency_seconds: Optional[float] = None
|
|
32
|
+
tool_calls: Optional[List[Any]] = None
|
|
33
|
+
metrics: List[RowMetric] = Field(default_factory=list)
|
|
34
|
+
error: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ThresholdEvaluation(BaseModel):
|
|
38
|
+
"""A pass/fail check for a single metric on the run aggregate."""
|
|
39
|
+
|
|
40
|
+
metric: str
|
|
41
|
+
criteria: str
|
|
42
|
+
expected: str
|
|
43
|
+
actual: str
|
|
44
|
+
passed: bool
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RunSummary(BaseModel):
|
|
48
|
+
"""Top-level pass/fail summary of an evaluation run."""
|
|
49
|
+
|
|
50
|
+
items_total: int
|
|
51
|
+
items_passed_all: int
|
|
52
|
+
items_pass_rate: float
|
|
53
|
+
thresholds_total: int
|
|
54
|
+
thresholds_passed: int
|
|
55
|
+
threshold_pass_rate: float
|
|
56
|
+
overall_passed: bool
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TargetInfo(BaseModel):
|
|
60
|
+
"""Resolved target information (echoed into results.json)."""
|
|
61
|
+
|
|
62
|
+
kind: str
|
|
63
|
+
raw: str
|
|
64
|
+
protocol: Optional[str] = None
|
|
65
|
+
name: Optional[str] = None
|
|
66
|
+
version: Optional[str] = None
|
|
67
|
+
url: Optional[str] = None
|
|
68
|
+
deployment: Optional[str] = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ComparisonMetric(BaseModel):
|
|
72
|
+
"""Per-metric delta between the current run and a baseline."""
|
|
73
|
+
|
|
74
|
+
metric: str
|
|
75
|
+
current: Optional[float] = None
|
|
76
|
+
baseline: Optional[float] = None
|
|
77
|
+
delta: Optional[float] = None
|
|
78
|
+
direction: str # "improved" | "regressed" | "unchanged"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ComparisonRow(BaseModel):
|
|
82
|
+
"""Per-row regression / improvement against a baseline."""
|
|
83
|
+
|
|
84
|
+
row_index: int
|
|
85
|
+
current_passed: bool
|
|
86
|
+
baseline_passed: Optional[bool] = None
|
|
87
|
+
direction: str # "improved" | "regressed" | "unchanged" | "new"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ComparisonInfo(BaseModel):
|
|
91
|
+
"""Comparison block included when ``--baseline`` was provided."""
|
|
92
|
+
|
|
93
|
+
baseline_path: str
|
|
94
|
+
baseline_started_at: Optional[str] = None
|
|
95
|
+
baseline_overall_passed: Optional[bool] = None
|
|
96
|
+
metrics: List[ComparisonMetric] = Field(default_factory=list)
|
|
97
|
+
rows: List[ComparisonRow] = Field(default_factory=list)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class RunResult(BaseModel):
|
|
101
|
+
"""Full ``results.json`` payload."""
|
|
102
|
+
|
|
103
|
+
version: int = 1
|
|
104
|
+
started_at: str
|
|
105
|
+
finished_at: str
|
|
106
|
+
duration_seconds: float
|
|
107
|
+
target: TargetInfo
|
|
108
|
+
dataset_path: str
|
|
109
|
+
evaluators: List[str] = Field(default_factory=list)
|
|
110
|
+
rows: List[RowResult] = Field(default_factory=list)
|
|
111
|
+
aggregate_metrics: Dict[str, float] = Field(default_factory=dict)
|
|
112
|
+
thresholds: List[ThresholdEvaluation] = Field(default_factory=list)
|
|
113
|
+
summary: RunSummary
|
|
114
|
+
comparison: Optional[ComparisonInfo] = None
|
|
115
|
+
config: Dict[str, Any] = Field(default_factory=dict)
|
|
116
|
+
|
|
117
|
+
model_config = ConfigDict(extra="forbid")
|
agentops/mcp/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) server for AgentOps.
|
|
2
|
+
|
|
3
|
+
Exposes the most-used AgentOps capabilities as MCP tools over stdio so that
|
|
4
|
+
MCP-aware coding agents (Claude Code, Copilot Chat, etc.) can drive AgentOps
|
|
5
|
+
without shelling out to the CLI.
|
|
6
|
+
|
|
7
|
+
The server is opt-in: it requires the optional ``mcp`` extra
|
|
8
|
+
(``pip install agentops-accelerator[mcp]``) and is started via
|
|
9
|
+
``agentops mcp serve``.
|
|
10
|
+
"""
|