kalibr 1.1.2a0__py3-none-any.whl → 1.3.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.
- kalibr/__init__.py +49 -53
- kalibr/cli/capsule_cmd.py +3 -3
- kalibr/cli/main.py +3 -3
- kalibr/cli/run.py +2 -2
- kalibr/client.py +1 -1
- kalibr/collector.py +227 -48
- kalibr/context.py +42 -0
- kalibr/cost_adapter.py +36 -104
- kalibr/instrumentation/anthropic_instr.py +34 -40
- kalibr/instrumentation/base.py +27 -9
- kalibr/instrumentation/google_instr.py +34 -39
- kalibr/instrumentation/openai_instr.py +34 -28
- kalibr/instrumentation/registry.py +38 -13
- kalibr/intelligence.py +662 -0
- kalibr/middleware/auto_tracer.py +1 -1
- kalibr/pricing.py +245 -0
- kalibr/router.py +499 -0
- kalibr/simple_tracer.py +17 -16
- kalibr/trace_capsule.py +19 -12
- kalibr/utils.py +2 -2
- kalibr-1.3.0.dist-info/LICENSE +190 -0
- kalibr-1.3.0.dist-info/METADATA +296 -0
- kalibr-1.3.0.dist-info/RECORD +52 -0
- {kalibr-1.1.2a0.dist-info → kalibr-1.3.0.dist-info}/WHEEL +1 -1
- kalibr_crewai/__init__.py +1 -1
- kalibr_crewai/callbacks.py +124 -16
- kalibr_crewai/instrumentor.py +197 -34
- kalibr_langchain/__init__.py +4 -2
- kalibr_langchain/async_callback.py +1 -1
- kalibr_langchain/callback.py +27 -1
- kalibr_langchain/chat_model.py +103 -0
- kalibr_openai_agents/__init__.py +1 -1
- kalibr_openai_agents/processor.py +1 -1
- kalibr-1.1.2a0.dist-info/METADATA +0 -236
- kalibr-1.1.2a0.dist-info/RECORD +0 -48
- kalibr-1.1.2a0.dist-info/licenses/LICENSE +0 -21
- {kalibr-1.1.2a0.dist-info → kalibr-1.3.0.dist-info}/entry_points.txt +0 -0
- {kalibr-1.1.2a0.dist-info → kalibr-1.3.0.dist-info}/top_level.txt +0 -0
kalibr_crewai/callbacks.py
CHANGED
|
@@ -60,6 +60,72 @@ def _get_provider_from_model(model: str) -> str:
|
|
|
60
60
|
return "openai"
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
def _extract_model_from_agent(agent) -> tuple[str, str]:
|
|
64
|
+
"""Extract model name and provider from agent's LLM config.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
agent: CrewAI agent instance
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Tuple of (model_name, provider)
|
|
71
|
+
"""
|
|
72
|
+
model_name = "unknown"
|
|
73
|
+
provider = "openai"
|
|
74
|
+
|
|
75
|
+
if not hasattr(agent, "llm"):
|
|
76
|
+
return model_name, provider
|
|
77
|
+
|
|
78
|
+
llm = agent.llm
|
|
79
|
+
|
|
80
|
+
# Case 1: LLM is a string like "openai/gpt-4o-mini" or "gpt-4"
|
|
81
|
+
if isinstance(llm, str):
|
|
82
|
+
if "/" in llm:
|
|
83
|
+
parts = llm.split("/", 1)
|
|
84
|
+
provider = parts[0]
|
|
85
|
+
model_name = parts[1]
|
|
86
|
+
else:
|
|
87
|
+
model_name = llm
|
|
88
|
+
provider = _get_provider_from_model(llm)
|
|
89
|
+
return model_name, provider
|
|
90
|
+
|
|
91
|
+
# Case 2: LLM has model or model_name attribute
|
|
92
|
+
if hasattr(llm, "model"):
|
|
93
|
+
model_name = str(llm.model)
|
|
94
|
+
elif hasattr(llm, "model_name"):
|
|
95
|
+
model_name = str(llm.model_name)
|
|
96
|
+
|
|
97
|
+
# Parse provider from model string if it contains "/"
|
|
98
|
+
if "/" in model_name:
|
|
99
|
+
parts = model_name.split("/", 1)
|
|
100
|
+
provider = parts[0]
|
|
101
|
+
model_name = parts[1]
|
|
102
|
+
else:
|
|
103
|
+
provider = _get_provider_from_model(model_name)
|
|
104
|
+
|
|
105
|
+
return model_name, provider
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _calculate_cost(provider: str, model: str, input_tokens: int, output_tokens: int) -> float:
|
|
109
|
+
"""Calculate cost using CostAdapterFactory.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
provider: Provider name (openai, anthropic, etc.)
|
|
113
|
+
model: Model name
|
|
114
|
+
input_tokens: Number of input tokens
|
|
115
|
+
output_tokens: Number of output tokens
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Cost in USD
|
|
119
|
+
"""
|
|
120
|
+
if CostAdapterFactory is None:
|
|
121
|
+
return 0.0
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
return CostAdapterFactory.compute_cost(provider, model, input_tokens, output_tokens)
|
|
125
|
+
except Exception:
|
|
126
|
+
return 0.0
|
|
127
|
+
|
|
128
|
+
|
|
63
129
|
class EventBatcher:
|
|
64
130
|
"""Shared event batching for callbacks."""
|
|
65
131
|
|
|
@@ -198,6 +264,7 @@ class KalibrAgentCallback:
|
|
|
198
264
|
service: Service name
|
|
199
265
|
workflow_id: Workflow identifier
|
|
200
266
|
metadata: Additional metadata for all events
|
|
267
|
+
agent: Optional agent reference for model extraction
|
|
201
268
|
|
|
202
269
|
Usage:
|
|
203
270
|
from kalibr_crewai import KalibrAgentCallback
|
|
@@ -210,6 +277,7 @@ class KalibrAgentCallback:
|
|
|
210
277
|
goal="Find information",
|
|
211
278
|
step_callback=callback,
|
|
212
279
|
)
|
|
280
|
+
callback.set_agent(agent) # Set agent reference for model extraction
|
|
213
281
|
"""
|
|
214
282
|
|
|
215
283
|
def __init__(
|
|
@@ -221,17 +289,19 @@ class KalibrAgentCallback:
|
|
|
221
289
|
service: Optional[str] = None,
|
|
222
290
|
workflow_id: Optional[str] = None,
|
|
223
291
|
metadata: Optional[Dict[str, Any]] = None,
|
|
292
|
+
agent: Optional[Any] = None,
|
|
224
293
|
):
|
|
225
294
|
self.api_key = api_key or os.getenv("KALIBR_API_KEY", "")
|
|
226
295
|
self.endpoint = endpoint or os.getenv(
|
|
227
296
|
"KALIBR_ENDPOINT",
|
|
228
|
-
os.getenv("KALIBR_API_ENDPOINT", "
|
|
297
|
+
os.getenv("KALIBR_API_ENDPOINT", "https://api.kalibr.systems/api/v1/traces")
|
|
229
298
|
)
|
|
230
299
|
self.tenant_id = tenant_id or os.getenv("KALIBR_TENANT_ID", "default")
|
|
231
300
|
self.environment = environment or os.getenv("KALIBR_ENVIRONMENT", "prod")
|
|
232
301
|
self.service = service or os.getenv("KALIBR_SERVICE", "crewai-app")
|
|
233
302
|
self.workflow_id = workflow_id or os.getenv("KALIBR_WORKFLOW_ID", "default-workflow")
|
|
234
303
|
self.default_metadata = metadata or {}
|
|
304
|
+
self._agent = agent
|
|
235
305
|
|
|
236
306
|
# Get shared batcher
|
|
237
307
|
self._batcher = EventBatcher.get_instance(
|
|
@@ -244,6 +314,14 @@ class KalibrAgentCallback:
|
|
|
244
314
|
self._agent_span_id: Optional[str] = None
|
|
245
315
|
self._step_count: int = 0
|
|
246
316
|
|
|
317
|
+
def set_agent(self, agent: Any) -> None:
|
|
318
|
+
"""Set the agent reference for model extraction.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
agent: CrewAI agent instance
|
|
322
|
+
"""
|
|
323
|
+
self._agent = agent
|
|
324
|
+
|
|
247
325
|
def __call__(self, step_output: Any) -> None:
|
|
248
326
|
"""Called after each agent step.
|
|
249
327
|
|
|
@@ -271,6 +349,12 @@ class KalibrAgentCallback:
|
|
|
271
349
|
|
|
272
350
|
span_id = str(uuid.uuid4())
|
|
273
351
|
|
|
352
|
+
# Extract model from agent if available
|
|
353
|
+
model_name = "unknown"
|
|
354
|
+
provider = "openai"
|
|
355
|
+
if self._agent:
|
|
356
|
+
model_name, provider = _extract_model_from_agent(self._agent)
|
|
357
|
+
|
|
274
358
|
# Extract step information
|
|
275
359
|
step_type = "agent_step"
|
|
276
360
|
operation = "agent_step"
|
|
@@ -307,8 +391,11 @@ class KalibrAgentCallback:
|
|
|
307
391
|
output_text = str(step_output)
|
|
308
392
|
|
|
309
393
|
# Count tokens
|
|
310
|
-
input_tokens = _count_tokens(tool_input or "",
|
|
311
|
-
output_tokens = _count_tokens(output_text,
|
|
394
|
+
input_tokens = _count_tokens(tool_input or "", model_name)
|
|
395
|
+
output_tokens = _count_tokens(output_text, model_name)
|
|
396
|
+
|
|
397
|
+
# Calculate cost using CostAdapterFactory
|
|
398
|
+
cost_usd = _calculate_cost(provider, model_name, input_tokens, output_tokens)
|
|
312
399
|
|
|
313
400
|
# Build event
|
|
314
401
|
event = {
|
|
@@ -318,9 +405,9 @@ class KalibrAgentCallback:
|
|
|
318
405
|
"parent_span_id": self._agent_span_id,
|
|
319
406
|
"tenant_id": self.tenant_id,
|
|
320
407
|
"workflow_id": self.workflow_id,
|
|
321
|
-
"provider":
|
|
322
|
-
"model_id":
|
|
323
|
-
"model_name":
|
|
408
|
+
"provider": provider,
|
|
409
|
+
"model_id": model_name,
|
|
410
|
+
"model_name": model_name,
|
|
324
411
|
"operation": operation,
|
|
325
412
|
"endpoint": operation,
|
|
326
413
|
"duration_ms": 0, # Step timing not available
|
|
@@ -328,8 +415,8 @@ class KalibrAgentCallback:
|
|
|
328
415
|
"input_tokens": input_tokens,
|
|
329
416
|
"output_tokens": output_tokens,
|
|
330
417
|
"total_tokens": input_tokens + output_tokens,
|
|
331
|
-
"cost_usd":
|
|
332
|
-
"total_cost_usd":
|
|
418
|
+
"cost_usd": cost_usd,
|
|
419
|
+
"total_cost_usd": cost_usd,
|
|
333
420
|
"status": status,
|
|
334
421
|
"timestamp": now.isoformat(),
|
|
335
422
|
"ts_start": now.isoformat(),
|
|
@@ -376,6 +463,7 @@ class KalibrTaskCallback:
|
|
|
376
463
|
service: Service name
|
|
377
464
|
workflow_id: Workflow identifier
|
|
378
465
|
metadata: Additional metadata for all events
|
|
466
|
+
agent: Optional agent reference for model extraction
|
|
379
467
|
|
|
380
468
|
Usage:
|
|
381
469
|
from kalibr_crewai import KalibrTaskCallback
|
|
@@ -388,6 +476,7 @@ class KalibrTaskCallback:
|
|
|
388
476
|
agent=my_agent,
|
|
389
477
|
callback=callback,
|
|
390
478
|
)
|
|
479
|
+
callback.set_agent(my_agent) # Set agent reference for model extraction
|
|
391
480
|
"""
|
|
392
481
|
|
|
393
482
|
def __init__(
|
|
@@ -399,17 +488,19 @@ class KalibrTaskCallback:
|
|
|
399
488
|
service: Optional[str] = None,
|
|
400
489
|
workflow_id: Optional[str] = None,
|
|
401
490
|
metadata: Optional[Dict[str, Any]] = None,
|
|
491
|
+
agent: Optional[Any] = None,
|
|
402
492
|
):
|
|
403
493
|
self.api_key = api_key or os.getenv("KALIBR_API_KEY", "")
|
|
404
494
|
self.endpoint = endpoint or os.getenv(
|
|
405
495
|
"KALIBR_ENDPOINT",
|
|
406
|
-
os.getenv("KALIBR_API_ENDPOINT", "
|
|
496
|
+
os.getenv("KALIBR_API_ENDPOINT", "https://api.kalibr.systems/api/v1/traces")
|
|
407
497
|
)
|
|
408
498
|
self.tenant_id = tenant_id or os.getenv("KALIBR_TENANT_ID", "default")
|
|
409
499
|
self.environment = environment or os.getenv("KALIBR_ENVIRONMENT", "prod")
|
|
410
500
|
self.service = service or os.getenv("KALIBR_SERVICE", "crewai-app")
|
|
411
501
|
self.workflow_id = workflow_id or os.getenv("KALIBR_WORKFLOW_ID", "default-workflow")
|
|
412
502
|
self.default_metadata = metadata or {}
|
|
503
|
+
self._agent = agent
|
|
413
504
|
|
|
414
505
|
# Get shared batcher
|
|
415
506
|
self._batcher = EventBatcher.get_instance(
|
|
@@ -421,6 +512,14 @@ class KalibrTaskCallback:
|
|
|
421
512
|
self._trace_id: Optional[str] = None
|
|
422
513
|
self._crew_span_id: Optional[str] = None
|
|
423
514
|
|
|
515
|
+
def set_agent(self, agent: Any) -> None:
|
|
516
|
+
"""Set the agent reference for model extraction.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
agent: CrewAI agent instance
|
|
520
|
+
"""
|
|
521
|
+
self._agent = agent
|
|
522
|
+
|
|
424
523
|
def __call__(self, task_output: Any) -> None:
|
|
425
524
|
"""Called when task completes.
|
|
426
525
|
|
|
@@ -467,9 +566,18 @@ class KalibrTaskCallback:
|
|
|
467
566
|
if hasattr(task_output, "agent"):
|
|
468
567
|
agent_role = str(task_output.agent)
|
|
469
568
|
|
|
569
|
+
# Extract model from agent if available
|
|
570
|
+
model_name = "unknown"
|
|
571
|
+
provider = "openai"
|
|
572
|
+
if self._agent:
|
|
573
|
+
model_name, provider = _extract_model_from_agent(self._agent)
|
|
574
|
+
|
|
470
575
|
# Token counting
|
|
471
|
-
input_tokens = _count_tokens(description,
|
|
472
|
-
output_tokens = _count_tokens(raw_output,
|
|
576
|
+
input_tokens = _count_tokens(description, model_name)
|
|
577
|
+
output_tokens = _count_tokens(raw_output, model_name)
|
|
578
|
+
|
|
579
|
+
# Calculate cost using CostAdapterFactory
|
|
580
|
+
cost_usd = _calculate_cost(provider, model_name, input_tokens, output_tokens)
|
|
473
581
|
|
|
474
582
|
# Build operation name from description
|
|
475
583
|
operation = "task_complete"
|
|
@@ -486,9 +594,9 @@ class KalibrTaskCallback:
|
|
|
486
594
|
"parent_span_id": self._crew_span_id,
|
|
487
595
|
"tenant_id": self.tenant_id,
|
|
488
596
|
"workflow_id": self.workflow_id,
|
|
489
|
-
"provider":
|
|
490
|
-
"model_id":
|
|
491
|
-
"model_name":
|
|
597
|
+
"provider": provider,
|
|
598
|
+
"model_id": model_name,
|
|
599
|
+
"model_name": model_name,
|
|
492
600
|
"operation": operation,
|
|
493
601
|
"endpoint": "task_complete",
|
|
494
602
|
"duration_ms": 0, # Task timing not available in callback
|
|
@@ -496,8 +604,8 @@ class KalibrTaskCallback:
|
|
|
496
604
|
"input_tokens": input_tokens,
|
|
497
605
|
"output_tokens": output_tokens,
|
|
498
606
|
"total_tokens": input_tokens + output_tokens,
|
|
499
|
-
"cost_usd":
|
|
500
|
-
"total_cost_usd":
|
|
607
|
+
"cost_usd": cost_usd,
|
|
608
|
+
"total_cost_usd": cost_usd,
|
|
501
609
|
"status": "success",
|
|
502
610
|
"timestamp": now.isoformat(),
|
|
503
611
|
"ts_start": now.isoformat(),
|
kalibr_crewai/instrumentor.py
CHANGED
|
@@ -12,6 +12,8 @@ from datetime import datetime, timezone
|
|
|
12
12
|
from functools import wraps
|
|
13
13
|
from typing import Any, Callable, Dict, List, Optional
|
|
14
14
|
|
|
15
|
+
from opentelemetry import trace as otel_trace
|
|
16
|
+
|
|
15
17
|
from .callbacks import EventBatcher, _count_tokens, _get_provider_from_model
|
|
16
18
|
|
|
17
19
|
# Import Kalibr cost adapters
|
|
@@ -21,6 +23,72 @@ except ImportError:
|
|
|
21
23
|
CostAdapterFactory = None
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
def _extract_model_from_agent(agent) -> tuple[str, str]:
|
|
27
|
+
"""Extract model name and provider from agent's LLM config.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
agent: CrewAI agent instance
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Tuple of (model_name, provider)
|
|
34
|
+
"""
|
|
35
|
+
model_name = "unknown"
|
|
36
|
+
provider = "openai"
|
|
37
|
+
|
|
38
|
+
if not hasattr(agent, "llm"):
|
|
39
|
+
return model_name, provider
|
|
40
|
+
|
|
41
|
+
llm = agent.llm
|
|
42
|
+
|
|
43
|
+
# Case 1: LLM is a string like "openai/gpt-4o-mini" or "gpt-4"
|
|
44
|
+
if isinstance(llm, str):
|
|
45
|
+
if "/" in llm:
|
|
46
|
+
parts = llm.split("/", 1)
|
|
47
|
+
provider = parts[0]
|
|
48
|
+
model_name = parts[1]
|
|
49
|
+
else:
|
|
50
|
+
model_name = llm
|
|
51
|
+
provider = _get_provider_from_model(llm)
|
|
52
|
+
return model_name, provider
|
|
53
|
+
|
|
54
|
+
# Case 2: LLM has model or model_name attribute
|
|
55
|
+
if hasattr(llm, "model"):
|
|
56
|
+
model_name = str(llm.model)
|
|
57
|
+
elif hasattr(llm, "model_name"):
|
|
58
|
+
model_name = str(llm.model_name)
|
|
59
|
+
|
|
60
|
+
# Parse provider from model string if it contains "/"
|
|
61
|
+
if "/" in model_name:
|
|
62
|
+
parts = model_name.split("/", 1)
|
|
63
|
+
provider = parts[0]
|
|
64
|
+
model_name = parts[1]
|
|
65
|
+
else:
|
|
66
|
+
provider = _get_provider_from_model(model_name)
|
|
67
|
+
|
|
68
|
+
return model_name, provider
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _calculate_cost(provider: str, model: str, input_tokens: int, output_tokens: int) -> float:
|
|
72
|
+
"""Calculate cost using CostAdapterFactory.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
provider: Provider name (openai, anthropic, etc.)
|
|
76
|
+
model: Model name
|
|
77
|
+
input_tokens: Number of input tokens
|
|
78
|
+
output_tokens: Number of output tokens
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Cost in USD
|
|
82
|
+
"""
|
|
83
|
+
if CostAdapterFactory is None:
|
|
84
|
+
return 0.0
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
return CostAdapterFactory.compute_cost(provider, model, input_tokens, output_tokens)
|
|
88
|
+
except Exception:
|
|
89
|
+
return 0.0
|
|
90
|
+
|
|
91
|
+
|
|
24
92
|
class KalibrCrewAIInstrumentor:
|
|
25
93
|
"""Auto-instrumentation for CrewAI.
|
|
26
94
|
|
|
@@ -66,7 +134,7 @@ class KalibrCrewAIInstrumentor:
|
|
|
66
134
|
self.api_key = api_key or os.getenv("KALIBR_API_KEY", "")
|
|
67
135
|
self.endpoint = endpoint or os.getenv(
|
|
68
136
|
"KALIBR_ENDPOINT",
|
|
69
|
-
os.getenv("KALIBR_API_ENDPOINT", "
|
|
137
|
+
os.getenv("KALIBR_API_ENDPOINT", "https://api.kalibr.systems/api/v1/traces")
|
|
70
138
|
)
|
|
71
139
|
self.tenant_id = tenant_id or os.getenv("KALIBR_TENANT_ID", "default")
|
|
72
140
|
self.environment = environment or os.getenv("KALIBR_ENVIRONMENT", "prod")
|
|
@@ -84,6 +152,10 @@ class KalibrCrewAIInstrumentor:
|
|
|
84
152
|
# Instrumentation state
|
|
85
153
|
self._is_instrumented = False
|
|
86
154
|
|
|
155
|
+
# Accumulated metrics for crew-level aggregation
|
|
156
|
+
self._accumulated_tokens = {"input": 0, "output": 0}
|
|
157
|
+
self._accumulated_cost = 0.0
|
|
158
|
+
|
|
87
159
|
def instrument(self) -> bool:
|
|
88
160
|
"""Instrument CrewAI classes.
|
|
89
161
|
|
|
@@ -170,11 +242,22 @@ class KalibrCrewAIInstrumentor:
|
|
|
170
242
|
start_time = time.time()
|
|
171
243
|
ts_start = datetime.now(timezone.utc)
|
|
172
244
|
|
|
245
|
+
# Reset accumulators before crew execution
|
|
246
|
+
instrumentor._accumulated_tokens = {"input": 0, "output": 0}
|
|
247
|
+
instrumentor._accumulated_cost = 0.0
|
|
248
|
+
|
|
173
249
|
# Capture crew info
|
|
174
250
|
crew_name = getattr(crew_self, "name", None) or "unnamed_crew"
|
|
175
|
-
|
|
251
|
+
agents = getattr(crew_self, "agents", [])
|
|
252
|
+
agent_count = len(agents)
|
|
176
253
|
task_count = len(getattr(crew_self, "tasks", []))
|
|
177
254
|
|
|
255
|
+
# Extract model from first agent if available
|
|
256
|
+
model_name = "unknown"
|
|
257
|
+
provider = "crewai"
|
|
258
|
+
if agents:
|
|
259
|
+
model_name, provider = _extract_model_from_agent(agents[0])
|
|
260
|
+
|
|
178
261
|
status = "success"
|
|
179
262
|
error_type = None
|
|
180
263
|
error_message = None
|
|
@@ -194,12 +277,33 @@ class KalibrCrewAIInstrumentor:
|
|
|
194
277
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
195
278
|
ts_end = datetime.now(timezone.utc)
|
|
196
279
|
|
|
280
|
+
# Enrich CrewAI's OTel span with Kalibr telemetry
|
|
281
|
+
try:
|
|
282
|
+
current_span = otel_trace.get_current_span()
|
|
283
|
+
if current_span and current_span.is_recording():
|
|
284
|
+
current_span.set_attribute("kalibr.cost_usd", instrumentor._accumulated_cost)
|
|
285
|
+
current_span.set_attribute("kalibr.input_tokens", instrumentor._accumulated_tokens["input"])
|
|
286
|
+
current_span.set_attribute("kalibr.output_tokens", instrumentor._accumulated_tokens["output"])
|
|
287
|
+
current_span.set_attribute("kalibr.total_tokens", instrumentor._accumulated_tokens["input"] + instrumentor._accumulated_tokens["output"])
|
|
288
|
+
current_span.set_attribute("kalibr.model_id", model_name)
|
|
289
|
+
current_span.set_attribute("kalibr.provider", provider)
|
|
290
|
+
current_span.set_attribute("kalibr.duration_ms", duration_ms)
|
|
291
|
+
current_span.set_attribute("kalibr.tenant_id", instrumentor.tenant_id)
|
|
292
|
+
except Exception:
|
|
293
|
+
pass # Don't fail if span enrichment fails
|
|
294
|
+
|
|
197
295
|
# Build output info
|
|
198
296
|
output_preview = None
|
|
199
297
|
if instrumentor.capture_output and result is not None:
|
|
200
298
|
output_preview = str(result)[:500]
|
|
201
299
|
|
|
202
|
-
#
|
|
300
|
+
# Get accumulated metrics from child agent/task executions
|
|
301
|
+
input_tokens = instrumentor._accumulated_tokens["input"]
|
|
302
|
+
output_tokens = instrumentor._accumulated_tokens["output"]
|
|
303
|
+
total_tokens = input_tokens + output_tokens
|
|
304
|
+
cost_usd = instrumentor._accumulated_cost
|
|
305
|
+
|
|
306
|
+
# Create event with aggregated metrics
|
|
203
307
|
event = {
|
|
204
308
|
"schema_version": "1.0",
|
|
205
309
|
"trace_id": trace_id,
|
|
@@ -207,18 +311,18 @@ class KalibrCrewAIInstrumentor:
|
|
|
207
311
|
"parent_span_id": None,
|
|
208
312
|
"tenant_id": instrumentor.tenant_id,
|
|
209
313
|
"workflow_id": instrumentor.workflow_id,
|
|
210
|
-
"provider":
|
|
211
|
-
"model_id":
|
|
212
|
-
"model_name":
|
|
314
|
+
"provider": provider,
|
|
315
|
+
"model_id": model_name,
|
|
316
|
+
"model_name": model_name,
|
|
213
317
|
"operation": f"crew:{crew_name}",
|
|
214
318
|
"endpoint": "crew.kickoff",
|
|
215
319
|
"duration_ms": duration_ms,
|
|
216
320
|
"latency_ms": duration_ms,
|
|
217
|
-
"input_tokens":
|
|
218
|
-
"output_tokens":
|
|
219
|
-
"total_tokens":
|
|
220
|
-
"cost_usd":
|
|
221
|
-
"total_cost_usd":
|
|
321
|
+
"input_tokens": input_tokens,
|
|
322
|
+
"output_tokens": output_tokens,
|
|
323
|
+
"total_tokens": total_tokens,
|
|
324
|
+
"cost_usd": cost_usd,
|
|
325
|
+
"total_cost_usd": cost_usd,
|
|
222
326
|
"status": status,
|
|
223
327
|
"error_type": error_type,
|
|
224
328
|
"error_message": error_message,
|
|
@@ -255,10 +359,21 @@ class KalibrCrewAIInstrumentor:
|
|
|
255
359
|
start_time = time.time()
|
|
256
360
|
ts_start = datetime.now(timezone.utc)
|
|
257
361
|
|
|
362
|
+
# Reset accumulators before crew execution
|
|
363
|
+
instrumentor._accumulated_tokens = {"input": 0, "output": 0}
|
|
364
|
+
instrumentor._accumulated_cost = 0.0
|
|
365
|
+
|
|
258
366
|
crew_name = getattr(crew_self, "name", None) or "unnamed_crew"
|
|
259
|
-
|
|
367
|
+
agents = getattr(crew_self, "agents", [])
|
|
368
|
+
agent_count = len(agents)
|
|
260
369
|
task_count = len(getattr(crew_self, "tasks", []))
|
|
261
370
|
|
|
371
|
+
# Extract model from first agent if available
|
|
372
|
+
model_name = "unknown"
|
|
373
|
+
provider = "crewai"
|
|
374
|
+
if agents:
|
|
375
|
+
model_name, provider = _extract_model_from_agent(agents[0])
|
|
376
|
+
|
|
262
377
|
status = "success"
|
|
263
378
|
error_type = None
|
|
264
379
|
error_message = None
|
|
@@ -278,10 +393,32 @@ class KalibrCrewAIInstrumentor:
|
|
|
278
393
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
279
394
|
ts_end = datetime.now(timezone.utc)
|
|
280
395
|
|
|
396
|
+
# Enrich CrewAI's OTel span with Kalibr telemetry
|
|
397
|
+
try:
|
|
398
|
+
current_span = otel_trace.get_current_span()
|
|
399
|
+
if current_span and current_span.is_recording():
|
|
400
|
+
current_span.set_attribute("kalibr.cost_usd", instrumentor._accumulated_cost)
|
|
401
|
+
current_span.set_attribute("kalibr.input_tokens", instrumentor._accumulated_tokens["input"])
|
|
402
|
+
current_span.set_attribute("kalibr.output_tokens", instrumentor._accumulated_tokens["output"])
|
|
403
|
+
current_span.set_attribute("kalibr.total_tokens", instrumentor._accumulated_tokens["input"] + instrumentor._accumulated_tokens["output"])
|
|
404
|
+
current_span.set_attribute("kalibr.model_id", model_name)
|
|
405
|
+
current_span.set_attribute("kalibr.provider", provider)
|
|
406
|
+
current_span.set_attribute("kalibr.duration_ms", duration_ms)
|
|
407
|
+
current_span.set_attribute("kalibr.tenant_id", instrumentor.tenant_id)
|
|
408
|
+
except Exception:
|
|
409
|
+
pass # Don't fail if span enrichment fails
|
|
410
|
+
|
|
281
411
|
output_preview = None
|
|
282
412
|
if instrumentor.capture_output and result is not None:
|
|
283
413
|
output_preview = str(result)[:500]
|
|
284
414
|
|
|
415
|
+
# Get accumulated metrics from child agent/task executions
|
|
416
|
+
input_tokens = instrumentor._accumulated_tokens["input"]
|
|
417
|
+
output_tokens = instrumentor._accumulated_tokens["output"]
|
|
418
|
+
total_tokens = input_tokens + output_tokens
|
|
419
|
+
cost_usd = instrumentor._accumulated_cost
|
|
420
|
+
|
|
421
|
+
# Create event with aggregated metrics
|
|
285
422
|
event = {
|
|
286
423
|
"schema_version": "1.0",
|
|
287
424
|
"trace_id": trace_id,
|
|
@@ -289,18 +426,18 @@ class KalibrCrewAIInstrumentor:
|
|
|
289
426
|
"parent_span_id": None,
|
|
290
427
|
"tenant_id": instrumentor.tenant_id,
|
|
291
428
|
"workflow_id": instrumentor.workflow_id,
|
|
292
|
-
"provider":
|
|
293
|
-
"model_id":
|
|
294
|
-
"model_name":
|
|
429
|
+
"provider": provider,
|
|
430
|
+
"model_id": model_name,
|
|
431
|
+
"model_name": model_name,
|
|
295
432
|
"operation": f"crew:{crew_name}",
|
|
296
433
|
"endpoint": "crew.kickoff_async",
|
|
297
434
|
"duration_ms": duration_ms,
|
|
298
435
|
"latency_ms": duration_ms,
|
|
299
|
-
"input_tokens":
|
|
300
|
-
"output_tokens":
|
|
301
|
-
"total_tokens":
|
|
302
|
-
"cost_usd":
|
|
303
|
-
"total_cost_usd":
|
|
436
|
+
"input_tokens": input_tokens,
|
|
437
|
+
"output_tokens": output_tokens,
|
|
438
|
+
"total_tokens": total_tokens,
|
|
439
|
+
"cost_usd": cost_usd,
|
|
440
|
+
"total_cost_usd": cost_usd,
|
|
304
441
|
"status": status,
|
|
305
442
|
"error_type": error_type,
|
|
306
443
|
"error_message": error_message,
|
|
@@ -341,6 +478,9 @@ class KalibrCrewAIInstrumentor:
|
|
|
341
478
|
role = getattr(agent_self, "role", "unknown")
|
|
342
479
|
goal = getattr(agent_self, "goal", "")
|
|
343
480
|
|
|
481
|
+
# Extract model from agent's LLM config
|
|
482
|
+
model_name, provider = _extract_model_from_agent(agent_self)
|
|
483
|
+
|
|
344
484
|
# Get task info
|
|
345
485
|
task_description = ""
|
|
346
486
|
if hasattr(task, "description"):
|
|
@@ -370,8 +510,16 @@ class KalibrCrewAIInstrumentor:
|
|
|
370
510
|
output_preview = str(result)[:500]
|
|
371
511
|
|
|
372
512
|
# Token estimation
|
|
373
|
-
input_tokens = _count_tokens(task_description + goal,
|
|
374
|
-
output_tokens = _count_tokens(output_preview or "",
|
|
513
|
+
input_tokens = _count_tokens(task_description + goal, model_name)
|
|
514
|
+
output_tokens = _count_tokens(output_preview or "", model_name)
|
|
515
|
+
|
|
516
|
+
# Calculate cost using CostAdapterFactory
|
|
517
|
+
cost_usd = _calculate_cost(provider, model_name, input_tokens, output_tokens)
|
|
518
|
+
|
|
519
|
+
# Accumulate metrics for crew-level aggregation
|
|
520
|
+
instrumentor._accumulated_tokens["input"] += input_tokens
|
|
521
|
+
instrumentor._accumulated_tokens["output"] += output_tokens
|
|
522
|
+
instrumentor._accumulated_cost += cost_usd
|
|
375
523
|
|
|
376
524
|
event = {
|
|
377
525
|
"schema_version": "1.0",
|
|
@@ -380,9 +528,9 @@ class KalibrCrewAIInstrumentor:
|
|
|
380
528
|
"parent_span_id": None,
|
|
381
529
|
"tenant_id": instrumentor.tenant_id,
|
|
382
530
|
"workflow_id": instrumentor.workflow_id,
|
|
383
|
-
"provider":
|
|
384
|
-
"model_id":
|
|
385
|
-
"model_name":
|
|
531
|
+
"provider": provider,
|
|
532
|
+
"model_id": model_name,
|
|
533
|
+
"model_name": model_name,
|
|
386
534
|
"operation": f"agent:{role}",
|
|
387
535
|
"endpoint": "agent.execute_task",
|
|
388
536
|
"duration_ms": duration_ms,
|
|
@@ -390,8 +538,8 @@ class KalibrCrewAIInstrumentor:
|
|
|
390
538
|
"input_tokens": input_tokens,
|
|
391
539
|
"output_tokens": output_tokens,
|
|
392
540
|
"total_tokens": input_tokens + output_tokens,
|
|
393
|
-
"cost_usd":
|
|
394
|
-
"total_cost_usd":
|
|
541
|
+
"cost_usd": cost_usd,
|
|
542
|
+
"total_cost_usd": cost_usd,
|
|
395
543
|
"status": status,
|
|
396
544
|
"error_type": error_type,
|
|
397
545
|
"error_message": error_message,
|
|
@@ -430,6 +578,13 @@ class KalibrCrewAIInstrumentor:
|
|
|
430
578
|
description = getattr(task_self, "description", "")
|
|
431
579
|
expected_output = getattr(task_self, "expected_output", "")
|
|
432
580
|
|
|
581
|
+
# Try to extract model from task's agent
|
|
582
|
+
model_name = "unknown"
|
|
583
|
+
provider = "openai"
|
|
584
|
+
agent = getattr(task_self, "agent", None)
|
|
585
|
+
if agent:
|
|
586
|
+
model_name, provider = _extract_model_from_agent(agent)
|
|
587
|
+
|
|
433
588
|
status = "success"
|
|
434
589
|
error_type = None
|
|
435
590
|
error_message = None
|
|
@@ -456,8 +611,16 @@ class KalibrCrewAIInstrumentor:
|
|
|
456
611
|
else:
|
|
457
612
|
output_preview = str(result)[:500]
|
|
458
613
|
|
|
459
|
-
input_tokens = _count_tokens(description,
|
|
460
|
-
output_tokens = _count_tokens(output_preview or "",
|
|
614
|
+
input_tokens = _count_tokens(description, model_name)
|
|
615
|
+
output_tokens = _count_tokens(output_preview or "", model_name)
|
|
616
|
+
|
|
617
|
+
# Calculate cost using CostAdapterFactory
|
|
618
|
+
cost_usd = _calculate_cost(provider, model_name, input_tokens, output_tokens)
|
|
619
|
+
|
|
620
|
+
# Accumulate metrics for crew-level aggregation
|
|
621
|
+
instrumentor._accumulated_tokens["input"] += input_tokens
|
|
622
|
+
instrumentor._accumulated_tokens["output"] += output_tokens
|
|
623
|
+
instrumentor._accumulated_cost += cost_usd
|
|
461
624
|
|
|
462
625
|
event = {
|
|
463
626
|
"schema_version": "1.0",
|
|
@@ -466,9 +629,9 @@ class KalibrCrewAIInstrumentor:
|
|
|
466
629
|
"parent_span_id": None,
|
|
467
630
|
"tenant_id": instrumentor.tenant_id,
|
|
468
631
|
"workflow_id": instrumentor.workflow_id,
|
|
469
|
-
"provider":
|
|
470
|
-
"model_id":
|
|
471
|
-
"model_name":
|
|
632
|
+
"provider": provider,
|
|
633
|
+
"model_id": model_name,
|
|
634
|
+
"model_name": model_name,
|
|
472
635
|
"operation": f"task:{description[:30]}..." if len(description) > 30 else f"task:{description}",
|
|
473
636
|
"endpoint": "task.execute_sync",
|
|
474
637
|
"duration_ms": duration_ms,
|
|
@@ -476,8 +639,8 @@ class KalibrCrewAIInstrumentor:
|
|
|
476
639
|
"input_tokens": input_tokens,
|
|
477
640
|
"output_tokens": output_tokens,
|
|
478
641
|
"total_tokens": input_tokens + output_tokens,
|
|
479
|
-
"cost_usd":
|
|
480
|
-
"total_cost_usd":
|
|
642
|
+
"cost_usd": cost_usd,
|
|
643
|
+
"total_cost_usd": cost_usd,
|
|
481
644
|
"status": status,
|
|
482
645
|
"error_type": error_type,
|
|
483
646
|
"error_message": error_message,
|
kalibr_langchain/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Kalibr LangChain Integration - Observability for LangChain applications.
|
|
1
|
+
"""Kalibr LangChain Integration - Observability and Routing for LangChain applications.
|
|
2
2
|
|
|
3
3
|
This package provides a callback handler that integrates LangChain with
|
|
4
4
|
Kalibr's observability platform, capturing:
|
|
@@ -29,7 +29,7 @@ Usage:
|
|
|
29
29
|
|
|
30
30
|
Environment Variables:
|
|
31
31
|
KALIBR_API_KEY: API key for authentication
|
|
32
|
-
|
|
32
|
+
KALIBR_COLLECTOR_URL: Backend endpoint URL
|
|
33
33
|
KALIBR_TENANT_ID: Tenant identifier
|
|
34
34
|
KALIBR_ENVIRONMENT: Environment (prod/staging/dev)
|
|
35
35
|
KALIBR_SERVICE: Service name
|
|
@@ -39,9 +39,11 @@ __version__ = "0.1.0"
|
|
|
39
39
|
|
|
40
40
|
from .callback import KalibrCallbackHandler
|
|
41
41
|
from .async_callback import AsyncKalibrCallbackHandler
|
|
42
|
+
from .chat_model import KalibrChatModel
|
|
42
43
|
|
|
43
44
|
__all__ = [
|
|
44
45
|
"KalibrCallbackHandler",
|
|
45
46
|
"AsyncKalibrCallbackHandler",
|
|
47
|
+
"KalibrChatModel",
|
|
46
48
|
"__version__",
|
|
47
49
|
]
|
|
@@ -65,7 +65,7 @@ class AsyncKalibrCallbackHandler(AsyncCallbackHandler):
|
|
|
65
65
|
self.api_key = api_key or os.getenv("KALIBR_API_KEY", "")
|
|
66
66
|
self.endpoint = endpoint or os.getenv(
|
|
67
67
|
"KALIBR_ENDPOINT",
|
|
68
|
-
os.getenv("KALIBR_API_ENDPOINT", "
|
|
68
|
+
os.getenv("KALIBR_API_ENDPOINT", "https://api.kalibr.systems/api/v1/traces")
|
|
69
69
|
)
|
|
70
70
|
self.tenant_id = tenant_id or os.getenv("KALIBR_TENANT_ID", "default")
|
|
71
71
|
self.environment = environment or os.getenv("KALIBR_ENVIRONMENT", "prod")
|