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,229 @@
1
+ """
2
+ init.py
3
+ -------
4
+ The public entry point for direct OpenAI SDK agents.
5
+
6
+ from continuous_intelligence_layer.openai import init
7
+ init()
8
+
9
+ That's all the user needs to write. Everything else is automatic.
10
+
11
+ How it works (internals)
12
+ ------------------------
13
+ 1. We create an ExecutionSpanExporter — our custom JSONL writer.
14
+ 2. We build an OpenTelemetry TracerProvider and attach the exporter via a
15
+ SimpleSpanProcessor. SimpleSpanProcessor calls exporter.export() *synchronously*
16
+ on every span that finishes, so nothing is lost even if the process exits abruptly.
17
+ 3. We set this provider as the GLOBAL OTel provider for the Python process.
18
+ Any library that uses `opentelemetry.trace.get_tracer(...)` will now use it.
19
+ 4. We call instrument(provider) which activates the OpenInference
20
+ OpenAIInstrumentor (see instrumentation.py). This patches the
21
+ `openai.OpenAI()` client to emit an OTel LLM span (with tool_calls
22
+ metadata) for every `chat.completions.create` call — completely
23
+ automatically.
24
+ 5. When the user's agent runs, spans flow:
25
+ openai SDK → OpenInference patch → OTel SDK → SimpleSpanProcessor
26
+ → ExecutionSpanExporter.export() → JSONL file on disk
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import uuid
32
+ import requests
33
+
34
+ from opentelemetry import trace
35
+ from opentelemetry.sdk.resources import Resource
36
+ from opentelemetry.sdk.trace import TracerProvider
37
+ from opentelemetry.sdk.trace.export import SimpleSpanProcessor
38
+
39
+ from .._core.exporter import ExecutionSpanExporter
40
+ from .instrumentation import instrument
41
+
42
+ # Guard against double-init in the same process
43
+ _INITIALIZED: bool = False
44
+ _PROVIDER: TracerProvider | None = None
45
+
46
+
47
+ def init(
48
+ api_key: str,
49
+ output_path: str = "./execution_trace.jsonl",
50
+ service_name: str = "continuous-intelligence-layer",
51
+ execution_id: str | None = None,
52
+ session_id: str | None = None,
53
+ store: str | None = "mongodb",
54
+ write_jsonl: bool = False,
55
+ verbose: bool = True,
56
+ base_url: str = "http://localhost:8080",
57
+ run_evaluations: bool = True,
58
+ evaluation_api_key: str = "",
59
+ evaluation_provider: str = "openai",
60
+ evaluation_model: str = "gpt-5.6-luna",
61
+ ) -> dict:
62
+ """
63
+ Initialize the Continuous Intelligence Layer SDK for direct OpenAI SDK agents.
64
+
65
+ Call this ONCE, at the very top of your script, before instantiating any
66
+ `openai.OpenAI()` client. No other changes to your agent code are required.
67
+
68
+ Parameters
69
+ ----------
70
+ output_path :
71
+ Path to the JSONL file where spans will be appended, if
72
+ ``write_jsonl=True``. File is created automatically; parent dirs are
73
+ created if needed. Each call to init() with a new execution_id
74
+ appends to the same file, so you can accumulate traces from multiple
75
+ runs.
76
+ write_jsonl :
77
+ If True, also append every span to a local JSONL file at
78
+ ``output_path``. Defaults to False when ``store="mongodb"`` — MongoDB
79
+ is the single source of truth by default, and JSONL is only an
80
+ opt-in local debug/backup mechanism. Automatically forced on when
81
+ ``store`` is not "mongodb" (otherwise nothing would be persisted).
82
+ service_name :
83
+ An arbitrary label written to every span as ``service.name``.
84
+ Useful when you have multiple agents writing to the same file.
85
+ execution_id :
86
+ A short identifier for *this* pipeline run. Auto-generated (12-char
87
+ UUID fragment) if not provided. Stored on every span as
88
+ ``execution_id``, so you can filter a single run later.
89
+ session_id :
90
+ Identifier for the conversational session/thread.
91
+ store :
92
+ Optional. If set to "mongodb", also buffers spans and saves them to
93
+ MongoDB on shutdown.
94
+ verbose :
95
+ Print a one-line confirmation when init succeeds.
96
+ run_evaluations :
97
+ If True (the default), evaluations and RCA run automatically on the
98
+ backend right after the trace finishes ingesting — no need to call
99
+ the ``run-evaluations`` endpoint or click "Run Evaluation" in the UI
100
+ separately. Pass False to keep today's behavior: evaluations only
101
+ run when triggered manually.
102
+
103
+ Returns
104
+ -------
105
+ dict
106
+ ``{"execution_id": str, "output_path": str}``
107
+ Keep a reference if you need to correlate spans to a run ID later.
108
+
109
+ Notes
110
+ -----
111
+ Calling init() a second time in the same process is a no-op — the existing
112
+ provider is reused. If you genuinely need a fresh provider (e.g., in
113
+ tests), call ``reset()`` first.
114
+ """
115
+ global _INITIALIZED, _PROVIDER
116
+
117
+ if _INITIALIZED:
118
+ if verbose:
119
+ print(
120
+ f"[continuous-intelligence-layer] ⚠ Already initialized — skipping. "
121
+ f"Call reset() first if you need a fresh provider."
122
+ )
123
+ return {"execution_id": execution_id or "unknown", "output_path": output_path}
124
+
125
+ execution_id = execution_id or str(uuid.uuid4())[:12]
126
+ session_id = session_id or str(uuid.uuid4())[:12]
127
+
128
+ # ── Step 1: Build the span exporter ─────────────────────────────────────
129
+ write_jsonl = write_jsonl or store != "mongodb"
130
+ exporter = ExecutionSpanExporter(
131
+ output_path=output_path,
132
+ execution_id=execution_id,
133
+ session_id=session_id,
134
+ )
135
+
136
+ # ── Step 2: Sync Project & Build TracerProvider ────────────────────────
137
+ if not api_key:
138
+ raise ValueError("API Key is not provided. An API Key is required to initialize the SDK.")
139
+
140
+ if not evaluation_api_key:
141
+ raise ValueError(
142
+ "evaluation_api_key is not provided. An LLM API key (OpenAI, Anthropic, "
143
+ "or Gemini) is required to initialize the SDK -- it's used to run "
144
+ "evaluations and RCA on your traces. We never use our own key for your work."
145
+ )
146
+
147
+ try:
148
+ res = requests.post(
149
+ f"{base_url}/projects/sync",
150
+ json={
151
+ "name": service_name,
152
+ "execution_id": execution_id,
153
+ "evaluation_provider": evaluation_provider,
154
+ "evaluation_model": evaluation_model,
155
+ "evaluation_api_key": evaluation_api_key,
156
+ },
157
+ headers={"x-api-key": api_key}
158
+ )
159
+ if res.status_code == 401:
160
+ raise ValueError("API Key is not valid.")
161
+ res.raise_for_status()
162
+ project_id = res.json()["id"]
163
+ except requests.exceptions.RequestException as e:
164
+ raise ValueError(f"Failed to verify API key with backend: {e}")
165
+
166
+ resource_attrs = {
167
+ "service.name": service_name,
168
+ "project.id": project_id,
169
+ "execution.id": execution_id,
170
+ "sdk.name": "continuous-intelligence-layer",
171
+ "sdk.version": "0.1.0",
172
+ }
173
+ if session_id:
174
+ resource_attrs["session.id"] = session_id
175
+
176
+ resource = Resource.create(resource_attrs)
177
+ provider = TracerProvider(resource=resource)
178
+
179
+ # ── Step 3: Attach SimpleSpanProcessor ────────────────────────────────
180
+ if write_jsonl:
181
+ provider.add_span_processor(SimpleSpanProcessor(exporter))
182
+
183
+ if store == "mongodb":
184
+ from .._core.graph_exporter import GraphSpanExporter
185
+ graph_exporter = GraphSpanExporter(
186
+ exporter,
187
+ session_id=session_id,
188
+ base_url=base_url,
189
+ api_key=api_key,
190
+ project_id=project_id,
191
+ run_evaluations=run_evaluations,
192
+ )
193
+ provider.add_span_processor(SimpleSpanProcessor(graph_exporter))
194
+
195
+ # ── Step 4: Set as global OTel provider ───────────────────────────────
196
+ trace.set_tracer_provider(provider)
197
+ _PROVIDER = provider
198
+
199
+ # ── Step 5: Auto-instrument the direct OpenAI SDK ──────────────────────
200
+ instrument(provider)
201
+
202
+ _INITIALIZED = True
203
+
204
+ if verbose:
205
+ store_line = f" Store : MongoDB (project={service_name})\n" if store == "mongodb" else ""
206
+ jsonl_line = f" Output file : {output_path}\n" if write_jsonl else ""
207
+ print(
208
+ f"[continuous-intelligence-layer] ✅ Initialized\n"
209
+ f" Execution ID : {execution_id}\n"
210
+ f"{store_line}"
211
+ f"{jsonl_line}"
212
+ f" Service name : {service_name}"
213
+ )
214
+
215
+ return {"execution_id": execution_id, "output_path": output_path}
216
+
217
+
218
+ def reset() -> None:
219
+ """
220
+ Reset the SDK so init() can be called again.
221
+
222
+ Primarily useful in test suites where each test needs a fresh provider.
223
+ Calling this in production code is rarely needed.
224
+ """
225
+ global _INITIALIZED, _PROVIDER
226
+ from .instrumentation import uninstrument
227
+ uninstrument()
228
+ _INITIALIZED = False
229
+ _PROVIDER = None
@@ -0,0 +1,67 @@
1
+ """
2
+ instrumentation.py
3
+ ------------------
4
+ Activates OpenInference auto-instrumentation for direct OpenAI SDK agents.
5
+
6
+ How OpenInference works internally
7
+ ------------------------------------
8
+ The instrumentor patches the `openai.OpenAI()` client at import/instantiation
9
+ time and emits OTel spans using the vendor-neutral semantic-convention
10
+ attribute names (openinference.span.kind, input.value, llm.model_name,
11
+ tool.name, etc. — see `continuous_intelligence_layer/_core/exporter.py`).
12
+ Because every OpenInference instrumentor speaks that shared vocabulary, the
13
+ rest of the SDK (`_core/`, `graph_builder/`, `evaluators/`, `rca_engine/`)
14
+ doesn't need to know which framework produced a given span.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from opentelemetry.sdk.trace import TracerProvider
20
+
21
+ _instrumentor = None
22
+
23
+
24
+ def instrument(provider: TracerProvider) -> None:
25
+ """
26
+ Activate direct OpenAI SDK OpenInference auto-instrumentation.
27
+
28
+ Parameters
29
+ ----------
30
+ provider :
31
+ The TracerProvider to attach spans to. Must be the same provider that
32
+ was set as the global OTel provider.
33
+ """
34
+ global _instrumentor
35
+ if _instrumentor is not None:
36
+ # Already instrumented — avoid double-patching
37
+ return
38
+
39
+ try:
40
+ from openinference.instrumentation.openai import OpenAIInstrumentor
41
+ except ImportError:
42
+ print(
43
+ "[continuous-intelligence-layer] ⚠️ openinference-instrumentation-openai not found.\n"
44
+ " openai spans will NOT be captured automatically.\n"
45
+ " Fix: pip install openinference-instrumentation-openai"
46
+ )
47
+ return
48
+
49
+ try:
50
+ instrumentor = OpenAIInstrumentor()
51
+ instrumentor.instrument(tracer_provider=provider)
52
+ _instrumentor = instrumentor
53
+ print("[continuous-intelligence-layer] 🔌 openai auto-instrumentation active.")
54
+ except Exception as exc:
55
+ print(f"[continuous-intelligence-layer] ⚠️ Instrumentation of 'openai' failed: {exc}")
56
+
57
+
58
+ def uninstrument() -> None:
59
+ """Remove direct OpenAI SDK OpenInference instrumentation."""
60
+ global _instrumentor
61
+ if _instrumentor is None:
62
+ return
63
+ try:
64
+ _instrumentor.uninstrument()
65
+ except Exception:
66
+ pass
67
+ _instrumentor = None