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.
Files changed (142) hide show
  1. agentops/__init__.py +10 -0
  2. agentops/__main__.py +6 -0
  3. agentops/agent/__init__.py +12 -0
  4. agentops/agent/_legacy_ids.py +92 -0
  5. agentops/agent/analyzer.py +207 -0
  6. agentops/agent/checks/__init__.py +1 -0
  7. agentops/agent/checks/catalog.py +880 -0
  8. agentops/agent/checks/errors.py +279 -0
  9. agentops/agent/checks/foundry_config.py +75 -0
  10. agentops/agent/checks/latency.py +84 -0
  11. agentops/agent/checks/opex.py +157 -0
  12. agentops/agent/checks/opex_workspace.py +874 -0
  13. agentops/agent/checks/posture.py +36 -0
  14. agentops/agent/checks/posture_rules/__init__.py +53 -0
  15. agentops/agent/checks/posture_rules/content_filter.py +59 -0
  16. agentops/agent/checks/posture_rules/diagnostics.py +74 -0
  17. agentops/agent/checks/posture_rules/local_auth.py +55 -0
  18. agentops/agent/checks/posture_rules/managed_identity.py +59 -0
  19. agentops/agent/checks/posture_rules/network.py +68 -0
  20. agentops/agent/checks/regression.py +78 -0
  21. agentops/agent/checks/release_readiness.py +182 -0
  22. agentops/agent/checks/safety.py +247 -0
  23. agentops/agent/checks/spec_conformance.py +375 -0
  24. agentops/agent/cockpit.py +5159 -0
  25. agentops/agent/config.py +240 -0
  26. agentops/agent/findings.py +113 -0
  27. agentops/agent/history.py +142 -0
  28. agentops/agent/knowledge/__init__.py +182 -0
  29. agentops/agent/knowledge/waf-checklist.csv +39 -0
  30. agentops/agent/llm_assist/__init__.py +16 -0
  31. agentops/agent/llm_assist/_base.py +124 -0
  32. agentops/agent/llm_assist/_bundle_rule.py +154 -0
  33. agentops/agent/llm_assist/_client.py +347 -0
  34. agentops/agent/llm_assist/_dataset_rules.py +191 -0
  35. agentops/agent/llm_assist/_engine.py +106 -0
  36. agentops/agent/llm_assist/_prompt_rules.py +291 -0
  37. agentops/agent/llm_assist/_spec_rules.py +235 -0
  38. agentops/agent/production_telemetry.py +430 -0
  39. agentops/agent/report.py +207 -0
  40. agentops/agent/server/__init__.py +1 -0
  41. agentops/agent/server/app.py +84 -0
  42. agentops/agent/server/auth.py +94 -0
  43. agentops/agent/server/chat.py +44 -0
  44. agentops/agent/server/protocol.py +72 -0
  45. agentops/agent/sources/__init__.py +1 -0
  46. agentops/agent/sources/azure_monitor.py +523 -0
  47. agentops/agent/sources/azure_resources.py +602 -0
  48. agentops/agent/sources/foundry_control.py +174 -0
  49. agentops/agent/sources/results_history.py +494 -0
  50. agentops/agent/sources/spec_detectors/__init__.py +42 -0
  51. agentops/agent/sources/spec_detectors/_base.py +58 -0
  52. agentops/agent/sources/spec_detectors/agents_md.py +75 -0
  53. agentops/agent/sources/spec_detectors/spec_kit.py +172 -0
  54. agentops/agent/time_range.py +117 -0
  55. agentops/cli/__init__.py +1 -0
  56. agentops/cli/app.py +4823 -0
  57. agentops/core/__init__.py +1 -0
  58. agentops/core/agentops_config.py +592 -0
  59. agentops/core/config_loader.py +22 -0
  60. agentops/core/evaluators.py +480 -0
  61. agentops/core/release_evidence.py +56 -0
  62. agentops/core/results.py +117 -0
  63. agentops/mcp/__init__.py +10 -0
  64. agentops/mcp/server.py +232 -0
  65. agentops/pipeline/__init__.py +8 -0
  66. agentops/pipeline/cloud_results.py +189 -0
  67. agentops/pipeline/cloud_runner.py +901 -0
  68. agentops/pipeline/comparison.py +108 -0
  69. agentops/pipeline/diagnostics.py +51 -0
  70. agentops/pipeline/invocations.py +535 -0
  71. agentops/pipeline/official_eval.py +414 -0
  72. agentops/pipeline/orchestrator.py +775 -0
  73. agentops/pipeline/prompt_deploy.py +377 -0
  74. agentops/pipeline/publisher.py +121 -0
  75. agentops/pipeline/reporter.py +202 -0
  76. agentops/pipeline/runtime.py +409 -0
  77. agentops/pipeline/thresholds.py +84 -0
  78. agentops/services/__init__.py +1 -0
  79. agentops/services/cicd.py +720 -0
  80. agentops/services/eval_analysis.py +848 -0
  81. agentops/services/evidence_pack.py +757 -0
  82. agentops/services/initializer.py +86 -0
  83. agentops/services/preflight.py +470 -0
  84. agentops/services/setup_wizard.py +709 -0
  85. agentops/services/skills.py +643 -0
  86. agentops/services/trace_promotion.py +300 -0
  87. agentops/services/workflow_analysis.py +1129 -0
  88. agentops/templates/.gitignore +15 -0
  89. agentops/templates/__init__.py +1 -0
  90. agentops/templates/agent-server/Dockerfile +23 -0
  91. agentops/templates/agent-server/README.md +61 -0
  92. agentops/templates/agent-server/main.bicep +94 -0
  93. agentops/templates/agent.yaml +87 -0
  94. agentops/templates/agentops.yaml +58 -0
  95. agentops/templates/foundry.svg +71 -0
  96. agentops/templates/icon.png +0 -0
  97. agentops/templates/pipelines/azuredevops/agentops-deploy-dev-azd.yml +118 -0
  98. agentops/templates/pipelines/azuredevops/agentops-deploy-dev.yml +73 -0
  99. agentops/templates/pipelines/azuredevops/agentops-deploy-prod-azd.yml +141 -0
  100. agentops/templates/pipelines/azuredevops/agentops-deploy-prod.yml +94 -0
  101. agentops/templates/pipelines/azuredevops/agentops-deploy-prompt-agent.yml +167 -0
  102. agentops/templates/pipelines/azuredevops/agentops-deploy-qa-azd.yml +118 -0
  103. agentops/templates/pipelines/azuredevops/agentops-deploy-qa.yml +68 -0
  104. agentops/templates/pipelines/azuredevops/agentops-pr-prompt-agent.yml +210 -0
  105. agentops/templates/pipelines/azuredevops/agentops-pr.yml +155 -0
  106. agentops/templates/pipelines/azuredevops/agentops-watchdog.yml +106 -0
  107. agentops/templates/project.gitignore +36 -0
  108. agentops/templates/sample-traces.jsonl +3 -0
  109. agentops/templates/skills/agentops-agent/SKILL.md +137 -0
  110. agentops/templates/skills/agentops-config/SKILL.md +113 -0
  111. agentops/templates/skills/agentops-dataset/SKILL.md +84 -0
  112. agentops/templates/skills/agentops-eval/SKILL.md +189 -0
  113. agentops/templates/skills/agentops-report/SKILL.md +71 -0
  114. agentops/templates/skills/agentops-workflow/SKILL.md +471 -0
  115. agentops/templates/smoke.jsonl +3 -0
  116. agentops/templates/waf-checklist.README.md +84 -0
  117. agentops/templates/waf-checklist.csv +22 -0
  118. agentops/templates/workflows/agentops-deploy-dev-azd.yml +166 -0
  119. agentops/templates/workflows/agentops-deploy-dev.yml +187 -0
  120. agentops/templates/workflows/agentops-deploy-prod-azd.yml +183 -0
  121. agentops/templates/workflows/agentops-deploy-prod.yml +171 -0
  122. agentops/templates/workflows/agentops-deploy-prompt-agent.yml +197 -0
  123. agentops/templates/workflows/agentops-deploy-qa-azd.yml +156 -0
  124. agentops/templates/workflows/agentops-deploy-qa.yml +145 -0
  125. agentops/templates/workflows/agentops-pr-prompt-agent.yml +210 -0
  126. agentops/templates/workflows/agentops-pr.yml +148 -0
  127. agentops/templates/workflows/agentops-watchdog.yml +122 -0
  128. agentops/utils/__init__.py +1 -0
  129. agentops/utils/azd_env.py +435 -0
  130. agentops/utils/azure_endpoints.py +62 -0
  131. agentops/utils/colors.py +47 -0
  132. agentops/utils/dotenv_loader.py +105 -0
  133. agentops/utils/foundry_discovery.py +229 -0
  134. agentops/utils/logging.py +59 -0
  135. agentops/utils/telemetry.py +554 -0
  136. agentops/utils/yaml.py +36 -0
  137. agentops_accelerator-0.3.0.dist-info/METADATA +278 -0
  138. agentops_accelerator-0.3.0.dist-info/RECORD +142 -0
  139. agentops_accelerator-0.3.0.dist-info/WHEEL +5 -0
  140. agentops_accelerator-0.3.0.dist-info/entry_points.txt +2 -0
  141. agentops_accelerator-0.3.0.dist-info/licenses/LICENSE +21 -0
  142. agentops_accelerator-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,300 @@
1
+ """Promote production trace exports into reviewable regression datasets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import textwrap
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+ from typing import Any, Iterable, Literal, Optional
11
+
12
+ _TEXT_WRAP_WIDTH = 92
13
+
14
+
15
+ LabelMode = Literal["self-similarity", "pending"]
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class TracePromotionPreview:
20
+ """Result of transforming traces into candidate dataset rows."""
21
+
22
+ source: Path
23
+ output_path: Path
24
+ manifest_path: Path
25
+ rows: list[dict[str, Any]]
26
+ skipped: int
27
+ label_mode: LabelMode
28
+ warnings: list[str] = field(default_factory=list)
29
+
30
+
31
+ def promote_traces(
32
+ *,
33
+ source: Path,
34
+ output_path: Path,
35
+ max_rows: int = 50,
36
+ label_mode: LabelMode = "self-similarity",
37
+ apply: bool = False,
38
+ ) -> TracePromotionPreview:
39
+ """Convert a JSON/JSONL trace export into AgentOps dataset rows.
40
+
41
+ ``self-similarity`` stores the production response as ``expected`` so future
42
+ evals catch behavior drift against a known production answer. ``pending``
43
+ leaves ``expected`` empty and marks every row for human completion.
44
+ """
45
+
46
+ if max_rows <= 0:
47
+ raise ValueError("max_rows must be greater than zero")
48
+ if label_mode not in {"self-similarity", "pending"}:
49
+ raise ValueError("label_mode must be self-similarity or pending")
50
+ if not source.exists():
51
+ raise FileNotFoundError(
52
+ f"trace source not found: {source}. Export Foundry/App Insights traces "
53
+ "first, or use the sample created by `agentops init` at "
54
+ ".agentops/traces/sample-traces.jsonl."
55
+ )
56
+
57
+ traces = _load_trace_export(source)
58
+ rows: list[dict[str, Any]] = []
59
+ warnings: list[str] = []
60
+ skipped = 0
61
+ seen: set[tuple[str, str]] = set()
62
+
63
+ for trace in traces:
64
+ if len(rows) >= max_rows:
65
+ break
66
+ candidate = _trace_to_row(trace, label_mode)
67
+ if candidate is None:
68
+ skipped += 1
69
+ continue
70
+ key = (candidate["input"], candidate.get("expected", ""))
71
+ if key in seen:
72
+ skipped += 1
73
+ continue
74
+ seen.add(key)
75
+ rows.append(candidate)
76
+
77
+ if not rows:
78
+ warnings.append("No usable input/response pairs were found in the trace export.")
79
+ if label_mode == "self-similarity":
80
+ warnings.append(
81
+ "Rows use production responses as expected values. Treat this as drift detection, not human-verified ground truth."
82
+ )
83
+ else:
84
+ warnings.append(
85
+ "Rows are pending human labels. Fill expected/context/tool fields before using this file as a blocking gate."
86
+ )
87
+
88
+ manifest_path = output_path.with_name("trace-regression-manifest.json")
89
+ preview = TracePromotionPreview(
90
+ source=source,
91
+ output_path=output_path,
92
+ manifest_path=manifest_path,
93
+ rows=rows,
94
+ skipped=skipped,
95
+ label_mode=label_mode,
96
+ warnings=warnings,
97
+ )
98
+ if apply:
99
+ _write_trace_dataset(preview)
100
+ return preview
101
+
102
+
103
+ def render_trace_promotion_preview(preview: TracePromotionPreview) -> str:
104
+ """Render a concise terminal-friendly summary."""
105
+
106
+ lines = [
107
+ "AgentOps trace-to-dataset preview",
108
+ f"Source: {preview.source}",
109
+ f"Output: {preview.output_path}",
110
+ "",
111
+ "Summary",
112
+ ]
113
+ lines.extend(
114
+ _render_text_fields(
115
+ [
116
+ ("rows", str(len(preview.rows))),
117
+ ("skipped", str(preview.skipped)),
118
+ ("label mode", preview.label_mode),
119
+ ]
120
+ )
121
+ )
122
+ if preview.warnings:
123
+ lines.append("")
124
+ lines.append("Warnings")
125
+ for warning in preview.warnings:
126
+ lines.extend(_wrapped_status_line("warn", "warning", warning))
127
+ if preview.rows:
128
+ lines.append("")
129
+ lines.append("Sample rows")
130
+ for index, row in enumerate(preview.rows[:3], start=1):
131
+ lines.extend(_wrapped_numbered_line(index, str(row["input"])[:100]))
132
+ return "\n".join(lines) + "\n"
133
+
134
+
135
+ def _render_text_fields(rows: list[tuple[str, str]]) -> list[str]:
136
+ width = max(len(label) for label, _ in rows)
137
+ lines: list[str] = []
138
+ for label, value in rows:
139
+ lines.extend(_wrap_text(value, indent=f" {label.ljust(width)} "))
140
+ return lines
141
+
142
+
143
+ def _wrapped_status_line(status: str, label: str, text: str) -> list[str]:
144
+ prefix = f" {status.ljust(4)} {label.ljust(10)} "
145
+ wrapped = textwrap.wrap(
146
+ text,
147
+ width=_TEXT_WRAP_WIDTH,
148
+ initial_indent=prefix,
149
+ subsequent_indent=" " * len(prefix),
150
+ break_long_words=False,
151
+ break_on_hyphens=False,
152
+ )
153
+ return wrapped or [prefix.rstrip()]
154
+
155
+
156
+ def _wrapped_numbered_line(index: int, text: str) -> list[str]:
157
+ prefix = f" {index}. "
158
+ wrapped = textwrap.wrap(
159
+ text,
160
+ width=_TEXT_WRAP_WIDTH,
161
+ initial_indent=prefix,
162
+ subsequent_indent=" " * len(prefix),
163
+ break_long_words=False,
164
+ break_on_hyphens=False,
165
+ )
166
+ return wrapped or [prefix.rstrip()]
167
+
168
+
169
+ def _wrap_text(text: str, *, indent: str) -> list[str]:
170
+ return textwrap.wrap(
171
+ text,
172
+ width=_TEXT_WRAP_WIDTH,
173
+ initial_indent=indent,
174
+ subsequent_indent=indent,
175
+ break_long_words=False,
176
+ break_on_hyphens=False,
177
+ ) or [indent.rstrip()]
178
+
179
+
180
+ def _load_trace_export(source: Path) -> Iterable[dict[str, Any]]:
181
+ text = source.read_text(encoding="utf-8")
182
+ stripped = text.strip()
183
+ if not stripped:
184
+ return []
185
+ if stripped.startswith("["):
186
+ payload = json.loads(stripped)
187
+ if not isinstance(payload, list):
188
+ raise ValueError("JSON trace export must be an array of objects")
189
+ return [item for item in payload if isinstance(item, dict)]
190
+
191
+ rows: list[dict[str, Any]] = []
192
+ for line_number, line in enumerate(text.splitlines(), start=1):
193
+ item = line.strip()
194
+ if not item:
195
+ continue
196
+ try:
197
+ payload = json.loads(item)
198
+ except json.JSONDecodeError as exc:
199
+ raise ValueError(f"{source}: invalid JSON on line {line_number}: {exc}") from exc
200
+ if isinstance(payload, dict):
201
+ rows.append(payload)
202
+ return rows
203
+
204
+
205
+ def _trace_to_row(trace: dict[str, Any], label_mode: LabelMode) -> Optional[dict[str, Any]]:
206
+ input_text = _first_text(
207
+ trace,
208
+ "input",
209
+ "query",
210
+ "prompt",
211
+ "message",
212
+ "user_message",
213
+ "request",
214
+ "customDimensions.input",
215
+ "customDimensions.query",
216
+ "customDimensions.gen_ai.prompt",
217
+ )
218
+ response_text = _first_text(
219
+ trace,
220
+ "response",
221
+ "output",
222
+ "answer",
223
+ "completion",
224
+ "assistant_message",
225
+ "customDimensions.response",
226
+ "customDimensions.output",
227
+ "customDimensions.gen_ai.completion",
228
+ )
229
+ if not input_text or not response_text:
230
+ return None
231
+
232
+ metadata = {
233
+ "source": "production_trace",
234
+ "trace_id": _first_text(trace, "trace_id", "operation_Id", "operationId", "id"),
235
+ "timestamp": _first_text(trace, "timestamp", "time", "TimeGenerated"),
236
+ "label_mode": label_mode,
237
+ "needs_review": True,
238
+ }
239
+ row: dict[str, Any] = {
240
+ "input": input_text,
241
+ "expected": response_text if label_mode == "self-similarity" else "",
242
+ "metadata": {k: v for k, v in metadata.items() if v not in (None, "")},
243
+ }
244
+ context = _first_text(trace, "context", "grounding", "retrieved_context", "customDimensions.context")
245
+ if context:
246
+ row["context"] = context
247
+ tool_calls = _first_value(trace, "tool_calls", "customDimensions.tool_calls")
248
+ if tool_calls:
249
+ row["tool_calls"] = tool_calls
250
+ return row
251
+
252
+
253
+ def _write_trace_dataset(preview: TracePromotionPreview) -> None:
254
+ preview.output_path.parent.mkdir(parents=True, exist_ok=True)
255
+ with preview.output_path.open("w", encoding="utf-8") as handle:
256
+ for row in preview.rows:
257
+ handle.write(json.dumps(row, ensure_ascii=False) + "\n")
258
+
259
+ manifest = {
260
+ "version": 1,
261
+ "generated_at": datetime.now(timezone.utc).isoformat(),
262
+ "source": str(preview.source),
263
+ "dataset_path": str(preview.output_path),
264
+ "rows": len(preview.rows),
265
+ "skipped": preview.skipped,
266
+ "label_mode": preview.label_mode,
267
+ "human_review_required": True,
268
+ }
269
+ preview.manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
270
+
271
+
272
+ def _first_text(data: dict[str, Any], *keys: str) -> Optional[str]:
273
+ value = _first_value(data, *keys)
274
+ if value is None:
275
+ return None
276
+ if isinstance(value, str):
277
+ text = value.strip()
278
+ return text or None
279
+ if isinstance(value, (dict, list)):
280
+ text = json.dumps(value, ensure_ascii=False)
281
+ return text if text not in ("{}", "[]") else None
282
+ text = str(value).strip()
283
+ return text or None
284
+
285
+
286
+ def _first_value(data: dict[str, Any], *keys: str) -> Any:
287
+ for key in keys:
288
+ value = _lookup(data, key)
289
+ if value not in (None, "", [], {}):
290
+ return value
291
+ return None
292
+
293
+
294
+ def _lookup(data: dict[str, Any], key: str) -> Any:
295
+ current: Any = data
296
+ for part in key.split("."):
297
+ if not isinstance(current, dict):
298
+ return None
299
+ current = current.get(part)
300
+ return current