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,633 @@
1
+ Metadata-Version: 2.4
2
+ Name: continuous-intelligence-layer
3
+ Version: 0.1.0
4
+ Summary: Multi-Agent Intelligent Validation & RCA Framework
5
+ Project-URL: Homepage, https://github.com/Jugal-lachhwani/Multi_agent_intelligence_layer
6
+ Project-URL: Repository, https://github.com/Jugal-lachhwani/Multi_agent_intelligence_layer
7
+ Author-email: dmlabs <dmlabs6@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,langgraph,llm,observability,opentelemetry,rca,tracing
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: System :: Monitoring
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: anthropic>=0.34.0
20
+ Requires-Dist: bcrypt>=4.0.0
21
+ Requires-Dist: colorama>=0.4.6
22
+ Requires-Dist: fastapi>=0.110.0
23
+ Requires-Dist: google-genai>=1.0.0
24
+ Requires-Dist: langchain-community>=0.2.0
25
+ Requires-Dist: langchain-openai>=0.1.0
26
+ Requires-Dist: langchain>=0.2.0
27
+ Requires-Dist: langgraph>=0.2.0
28
+ Requires-Dist: networkx>=3.3
29
+ Requires-Dist: openai>=1.30.0
30
+ Requires-Dist: openinference-instrumentation-langchain>=0.1.0
31
+ Requires-Dist: openinference-semantic-conventions>=0.1.0
32
+ Requires-Dist: opentelemetry-api>=1.25.0
33
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.25.0
34
+ Requires-Dist: opentelemetry-sdk>=1.25.0
35
+ Requires-Dist: passlib[bcrypt]>=1.7.4
36
+ Requires-Dist: pydantic>=2.7.0
37
+ Requires-Dist: pyjwt>=2.8.0
38
+ Requires-Dist: pymongo>=4.8.0
39
+ Requires-Dist: python-dotenv>=1.0.0
40
+ Requires-Dist: python-multipart>=0.0.9
41
+ Requires-Dist: requests>=2.31.0
42
+ Requires-Dist: rich>=13.7.0
43
+ Requires-Dist: tenacity>=8.3.0
44
+ Requires-Dist: uvicorn>=0.29.0
45
+ Provides-Extra: all
46
+ Requires-Dist: openinference-instrumentation-anthropic>=1.0.0; extra == 'all'
47
+ Requires-Dist: openinference-instrumentation-crewai>=1.0.0; extra == 'all'
48
+ Requires-Dist: openinference-instrumentation-llama-index>=3.0.0; extra == 'all'
49
+ Requires-Dist: openinference-instrumentation-openai>=0.1.19; extra == 'all'
50
+ Provides-Extra: anthropic
51
+ Requires-Dist: openinference-instrumentation-anthropic>=1.0.0; extra == 'anthropic'
52
+ Provides-Extra: crewai
53
+ Requires-Dist: openinference-instrumentation-crewai>=1.0.0; extra == 'crewai'
54
+ Provides-Extra: dev
55
+ Requires-Dist: build>=1.2.0; extra == 'dev'
56
+ Requires-Dist: ipykernel; extra == 'dev'
57
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
58
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
59
+ Requires-Dist: twine>=5.0.0; extra == 'dev'
60
+ Provides-Extra: llamaindex
61
+ Requires-Dist: openinference-instrumentation-llama-index>=3.0.0; extra == 'llamaindex'
62
+ Provides-Extra: openai
63
+ Requires-Dist: openinference-instrumentation-openai>=0.1.19; extra == 'openai'
64
+ Description-Content-Type: text/markdown
65
+
66
+ # Multi-Agent Intelligence Layer
67
+
68
+ **A zero-infrastructure, OpenTelemetry-based observability and validation layer for LangGraph / Agentic AI systems.**
69
+
70
+ This project automatically instruments any LangChain/LangGraph agent, captures full execution traces (nodes, LLMs, tools, tokens, latency), builds an interactive **Execution Graph** in MongoDB, and runs a comprehensive **Validation and Root Cause Analysis (RCA) Engine** over the results.
71
+
72
+ Trace data never touches MongoDB directly from the agent's machine — the SDK
73
+ POSTs the built graph to the API over HTTP (`POST /traces/ingest`), using the
74
+ same `api_key` already used for project sync. Only the API process holds
75
+ `MONGODB_URI`; agent machines need nothing but an `api_key` and network
76
+ access to the API.
77
+
78
+ ---
79
+
80
+ ## 🏗️ Complete Architecture
81
+
82
+ ```text
83
+ Your LangGraph Agent
84
+
85
+
86
+ Continuous Intelligence Layer SDK (Zero-config OpenTelemetry + OpenInference)
87
+
88
+ ├──▶ JSONL Exporter (Local trace backup)
89
+
90
+ └──▶ GraphSpanExporter (Buffers spans & builds graph)
91
+
92
+
93
+ POST /traces/ingest (x-api-key auth)
94
+
95
+
96
+ FastAPI Backend
97
+
98
+
99
+ MongoDB (Atlas or self-hosted)
100
+ (executions / nodes / evaluations / rca_reports)
101
+
102
+
103
+ Evaluation Runner Pipeline
104
+ ┌──────────────────────┼──────────────────────┐
105
+ ▼ ▼ ▼
106
+ Input Evaluator Output Evaluator Tool/Agent Evaluator
107
+ │ │ │
108
+ └──────────────────────┼──────────────────────┘
109
+
110
+ Validation Results
111
+ (Saved to JSONL & MongoDB)
112
+
113
+
114
+ Root Cause Analysis (RCA) Engine
115
+ (Analyzes failures & saves to MongoDB)
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 🚀 Quick Start
121
+
122
+ ### 1. Prerequisites
123
+ - Python 3.11+
124
+ - A MongoDB instance — the easiest path is a free [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) cluster (no local install/Docker needed); a local `mongod` also works.
125
+ `MONGODB_URI` is only needed by the **API server** process — it can live
126
+ fully private (Atlas network access rules / same VPC as the API) since
127
+ agent machines running `continuous_intelligence_layer.<framework>.init()` never connect to it
128
+ directly; they only need an `api_key` and HTTP access to the API.
129
+
130
+ ### 2. Setup
131
+
132
+ **Just want to instrument an agent against a hosted/existing AgentOPS API?**
133
+ Install the published SDK directly — no repo clone needed:
134
+
135
+ ```bash
136
+ pip install continuous-intelligence-layer
137
+
138
+ # Or with a specific framework's instrumentor extra:
139
+ pip install "continuous-intelligence-layer[crewai]"
140
+ pip install "continuous-intelligence-layer[openai]"
141
+ pip install "continuous-intelligence-layer[anthropic]"
142
+ ```
143
+
144
+ **Running the full stack (API + UI + MongoDB) yourself?** Clone the repo:
145
+
146
+ ```bash
147
+ # Clone the repository
148
+ git clone https://github.com/Jugal-lachhwani/Multi_agent_intelligence_layer.git
149
+ cd Multi_agent_intelligence_layer
150
+
151
+ # Create a virtual environment and install
152
+ python -m venv .venv
153
+ source .venv/bin/activate
154
+ pip install -e .
155
+
156
+ # Setup environment variables (requires OpenAI API Key for evaluation & RCA,
157
+ # and a MongoDB connection string)
158
+ cp .env.example .env
159
+ # Edit .env and set:
160
+ # OPENAI_API_KEY="sk-..."
161
+ # MONGODB_URI="mongodb+srv://<user>:<password>@<cluster>.mongodb.net/?retryWrites=true&w=majority"
162
+ ```
163
+
164
+ ### 3. MongoDB
165
+
166
+ No separate service to start — the app connects directly to whatever `MONGODB_URI` in `.env` points at (Atlas cluster or local `mongod`). Collections and indexes are created automatically the first time the API starts or `seed_db.py` runs.
167
+
168
+ ### 4. Start the AgentOPS UI and API
169
+
170
+ The AgentOPS layer includes a full observability dashboard to view traces, token usage, and evaluation results.
171
+
172
+ 1. **Start the FastAPI Backend:**
173
+ ```bash
174
+ # Seed a default demo user + project into MongoDB (safe to re-run — idempotent)
175
+ python seed_db.py
176
+
177
+ # Start the API server (runs on http://localhost:8080)
178
+ uvicorn api.main:app --reload --port 8080
179
+ ```
180
+
181
+ 2. **Start the React Frontend:**
182
+ ```bash
183
+ # In a new terminal window
184
+ cd ui
185
+ npm install
186
+ npm run dev
187
+ ```
188
+ Open **[http://localhost:5173/projects/proj_default_123](http://localhost:5173/projects/proj_default_123)** in your browser to view the dashboard!
189
+
190
+ ### 5. Run the Pipeline
191
+
192
+ 1. **Run the Demo Agent:**
193
+ This executes the multi-agent LangGraph demo and POSTs the built graph to
194
+ the API's `/traces/ingest` endpoint, which is the only thing that writes
195
+ it to MongoDB.
196
+ ```bash
197
+ AGENTOPS_API_KEY="ao_..." python examples/multi_agent_demo.py "Explain quantum computing simply"
198
+ ```
199
+ `AGENTOPS_API_KEY` should be an API key from a registered user (see `POST /register` then `POST /api_key`, or use the one printed by `seed_db.py`). The demo agent's machine needs this key and the API's `base_url` (`http://localhost:8080` by default) — it does **not** need `MONGODB_URI`.
200
+
201
+ 2. **Run the Evaluator and RCA Engine:**
202
+ This evaluates the execution, saves validation results, and generates a Root Cause Analysis report.
203
+ *(Make sure you grab the `execution_id` from the output of the demo script).*
204
+ ```bash
205
+ python -c "
206
+ from evaluators.runner import EvaluationRunner
207
+ # Replace with your actual execution ID
208
+ runner = EvaluationRunner('YOUR_EXECUTION_ID_HERE')
209
+ runner.run()
210
+ "
211
+ ```
212
+
213
+ ### 6. View the Results in MongoDB (Advanced)
214
+
215
+ The dashboard UI is the intended way to inspect traces, but the raw documents
216
+ are also queryable directly — via [MongoDB Atlas's Data Explorer](https://www.mongodb.com/docs/atlas/atlas-ui/data-explorer/)
217
+ in the browser (if using Atlas), or the `mongosh` shell / any GUI client
218
+ (Compass, Studio 3T) pointed at your `MONGODB_URI`.
219
+
220
+ 1. **View the Execution Graph** (nodes for one execution):
221
+ ```js
222
+ db.nodes.find({ execution_id: "YOUR_EXECUTION_ID" })
223
+ db.executions.findOne({ _id: "YOUR_EXECUTION_ID" }) // summary + edges
224
+ ```
225
+ 2. **View Evaluation Verdicts:**
226
+ ```js
227
+ db.evaluations.find(
228
+ { execution_id: "YOUR_EXECUTION_ID", confidence: { $gt: 0.3 } },
229
+ { node_name: 1, evaluator: 1, status: 1, severity: 1, reason: 1 }
230
+ ).sort({ timestamp: 1 })
231
+ ```
232
+ 3. **View the Root Cause Analysis (RCA) Report:**
233
+ ```js
234
+ db.rca_reports.findOne(
235
+ { _id: "YOUR_EXECUTION_ID" },
236
+ { overall_status: 1, root_cause: 1, recommendations: 1 }
237
+ )
238
+ ```
239
+
240
+ ---
241
+
242
+ ## 📊 Example Evaluation & RCA Output
243
+
244
+ Below is an example of the Evaluation Engine successfully detecting a "silent failure" where a search tool returned irrelevant information, causing downstream agents to struggle.
245
+
246
+ ### Node Executions & Metrics
247
+ *(Extracted from the MongoDB `nodes` collection)*
248
+
249
+ | Output | Status | Latency_ms |
250
+ |--------|--------|------------|
251
+ | `[Supervisor] I’ll first use the Research Agent...` | OK | 2387.188 |
252
+ | `[web_search] Results for 'Explain quantum computing simply': Recent AI agent research highlights...` | OK | 0.340 |
253
+ | `[document_retriever] Retrieved 1 document... Document: 'General Overview'` | OK | 0.344 |
254
+ | `[Research Agent] - The web results are not relevant to explaining quantum computing...` | OK | 2699.448 |
255
+ | `[Writer Agent] # Quantum Computing, Simply Explained...` (Truncated mid-sentence) | OK | 6346.446 |
256
+
257
+ ### Evaluation Verdicts
258
+
259
+ | Node Name | Evaluator | Status | Reason |
260
+ |-----------|-----------|--------|--------|
261
+ | `supervisor` | `OutputEvaluator` | **PASS** | "The output is well-formed, preserves the user query, and provides an appropriate orchestration plan..." |
262
+ | `web_search` | `OutputEvaluator` | **FAIL** (High) | "The output is completely unrelated to the request to explain quantum computing simply and instead discusses AI-agent architectures." |
263
+ | `document_retriever` | `ToolAgentEvaluator` | **FAIL** (High) | "The returned document contains no substantive quantum-computing information. The result is effectively empty." |
264
+ | `research_agent` | `OutputEvaluator` | **WARNING** (Med) | "Honestly reports that retrieved sources are inadequate... However, it does not provide substantive research for a downstream writer." |
265
+ | `writer_agent` | `InputEvaluator` | **WARNING** (Med) | "The supplied research is largely irrelevant... which could confuse the writer." |
266
+ | `LangGraph` | `OutputEvaluator` | **FAIL** (Med) | "The response is relevant and generally accurate, but the writer's explanation is visibly truncated mid-sentence..." |
267
+
268
+ ### Root Cause Analysis (RCA) Report
269
+
270
+ ```json
271
+ {
272
+ "overall_status": "PARTIAL",
273
+ "root_cause": "The primary root cause was a research-tool grounding failure: the web_search node returned semantically unrelated AI-agent content for a quantum-computing query, and the system accepted that output instead of detecting and recovering from the relevance mismatch.",
274
+ "confidence": 0.91,
275
+ "propagation_chain": [
276
+ "LangGraph",
277
+ "supervisor",
278
+ "research_agent",
279
+ "web_search",
280
+ "document_retriever",
281
+ "research_agent",
282
+ "writer_agent",
283
+ "LangGraph"
284
+ ],
285
+ "contributing_factors": [
286
+ "The document_retriever node also returned non-substantive content (only a generic document label), leaving the research agent without usable grounded material.",
287
+ "No tool-output relevance, topic-consistency, or minimum-content validation gate stopped unrelated or empty retrieval results before they reached downstream agents.",
288
+ "The research agent appears to have continued despite both retrieval sources being unusable, rather than retrying with a reformulated query, selecting an alternate source, or explicitly reporting insufficient evidence.",
289
+ "The writer agent likely relied on general model knowledge because the provided research context was weak or invalid, reducing traceability and reliability even though the visible content was broadly relevant.",
290
+ "The final LangGraph output was truncated mid-sentence, indicating missing output-length budgeting, incomplete-response detection, or response serialization/stream-finalization safeguards.",
291
+ "The initially empty top-level query field was noted as mildly ambiguous; although the supervisor correctly used the message content, this creates avoidable risk for downstream components that rely on the query state field."
292
+ ],
293
+ "recommendations": [
294
+ "Add a mandatory semantic relevance check after web_search and document_retriever. Require returned content to match the requested topic using query-document similarity thresholds, keyword/entity overlap, and an LLM relevance classifier.",
295
+ "Reject and retry tool results when they are unrelated, empty, generic, below a minimum substantive-content threshold, or lack extractable facts. For this request, a result focused on AI agents should trigger an automatic retry with a quantum-computing-specific query.",
296
+ "Implement a retrieval fallback policy: reformulate the query, use a second search provider or corpus, request multiple results, and stop only after obtaining at least one validated source containing relevant explanatory material.",
297
+ "Require the research agent to emit structured evidence with source snippets, topic labels, and a research-quality status. Prevent handoff to the writer when evidence quality is below threshold unless the writer is explicitly instructed to answer from general knowledge.",
298
+ "Add a supervisor-level quality gate that checks whether research findings answer the original user question before routing to the writer agent.",
299
+ "Fix final-response truncation by enforcing token/output budgets, reserving completion tokens for conclusions, validating that the final response ends cleanly, and retrying or continuing generation when termination occurs mid-sentence.",
300
+ "Populate and normalize the top-level query field from the user message at graph entry so all nodes consume a single canonical request representation.",
301
+ "Track tool relevance failures as explicit failed or degraded node states rather than reporting zero failed nodes when validation shows unusable tool outputs."
302
+ ]
303
+ }
304
+ ```
305
+
306
+ ---
307
+
308
+ ## 🛠️ Usage in Your Own Code
309
+
310
+ The SDK is designed to be **plug-and-play**. You don't need to change any of your LangGraph nodes or edges. Just add two lines to the very top of your main script:
311
+
312
+ ```python
313
+ from continuous_intelligence_layer.langgraph import init # or .crewai / .openai
314
+
315
+ # Initialize before importing LangGraph/LangChain
316
+ init(
317
+ session_id="my-conversation-123", # Groups multiple runs together
318
+ store="mongodb" # Saves the execution graph to MongoDB
319
+ # run_evaluations=True is the default — evaluations + RCA run automatically
320
+ # right after the trace is ingested. Pass run_evaluations=False if you'd
321
+ # rather trigger them yourself later (via the API or the UI's "Run
322
+ # Evaluations" button).
323
+ )
324
+
325
+ # ... run your normal LangGraph code here ...
326
+ ```
327
+
328
+ ---
329
+
330
+ ## 📂 Project Structure
331
+
332
+ - **`continuous_intelligence_layer/`**: The core SDK. One subpackage per agent framework (`langgraph/`, `crewai/`, `openai/`), each with its own `init()` that monkey-patches that framework to automatically emit OTel spans. Framework-agnostic pieces (JSONL exporter, `GraphSpanExporter`, which POSTs the built graph to the API's `/traces/ingest` endpoint with `x-api-key` auth instead of writing to MongoDB directly) live in the shared internal `_core/` module.
333
+ - **`ui/`**: React-based frontend dashboard for viewing execution traces, token costs, and evaluation results.
334
+ - **`api/`**: FastAPI backend that serves traces and evaluations from MongoDB to the frontend UI, and is the **only** process that writes trace data to MongoDB — via `POST /traces/ingest`, called by the SDK. Also owns `users`/`projects` auth data (also in MongoDB).
335
+ - **`evaluators/`**: The Evaluation Pipeline containing `InputEvaluator`, `OutputEvaluator`, and `ToolAgentEvaluator`.
336
+ - **`rca_engine/`**: Synthesizes the evaluation results across the graph to generate the final Root Cause Analysis and fixes.
337
+ - **`graph_builder/`**: Converts raw OpenTelemetry spans into structured `ExecutionNode` and `ExecutionEdge` Pydantic models. `MongoStore` (idempotent upserts, one document per node) is used only from within the API process, not from the SDK.
338
+ - **`examples/`**: Contains `multi_agent_demo.py`, a realistic implementation of a hierarchical agent system used for testing the SDK.
339
+
340
+ ---
341
+
342
+ Models_used =
343
+ - examples/multi_agent_demo.py (demo agent) → gpt-5.6-luna (reads VALIDATOR_MODEL, not AGENT_MODEL as .env.example implies)
344
+ - examples/support_triage_multi_agent.py (demo agent) → not set in .env, so it falls back to its own default, gpt-4o-mini (reads SUPPORT_AGENT_MODEL)
345
+ - evaluators/ (Input/Output/ToolAgent, via base_evaluator.py) → gpt-5.6-luna (reads VALIDATOR_MODEL)
346
+ - rca_engine/ → gpt-5.6-terra (reads RCA_MODEL)"
347
+
348
+ ## 🔎 In-Depth Feature Reference (Built System)
349
+
350
+ This section documents what is **actually implemented** in each module of the
351
+ built system, verified by a full code read-through (audit date: 2026-08-03).
352
+ It excludes `examples/` — those are demo agents used to exercise the SDK, not
353
+ part of the product itself. For the full bug/fix history behind these
354
+ findings, see [`ISSUES.md`](./ISSUES.md).
355
+
356
+ ### `continuous_intelligence_layer/` — SDK
357
+
358
+ - Each framework subpackage exposes its own **`init(api_key, service_name, session_id=None, execution_id=None, store="mongodb", write_jsonl=False, base_url="http://localhost:8080", verbose=True, run_evaluations=True)`**
359
+ — `continuous_intelligence_layer.langgraph.init()` (covers LangGraph and
360
+ plain LangChain, since both share the same OpenInference instrumentor),
361
+ `continuous_intelligence_layer.crewai.init()`, and
362
+ `continuous_intelligence_layer.openai.init()`. The framework is selected by
363
+ which subpackage you import, not by a `frameworks=` argument. Each is
364
+ idempotent within a process (a second call is a no-op) and provides
365
+ `reset()` for test suites that need to re-init.
366
+ - Verifies `api_key` against the backend at startup and auto-registers the
367
+ project via `POST /projects/sync` — `service_name` becomes the project name.
368
+ The returned `project_id`, together with `api_key` and `base_url`, is
369
+ threaded into `GraphSpanExporter` so it can authenticate the later
370
+ `/traces/ingest` call — the SDK never needs `MONGODB_URI`.
371
+ - **`run_evaluations` (default `True`)** is threaded into `GraphSpanExporter`
372
+ and sent as a `run_evaluations` query param on its `/traces/ingest` POST.
373
+ The SDK itself never runs evaluations or waits on them — it just tells the
374
+ API whether to kick them off. Pass `run_evaluations=False` to keep the
375
+ pre-existing manual-only flow (evaluations only run when triggered via the
376
+ API or the UI's "Run Evaluations" button).
377
+ - Resource attributes attached to every span: `service.name`, `project.id`,
378
+ `execution.id`, `session.id`, `sdk.name`, `sdk.version`.
379
+ - Each subpackage's `instrumentation.py::instrument()` activates that
380
+ framework's single OpenInference instrumentor (`LangChainInstrumentor`,
381
+ `CrewAIInstrumentor`, or `OpenAIInstrumentor`, guarded against
382
+ double-patching); `uninstrument()` restores original callbacks. A missing
383
+ `openinference-instrumentation-*` package degrades gracefully with a fix-it
384
+ message instead of crashing.
385
+ - **Span → record extraction** (`ExecutionSpanExporter._extract`) captures:
386
+ trace/span/parent IDs, latency (ms), node-type classification (from
387
+ OpenInference span kind, with a keyword-based fallback on span name),
388
+ status/error (from OTel span events), token usage (prompt/completion/total),
389
+ prompt/response (both legacy and current OpenInference attribute keys),
390
+ tool name/description/retrieved docs, and any remaining attributes as
391
+ free-form `metadata`.
392
+ - **Two export paths**: `ExecutionSpanExporter` writes durable JSONL
393
+ (`SimpleSpanProcessor`, synchronous per-span); `GraphSpanExporter` buffers
394
+ spans **in memory** and, at `TracerProvider` shutdown, builds the graph and
395
+ `POST`s it as JSON to `{base_url}/traces/ingest` with an `x-api-key` header
396
+ (see Known Gaps below) — it never imports or connects to `MongoStore`.
397
+ - `_core/utils.py` (re-exported from every framework subpackage) provides
398
+ `load_traces()`, `print_trace_summary()` (terminal pretty-printer with
399
+ per-span status icons and an aggregate footer), and `spans_to_dataframe()`
400
+ (pandas export).
401
+
402
+ ### `graph_builder/` — DAG construction & persistence
403
+
404
+ - **Data model** (`models.py`): `ExecutionNode` (~20 fields — identity,
405
+ `node_type` enum, LLM/tool-specific fields, `TokenUsage`, timestamps,
406
+ `metadata`, `status`, `validation_results`), `ExecutionEdge`
407
+ (`source_id`/`target_id`/`edge_type`), `ExecutionGraph` (nodes + edges, with
408
+ helpers: `get_node`, `get_children`, `get_root_nodes`, `get_nodes_by_type`,
409
+ `get_failed_nodes`, `summary()`).
410
+ - **Builder algorithm** (`builder.py::TraceToGraphBuilder.build`): sorts spans
411
+ by timestamp, maps each span to an `ExecutionNode` keyed by `span_id`,
412
+ creates a `CALLS` edge from `parent_span_id → node_id` when the parent span
413
+ is present in the same batch, then groups nodes by `parent_id` and chains
414
+ each sibling group into a timestamp-ordered `NEXT` edge list. Only `CALLS`
415
+ and `NEXT` are ever produced — `RETURNS`/`USES`/`READS`/`WRITES` exist as
416
+ enum values but are never emitted.
417
+ - **MongoStore** (`mongo_store.py`): only ever instantiated inside the API
418
+ process now — `save_graph()` is called from `POST /traces/ingest`, not
419
+ from the SDK. Idempotent `update_one(..., upsert=True)`
420
+ writes, one document per node in a `nodes` collection (keyed by `node_id`)
421
+ — deliberately **not** one document per execution embedding a `nodes`
422
+ array, since that risks hitting MongoDB's 16MB per-document limit on large
423
+ executions or long LLM outputs. A lightweight `executions` header document
424
+ (status, latency, node count, timestamps, embedded `edges` array, root
425
+ input/output snippets) is written alongside every save, so the trace-list
426
+ view never has to fetch full node bodies. Indexes on `execution_id`,
427
+ `project_id`, `session_id`, and compound indexes for the KPI/trace-list
428
+ query patterns are created once at startup. Read helpers: `get_graph`,
429
+ `get_session_graph`, `get_llm_spans`, `get_tool_spans`, `get_failed_nodes`,
430
+ `get_all_execution_ids`, `get_all_session_ids`, plus `get_trace_summaries`/
431
+ `get_trace_detail` (purpose-built to avoid the old per-row full-graph
432
+ fetch). Also exposes the write path used by evaluators/RCA:
433
+ `write_evaluation_result` (one document per evaluation in an `evaluations`
434
+ collection, referencing `node_id`/`project_id` as plain fields rather than
435
+ a graph edge), `update_node_evaluation` (flattened eval props on the node
436
+ document for fast filtering, plus denormalizing `has_evaluation`/`status`
437
+ onto the execution header), `write_rca_result` (one document per
438
+ `execution_id` in `rca_reports`), plus `clear_execution` / `clear_session`
439
+ maintenance methods (wrapped in a MongoDB multi-document transaction so the
440
+ node/evaluation/RCA/header deletes for one execution are all-or-nothing).
441
+
442
+ ### `evaluators/` — Evaluation Engine
443
+
444
+ Three evaluators, orchestrated by `EvaluationRunner`, all sharing one
445
+ `BaseEvaluator` (OpenAI `gpt-4o-mini` by default via `VALIDATOR_MODEL`,
446
+ JSON-mode responses, no retry logic — any failure degrades to a generic
447
+ `FAIL`/low-confidence result rather than crashing the run):
448
+
449
+ - **`InputEvaluator`** — runs on every node. Checks completeness, context
450
+ relevance, well-formedness, prompt-injection detection, and ambiguity.
451
+ Detected injection **always** forces `status=FAIL`, `severity=CRITICAL`,
452
+ overriding whatever the LLM itself returned.
453
+ - **`OutputEvaluator`** — runs on every node. Auto-detects structured
454
+ (dict/JSON) vs. unstructured (text) output. Structured checks: relevance,
455
+ structural correctness, internal consistency, handoff validity, schema
456
+ violations. Unstructured checks: relevance, completeness, hallucination
457
+ (with specific unsupported-claim evidence), toxicity. Detected toxicity
458
+ always forces `status=FAIL`, `severity=CRITICAL`.
459
+ - **`ToolAgentEvaluator`** — runs only on Tool/Retriever nodes, and on
460
+ Agent/Router nodes that have at least one tool-type child. Tool-node mode
461
+ grades input/output quality given the calling agent's context; agent-node
462
+ mode grades whether tool selection was appropriate (input/output quality of
463
+ the tool call itself is intentionally left to the tool-node evaluation).
464
+ - **`EvaluationRunner.run()`**: loads the graph from MongoDB, evaluates every
465
+ node with all 3 evaluators sequentially (no parallelism), builds a
466
+ `NodeEvaluationSummary` per node (overall status + worst severity rollup),
467
+ persists each result to MongoDB immediately, appends everything to a JSONL
468
+ log, then automatically invokes the RCA Engine and persists its report.
469
+ **Not idempotent**: because `EvaluationResult.evaluation_id` is a fresh
470
+ random UUID each run, re-running the pipeline for the same `execution_id`
471
+ creates duplicate `evaluations` documents (the flattened eval props on the
472
+ node document and the `rca_reports` document *are* overwritten cleanly on
473
+ rerun, since those are keyed by stable ids).
474
+ - **Triggering**: `EvaluationRunner` itself has no idea whether it was invoked
475
+ manually or automatically — both paths call the exact same constructor/`run()`.
476
+ It's invoked from two call sites: `POST .../run-evaluations` (`api/routers/traces.py`,
477
+ synchronous, request/response cycle) and, since `init(run_evaluations=True)`
478
+ became the SDK default, from a `BackgroundTasks` job scheduled inside
479
+ `POST /traces/ingest` right after a trace is saved. The one-shot guard on
480
+ the manual endpoint (`count_evaluations(...) > 0` → 400) is what keeps the
481
+ two triggers from racing into duplicate writes: once auto-evaluation has
482
+ run for a trace, a later manual "Run Evaluations" click just 400s instead
483
+ of re-running.
484
+
485
+ ### `rca_engine/` — Root Cause Analysis
486
+
487
+ - **`RCAEngine.analyze(graph)`** takes an in-memory `ExecutionGraph` whose
488
+ nodes already have `validation_results` attached (done by the runner, not
489
+ a separate DB fetch). One LLM call (`gpt-4o` by default via `RCA_MODEL`)
490
+ produces `overall_status`, `root_cause`, `confidence`, `propagation_chain`,
491
+ `contributing_factors`, and `recommendations`.
492
+ - **`evidence` is deterministic, not LLM-generated** — it's built directly
493
+ from every `FAIL`-status evaluation entry in code, which keeps root-cause
494
+ claims grounded in actual evaluator findings rather than model invention.
495
+ - Any LLM/parsing failure degrades to a fixed fallback `RCAResult`
496
+ (`overall_status` derived from whether any failures exist) instead of
497
+ raising — the evaluation run is never blocked by an RCA failure.
498
+ - Persisted as a standalone document in `rca_reports`, keyed by
499
+ `execution_id` (safe to re-run, always overwrites in place via upsert).
500
+ - **`IncidentReportGenerator`** (`incident_report.py`) is a separate,
501
+ fully-working Markdown report exporter (status badge, overview table, ASCII
502
+ propagation-chain diagram, evidence table, node summary table) — it is
503
+ **not currently wired into the automatic evaluation pipeline** (no caller
504
+ outside `examples/`), so it exists as a ready-to-use feature rather than
505
+ dead code.
506
+
507
+ ### LLM Models Actually Configured
508
+
509
+ All LLM calls in this project go through the OpenAI SDK (`OPENAI_API_KEY`).
510
+ There is no single global "the LLM" — four independent env vars pick the
511
+ model per component:
512
+
513
+ | Component | Env var | Code default (`.env.example`) | Currently set in `.env` |
514
+ |---|---|---|---|
515
+ | `evaluators/base_evaluator.py` (all 3 evaluators) | `VALIDATOR_MODEL` | `gpt-4o-mini` | `gpt-5.6-luna` |
516
+ | `rca_engine/rca_engine.py` | `RCA_MODEL` | `gpt-4o` | `gpt-5.6-terra` |
517
+ | `examples/multi_agent_demo.py` (demo agent's own LLM) | `VALIDATOR_MODEL` *(reuses the evaluator's var — see note)* | `gpt-4o-mini` | `gpt-5.6-luna` |
518
+ | `examples/support_triage_multi_agent.py` (demo agent's own LLM) | `SUPPORT_AGENT_MODEL` | `gpt-4o-mini` | *(not set in `.env` — falls back to default)* |
519
+
520
+ - `.env.example` documents an `AGENT_MODEL` var as controlling "the demo
521
+ agent", but `examples/multi_agent_demo.py` actually reads `VALIDATOR_MODEL`,
522
+ not `AGENT_MODEL` — so that demo agent currently shares a model with the
523
+ evaluators rather than having its own knob, and `AGENT_MODEL` is unused/dead.
524
+ - `gpt-5.6-luna` / `gpt-5.6-terra` are custom/internal aliases currently set
525
+ in the live `.env`, not OpenAI's public model names — swap them for real
526
+ model IDs (e.g. `gpt-4o-mini` / `gpt-4o`) if pointing at the standard
527
+ OpenAI API.
528
+
529
+ ### `api/` — FastAPI Backend
530
+
531
+ - **Auth**: two schemes — JWT bearer tokens (7-day expiry, `HS256`, bcrypt
532
+ password hashing) for the dashboard, and a static per-user API key
533
+ (`ao_<32 hex chars>`) for agent-SDK ingestion. `SECRET_KEY` is required from
534
+ the environment with no insecure fallback.
535
+ - **Endpoints**:
536
+ - Auth: `POST /register`, `POST /token`, `GET /me`, `POST /api_key`.
537
+ - Projects: `GET/POST /projects`, `POST /projects/sync` (idempotent
538
+ find-or-create per `(user, service_name)` — the mechanism behind the
539
+ "service_name = project" design), `DELETE /projects/{id}`.
540
+ - Traces: `GET /projects/{project_id}/traces` (per-project rollup: status,
541
+ latency, evaluation presence), `GET /projects/{project_id}/traces/{execution_id}`
542
+ (full `{graph, evaluations, rca}` payload), `POST /projects/{project_id}/traces/{execution_id}/run-evaluations`
543
+ (synchronous — runs `EvaluationRunner` **inline** in the request/response
544
+ cycle; guarded to only run once per execution, with no reset/re-run path —
545
+ this is the manual trigger, still available regardless of whether
546
+ auto-evaluation already ran).
547
+ - Ingest: `POST /traces/ingest` — the SDK's only write path into MongoDB.
548
+ Accepts a serialized `ExecutionGraph` plus a `run_evaluations` query param
549
+ (default `True`, set by the SDK from `init(run_evaluations=...)`).
550
+ Authenticates via the same `x-api-key` header `/projects/sync` uses,
551
+ verifies the payload's `project_id` belongs to that api key's user (403
552
+ otherwise), re-stamps `project_id` onto every node server-side (never
553
+ trusts the client's per-node values), then calls `MongoStore.save_graph()`.
554
+ This is what lets `MONGODB_URI` stay private to the API process. When
555
+ `run_evaluations` is true, it then schedules `EvaluationRunner(...).run()`
556
+ (which triggers RCA too) as a FastAPI `BackgroundTasks` job **after** the
557
+ response is sent, so the SDK's shutdown-time POST isn't held open for the
558
+ LLM-bound evaluation/RCA pipeline. A background failure is only logged
559
+ server-side (`print`) — there's no request left to report it to — so a
560
+ trace can still be evaluated later via the manual `run-evaluations`
561
+ endpoint if the background run fails.
562
+ - **Multi-tenancy**: every project/trace endpoint scopes by `user_id` and
563
+ `project_id` (both plain MongoDB query filters now — no FK/graph traversal
564
+ involved), closing the cross-tenant leakage class of bug described in
565
+ `ISSUES.md` #1.
566
+ - **Data model**: a single MongoDB database (`agentops` by default) holds
567
+ everything — `users` (username, bcrypt hash, nullable API key, unique
568
+ indexes), `projects` (`_id` = slugified name, `user_id` field, compound
569
+ unique index on `(user_id, name)`), and `executions`/`nodes`/`evaluations`/
570
+ `rca_reports` for all execution/evaluation/RCA data. No SQLite, no second
571
+ database to run.
572
+
573
+ ### `ui/` — React Dashboard
574
+
575
+ - **5 pages**: `Login`, `Register`, `Dashboard` (project grid + API key
576
+ management), `ProjectView` (trace list table), `TraceView` (the primary
577
+ trace-inspection surface).
578
+ - `Dashboard` and `ProjectView` auto-refresh every 3 seconds and surface
579
+ run/failure counts per project.
580
+ - **`TraceView`**: renders the span hierarchy as a hand-rolled recursive tree
581
+ component (not a graph library — `reactflow` is an installed dependency but
582
+ currently unused/unrendered anywhere). Each row shows a colored evaluation-
583
+ status dot (red/amber/green) and, for LLM nodes, the model name and an
584
+ estimated per-call cost (client-side, against a small hardcoded pricing
585
+ table). The right pane has Details / Evaluations / RCA Report tabs; the RCA
586
+ summary banner is trace-level and stays visible regardless of the selected
587
+ node. The "Run Evaluations" button calls the backend synchronously and
588
+ disappears once evaluations exist for the trace (no re-run affordance in
589
+ the UI either) — with `init(run_evaluations=True)` (the SDK default), the
590
+ button will typically already be hidden by the time a trace is opened,
591
+ since evaluation runs automatically in the background right after ingest.
592
+ - Several controls are currently **decorative placeholders** with no backing
593
+ logic: the ProjectView search bar, its "With errors"/"Tags" filter buttons,
594
+ its row checkboxes, and two Sidebar nav items ("Test suites", "Experiments").
595
+
596
+ ### Known Gaps & Next-Phase Backlog
597
+
598
+ Captured here so they can be triaged deliberately rather than rediscovered.
599
+ See `ISSUES.md` for the full audit trail of what's already been fixed.
600
+
601
+ - **Non-idempotent evaluation writes** — re-running evaluations for an
602
+ execution creates duplicate `evaluations` documents; the API also only
603
+ allows running evaluations once per trace, with no delete/reset endpoint.
604
+ - **Auto-triggered evaluation has no retry/observability** — the
605
+ `BackgroundTasks` job `POST /traces/ingest` schedules when
606
+ `run_evaluations=True` (the default) has no retry, no dead-letter queue,
607
+ and no status surfaced anywhere if it fails — a failure is only a `print`
608
+ in the API server's own logs. The trace itself still ingests successfully
609
+ either way; a user who notices missing evaluations can always fall back to
610
+ the manual "Run Evaluations" button/endpoint.
611
+ - **Trace data is only sent at process shutdown** (buffer-then-flush in
612
+ `GraphSpanExporter`, now a single `POST /traces/ingest` instead of a direct
613
+ Mongo write) — long-running/persistent agent processes show nothing in the
614
+ UI until they exit. Unchanged by the Neo4j → MongoDB migration or the
615
+ move to routing writes through the API; would need a real per-span write
616
+ path to fix.
617
+ - **Fan-out/fan-in topology is flattened** into a single linear `NEXT` chain
618
+ (see `builder.py` note in `CLAUDE.md`) — a supervisor's parallel branches
619
+ aren't distinguishable from a genuinely linear pipeline today.
620
+ - **No pagination anywhere** — API list endpoints and every UI list view load
621
+ the entire dataset in one shot. (The trace-list endpoint's old N+1 —
622
+ fetching the full graph per execution just to compute a summary row — was
623
+ fixed as part of the MongoDB migration: a lightweight `executions` header
624
+ document now carries the summary fields directly, so listing traces no
625
+ longer touches node bodies at all. Pagination itself is still not
626
+ implemented.)
627
+ - **Permissive CORS** (`allow_origins=["*"]` with `allow_credentials=True`),
628
+ no JWT revocation/logout flow, and a single non-expiring API key per user.
629
+ - **No true graph visualization yet** — `reactflow` is installed but unused;
630
+ `TraceView` uses a recursive-tree approximation instead of a node-link view.
631
+
632
+ ---
633
+