kalibr 1.2.8__py3-none-any.whl → 1.2.10__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.
kalibr/router.py CHANGED
@@ -8,6 +8,8 @@ import uuid
8
8
  from typing import Any, Callable, Dict, List, Optional, Union
9
9
 
10
10
  from opentelemetry import trace as otel_trace
11
+ from opentelemetry.trace import SpanContext, TraceFlags, NonRecordingSpan, set_span_in_context
12
+ from opentelemetry.context import Context
11
13
 
12
14
  logger = logging.getLogger(__name__)
13
15
 
@@ -15,6 +17,34 @@ logger = logging.getLogger(__name__)
15
17
  PathSpec = Union[str, Dict[str, Any]]
16
18
 
17
19
 
20
+ def _create_context_with_trace_id(trace_id_hex: str) -> Optional[Context]:
21
+ """Create an OTel context with a specific trace_id.
22
+
23
+ This allows child spans to inherit the intelligence service's trace_id,
24
+ enabling JOINs between outcomes and traces tables.
25
+ """
26
+ try:
27
+ # Convert 32-char hex string to 128-bit int
28
+ trace_id_int = int(trace_id_hex, 16)
29
+ if trace_id_int == 0:
30
+ return None
31
+
32
+ # Create span context with our trace_id
33
+ span_context = SpanContext(
34
+ trace_id=trace_id_int,
35
+ span_id=0xDEADBEEF, # Placeholder, real span will have its own
36
+ is_remote=True, # Treat as remote parent so new span_id is generated
37
+ trace_flags=TraceFlags(TraceFlags.SAMPLED),
38
+ )
39
+
40
+ # Create a non-recording parent span and set in context
41
+ parent_span = NonRecordingSpan(span_context)
42
+ return set_span_in_context(parent_span)
43
+ except (ValueError, TypeError) as e:
44
+ logger.warning(f"Could not create OTel context with trace_id: {e}")
45
+ return None
46
+
47
+
18
48
  class Router:
19
49
  """
20
50
  Routes LLM requests to the best model based on learned outcomes.
@@ -145,78 +175,73 @@ class Router:
145
175
  Returns:
146
176
  OpenAI-compatible ChatCompletion response with added attribute:
147
177
  - kalibr_trace_id: Trace ID for explicit outcome reporting
148
-
149
- Raises:
150
- openai.OpenAIError: If OpenAI API call fails
151
- anthropic.AnthropicError: If Anthropic API call fails
152
- google.generativeai.GenerativeAIError: If Google API call fails
153
- ImportError: If required provider SDK is not installed
154
178
  """
155
179
  from kalibr.intelligence import decide
156
180
 
181
+ # Reset state for new request
182
+ self._outcome_reported = False
183
+
184
+ # Step 1: Get routing decision FIRST (before creating span)
185
+ decision = None
186
+ model_id = None
187
+ tool_id = None
188
+ params = {}
189
+
190
+ if force_model:
191
+ model_id = force_model
192
+ self._last_decision = {"model_id": model_id, "forced": True}
193
+ else:
194
+ try:
195
+ decision = decide(goal=self.goal)
196
+ model_id = decision.get("model_id") or self._paths[0]["model"]
197
+ tool_id = decision.get("tool_id")
198
+ params = decision.get("params") or {}
199
+ self._last_decision = decision
200
+ except Exception as e:
201
+ logger.warning(f"Routing failed, using fallback: {e}")
202
+ model_id = self._paths[0]["model"]
203
+ tool_id = self._paths[0].get("tools")
204
+ params = self._paths[0].get("params") or {}
205
+ self._last_decision = {"model_id": model_id, "fallback": True, "error": str(e)}
206
+
207
+ # Step 2: Determine trace_id
208
+ decision_trace_id = self._last_decision.get("trace_id") if self._last_decision else None
209
+
210
+ if decision_trace_id:
211
+ trace_id = decision_trace_id
212
+ else:
213
+ trace_id = uuid.uuid4().hex # Fallback: generate OTel-compatible format
214
+
215
+ self._last_trace_id = trace_id
216
+ self._last_model_id = model_id
217
+
218
+ # Step 3: Create OTel context with intelligence trace_id
219
+ otel_context = _create_context_with_trace_id(trace_id) if trace_id else None
220
+
221
+ # Step 4: Create span with custom context (child spans inherit trace_id)
157
222
  tracer = otel_trace.get_tracer("kalibr.router")
158
223
 
159
224
  with tracer.start_as_current_span(
160
225
  "kalibr.router.completion",
226
+ context=otel_context,
161
227
  attributes={
162
228
  "kalibr.goal": self.goal,
229
+ "kalibr.trace_id": trace_id,
230
+ "kalibr.model_id": model_id,
163
231
  }
164
232
  ) as router_span:
165
- # Reset state for new request
166
- self._outcome_reported = False
167
-
168
- # Get routing decision (or use forced model)
233
+ # Add decision attributes
169
234
  if force_model:
170
- model_id = force_model
171
- tool_id = None
172
- params = {}
173
- self._last_decision = {"model_id": model_id, "forced": True}
174
- router_span.set_attribute("kalibr.model_id", model_id)
175
235
  router_span.set_attribute("kalibr.forced", True)
236
+ elif decision:
237
+ router_span.set_attribute("kalibr.path_id", decision.get("path_id", ""))
238
+ router_span.set_attribute("kalibr.reason", decision.get("reason", ""))
239
+ router_span.set_attribute("kalibr.exploration", decision.get("exploration", False))
240
+ router_span.set_attribute("kalibr.confidence", decision.get("confidence", 0.0))
176
241
  else:
177
- try:
178
- decision = decide(goal=self.goal)
179
- model_id = decision.get("model_id") or self._paths[0]["model"]
180
- tool_id = decision.get("tool_id")
181
- params = decision.get("params") or {}
182
- self._last_decision = decision
183
-
184
- # Add decision attributes to span
185
- router_span.set_attribute("kalibr.path_id", decision.get("path_id", ""))
186
- router_span.set_attribute("kalibr.model_id", model_id)
187
- router_span.set_attribute("kalibr.reason", decision.get("reason", ""))
188
- router_span.set_attribute("kalibr.exploration", decision.get("exploration", False))
189
- router_span.set_attribute("kalibr.confidence", decision.get("confidence", 0.0))
190
- except Exception as e:
191
- # Fallback to first path if routing fails
192
- logger.warning(f"Routing failed, using fallback: {e}")
193
- model_id = self._paths[0]["model"]
194
- tool_id = self._paths[0].get("tools")
195
- params = self._paths[0].get("params") or {}
196
- self._last_decision = {"model_id": model_id, "fallback": True, "error": str(e)}
197
- router_span.set_attribute("kalibr.model_id", model_id)
198
- router_span.set_attribute("kalibr.fallback", True)
199
- router_span.set_attribute("kalibr.fallback_reason", str(e))
200
-
201
- # Use trace_id from decision if available (links outcome to routing decision)
202
- # Fall back to OTel span trace_id for backwards compatibility
203
- decision_trace_id = self._last_decision.get("trace_id") if self._last_decision else None
204
-
205
- if decision_trace_id:
206
- trace_id = decision_trace_id
207
- else:
208
- # Fallback: generate from OTel span or UUID
209
- span_context = router_span.get_span_context()
210
- trace_id = format(span_context.trace_id, "032x")
211
- if trace_id == "0" * 32:
212
- trace_id = uuid.uuid4().hex
213
-
214
- logger.debug(f"Using trace_id={trace_id} (from_decision={bool(decision_trace_id)})")
215
- self._last_trace_id = trace_id
216
- self._last_model_id = model_id
217
- router_span.set_attribute("kalibr.trace_id", trace_id)
218
-
219
- # Dispatch to provider (will be child span via auto-instrumentation)
242
+ router_span.set_attribute("kalibr.fallback", True)
243
+
244
+ # Step 5: Dispatch to provider
220
245
  try:
221
246
  response = self._dispatch(model_id, messages, tool_id, **{**params, **kwargs})
222
247
 
@@ -234,7 +259,6 @@ class Router:
234
259
  return response
235
260
 
236
261
  except Exception as e:
237
- # Record error on span
238
262
  router_span.set_attribute("error", True)
239
263
  router_span.set_attribute("error.type", type(e).__name__)
240
264
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: kalibr
3
- Version: 1.2.8
3
+ Version: 1.2.10
4
4
  Summary: Adaptive routing for AI agents. Learns which models work best and routes automatically.
5
5
  Author-email: Kalibr Team <support@kalibr.systems>
6
6
  License: Apache-2.0
@@ -12,7 +12,7 @@ kalibr/kalibr_app.py,sha256=ItZwEh0FZPx9_BE-zPQajC2yxI2y9IHYwJD0k9tbHvY,2773
12
12
  kalibr/models.py,sha256=HwD_-iysZMSnCzMQYO1Qcf0aeXySupY7yJeBwl_dLS0,1024
13
13
  kalibr/pricing.py,sha256=wY0GzcrZdXuHlZoq2e74RkX0scd6somk_KYbr-RSHdE,8844
14
14
  kalibr/redaction.py,sha256=XibxX4Lv1Ci0opE6Tb5ZI2GLbO0a8E9U66MAg60llnc,1139
15
- kalibr/router.py,sha256=ari4XOogl3tzvcVlGc1Qg3hOnYI2hGaCweLXx07f_fU,17820
15
+ kalibr/router.py,sha256=0fTFo-vPJWCKHhereu7drzO-oDvYr2pNOq2UzPe6u-I,18173
16
16
  kalibr/schemas.py,sha256=XLZNLkXca6jbj9AF6gDIyGVnIcr1SVOsNYaKvW-wbgE,3669
17
17
  kalibr/simple_tracer.py,sha256=oiwXtiYaIqZxqCNV-b79_dsiJT0D3XvKhNT_LF6bRD4,9736
18
18
  kalibr/tokens.py,sha256=istjgaxi9S4dMddjuGtoQaTnZYcWLCqdnxRjV86yNXA,1297
@@ -44,9 +44,9 @@ kalibr_langchain/callback.py,sha256=SNM1aHOXdG55grHmGyTwbXOeM6hjZTub2REiZD2H-d8,
44
44
  kalibr_langchain/chat_model.py,sha256=Y4xsZGx9gZpDUF8NP-edJuYam4k0NBySdA6B5484MKk,3190
45
45
  kalibr_openai_agents/__init__.py,sha256=wL59LzGstptKigfQDrKKt_7hcMO1JGVQtVAsE0lz-Zw,1367
46
46
  kalibr_openai_agents/processor.py,sha256=F550sdRf3rpguP1yOlgAUQWDLPBy4hSACV3-zOyCpOU,18257
47
- kalibr-1.2.8.dist-info/LICENSE,sha256=5mwAnB38l3_PjmOQn6_L6cZnJvus143DUjMBPIH1yso,10768
48
- kalibr-1.2.8.dist-info/METADATA,sha256=DR_yUR8AmRmGL3Wgp9EhMaIkGIvzh9es5S_1v7b8vEM,9014
49
- kalibr-1.2.8.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
50
- kalibr-1.2.8.dist-info/entry_points.txt,sha256=Kojlc6WRX8V1qS9lOMdDPZpTUVHCtzGtHqXusErgmLY,47
51
- kalibr-1.2.8.dist-info/top_level.txt,sha256=dIfBOWUnnHGFDwgz5zfIx5_0bU3wOUgAbYr4JcFHZmo,59
52
- kalibr-1.2.8.dist-info/RECORD,,
47
+ kalibr-1.2.10.dist-info/LICENSE,sha256=5mwAnB38l3_PjmOQn6_L6cZnJvus143DUjMBPIH1yso,10768
48
+ kalibr-1.2.10.dist-info/METADATA,sha256=hvSe1wb_N5KSxn_dhqqkEDMfwQL1X0NS_JlxebVTc3k,9015
49
+ kalibr-1.2.10.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
50
+ kalibr-1.2.10.dist-info/entry_points.txt,sha256=Kojlc6WRX8V1qS9lOMdDPZpTUVHCtzGtHqXusErgmLY,47
51
+ kalibr-1.2.10.dist-info/top_level.txt,sha256=dIfBOWUnnHGFDwgz5zfIx5_0bU3wOUgAbYr4JcFHZmo,59
52
+ kalibr-1.2.10.dist-info/RECORD,,