continuous-intelligence-layer 0.1.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 (38) hide show
  1. continuous_intelligence_layer/__init__.py +28 -0
  2. continuous_intelligence_layer/_core/__init__.py +6 -0
  3. continuous_intelligence_layer/_core/exporter.py +517 -0
  4. continuous_intelligence_layer/_core/graph_exporter.py +92 -0
  5. continuous_intelligence_layer/_core/utils.py +170 -0
  6. continuous_intelligence_layer/anthropic/__init__.py +9 -0
  7. continuous_intelligence_layer/anthropic/init.py +232 -0
  8. continuous_intelligence_layer/anthropic/instrumentation.py +91 -0
  9. continuous_intelligence_layer/crewai/__init__.py +9 -0
  10. continuous_intelligence_layer/crewai/init.py +228 -0
  11. continuous_intelligence_layer/crewai/instrumentation.py +83 -0
  12. continuous_intelligence_layer/langgraph/__init__.py +12 -0
  13. continuous_intelligence_layer/langgraph/init.py +253 -0
  14. continuous_intelligence_layer/langgraph/instrumentation.py +71 -0
  15. continuous_intelligence_layer/openai/__init__.py +9 -0
  16. continuous_intelligence_layer/openai/init.py +229 -0
  17. continuous_intelligence_layer/openai/instrumentation.py +67 -0
  18. continuous_intelligence_layer-0.1.0.dist-info/METADATA +633 -0
  19. continuous_intelligence_layer-0.1.0.dist-info/RECORD +38 -0
  20. continuous_intelligence_layer-0.1.0.dist-info/WHEEL +4 -0
  21. continuous_intelligence_layer-0.1.0.dist-info/licenses/LICENSE +21 -0
  22. evaluators/__init__.py +32 -0
  23. evaluators/base_evaluator.py +181 -0
  24. evaluators/crewai_input_evaluator.py +249 -0
  25. evaluators/input_evaluator.py +121 -0
  26. evaluators/models.py +180 -0
  27. evaluators/output_evaluator.py +247 -0
  28. evaluators/runner.py +313 -0
  29. evaluators/tool_agent_evaluator.py +305 -0
  30. graph_builder/__init__.py +6 -0
  31. graph_builder/builder.py +212 -0
  32. graph_builder/models.py +159 -0
  33. graph_builder/mongo_store.py +588 -0
  34. llm_router/__init__.py +3 -0
  35. llm_router/router.py +71 -0
  36. rca_engine/__init__.py +5 -0
  37. rca_engine/incident_report.py +162 -0
  38. rca_engine/rca_engine.py +202 -0
@@ -0,0 +1,162 @@
1
+ """
2
+ incident_report.py
3
+ ------------------
4
+ Generates a human-readable Markdown incident report from an RCAResult.
5
+
6
+ Includes:
7
+ - Execution metadata
8
+ - Overall status
9
+ - Primary root cause
10
+ - Failure propagation chain (ASCII diagram)
11
+ - Evidence table
12
+ - Contributing factors
13
+ - Recommendations
14
+ - Confidence score
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import os
20
+ from datetime import datetime, timezone
21
+ from pathlib import Path
22
+
23
+ from graph_builder.models import ExecutionGraph
24
+ from .rca_engine import RCAResult
25
+
26
+
27
+ class IncidentReportGenerator:
28
+ """Generates Markdown incident reports from RCA results."""
29
+
30
+ def __init__(self, output_dir: str = "./reports"):
31
+ self._output_dir = Path(output_dir)
32
+ self._output_dir.mkdir(parents=True, exist_ok=True)
33
+
34
+ def generate(self, rca: RCAResult, graph: ExecutionGraph) -> str:
35
+ """Generate a Markdown incident report and save it to disk."""
36
+ report = self._build_report(rca, graph)
37
+ filepath = self._save(rca.execution_id, report)
38
+ return filepath
39
+
40
+ def _build_report(self, rca: RCAResult, graph: ExecutionGraph) -> str:
41
+ summary = graph.summary()
42
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
43
+ status_badge = "🔴 FAILED" if rca.overall_status == "FAIL" else (
44
+ "🟡 PARTIAL" if rca.overall_status == "PARTIAL" else "🟢 PASSED")
45
+
46
+ lines = [
47
+ f"# Incident Report — Execution `{rca.execution_id}`",
48
+ "",
49
+ f"**Generated:** {now}",
50
+ "",
51
+ "---",
52
+ "",
53
+ "## Overview",
54
+ "",
55
+ f"| Field | Value |",
56
+ f"|---|---|",
57
+ f"| Execution ID | `{rca.execution_id}` |",
58
+ f"| Status | {status_badge} |",
59
+ f"| Total Nodes | {summary['total_nodes']} |",
60
+ f"| Failed Nodes | {summary['failed_nodes']} |",
61
+ f"| Total Latency | {summary['total_latency_ms']} ms |",
62
+ f"| Total Tokens | {summary['total_tokens']} |",
63
+ f"| RCA Confidence | {rca.confidence * 100:.1f}% |",
64
+ "",
65
+ "---",
66
+ "",
67
+ "## 🔍 Primary Root Cause",
68
+ "",
69
+ f"> **{rca.root_cause}**",
70
+ "",
71
+ "---",
72
+ "",
73
+ ]
74
+
75
+ # Propagation chain
76
+ if rca.propagation_chain:
77
+ lines += [
78
+ "## 🔗 Failure Propagation Chain",
79
+ "",
80
+ "```",
81
+ self._ascii_chain(rca.propagation_chain),
82
+ "```",
83
+ "",
84
+ "---",
85
+ "",
86
+ ]
87
+
88
+ # Evidence table
89
+ lines += [
90
+ "## 🧾 Evidence",
91
+ "",
92
+ "| Node | Validator | Status | Reason |",
93
+ "|---|---|:---:|---|",
94
+ ]
95
+ for ev in rca.evidence:
96
+ status_str = "❌ FAIL" if ev.status == "FAIL" else "✅ PASS"
97
+ reason = ev.reason.replace("|", "\\|")[:120]
98
+ lines.append(f"| `{ev.node_name}` | {ev.validator} | {status_str} | {reason} |")
99
+
100
+ if not rca.evidence:
101
+ lines.append("| — | — | — | No failures detected |")
102
+
103
+ lines += ["", "---", ""]
104
+
105
+ # Contributing factors
106
+ if rca.contributing_factors:
107
+ lines += [
108
+ "## ⚠️ Contributing Factors",
109
+ "",
110
+ ]
111
+ for factor in rca.contributing_factors:
112
+ lines.append(f"- {factor}")
113
+ lines += ["", "---", ""]
114
+
115
+ # Recommendations
116
+ lines += [
117
+ "## ✅ Recommendations",
118
+ "",
119
+ ]
120
+ for i, rec in enumerate(rca.recommendations, 1):
121
+ lines.append(f"{i}. {rec}")
122
+ if not rca.recommendations:
123
+ lines.append("No specific recommendations generated.")
124
+
125
+ lines += [
126
+ "",
127
+ "---",
128
+ "",
129
+ "## 📊 Node Summary",
130
+ "",
131
+ "| Node | Type | Status | Latency (ms) | Tokens |",
132
+ "|---|---|:---:|---:|---:|",
133
+ ]
134
+ for node in graph.nodes:
135
+ n_status = "❌ ERROR" if node.error else "✅ OK"
136
+ tokens = node.tokens.total or 0
137
+ lines.append(
138
+ f"| `{node.name[:35]}` | {node.node_type} | {n_status} "
139
+ f"| {node.latency_ms:.1f} | {tokens} |"
140
+ )
141
+
142
+ lines += [
143
+ "",
144
+ "---",
145
+ "",
146
+ f"*Report generated by AgentOPS Intelligent Layer — confidence: {rca.confidence*100:.1f}%*",
147
+ ]
148
+
149
+ return "\n".join(lines)
150
+
151
+ @staticmethod
152
+ def _ascii_chain(chain: list[str]) -> str:
153
+ if not chain:
154
+ return "(empty)"
155
+ return "\n↓\n".join(chain)
156
+
157
+ def _save(self, execution_id: str, content: str) -> str:
158
+ ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S")
159
+ name = f"incident_{execution_id}_{ts}.md"
160
+ path = self._output_dir / name
161
+ path.write_text(content, encoding="utf-8")
162
+ return str(path)
@@ -0,0 +1,202 @@
1
+ """
2
+ rca_engine.py
3
+ -------------
4
+ RCA Engine — Phase 4 Intelligence Layer
5
+
6
+ Input:
7
+ - ExecutionGraph (with validation_results attached to every node)
8
+
9
+ Responsibilities:
10
+ 1. Identify the primary root cause
11
+ 2. Identify contributing factors
12
+ 3. Explain failure propagation (which node failure caused downstream failures)
13
+ 4. Suggest fixes
14
+ 5. Produce confidence scores
15
+
16
+ Output: RCAResult (structured JSON + evidence list)
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import re
23
+ from typing import Any
24
+
25
+ from pydantic import BaseModel
26
+
27
+ from graph_builder.models import ExecutionGraph, ExecutionNode
28
+ from llm_router import LLMRouter
29
+
30
+
31
+ def _parse_json(text: str) -> dict:
32
+ text = re.sub(r"```(?:json)?", "", text).replace("```", "").strip()
33
+ match = re.search(r"\{.*\}", text, re.DOTALL)
34
+ if match:
35
+ return json.loads(match.group())
36
+ raise ValueError(f"No JSON in LLM response: {text[:400]}")
37
+
38
+
39
+ # ── Result model ───────────────────────────────────────────────────────────
40
+
41
+ class EvidenceItem(BaseModel):
42
+ node_id: str
43
+ node_name: str
44
+ validator: str
45
+ status: str
46
+ reason: str
47
+
48
+
49
+ class RCAResult(BaseModel):
50
+ execution_id: str
51
+ project_id: str | None = None
52
+ overall_status: str # "PASS" | "FAIL" | "PARTIAL"
53
+ root_cause: str
54
+ confidence: float
55
+ propagation_chain: list[str] # node names in order of failure spread
56
+ contributing_factors: list[str]
57
+ evidence: list[EvidenceItem]
58
+ recommendations: list[str]
59
+ raw_llm_analysis: str = ""
60
+
61
+
62
+ # ── Engine ─────────────────────────────────────────────────────────────────
63
+
64
+ _PROMPT_TEMPLATE = """
65
+ You are an expert AI systems reliability engineer performing Root Cause Analysis (RCA)
66
+ on a failed multi-agent AI system execution.
67
+
68
+ ## Execution Summary
69
+ Execution ID: {execution_id}
70
+ Total Nodes: {total_nodes}
71
+ Failed Nodes: {failed_nodes}
72
+ Total Latency: {total_latency_ms} ms
73
+ Total Tokens: {total_tokens}
74
+
75
+ ## Execution Graph (node hierarchy)
76
+ {graph_structure}
77
+
78
+ ## Validation Failures (Evidence)
79
+ {validation_failures}
80
+
81
+ ## All Validation Results
82
+ {all_validations}
83
+
84
+ ## Your Task
85
+ Perform a thorough Root Cause Analysis.
86
+
87
+ 1. Identify the PRIMARY ROOT CAUSE — what single issue was most responsible for the failure?
88
+ 2. Identify CONTRIBUTING FACTORS — secondary issues that amplified the failure.
89
+ 3. Explain FAILURE PROPAGATION — how did the root cause cascade through the execution graph?
90
+ 4. Provide specific, actionable RECOMMENDATIONS to fix the issues.
91
+ 5. Assign a CONFIDENCE SCORE (0.0-1.0) to your analysis.
92
+
93
+ Consider:
94
+ - Which node failed FIRST chronologically?
95
+ - Did that failure cause downstream nodes to fail?
96
+ - Was the issue in the input, the decision, the execution, or the goal?
97
+ - What could have prevented this?
98
+
99
+ ## Response Format
100
+ Respond ONLY with a valid JSON object. No explanation outside the JSON. No markdown.
101
+
102
+ {{
103
+ "overall_status": "PASS" or "FAIL" or "PARTIAL",
104
+ "root_cause": "<concise primary root cause in 1-2 sentences>",
105
+ "confidence": <float 0.0-1.0>,
106
+ "propagation_chain": ["<node_name_1>", "<node_name_2>", ...],
107
+ "contributing_factors": ["<factor 1>", "<factor 2>", ...],
108
+ "recommendations": ["<specific fix 1>", "<specific fix 2>", ...]
109
+ }}
110
+ """
111
+
112
+
113
+ class RCAEngine:
114
+ """Analyzes an ExecutionGraph's validation results and produces an RCA."""
115
+
116
+ def __init__(self, router: LLMRouter):
117
+ self._router = router
118
+
119
+ def analyze(self, graph: ExecutionGraph) -> RCAResult:
120
+ summary = graph.summary()
121
+
122
+ # ── Collect evidence ───────────────────────────────────────────────
123
+ all_evidence: list[EvidenceItem] = []
124
+ failures: list[EvidenceItem] = []
125
+ graph_structure_lines = []
126
+
127
+ for node in graph.nodes:
128
+ indent = " " if node.parent_id else ""
129
+ graph_structure_lines.append(
130
+ f"{indent}• [{node.node_type}] {node.name} (id={node.node_id[:8]})"
131
+ )
132
+
133
+ for vr in node.validation_results:
134
+ if not isinstance(vr, dict):
135
+ continue
136
+ item = EvidenceItem(
137
+ node_id = node.node_id,
138
+ node_name = node.name,
139
+ validator = vr.get("evaluator", "Unknown"),
140
+ status = vr.get("status", "UNKNOWN"),
141
+ reason = vr.get("reason", "")[:300],
142
+ )
143
+ all_evidence.append(item)
144
+ if vr.get("status") == "FAIL":
145
+ failures.append(item)
146
+
147
+ # ── Format for LLM ─────────────────────────────────────────────────
148
+ graph_structure = "\n".join(graph_structure_lines)
149
+
150
+ validation_failures_str = (
151
+ "\n".join(
152
+ f"- [{f.validator}] Node '{f.node_name}': {f.reason}"
153
+ for f in failures
154
+ )
155
+ if failures else "No failures detected."
156
+ )
157
+
158
+ all_validations_str = json.dumps(
159
+ {node.node_id: node.validation_results for node in graph.nodes},
160
+ indent=2, default=str
161
+ )[:8000] # cap to avoid huge prompts
162
+
163
+ prompt = _PROMPT_TEMPLATE.format(
164
+ execution_id = graph.execution_id,
165
+ total_nodes = summary["total_nodes"],
166
+ failed_nodes = summary["failed_nodes"],
167
+ total_latency_ms = summary["total_latency_ms"],
168
+ total_tokens = summary["total_tokens"],
169
+ graph_structure = graph_structure,
170
+ validation_failures = validation_failures_str,
171
+ all_validations = all_validations_str,
172
+ )
173
+
174
+ # ── Call LLM ──────────────────────────────────────────────────────
175
+ raw_text = self._router.call(prompt)
176
+ try:
177
+ parsed = _parse_json(raw_text)
178
+ except Exception as exc:
179
+ parsed = {
180
+ "overall_status": "FAIL" if failures else "PASS",
181
+ "root_cause": f"RCA LLM parsing failed: {exc}",
182
+ "confidence": 0.3,
183
+ "propagation_chain": [],
184
+ "contributing_factors": [],
185
+ "recommendations": ["Review validation failures manually."],
186
+ }
187
+
188
+ # ── Determine overall status if not provided ───────────────────────
189
+ overall = parsed.get("overall_status", "FAIL" if failures else "PASS")
190
+
191
+ return RCAResult(
192
+ execution_id = graph.execution_id,
193
+ project_id = graph.project_id,
194
+ overall_status = overall,
195
+ root_cause = parsed.get("root_cause", "Unknown"),
196
+ confidence = float(parsed.get("confidence", 0.5)),
197
+ propagation_chain = parsed.get("propagation_chain", []),
198
+ contributing_factors = parsed.get("contributing_factors", []),
199
+ evidence = failures,
200
+ recommendations = parsed.get("recommendations", []),
201
+ raw_llm_analysis = raw_text,
202
+ )