kalibr 1.2.8__py3-none-any.whl → 1.2.9__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 +83 -59
- {kalibr-1.2.8.dist-info → kalibr-1.2.9.dist-info}/METADATA +1 -1
- {kalibr-1.2.8.dist-info → kalibr-1.2.9.dist-info}/RECORD +7 -7
- {kalibr-1.2.8.dist-info → kalibr-1.2.9.dist-info}/LICENSE +0 -0
- {kalibr-1.2.8.dist-info → kalibr-1.2.9.dist-info}/WHEEL +0 -0
- {kalibr-1.2.8.dist-info → kalibr-1.2.9.dist-info}/entry_points.txt +0 -0
- {kalibr-1.2.8.dist-info → kalibr-1.2.9.dist-info}/top_level.txt +0 -0
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 decision_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
|
-
#
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
|
@@ -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=
|
|
15
|
+
kalibr/router.py,sha256=aYYgI-rcibU8QI9mnGvLc2XmHNn6HZSB3-CO8mOwPRI,18182
|
|
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.
|
|
48
|
-
kalibr-1.2.
|
|
49
|
-
kalibr-1.2.
|
|
50
|
-
kalibr-1.2.
|
|
51
|
-
kalibr-1.2.
|
|
52
|
-
kalibr-1.2.
|
|
47
|
+
kalibr-1.2.9.dist-info/LICENSE,sha256=5mwAnB38l3_PjmOQn6_L6cZnJvus143DUjMBPIH1yso,10768
|
|
48
|
+
kalibr-1.2.9.dist-info/METADATA,sha256=raFHt8CD3sUlapfeAGKOo4m_BOI0HQvmmtFs1wyqBfI,9014
|
|
49
|
+
kalibr-1.2.9.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
50
|
+
kalibr-1.2.9.dist-info/entry_points.txt,sha256=Kojlc6WRX8V1qS9lOMdDPZpTUVHCtzGtHqXusErgmLY,47
|
|
51
|
+
kalibr-1.2.9.dist-info/top_level.txt,sha256=dIfBOWUnnHGFDwgz5zfIx5_0bU3wOUgAbYr4JcFHZmo,59
|
|
52
|
+
kalibr-1.2.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|