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,720 @@
1
+ """CI/CD workflow generation service for `agentops workflow generate`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from importlib.resources import files
7
+ from pathlib import Path
8
+ from typing import Dict, List, Mapping, Sequence, Tuple
9
+
10
+ from agentops.pipeline.official_eval import (
11
+ AGENTOPS_CLOUD_RUNNER,
12
+ AGENTOPS_LOCAL_RUNNER,
13
+ OFFICIAL_EVAL_ACTION_ENV,
14
+ OFFICIAL_EVAL_ADO_TASK_ENV,
15
+ OFFICIAL_EVAL_RUNNER,
16
+ official_eval_action_ref,
17
+ official_eval_ado_task_ref,
18
+ )
19
+ from agentops.services.workflow_analysis import (
20
+ has_ailz_preflight,
21
+ recommended_deploy_mode,
22
+ recommended_eval_runner,
23
+ )
24
+
25
+
26
+ _TEMPLATE_PACKAGE = "agentops.templates"
27
+ _CLOUD_EVAL_CONFIG_NAME = ".agentops.cloud.yaml"
28
+ _CI_EVAL_OUTPUT = ".agentops/results/latest"
29
+
30
+ # CI/CD platforms supported by ``agentops workflow generate``.
31
+ PLATFORMS: Tuple[str, ...] = ("github", "azure-devops")
32
+
33
+ # Deployment template modes. ``placeholder`` keeps the stack-agnostic
34
+ # scaffold; ``azd`` delegates infrastructure and app deployment to Azure
35
+ # Developer CLI; ``prompt-agent`` creates/evaluates a candidate Foundry prompt
36
+ # agent version from a source-controlled prompt file. ``auto`` selects
37
+ # ``azd`` when the target repo has ``azure.yaml`` and ``prompt-agent`` when
38
+ # ``agentops.yaml`` targets a Foundry prompt agent.
39
+ DEPLOY_MODES: Tuple[str, ...] = ("auto", "placeholder", "azd", "prompt-agent")
40
+
41
+ # Doctor gate severities supported by ``agentops workflow generate
42
+ # --doctor-gate``. The PR workflow template runs `agentops doctor
43
+ # --severity-fail <gate>`; ``critical`` (the default) blocks the PR on
44
+ # critical Doctor findings (including regression detection), ``warning``
45
+ # blocks on warning or higher, and ``none`` keeps Doctor advisory.
46
+ DOCTOR_GATES: Tuple[str, ...] = ("critical", "warning", "none")
47
+ DEFAULT_DOCTOR_GATE: str = "critical"
48
+
49
+ # Per-platform mapping of workflow kind -> (template path inside package,
50
+ # output path in repo).
51
+ #
52
+ # The default templates form a complete GenAIOps GitFlow scaffold:
53
+ #
54
+ # pr -> agentops-pr (PR gate; PRs to develop, release/**, main)
55
+ # dev -> agentops-deploy-dev (push to develop -> environment: dev)
56
+ # qa -> agentops-deploy-qa (push to release/** -> environment: qa)
57
+ # prod -> agentops-deploy-prod (push to main -> environment: production)
58
+ #
59
+ # A scheduled Doctor workflow is also available as an explicit optional kind:
60
+ #
61
+ # doctor -> agentops-doctor (scheduled Doctor + eval health check)
62
+ _TEMPLATES_BY_PLATFORM: Dict[str, Dict[str, Tuple[str, str]]] = {
63
+ "github": {
64
+ "pr": ("workflows/agentops-pr.yml", ".github/workflows/agentops-pr.yml"),
65
+ "dev": ("workflows/agentops-deploy-dev.yml", ".github/workflows/agentops-deploy-dev.yml"),
66
+ "qa": ("workflows/agentops-deploy-qa.yml", ".github/workflows/agentops-deploy-qa.yml"),
67
+ "prod": ("workflows/agentops-deploy-prod.yml", ".github/workflows/agentops-deploy-prod.yml"),
68
+ "doctor": ("workflows/agentops-watchdog.yml", ".github/workflows/agentops-doctor.yml"),
69
+ },
70
+ "azure-devops": {
71
+ "pr": (
72
+ "pipelines/azuredevops/agentops-pr.yml",
73
+ ".azuredevops/pipelines/agentops-pr.yml",
74
+ ),
75
+ "dev": (
76
+ "pipelines/azuredevops/agentops-deploy-dev.yml",
77
+ ".azuredevops/pipelines/agentops-deploy-dev.yml",
78
+ ),
79
+ "qa": (
80
+ "pipelines/azuredevops/agentops-deploy-qa.yml",
81
+ ".azuredevops/pipelines/agentops-deploy-qa.yml",
82
+ ),
83
+ "prod": (
84
+ "pipelines/azuredevops/agentops-deploy-prod.yml",
85
+ ".azuredevops/pipelines/agentops-deploy-prod.yml",
86
+ ),
87
+ "doctor": (
88
+ "pipelines/azuredevops/agentops-watchdog.yml",
89
+ ".azuredevops/pipelines/agentops-doctor.yml",
90
+ ),
91
+ },
92
+ }
93
+
94
+ _AZD_TEMPLATES_BY_PLATFORM: Dict[str, Dict[str, Tuple[str, str]]] = {
95
+ "github": {
96
+ "dev": ("workflows/agentops-deploy-dev-azd.yml", ".github/workflows/agentops-deploy-dev.yml"),
97
+ "qa": ("workflows/agentops-deploy-qa-azd.yml", ".github/workflows/agentops-deploy-qa.yml"),
98
+ "prod": ("workflows/agentops-deploy-prod-azd.yml", ".github/workflows/agentops-deploy-prod.yml"),
99
+ },
100
+ "azure-devops": {
101
+ "dev": (
102
+ "pipelines/azuredevops/agentops-deploy-dev-azd.yml",
103
+ ".azuredevops/pipelines/agentops-deploy-dev.yml",
104
+ ),
105
+ "qa": (
106
+ "pipelines/azuredevops/agentops-deploy-qa-azd.yml",
107
+ ".azuredevops/pipelines/agentops-deploy-qa.yml",
108
+ ),
109
+ "prod": (
110
+ "pipelines/azuredevops/agentops-deploy-prod-azd.yml",
111
+ ".azuredevops/pipelines/agentops-deploy-prod.yml",
112
+ ),
113
+ },
114
+ }
115
+
116
+ ALL_KINDS: tuple[str, ...] = ("pr", "dev", "qa", "prod", "doctor")
117
+ DEFAULT_KINDS: tuple[str, ...] = ("pr", "dev", "qa", "prod")
118
+ LEGACY_KIND_ALIASES: Mapping[str, str] = {"watchdog": "doctor"}
119
+
120
+
121
+ @dataclass
122
+ class CicdResult:
123
+ """Result of generating CI/CD workflow files."""
124
+
125
+ platform: str = "github"
126
+ deploy_mode: str = "placeholder"
127
+ eval_runner: str = AGENTOPS_LOCAL_RUNNER
128
+ doctor_gate: str = "critical"
129
+ kinds: List[str] = field(default_factory=list)
130
+ created_files: List[Path] = field(default_factory=list)
131
+ overwritten_files: List[Path] = field(default_factory=list)
132
+ skipped_files: List[Path] = field(default_factory=list)
133
+
134
+
135
+ def _write_template(
136
+ templates_root,
137
+ template_path: str,
138
+ output_path: Path,
139
+ force: bool,
140
+ result: CicdResult,
141
+ substitutions: Mapping[str, str] | None = None,
142
+ ) -> None:
143
+ template_resource = templates_root.joinpath(template_path)
144
+ template_content = template_resource.read_text(encoding="utf-8")
145
+ for key, value in (substitutions or {}).items():
146
+ template_content = template_content.replace(key, value)
147
+
148
+ existed_before = output_path.exists()
149
+
150
+ if existed_before and not force:
151
+ result.skipped_files.append(output_path)
152
+ return
153
+
154
+ output_path.parent.mkdir(parents=True, exist_ok=True)
155
+ output_path.write_text(template_content, encoding="utf-8")
156
+
157
+ if existed_before:
158
+ result.overwritten_files.append(output_path)
159
+ else:
160
+ result.created_files.append(output_path)
161
+
162
+
163
+ def _branch_block_github(*branches: str) -> str:
164
+ return "".join(f" - {branch}\n" for branch in branches).rstrip()
165
+
166
+
167
+ def _branch_block_ado(*branches: str) -> str:
168
+ return "".join(f" - {branch}\n" for branch in branches).rstrip()
169
+
170
+
171
+ _PROMPT_AGENT_VALUES: Dict[str, Dict[str, str]] = {
172
+ "pr": {
173
+ "__ENV_LABEL__": "PR",
174
+ "__ENV_KEY__": "pr",
175
+ # PR candidates are staged in the dev Foundry project so the
176
+ # gate evaluates the same target the deploy workflow will use.
177
+ # Sandbox is the author's playground only.
178
+ "__ENV_NAME__": "dev",
179
+ "__BRANCHES__": "",
180
+ "__EVAL_JOB_NAME__": "AgentOps eval (PR gate)",
181
+ },
182
+ "dev": {
183
+ "__ENV_LABEL__": "DEV",
184
+ "__ENV_KEY__": "dev",
185
+ "__ENV_NAME__": "dev",
186
+ "__BRANCHES__": _branch_block_github("develop"),
187
+ "__EVAL_JOB_NAME__": "Eval candidate (gate)",
188
+ },
189
+ "qa": {
190
+ "__ENV_LABEL__": "QA",
191
+ "__ENV_KEY__": "qa",
192
+ "__ENV_NAME__": "qa",
193
+ "__BRANCHES__": _branch_block_github('"release/**"'),
194
+ "__EVAL_JOB_NAME__": "Eval candidate (gate)",
195
+ },
196
+ "prod": {
197
+ "__ENV_LABEL__": "PROD",
198
+ "__ENV_KEY__": "prod",
199
+ "__ENV_NAME__": "production",
200
+ "__BRANCHES__": _branch_block_github("main"),
201
+ "__EVAL_JOB_NAME__": "Safety eval candidate (gate)",
202
+ },
203
+ }
204
+
205
+ _PROMPT_AGENT_VALUES_ADO: Dict[str, Dict[str, str]] = {
206
+ "pr": {
207
+ "__ENV_LABEL__": "PR",
208
+ "__ENV_KEY__": "pr",
209
+ # PR candidates are staged in the dev Foundry project so the
210
+ # gate evaluates the same target the deploy pipeline will use.
211
+ "__ENV_NAME__": "dev",
212
+ "__BRANCHES__": "",
213
+ },
214
+ "dev": {
215
+ "__ENV_LABEL__": "dev",
216
+ "__ENV_KEY__": "dev",
217
+ "__ENV_NAME__": "dev",
218
+ "__BRANCHES__": _branch_block_ado("develop"),
219
+ },
220
+ "qa": {
221
+ "__ENV_LABEL__": "qa",
222
+ "__ENV_KEY__": "qa",
223
+ "__ENV_NAME__": "qa",
224
+ "__BRANCHES__": _branch_block_ado("release/*"),
225
+ },
226
+ "prod": {
227
+ "__ENV_LABEL__": "production",
228
+ "__ENV_KEY__": "prod",
229
+ "__ENV_NAME__": "production",
230
+ "__BRANCHES__": _branch_block_ado("main"),
231
+ },
232
+ }
233
+
234
+ _PROMPT_AGENT_TEMPLATES_BY_PLATFORM: Dict[str, Dict[str, Tuple[str, str]]] = {
235
+ "github": {
236
+ "pr": (
237
+ "workflows/agentops-pr-prompt-agent.yml",
238
+ ".github/workflows/agentops-pr.yml",
239
+ ),
240
+ "dev": ("workflows/agentops-deploy-prompt-agent.yml", ".github/workflows/agentops-deploy-dev.yml"),
241
+ "qa": ("workflows/agentops-deploy-prompt-agent.yml", ".github/workflows/agentops-deploy-qa.yml"),
242
+ "prod": ("workflows/agentops-deploy-prompt-agent.yml", ".github/workflows/agentops-deploy-prod.yml"),
243
+ },
244
+ "azure-devops": {
245
+ "pr": (
246
+ "pipelines/azuredevops/agentops-pr-prompt-agent.yml",
247
+ ".azuredevops/pipelines/agentops-pr.yml",
248
+ ),
249
+ "dev": (
250
+ "pipelines/azuredevops/agentops-deploy-prompt-agent.yml",
251
+ ".azuredevops/pipelines/agentops-deploy-dev.yml",
252
+ ),
253
+ "qa": (
254
+ "pipelines/azuredevops/agentops-deploy-prompt-agent.yml",
255
+ ".azuredevops/pipelines/agentops-deploy-qa.yml",
256
+ ),
257
+ "prod": (
258
+ "pipelines/azuredevops/agentops-deploy-prompt-agent.yml",
259
+ ".azuredevops/pipelines/agentops-deploy-prod.yml",
260
+ ),
261
+ },
262
+ }
263
+
264
+
265
+ def _eval_substitutions(
266
+ platform: str,
267
+ eval_runner: str,
268
+ config_path: str,
269
+ *,
270
+ ado_indent: int = 10,
271
+ ) -> Mapping[str, str]:
272
+ if platform == "azure-devops":
273
+ return _ado_eval_substitutions(eval_runner, config_path, base_indent=ado_indent)
274
+ return _github_eval_substitutions(eval_runner, config_path)
275
+
276
+
277
+ def _github_eval_substitutions(eval_runner: str, config_path: str) -> Mapping[str, str]:
278
+ if eval_runner == AGENTOPS_CLOUD_RUNNER:
279
+ return {
280
+ "__EVAL_STEPS__": f""" - name: Prepare AgentOps cloud eval config
281
+ env:
282
+ AGENTOPS_SOURCE_CONFIG: "{config_path}"
283
+ run: |
284
+ python - <<'PY'
285
+ import os
286
+ from pathlib import Path
287
+ from agentops.utils.yaml import load_yaml, save_yaml
288
+
289
+ source = Path(os.environ["AGENTOPS_SOURCE_CONFIG"])
290
+ target = source.with_name("{_CLOUD_EVAL_CONFIG_NAME}")
291
+ data = load_yaml(source)
292
+ data["execution"] = "cloud"
293
+ data["publish"] = True
294
+ save_yaml(target, data)
295
+ with Path(os.environ["GITHUB_ENV"]).open("a", encoding="utf-8") as env_file:
296
+ env_file.write(f"AGENTOPS_CI_CONFIG={{target}}\\n")
297
+ print(f"Prepared AgentOps cloud eval config: {{target}}")
298
+ PY
299
+
300
+ - name: Run AgentOps Foundry cloud eval
301
+ id: eval
302
+ env:
303
+ AZURE_AI_FOUNDRY_PROJECT_ENDPOINT: ${{{{ vars.AZURE_AI_FOUNDRY_PROJECT_ENDPOINT }}}}
304
+ AZURE_OPENAI_ENDPOINT: ${{{{ vars.AZURE_OPENAI_ENDPOINT }}}}
305
+ AZURE_OPENAI_DEPLOYMENT: ${{{{ vars.AZURE_OPENAI_DEPLOYMENT }}}}
306
+ APPLICATIONINSIGHTS_CONNECTION_STRING: ${{{{ secrets.APPLICATIONINSIGHTS_CONNECTION_STRING || vars.APPLICATIONINSIGHTS_CONNECTION_STRING }}}}
307
+ run: |
308
+ set +e
309
+ agentops eval run --config "$AGENTOPS_CI_CONFIG" --output "{_CI_EVAL_OUTPUT}"
310
+ ec=$?
311
+ echo "exit_code=$ec" >> "$GITHUB_OUTPUT"
312
+ if [ $ec -eq 0 ]; then
313
+ echo "result=pass" >> "$GITHUB_OUTPUT"
314
+ elif [ $ec -eq 2 ]; then
315
+ echo "result=threshold_failed" >> "$GITHUB_OUTPUT"
316
+ else
317
+ echo "result=error" >> "$GITHUB_OUTPUT"
318
+ fi
319
+ exit $ec""",
320
+ "__EVAL_ARTIFACT_PATHS__": f"""{_CI_EVAL_OUTPUT}/results.json
321
+ {_CI_EVAL_OUTPUT}/report.md
322
+ {_CI_EVAL_OUTPUT}/cloud_evaluation.json""",
323
+ }
324
+ if eval_runner == OFFICIAL_EVAL_RUNNER:
325
+ official_action = official_eval_action_ref()
326
+ return {
327
+ "__EVAL_STEPS__": f""" - name: Prepare official AI Agent Evaluation input
328
+ id: official_eval_input
329
+ env:
330
+ AZURE_OPENAI_DEPLOYMENT: ${{{{ vars.AZURE_OPENAI_DEPLOYMENT }}}}
331
+ {OFFICIAL_EVAL_ACTION_ENV}: {official_action}
332
+ run: |
333
+ python -m agentops.pipeline.official_eval prepare \\
334
+ --config \"{config_path}\" \\
335
+ --out \".agentops/official-eval/input.json\" \\
336
+ --github-output \"$GITHUB_OUTPUT\"
337
+
338
+ # Official runner for Foundry prompt agents. AgentOps keeps the prepared
339
+ # input/metadata as release evidence until the action exposes a stable
340
+ # machine-readable threshold artifact.
341
+ - name: Run official AI Agent Evaluation
342
+ id: official_eval
343
+ uses: {official_action}
344
+ with:
345
+ azure-ai-project-endpoint: ${{{{ vars.AZURE_AI_FOUNDRY_PROJECT_ENDPOINT }}}}
346
+ deployment-name: ${{{{ steps.official_eval_input.outputs.deployment_name }}}}
347
+ agent-ids: ${{{{ steps.official_eval_input.outputs.agent_ids }}}}
348
+ data-path: ${{{{ steps.official_eval_input.outputs.data_path }}}}
349
+
350
+ - name: Record official eval result
351
+ if: always()
352
+ id: eval
353
+ run: |
354
+ mkdir -p .agentops/official-eval
355
+ outcome=\"${{{{ steps.official_eval.outcome }}}}\"
356
+ conclusion=\"${{{{ steps.official_eval.conclusion }}}}\"
357
+ if [ \"$outcome\" = \"success\" ]; then
358
+ echo \"exit_code=0\" >> \"$GITHUB_OUTPUT\"
359
+ echo \"result=official-ai-agent-evaluation\" >> \"$GITHUB_OUTPUT\"
360
+ else
361
+ echo \"exit_code=1\" >> \"$GITHUB_OUTPUT\"
362
+ echo \"result=official-ai-agent-evaluation-failed\" >> \"$GITHUB_OUTPUT\"
363
+ fi
364
+ python - <<'PY'
365
+ import json
366
+ from datetime import datetime, timezone
367
+ from pathlib import Path
368
+
369
+ output = Path('.agentops/official-eval/result.json')
370
+ outcome = \"${{{{ steps.official_eval.outcome }}}}\"
371
+ conclusion = \"${{{{ steps.official_eval.conclusion }}}}\"
372
+ status = 'success' if outcome == 'success' else 'failed' if outcome else 'unknown'
373
+ payload = dict(
374
+ runner='{OFFICIAL_EVAL_RUNNER}',
375
+ system='github-actions',
376
+ action='{official_action}',
377
+ status=status,
378
+ outcome=outcome,
379
+ conclusion=conclusion,
380
+ agent_ids=\"${{{{ steps.official_eval_input.outputs.agent_ids }}}}\",
381
+ deployment_name=\"${{{{ steps.official_eval_input.outputs.deployment_name }}}}\",
382
+ data_path=\"${{{{ steps.official_eval_input.outputs.data_path }}}}\",
383
+ metadata_path=\"${{{{ steps.official_eval_input.outputs.metadata_path }}}}\",
384
+ machine_readable_thresholds=False,
385
+ recorded_at=datetime.now(timezone.utc).isoformat(),
386
+ )
387
+ output.write_text(json.dumps(payload, indent=2) + '\\n', encoding='utf-8')
388
+ PY""",
389
+ "__EVAL_ARTIFACT_PATHS__": """.agentops/official-eval/input.json
390
+ .agentops/official-eval/metadata.json
391
+ .agentops/official-eval/result.json""",
392
+ }
393
+ return {
394
+ "__EVAL_STEPS__": f""" - name: Run AgentOps eval
395
+ id: eval
396
+ env:
397
+ AZURE_AI_FOUNDRY_PROJECT_ENDPOINT: ${{{{ vars.AZURE_AI_FOUNDRY_PROJECT_ENDPOINT }}}}
398
+ AZURE_OPENAI_ENDPOINT: ${{{{ vars.AZURE_OPENAI_ENDPOINT }}}}
399
+ AZURE_OPENAI_DEPLOYMENT: ${{{{ vars.AZURE_OPENAI_DEPLOYMENT }}}}
400
+ APPLICATIONINSIGHTS_CONNECTION_STRING: ${{{{ secrets.APPLICATIONINSIGHTS_CONNECTION_STRING || vars.APPLICATIONINSIGHTS_CONNECTION_STRING }}}}
401
+ run: |
402
+ set +e
403
+ agentops eval run --config \"{config_path}\"
404
+ ec=$?
405
+ echo \"exit_code=$ec\" >> \"$GITHUB_OUTPUT\"
406
+ if [ $ec -eq 0 ]; then
407
+ echo \"result=pass\" >> \"$GITHUB_OUTPUT\"
408
+ elif [ $ec -eq 2 ]; then
409
+ echo \"result=threshold_failed\" >> \"$GITHUB_OUTPUT\"
410
+ else
411
+ echo \"result=error\" >> \"$GITHUB_OUTPUT\"
412
+ fi
413
+ exit $ec""",
414
+ "__EVAL_ARTIFACT_PATHS__": """.agentops/results/latest/results.json
415
+ .agentops/results/latest/report.md
416
+ .agentops/results/latest/cloud_evaluation.json""",
417
+ }
418
+
419
+
420
+ def _ado_eval_substitutions(
421
+ eval_runner: str,
422
+ config_path: str,
423
+ *,
424
+ base_indent: int,
425
+ ) -> Mapping[str, str]:
426
+ if eval_runner == AGENTOPS_CLOUD_RUNNER:
427
+ return {
428
+ "__EVAL_TASKS__": _indent_block(
429
+ f"""- bash: |
430
+ python - <<'PY'
431
+ import os
432
+ from pathlib import Path
433
+ from agentops.utils.yaml import load_yaml, save_yaml
434
+
435
+ source = Path(os.environ["AGENTOPS_SOURCE_CONFIG"])
436
+ target = source.with_name("{_CLOUD_EVAL_CONFIG_NAME}")
437
+ data = load_yaml(source)
438
+ data["execution"] = "cloud"
439
+ data["publish"] = True
440
+ save_yaml(target, data)
441
+ print(f"##vso[task.setvariable variable=AGENTOPS_CI_CONFIG]{{target}}")
442
+ print(f"Prepared AgentOps cloud eval config: {{target}}")
443
+ PY
444
+ displayName: Prepare AgentOps cloud eval config
445
+ env:
446
+ AGENTOPS_SOURCE_CONFIG: "{config_path}"
447
+
448
+ - task: AzureCLI@2
449
+ displayName: Run AgentOps Foundry cloud eval
450
+ inputs:
451
+ azureSubscription: $(AZURE_SERVICE_CONNECTION)
452
+ scriptType: bash
453
+ scriptLocation: inlineScript
454
+ inlineScript: |
455
+ set +e
456
+ agentops eval run --config "$(AGENTOPS_CI_CONFIG)" --output "{_CI_EVAL_OUTPUT}"
457
+ code=$?
458
+ echo "##vso[task.setvariable variable=AGENTOPS_EVAL_EXIT_CODE]$code"
459
+ exit $code
460
+ env:
461
+ AZURE_AI_FOUNDRY_PROJECT_ENDPOINT: $(AZURE_AI_FOUNDRY_PROJECT_ENDPOINT)
462
+ AZURE_OPENAI_ENDPOINT: $(AZURE_OPENAI_ENDPOINT)
463
+ AZURE_OPENAI_DEPLOYMENT: $(AZURE_OPENAI_DEPLOYMENT)
464
+ APPLICATIONINSIGHTS_CONNECTION_STRING: $(APPLICATIONINSIGHTS_CONNECTION_STRING)""",
465
+ base_indent,
466
+ ),
467
+ "__EVAL_ARTIFACT_TARGET__": _CI_EVAL_OUTPUT,
468
+ }
469
+ if eval_runner == OFFICIAL_EVAL_RUNNER:
470
+ official_task = official_eval_ado_task_ref()
471
+ return {
472
+ "__EVAL_TASKS__": _indent_block(
473
+ f"""- task: AzureCLI@2
474
+ name: official_eval_input
475
+ displayName: Prepare official AI Agent Evaluation input
476
+ inputs:
477
+ azureSubscription: $(AZURE_SERVICE_CONNECTION)
478
+ scriptType: bash
479
+ scriptLocation: inlineScript
480
+ inlineScript: |
481
+ python -m agentops.pipeline.official_eval prepare \\
482
+ --config \"{config_path}\" \\
483
+ --out \".agentops/official-eval/input.json\" \\
484
+ --ado-output
485
+ env:
486
+ AZURE_OPENAI_DEPLOYMENT: $(AZURE_OPENAI_DEPLOYMENT)
487
+ {OFFICIAL_EVAL_ADO_TASK_ENV}: {official_task}
488
+
489
+ - task: {official_task}
490
+ displayName: Run official AI Agent Evaluation
491
+ inputs:
492
+ azure-ai-project-endpoint: $(AZURE_AI_FOUNDRY_PROJECT_ENDPOINT)
493
+ deployment-name: $(official_eval_input.officialDeploymentName)
494
+ data-path: $(official_eval_input.officialDataPath)
495
+ agent-ids: $(official_eval_input.officialAgentIds)
496
+
497
+ - bash: |
498
+ mkdir -p .agentops/official-eval
499
+ python - <<'PY'
500
+ import json
501
+ from datetime import datetime, timezone
502
+ from pathlib import Path
503
+
504
+ job_status = "$(Agent.JobStatus)"
505
+ normalized = job_status.strip().lower().replace("_", "").replace("-", "")
506
+ status = "success" if normalized == "succeeded" else "failed" if normalized in ("failed", "canceled", "cancelled") else "unknown"
507
+ payload = dict(
508
+ runner="{OFFICIAL_EVAL_RUNNER}",
509
+ system="azure-devops",
510
+ task="{official_task}",
511
+ status=status,
512
+ job_status=job_status,
513
+ agent_ids="$(official_eval_input.officialAgentIds)",
514
+ deployment_name="$(official_eval_input.officialDeploymentName)",
515
+ data_path="$(official_eval_input.officialDataPath)",
516
+ metadata_path="$(official_eval_input.officialMetadataPath)",
517
+ machine_readable_thresholds=False,
518
+ recorded_at=datetime.now(timezone.utc).isoformat(),
519
+ )
520
+ Path(".agentops/official-eval/result.json").write_text(
521
+ json.dumps(payload, indent=2) + "\\n",
522
+ encoding="utf-8",
523
+ )
524
+ PY
525
+ displayName: Record official eval result
526
+ condition: always()""",
527
+ base_indent,
528
+ ),
529
+ "__EVAL_ARTIFACT_TARGET__": ".agentops/official-eval",
530
+ }
531
+ return {
532
+ "__EVAL_TASKS__": _indent_block(
533
+ f"""- task: AzureCLI@2
534
+ displayName: Run AgentOps eval
535
+ inputs:
536
+ azureSubscription: $(AZURE_SERVICE_CONNECTION)
537
+ scriptType: bash
538
+ scriptLocation: inlineScript
539
+ inlineScript: |
540
+ set +e
541
+ agentops eval run --config \"{config_path}\"
542
+ code=$?
543
+ echo \"##vso[task.setvariable variable=AGENTOPS_EVAL_EXIT_CODE]$code\"
544
+ exit $code
545
+ env:
546
+ AZURE_AI_FOUNDRY_PROJECT_ENDPOINT: $(AZURE_AI_FOUNDRY_PROJECT_ENDPOINT)
547
+ AZURE_OPENAI_ENDPOINT: $(AZURE_OPENAI_ENDPOINT)
548
+ AZURE_OPENAI_DEPLOYMENT: $(AZURE_OPENAI_DEPLOYMENT)
549
+ APPLICATIONINSIGHTS_CONNECTION_STRING: $(APPLICATIONINSIGHTS_CONNECTION_STRING)""",
550
+ base_indent,
551
+ ),
552
+ "__EVAL_ARTIFACT_TARGET__": ".agentops/results/latest",
553
+ }
554
+
555
+
556
+ def _indent_block(block: str, spaces: int) -> str:
557
+ prefix = " " * spaces
558
+ return "\n".join(prefix + line if line else "" for line in block.splitlines())
559
+
560
+
561
+ def normalize_workflow_kind(kind: str) -> str:
562
+ """Return the canonical workflow kind, accepting legacy aliases."""
563
+ return LEGACY_KIND_ALIASES.get(kind, kind)
564
+
565
+
566
+ def generate_cicd_workflows(
567
+ directory: Path,
568
+ force: bool = False,
569
+ kinds: Sequence[str] | None = None,
570
+ platform: str = "github",
571
+ deploy_mode: str = "auto",
572
+ doctor_gate: str = DEFAULT_DOCTOR_GATE,
573
+ ) -> CicdResult:
574
+ """Generate AgentOps GitFlow CI/CD workflows.
575
+
576
+ By default writes the release-path templates (``pr``, ``dev``, ``qa``,
577
+ ``prod``) for the requested *platform*. Pass *kinds* to opt into a subset,
578
+ including the optional scheduled ``doctor`` workflow.
579
+
580
+ Args:
581
+ directory: Root directory of the consumer repository.
582
+ force: When True, overwrite existing workflow files.
583
+ kinds: Optional explicit list of workflow kinds. ``None`` means
584
+ "generate the default release-path templates". Unknown kinds are ignored.
585
+ platform: ``"github"`` (default) writes ``.github/workflows/*.yml``
586
+ using GitHub Actions; ``"azure-devops"`` writes
587
+ ``.azuredevops/pipelines/*.yml`` using Azure DevOps Pipelines.
588
+ The conceptual workflows (PR gate + three deploy stages) are
589
+ identical across platforms.
590
+ deploy_mode: ``"placeholder"`` writes the stack-agnostic deploy
591
+ scaffold, ``"azd"`` writes Azure Developer CLI provision/deploy
592
+ workflows, ``"prompt-agent"`` writes Foundry prompt-agent
593
+ candidate/eval/deploy workflows, and ``"auto"`` selects
594
+ ``"azd"`` when ``azure.yaml`` exists or ``"prompt-agent"`` when
595
+ ``agentops.yaml`` targets a Foundry prompt agent.
596
+ doctor_gate: Severity floor for the PR-gate Doctor step. One of
597
+ ``"critical"`` (default), ``"warning"``, or ``"none"``.
598
+ ``critical`` blocks the PR on critical Doctor findings such as
599
+ regression drops; ``warning`` blocks on warning or higher;
600
+ ``none`` keeps Doctor advisory (pre-1.x behavior). Only the PR
601
+ template uses this value; deploy templates keep the
602
+ hardcoded ``critical`` gate.
603
+
604
+ Returns:
605
+ CicdResult with platform and paths of created, overwritten, or
606
+ skipped files.
607
+ """
608
+ if platform not in _TEMPLATES_BY_PLATFORM:
609
+ raise ValueError(
610
+ f"unknown platform {platform!r}; valid: {', '.join(PLATFORMS)}"
611
+ )
612
+ if deploy_mode not in DEPLOY_MODES:
613
+ raise ValueError(
614
+ f"unknown deploy mode {deploy_mode!r}; valid: {', '.join(DEPLOY_MODES)}"
615
+ )
616
+ if doctor_gate not in DOCTOR_GATES:
617
+ raise ValueError(
618
+ f"unknown doctor gate {doctor_gate!r}; valid: {', '.join(DOCTOR_GATES)}"
619
+ )
620
+
621
+ if kinds is None:
622
+ kinds = DEFAULT_KINDS
623
+
624
+ directory = directory.resolve()
625
+ effective_deploy_mode = deploy_mode
626
+ if effective_deploy_mode == "auto":
627
+ effective_deploy_mode = recommended_deploy_mode(directory)
628
+ effective_eval_runner = recommended_eval_runner(directory)
629
+
630
+ result = CicdResult(
631
+ platform=platform,
632
+ deploy_mode=effective_deploy_mode,
633
+ eval_runner=effective_eval_runner,
634
+ doctor_gate=doctor_gate,
635
+ )
636
+ templates_root = files(_TEMPLATE_PACKAGE)
637
+ template_map = _TEMPLATES_BY_PLATFORM[platform]
638
+ azd_template_map = _AZD_TEMPLATES_BY_PLATFORM.get(platform, {})
639
+ prompt_agent_template_map = _PROMPT_AGENT_TEMPLATES_BY_PLATFORM.get(platform, {})
640
+ azd_substitutions = _azd_substitutions(platform, has_ailz_preflight(directory))
641
+
642
+ seen: set[str] = set()
643
+ for requested_kind in kinds:
644
+ kind = normalize_workflow_kind(requested_kind)
645
+ if kind in seen or kind not in template_map:
646
+ continue
647
+ seen.add(kind)
648
+ result.kinds.append(kind)
649
+ substitutions: dict[str, str] = {"__DOCTOR_GATE__": doctor_gate}
650
+ eval_config = (
651
+ "${{ inputs.config || 'agentops.yaml' }}"
652
+ if platform == "github" and kind == "pr"
653
+ else "$(AGENTOPS_CONFIG)"
654
+ if platform == "azure-devops"
655
+ else "agentops.yaml"
656
+ )
657
+ if effective_deploy_mode == "azd" and kind in azd_template_map:
658
+ template_path, output_rel = azd_template_map[kind]
659
+ substitutions.update(azd_substitutions)
660
+ elif effective_deploy_mode == "prompt-agent" and kind in prompt_agent_template_map:
661
+ template_path, output_rel = prompt_agent_template_map[kind]
662
+ eval_config = ".agentops/deployments/agentops.candidate.yaml"
663
+ prompt_values = (
664
+ _PROMPT_AGENT_VALUES if platform == "github" else _PROMPT_AGENT_VALUES_ADO
665
+ )[kind]
666
+ substitutions.update(prompt_values)
667
+ else:
668
+ template_path, output_rel = template_map[kind]
669
+ ado_indent = (
670
+ 10
671
+ if kind == "pr"
672
+ or (effective_deploy_mode == "azd" and kind in azd_template_map)
673
+ else 16
674
+ )
675
+ substitutions.update(
676
+ _eval_substitutions(
677
+ platform,
678
+ effective_eval_runner,
679
+ eval_config,
680
+ ado_indent=ado_indent,
681
+ )
682
+ )
683
+ output_path = (directory / output_rel).resolve()
684
+ _write_template(
685
+ templates_root,
686
+ template_path,
687
+ output_path,
688
+ force,
689
+ result,
690
+ substitutions=substitutions,
691
+ )
692
+
693
+ return result
694
+
695
+
696
+ def _azd_substitutions(platform: str, ailz_preflight: bool) -> Mapping[str, str]:
697
+ if not ailz_preflight:
698
+ return {"__AILZ_PREFLIGHT_COMMAND__": ""}
699
+ if platform == "azure-devops":
700
+ return {
701
+ "__AILZ_PREFLIGHT_COMMAND__": (
702
+ " echo \"Running AI Landing Zone preflight.\"\n"
703
+ " pwsh ./scripts/Invoke-PreflightChecks.ps1 -Strict"
704
+ )
705
+ }
706
+ return {
707
+ "__AILZ_PREFLIGHT_COMMAND__": (
708
+ " echo \"Running AI Landing Zone preflight.\"\n"
709
+ " pwsh ./scripts/Invoke-PreflightChecks.ps1 -Strict"
710
+ )
711
+ }
712
+
713
+
714
+ def generate_cicd_workflow(
715
+ directory: Path,
716
+ force: bool = False,
717
+ platform: str = "github",
718
+ ) -> CicdResult:
719
+ """Generate only the PR workflow template (legacy convenience)."""
720
+ return generate_cicd_workflows(directory, force=force, kinds=["pr"], platform=platform)