kalibr 1.0.28__py3-none-any.whl → 1.1.2a0__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 +170 -3
- kalibr/__main__.py +3 -203
- kalibr/capsule_middleware.py +108 -0
- kalibr/cli/__init__.py +5 -0
- kalibr/cli/capsule_cmd.py +174 -0
- kalibr/cli/deploy_cmd.py +114 -0
- kalibr/cli/main.py +67 -0
- kalibr/cli/run.py +203 -0
- kalibr/cli/serve.py +59 -0
- kalibr/client.py +293 -0
- kalibr/collector.py +173 -0
- kalibr/context.py +132 -0
- kalibr/cost_adapter.py +222 -0
- kalibr/decorators.py +140 -0
- kalibr/instrumentation/__init__.py +13 -0
- kalibr/instrumentation/anthropic_instr.py +282 -0
- kalibr/instrumentation/base.py +108 -0
- kalibr/instrumentation/google_instr.py +281 -0
- kalibr/instrumentation/openai_instr.py +265 -0
- kalibr/instrumentation/registry.py +153 -0
- kalibr/kalibr.py +144 -230
- kalibr/kalibr_app.py +53 -314
- kalibr/middleware/__init__.py +5 -0
- kalibr/middleware/auto_tracer.py +356 -0
- kalibr/models.py +41 -0
- kalibr/redaction.py +44 -0
- kalibr/schemas.py +116 -0
- kalibr/simple_tracer.py +258 -0
- kalibr/tokens.py +52 -0
- kalibr/trace_capsule.py +296 -0
- kalibr/trace_models.py +201 -0
- kalibr/tracer.py +354 -0
- kalibr/types.py +25 -93
- kalibr/utils.py +198 -0
- kalibr-1.1.2a0.dist-info/METADATA +236 -0
- kalibr-1.1.2a0.dist-info/RECORD +48 -0
- kalibr-1.1.2a0.dist-info/entry_points.txt +2 -0
- kalibr-1.1.2a0.dist-info/licenses/LICENSE +21 -0
- kalibr-1.1.2a0.dist-info/top_level.txt +4 -0
- kalibr_crewai/__init__.py +65 -0
- kalibr_crewai/callbacks.py +539 -0
- kalibr_crewai/instrumentor.py +513 -0
- kalibr_langchain/__init__.py +47 -0
- kalibr_langchain/async_callback.py +850 -0
- kalibr_langchain/callback.py +1064 -0
- kalibr_openai_agents/__init__.py +43 -0
- kalibr_openai_agents/processor.py +554 -0
- kalibr/deployment.py +0 -41
- kalibr/packager.py +0 -43
- kalibr/runtime_router.py +0 -138
- kalibr/schema_generators.py +0 -159
- kalibr/validator.py +0 -70
- kalibr-1.0.28.data/data/examples/README.md +0 -173
- kalibr-1.0.28.data/data/examples/basic_kalibr_example.py +0 -66
- kalibr-1.0.28.data/data/examples/enhanced_kalibr_example.py +0 -347
- kalibr-1.0.28.dist-info/METADATA +0 -175
- kalibr-1.0.28.dist-info/RECORD +0 -19
- kalibr-1.0.28.dist-info/entry_points.txt +0 -2
- kalibr-1.0.28.dist-info/licenses/LICENSE +0 -11
- kalibr-1.0.28.dist-info/top_level.txt +0 -1
- {kalibr-1.0.28.dist-info → kalibr-1.1.2a0.dist-info}/WHEEL +0 -0
kalibr/trace_models.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified Trace Event Models - Generated from trace_event_schema.json v1.0.0
|
|
3
|
+
|
|
4
|
+
This module provides Pydantic models for trace events that are shared
|
|
5
|
+
between the SDK and backend to ensure schema consistency.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Any, Dict, Literal, Optional
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TraceEvent(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
Unified trace event model for LLM observability.
|
|
17
|
+
|
|
18
|
+
Compatible with:
|
|
19
|
+
- Kalibr SDK v1.0.30+
|
|
20
|
+
- Kalibr Backend v1.0+
|
|
21
|
+
- ClickHouse storage schema
|
|
22
|
+
|
|
23
|
+
Phase 4.5: Strict validation enabled to enforce data quality.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
model_config = ConfigDict(
|
|
27
|
+
# Phase 4.5: Relaxed strict mode to allow type coercion (str -> datetime)
|
|
28
|
+
# but keep extra='forbid' to reject unknown fields
|
|
29
|
+
extra="forbid", # Reject unknown fields - this is the key validation
|
|
30
|
+
validate_assignment=True,
|
|
31
|
+
json_schema_extra={
|
|
32
|
+
"example": {
|
|
33
|
+
"schema_version": "1.0",
|
|
34
|
+
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
35
|
+
"span_id": "a1b2c3d4-e5f6-47a8-b9c0-123456789abc",
|
|
36
|
+
"parent_span_id": None,
|
|
37
|
+
"tenant_id": "acme-corp",
|
|
38
|
+
"provider": "openai",
|
|
39
|
+
"model_id": "gpt-4o",
|
|
40
|
+
"operation": "chat_completion",
|
|
41
|
+
"duration_ms": 250,
|
|
42
|
+
"input_tokens": 100,
|
|
43
|
+
"output_tokens": 50,
|
|
44
|
+
"cost_usd": 0.000375,
|
|
45
|
+
"status": "success",
|
|
46
|
+
"timestamp": "2025-10-30T12:00:00.000Z",
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Schema metadata
|
|
52
|
+
schema_version: Literal["1.0"] = Field(description="Schema version (1.0)")
|
|
53
|
+
|
|
54
|
+
# Identity
|
|
55
|
+
trace_id: str = Field(
|
|
56
|
+
min_length=16,
|
|
57
|
+
max_length=36,
|
|
58
|
+
description="Unique trace identifier (UUID or 16-char alphanumeric)",
|
|
59
|
+
)
|
|
60
|
+
span_id: str = Field(
|
|
61
|
+
min_length=16,
|
|
62
|
+
max_length=36,
|
|
63
|
+
description="Unique span identifier (UUIDv4 format)"
|
|
64
|
+
)
|
|
65
|
+
parent_span_id: Optional[str] = Field(
|
|
66
|
+
None,
|
|
67
|
+
min_length=16,
|
|
68
|
+
max_length=36,
|
|
69
|
+
description="Parent span ID for nested operations (UUIDv4 format)"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Tenant & Context
|
|
73
|
+
tenant_id: str = Field(min_length=1, max_length=64, description="Tenant identifier")
|
|
74
|
+
workflow_id: Optional[str] = Field(
|
|
75
|
+
None, max_length=64, description="Workflow identifier for multi-step operations"
|
|
76
|
+
)
|
|
77
|
+
sandbox_id: Optional[str] = Field(
|
|
78
|
+
None, max_length=64, description="Sandbox/VM/Environment identifier"
|
|
79
|
+
)
|
|
80
|
+
runtime_env: Optional[str] = Field(
|
|
81
|
+
None, max_length=32, description="Runtime environment (vercel_vm, fly_io, local, etc.)"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# LLM Details
|
|
85
|
+
provider: Literal["openai", "anthropic", "google", "cohere", "custom"] = Field(
|
|
86
|
+
description="LLM provider"
|
|
87
|
+
)
|
|
88
|
+
model_id: str = Field(
|
|
89
|
+
min_length=1, max_length=64, description="Model identifier (e.g., gpt-4o, claude-3-opus)"
|
|
90
|
+
)
|
|
91
|
+
model_name: Optional[str] = Field(
|
|
92
|
+
None, description="Human-readable model name (optional, defaults to model_id)"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Operation
|
|
96
|
+
operation: str = Field(
|
|
97
|
+
min_length=1,
|
|
98
|
+
max_length=64,
|
|
99
|
+
description="Operation type (e.g., chat_completion, summarize, refine)",
|
|
100
|
+
)
|
|
101
|
+
endpoint: Optional[str] = Field(
|
|
102
|
+
None, max_length=128, description="API endpoint or function name"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Performance
|
|
106
|
+
duration_ms: int = Field(ge=0, description="Total duration in milliseconds")
|
|
107
|
+
latency_ms: Optional[int] = Field(None, description="Legacy field, same as duration_ms")
|
|
108
|
+
|
|
109
|
+
# Tokens
|
|
110
|
+
input_tokens: int = Field(ge=0, description="Number of input tokens")
|
|
111
|
+
output_tokens: int = Field(ge=0, description="Number of output tokens")
|
|
112
|
+
total_tokens: Optional[int] = Field(
|
|
113
|
+
None, description="Total tokens (input + output), computed if not provided"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Cost
|
|
117
|
+
cost_usd: float = Field(ge=0.0, description="Total cost in USD")
|
|
118
|
+
total_cost_usd: Optional[float] = Field(None, description="Legacy field, same as cost_usd")
|
|
119
|
+
unit_price_usd: Optional[float] = Field(None, ge=0.0, description="Price per token in USD")
|
|
120
|
+
|
|
121
|
+
# Status & Errors
|
|
122
|
+
status: Literal["success", "error", "timeout"] = Field(description="Execution status")
|
|
123
|
+
error_type: Optional[str] = Field(
|
|
124
|
+
None, max_length=64, description="Error class name if status is error"
|
|
125
|
+
)
|
|
126
|
+
error_message: Optional[str] = Field(
|
|
127
|
+
None, max_length=512, description="Error message if status is error"
|
|
128
|
+
)
|
|
129
|
+
stack_trace: Optional[str] = Field(None, description="Stack trace for errors (optional)")
|
|
130
|
+
|
|
131
|
+
# Timestamps
|
|
132
|
+
timestamp: datetime = Field(description="Event timestamp (ISO 8601 UTC)")
|
|
133
|
+
ts_start: Optional[datetime] = Field(None, description="Operation start time (ISO 8601 UTC)")
|
|
134
|
+
ts_end: Optional[datetime] = Field(None, description="Operation end time (ISO 8601 UTC)")
|
|
135
|
+
|
|
136
|
+
# Environment
|
|
137
|
+
environment: Optional[Literal["prod", "staging", "dev"]] = Field(
|
|
138
|
+
None, description="Deployment environment"
|
|
139
|
+
)
|
|
140
|
+
service: Optional[str] = Field(None, max_length=64, description="Service name")
|
|
141
|
+
|
|
142
|
+
# User Context
|
|
143
|
+
user_id: Optional[str] = Field(
|
|
144
|
+
None, max_length=64, description="End user identifier (anonymized)"
|
|
145
|
+
)
|
|
146
|
+
request_id: Optional[str] = Field(
|
|
147
|
+
None, max_length=64, description="Request identifier for correlation"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Metadata
|
|
151
|
+
metadata: Optional[Dict[str, Any]] = Field(None, description="Additional custom metadata")
|
|
152
|
+
data_class: Optional[Literal["economic", "performance", "diagnostic"]] = Field(
|
|
153
|
+
None, description="Data classification"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Legacy fields
|
|
157
|
+
vendor: Optional[str] = Field(None, description="Legacy field, same as provider")
|
|
158
|
+
|
|
159
|
+
@field_validator("total_tokens", mode="before")
|
|
160
|
+
@classmethod
|
|
161
|
+
def compute_total_tokens(cls, v, info):
|
|
162
|
+
"""Auto-compute total_tokens if not provided."""
|
|
163
|
+
if v is None and "input_tokens" in info.data and "output_tokens" in info.data:
|
|
164
|
+
return info.data["input_tokens"] + info.data["output_tokens"]
|
|
165
|
+
return v
|
|
166
|
+
|
|
167
|
+
@field_validator("model_name", mode="before")
|
|
168
|
+
@classmethod
|
|
169
|
+
def default_model_name(cls, v, info):
|
|
170
|
+
"""Default model_name to model_id if not provided."""
|
|
171
|
+
if v is None and "model_id" in info.data:
|
|
172
|
+
return info.data["model_id"]
|
|
173
|
+
return v
|
|
174
|
+
|
|
175
|
+
@field_validator("latency_ms", mode="before")
|
|
176
|
+
@classmethod
|
|
177
|
+
def sync_latency(cls, v, info):
|
|
178
|
+
"""Sync latency_ms with duration_ms if not provided."""
|
|
179
|
+
if v is None and "duration_ms" in info.data:
|
|
180
|
+
return info.data["duration_ms"]
|
|
181
|
+
return v
|
|
182
|
+
|
|
183
|
+
@field_validator("total_cost_usd", mode="before")
|
|
184
|
+
@classmethod
|
|
185
|
+
def sync_cost(cls, v, info):
|
|
186
|
+
"""Sync total_cost_usd with cost_usd if not provided."""
|
|
187
|
+
if v is None and "cost_usd" in info.data:
|
|
188
|
+
return info.data["cost_usd"]
|
|
189
|
+
return v
|
|
190
|
+
|
|
191
|
+
@field_validator("vendor", mode="before")
|
|
192
|
+
@classmethod
|
|
193
|
+
def sync_vendor(cls, v, info):
|
|
194
|
+
"""Sync vendor with provider if not provided."""
|
|
195
|
+
if v is None and "provider" in info.data:
|
|
196
|
+
return info.data["provider"]
|
|
197
|
+
return v
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# Convenience type aliases
|
|
201
|
+
TraceEventDict = Dict[str, Any]
|
kalibr/tracer.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Centralized trace management and event creation.
|
|
2
|
+
|
|
3
|
+
This module handles:
|
|
4
|
+
- Trace lifecycle management
|
|
5
|
+
- Span creation and context propagation
|
|
6
|
+
- Event standardization to schema v1.0
|
|
7
|
+
- Error capturing with stack traces
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import traceback
|
|
11
|
+
import uuid
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Any, Dict, Optional
|
|
14
|
+
|
|
15
|
+
from .context import get_trace_id, new_span_id, trace_context
|
|
16
|
+
from .cost_adapter import CostAdapterFactory
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Tracer:
|
|
20
|
+
"""Centralized tracer for creating and managing trace events."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
tenant_id: str = "default",
|
|
25
|
+
environment: str = "prod",
|
|
26
|
+
service: str = "kalibr-app",
|
|
27
|
+
workflow_id: str = "default-workflow",
|
|
28
|
+
workflow_version: str = "1.0",
|
|
29
|
+
sandbox_id: str = "local",
|
|
30
|
+
runtime_env: str = "local",
|
|
31
|
+
parent_trace_id: Optional[str] = None,
|
|
32
|
+
):
|
|
33
|
+
"""Initialize tracer.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
tenant_id: Tenant identifier
|
|
37
|
+
environment: Environment (prod/staging/dev)
|
|
38
|
+
service: Service name
|
|
39
|
+
workflow_id: Workflow identifier
|
|
40
|
+
workflow_version: Workflow version for A/B testing
|
|
41
|
+
sandbox_id: Sandbox/VM identifier
|
|
42
|
+
runtime_env: Runtime environment
|
|
43
|
+
parent_trace_id: Parent trace ID for nested workflows
|
|
44
|
+
"""
|
|
45
|
+
self.tenant_id = tenant_id
|
|
46
|
+
self.environment = environment
|
|
47
|
+
self.service = service
|
|
48
|
+
self.workflow_id = workflow_id
|
|
49
|
+
self.workflow_version = workflow_version
|
|
50
|
+
self.sandbox_id = sandbox_id
|
|
51
|
+
self.runtime_env = runtime_env
|
|
52
|
+
self.parent_trace_id = parent_trace_id
|
|
53
|
+
|
|
54
|
+
def create_span(
|
|
55
|
+
self,
|
|
56
|
+
operation: str,
|
|
57
|
+
vendor: str = "unknown",
|
|
58
|
+
model_name: str = "unknown",
|
|
59
|
+
endpoint: Optional[str] = None,
|
|
60
|
+
) -> "SpanContext":
|
|
61
|
+
"""Create a new span context.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
operation: Operation type (chat_completion, embedding, etc.)
|
|
65
|
+
vendor: Vendor name (openai, anthropic, etc.)
|
|
66
|
+
model_name: Model identifier
|
|
67
|
+
endpoint: API endpoint or function name
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
SpanContext instance for managing span lifecycle
|
|
71
|
+
"""
|
|
72
|
+
# Get or create trace ID
|
|
73
|
+
trace_id = get_trace_id()
|
|
74
|
+
if not trace_id:
|
|
75
|
+
trace_id = str(uuid.uuid4())
|
|
76
|
+
|
|
77
|
+
# Create span ID
|
|
78
|
+
span_id = new_span_id()
|
|
79
|
+
|
|
80
|
+
# Get parent span ID from context
|
|
81
|
+
ctx = trace_context.get()
|
|
82
|
+
span_stack = ctx.get("span_stack", [])
|
|
83
|
+
parent_id = span_stack[-1] if span_stack else None
|
|
84
|
+
|
|
85
|
+
# Push span to stack
|
|
86
|
+
ctx["trace_id"] = trace_id
|
|
87
|
+
if "span_stack" not in ctx:
|
|
88
|
+
ctx["span_stack"] = []
|
|
89
|
+
ctx["span_stack"].append(span_id)
|
|
90
|
+
trace_context.set(ctx)
|
|
91
|
+
|
|
92
|
+
return SpanContext(
|
|
93
|
+
tracer=self,
|
|
94
|
+
trace_id=trace_id,
|
|
95
|
+
span_id=span_id,
|
|
96
|
+
parent_id=parent_id,
|
|
97
|
+
operation=operation,
|
|
98
|
+
vendor=vendor,
|
|
99
|
+
model_name=model_name,
|
|
100
|
+
endpoint=endpoint or operation,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def create_event(
|
|
104
|
+
self,
|
|
105
|
+
trace_id: str,
|
|
106
|
+
span_id: str,
|
|
107
|
+
parent_id: Optional[str],
|
|
108
|
+
operation: str,
|
|
109
|
+
vendor: str,
|
|
110
|
+
model_name: str,
|
|
111
|
+
endpoint: str,
|
|
112
|
+
timestamp: datetime,
|
|
113
|
+
latency_ms: int,
|
|
114
|
+
status: str,
|
|
115
|
+
tokens_in: int = 0,
|
|
116
|
+
tokens_out: int = 0,
|
|
117
|
+
error_type: Optional[str] = None,
|
|
118
|
+
error_message: Optional[str] = None,
|
|
119
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
120
|
+
) -> Dict[str, Any]:
|
|
121
|
+
"""Create standardized trace event matching schema v1.0 with billing fields.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
trace_id: Trace identifier
|
|
125
|
+
span_id: Span identifier
|
|
126
|
+
parent_id: Parent span ID (None for root)
|
|
127
|
+
operation: Operation type
|
|
128
|
+
vendor: Vendor name
|
|
129
|
+
model_name: Model identifier
|
|
130
|
+
endpoint: API endpoint or function name
|
|
131
|
+
timestamp: Event timestamp
|
|
132
|
+
latency_ms: Duration in milliseconds
|
|
133
|
+
status: Status (success/error)
|
|
134
|
+
tokens_in: Input token count
|
|
135
|
+
tokens_out: Output token count
|
|
136
|
+
error_type: Error type if status=error
|
|
137
|
+
error_message: Error message if status=error
|
|
138
|
+
metadata: Additional metadata
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Standardized event dict with billing attribution
|
|
142
|
+
"""
|
|
143
|
+
# Compute cost using adapter
|
|
144
|
+
cost_adapter_result = CostAdapterFactory.compute_cost(
|
|
145
|
+
vendor=vendor, model_name=model_name, tokens_in=tokens_in, tokens_out=tokens_out
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Calculate unit price (approximate)
|
|
149
|
+
total_tokens = tokens_in + tokens_out
|
|
150
|
+
unit_price_usd = cost_adapter_result / total_tokens if total_tokens > 0 else 0.0
|
|
151
|
+
|
|
152
|
+
# Classify data tier
|
|
153
|
+
if cost_adapter_result > 0 or tokens_in > 0:
|
|
154
|
+
data_class = "economic"
|
|
155
|
+
elif error_type:
|
|
156
|
+
data_class = "system"
|
|
157
|
+
else:
|
|
158
|
+
data_class = "economic"
|
|
159
|
+
|
|
160
|
+
event = {
|
|
161
|
+
"schema_version": "1.0", # Required by validator (legacy)
|
|
162
|
+
"data_class": data_class,
|
|
163
|
+
"trace_id": trace_id,
|
|
164
|
+
"span_id": span_id,
|
|
165
|
+
"parent_id": parent_id,
|
|
166
|
+
"timestamp": timestamp.isoformat(),
|
|
167
|
+
"ts_start": timestamp.isoformat(), # For backward compatibility
|
|
168
|
+
"ts_end": timestamp.isoformat(), # For backward compatibility
|
|
169
|
+
"endpoint": endpoint,
|
|
170
|
+
"service": self.service,
|
|
171
|
+
"vendor": vendor,
|
|
172
|
+
"operation": operation,
|
|
173
|
+
"cost_usd": cost_adapter_result,
|
|
174
|
+
"latency_ms": latency_ms,
|
|
175
|
+
"duration_ms": latency_ms, # For backward compatibility
|
|
176
|
+
"status": status,
|
|
177
|
+
# Billing & Attribution
|
|
178
|
+
"tenant_id": self.tenant_id,
|
|
179
|
+
"workflow_id": self.workflow_id,
|
|
180
|
+
"sandbox_id": self.sandbox_id,
|
|
181
|
+
"runtime_env": self.runtime_env,
|
|
182
|
+
# Workflow Context (NEW)
|
|
183
|
+
"parent_trace_id": self.parent_trace_id,
|
|
184
|
+
"workflow_version": self.workflow_version,
|
|
185
|
+
# Model details
|
|
186
|
+
"provider": vendor, # Same as vendor for compatibility
|
|
187
|
+
"model_id": model_name, # For backward compatibility
|
|
188
|
+
"model_name": model_name,
|
|
189
|
+
# Token usage
|
|
190
|
+
"input_tokens": tokens_in,
|
|
191
|
+
"output_tokens": tokens_out,
|
|
192
|
+
# Cost breakdown
|
|
193
|
+
"unit_price_usd": unit_price_usd,
|
|
194
|
+
"total_cost_usd": cost_adapter_result,
|
|
195
|
+
"execution_cost_usd": 0.0, # Infrastructure cost (enriched by collector)
|
|
196
|
+
"kalibr_fee_usd": 0.0, # Platform fee (enriched by collector)
|
|
197
|
+
# Error tracking
|
|
198
|
+
"error_type": error_type,
|
|
199
|
+
"error_message": error_message,
|
|
200
|
+
"error_class": None, # Enriched by collector
|
|
201
|
+
# Reliability Metrics (NEW)
|
|
202
|
+
"retry_count": metadata.get("retry_count", 0) if metadata else 0,
|
|
203
|
+
"queue_latency_ms": metadata.get("queue_latency_ms") if metadata else None,
|
|
204
|
+
"cold_start": metadata.get("cold_start", False) if metadata else False,
|
|
205
|
+
# Metadata
|
|
206
|
+
"metadata": {
|
|
207
|
+
"environment": self.environment,
|
|
208
|
+
"endpoint": endpoint,
|
|
209
|
+
**(metadata or {}),
|
|
210
|
+
},
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return event
|
|
214
|
+
|
|
215
|
+
def pop_span(self):
|
|
216
|
+
"""Pop current span from context stack."""
|
|
217
|
+
ctx = trace_context.get()
|
|
218
|
+
if ctx.get("span_stack"):
|
|
219
|
+
ctx["span_stack"].pop()
|
|
220
|
+
trace_context.set(ctx)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class SpanContext:
|
|
224
|
+
"""Context manager for span lifecycle."""
|
|
225
|
+
|
|
226
|
+
def __init__(
|
|
227
|
+
self,
|
|
228
|
+
tracer: Tracer,
|
|
229
|
+
trace_id: str,
|
|
230
|
+
span_id: str,
|
|
231
|
+
parent_id: Optional[str],
|
|
232
|
+
operation: str,
|
|
233
|
+
vendor: str,
|
|
234
|
+
model_name: str,
|
|
235
|
+
endpoint: str,
|
|
236
|
+
):
|
|
237
|
+
self.tracer = tracer
|
|
238
|
+
self.trace_id = trace_id
|
|
239
|
+
self.span_id = span_id
|
|
240
|
+
self.parent_id = parent_id
|
|
241
|
+
self.operation = operation
|
|
242
|
+
self.vendor = vendor
|
|
243
|
+
self.model_name = model_name
|
|
244
|
+
self.endpoint = endpoint
|
|
245
|
+
|
|
246
|
+
self.ts_start: Optional[datetime] = None
|
|
247
|
+
self.ts_end: Optional[datetime] = None
|
|
248
|
+
self.tokens_in: int = 0
|
|
249
|
+
self.tokens_out: int = 0
|
|
250
|
+
self.status: str = "success"
|
|
251
|
+
self.error_type: Optional[str] = None
|
|
252
|
+
self.error_message: Optional[str] = None
|
|
253
|
+
self.metadata: Dict[str, Any] = {}
|
|
254
|
+
|
|
255
|
+
# NEW: Reliability metrics
|
|
256
|
+
self.retry_count: int = 0
|
|
257
|
+
self.queue_latency_ms: Optional[int] = None
|
|
258
|
+
self.cold_start: bool = False
|
|
259
|
+
|
|
260
|
+
def __enter__(self):
|
|
261
|
+
"""Start span."""
|
|
262
|
+
self.ts_start = datetime.now(timezone.utc)
|
|
263
|
+
return self
|
|
264
|
+
|
|
265
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
266
|
+
"""End span and create event."""
|
|
267
|
+
self.ts_end = datetime.now(timezone.utc)
|
|
268
|
+
|
|
269
|
+
# Calculate duration
|
|
270
|
+
latency_ms = int((self.ts_end - self.ts_start).total_seconds() * 1000)
|
|
271
|
+
|
|
272
|
+
# Handle errors
|
|
273
|
+
if exc_type is not None:
|
|
274
|
+
self.status = "error"
|
|
275
|
+
self.error_type = exc_type.__name__
|
|
276
|
+
self.error_message = str(exc_val)
|
|
277
|
+
|
|
278
|
+
# Capture stack trace
|
|
279
|
+
tb_lines = traceback.format_exception(exc_type, exc_val, exc_tb)
|
|
280
|
+
self.metadata["stack_trace"] = "".join(tb_lines)
|
|
281
|
+
|
|
282
|
+
# Create event
|
|
283
|
+
event = self.tracer.create_event(
|
|
284
|
+
trace_id=self.trace_id,
|
|
285
|
+
span_id=self.span_id,
|
|
286
|
+
parent_id=self.parent_id,
|
|
287
|
+
operation=self.operation,
|
|
288
|
+
vendor=self.vendor,
|
|
289
|
+
model_name=self.model_name,
|
|
290
|
+
endpoint=self.endpoint,
|
|
291
|
+
timestamp=self.ts_start,
|
|
292
|
+
latency_ms=latency_ms,
|
|
293
|
+
status=self.status,
|
|
294
|
+
tokens_in=self.tokens_in,
|
|
295
|
+
tokens_out=self.tokens_out,
|
|
296
|
+
error_type=self.error_type,
|
|
297
|
+
error_message=self.error_message,
|
|
298
|
+
metadata=self.metadata,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Store event in context for retrieval
|
|
302
|
+
ctx = trace_context.get()
|
|
303
|
+
if "events" not in ctx:
|
|
304
|
+
ctx["events"] = []
|
|
305
|
+
ctx["events"].append(event)
|
|
306
|
+
trace_context.set(ctx)
|
|
307
|
+
|
|
308
|
+
# Pop span from stack
|
|
309
|
+
self.tracer.pop_span()
|
|
310
|
+
|
|
311
|
+
# Don't suppress exceptions
|
|
312
|
+
return False
|
|
313
|
+
|
|
314
|
+
def set_tokens(self, tokens_in: int, tokens_out: int):
|
|
315
|
+
"""Set token counts for span."""
|
|
316
|
+
self.tokens_in = tokens_in
|
|
317
|
+
self.tokens_out = tokens_out
|
|
318
|
+
|
|
319
|
+
def add_metadata(self, key: str, value: Any):
|
|
320
|
+
"""Add metadata to span."""
|
|
321
|
+
self.metadata[key] = value
|
|
322
|
+
|
|
323
|
+
def set_reliability_metrics(
|
|
324
|
+
self, retry_count: int = 0, queue_latency_ms: Optional[int] = None, cold_start: bool = False
|
|
325
|
+
):
|
|
326
|
+
"""Set reliability metrics for span.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
retry_count: Number of retries attempted
|
|
330
|
+
queue_latency_ms: Time spent in queue
|
|
331
|
+
cold_start: Whether this was a cold start
|
|
332
|
+
"""
|
|
333
|
+
self.retry_count = retry_count
|
|
334
|
+
self.queue_latency_ms = queue_latency_ms
|
|
335
|
+
self.cold_start = cold_start
|
|
336
|
+
|
|
337
|
+
# Also add to metadata for persistence
|
|
338
|
+
self.metadata["retry_count"] = retry_count
|
|
339
|
+
if queue_latency_ms is not None:
|
|
340
|
+
self.metadata["queue_latency_ms"] = queue_latency_ms
|
|
341
|
+
self.metadata["cold_start"] = cold_start
|
|
342
|
+
|
|
343
|
+
def set_error(self, error: Exception):
|
|
344
|
+
"""Mark span as error and capture details."""
|
|
345
|
+
self.status = "error"
|
|
346
|
+
self.error_message = str(error)
|
|
347
|
+
|
|
348
|
+
# Capture error type
|
|
349
|
+
if not hasattr(self, "error_type"):
|
|
350
|
+
self.error_type = type(error).__name__
|
|
351
|
+
|
|
352
|
+
# Capture stack trace
|
|
353
|
+
tb_lines = traceback.format_exception(type(error), error, error.__traceback__)
|
|
354
|
+
self.metadata["stack_trace"] = "".join(tb_lines)
|
kalibr/types.py
CHANGED
|
@@ -1,106 +1,38 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"""Type definitions for Kalibr SDK"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class FileUpload:
|
|
9
|
+
"""Represents an uploaded file"""
|
|
9
10
|
|
|
10
|
-
class FileUpload(BaseModel):
|
|
11
|
-
"""Enhanced file upload handling for AI model integrations"""
|
|
12
11
|
filename: str
|
|
13
12
|
content_type: str
|
|
14
13
|
size: int
|
|
15
14
|
content: bytes
|
|
16
|
-
upload_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
17
|
-
uploaded_at: datetime = Field(default_factory=datetime.now)
|
|
18
|
-
|
|
19
|
-
class Config:
|
|
20
|
-
arbitrary_types_allowed = True
|
|
21
15
|
|
|
22
|
-
class ImageData(BaseModel):
|
|
23
|
-
"""Image data type for AI vision capabilities"""
|
|
24
|
-
filename: str
|
|
25
|
-
content_type: str
|
|
26
|
-
width: Optional[int] = None
|
|
27
|
-
height: Optional[int] = None
|
|
28
|
-
format: str # jpeg, png, webp, etc.
|
|
29
|
-
content: bytes
|
|
30
|
-
image_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
31
|
-
|
|
32
|
-
class Config:
|
|
33
|
-
arbitrary_types_allowed = True
|
|
34
16
|
|
|
35
|
-
class
|
|
36
|
-
"""
|
|
37
|
-
headers: List[str]
|
|
38
|
-
rows: List[List[Any]]
|
|
39
|
-
table_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
40
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
17
|
+
class Session:
|
|
18
|
+
"""Session management for stateful interactions"""
|
|
41
19
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
content: Any
|
|
46
|
-
is_final: bool = False
|
|
47
|
-
timestamp: datetime = Field(default_factory=datetime.now)
|
|
20
|
+
def __init__(self, session_id: str):
|
|
21
|
+
self.session_id = session_id
|
|
22
|
+
self._data: Dict[str, Any] = {}
|
|
48
23
|
|
|
49
|
-
|
|
50
|
-
"""Session management for stateful interactions"""
|
|
51
|
-
session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
52
|
-
user_id: Optional[str] = None
|
|
53
|
-
created_at: datetime = Field(default_factory=datetime.now)
|
|
54
|
-
last_accessed: datetime = Field(default_factory=datetime.now)
|
|
55
|
-
data: Dict[str, Any] = Field(default_factory=dict)
|
|
56
|
-
expires_at: Optional[datetime] = None
|
|
57
|
-
|
|
58
|
-
def get(self, key: str, default=None):
|
|
24
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
59
25
|
"""Get session data"""
|
|
60
|
-
return self.
|
|
61
|
-
|
|
62
|
-
def set(self, key: str, value: Any):
|
|
63
|
-
"""Set session data"""
|
|
64
|
-
self.data[key] = value
|
|
65
|
-
self.last_accessed = datetime.now()
|
|
66
|
-
|
|
67
|
-
def delete(self, key: str):
|
|
68
|
-
"""Delete session data"""
|
|
69
|
-
if key in self.data:
|
|
70
|
-
del self.data[key]
|
|
26
|
+
return self._data.get(key, default)
|
|
71
27
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
username: str
|
|
76
|
-
email: Optional[str] = None
|
|
77
|
-
roles: List[str] = Field(default_factory=list)
|
|
78
|
-
permissions: List[str] = Field(default_factory=list)
|
|
79
|
-
auth_method: str # "jwt", "oauth", "api_key", etc.
|
|
80
|
-
|
|
81
|
-
class FileDownload(BaseModel):
|
|
82
|
-
"""File download response"""
|
|
83
|
-
filename: str
|
|
84
|
-
content_type: str
|
|
85
|
-
content: bytes
|
|
86
|
-
|
|
87
|
-
class Config:
|
|
88
|
-
arbitrary_types_allowed = True
|
|
28
|
+
def set(self, key: str, value: Any) -> None:
|
|
29
|
+
"""Set session data"""
|
|
30
|
+
self._data[key] = value
|
|
89
31
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
status: str # "success", "error", "pending"
|
|
94
|
-
data: Dict[str, Any] = Field(default_factory=dict)
|
|
95
|
-
created_at: datetime = Field(default_factory=datetime.now)
|
|
96
|
-
processing_time: Optional[float] = None
|
|
97
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
32
|
+
def delete(self, key: str) -> None:
|
|
33
|
+
"""Delete session data"""
|
|
34
|
+
self._data.pop(key, None)
|
|
98
35
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
step: str
|
|
103
|
-
status: str
|
|
104
|
-
data: Dict[str, Any] = Field(default_factory=dict)
|
|
105
|
-
created_at: datetime = Field(default_factory=datetime.now)
|
|
106
|
-
updated_at: datetime = Field(default_factory=datetime.now)
|
|
36
|
+
def clear(self) -> None:
|
|
37
|
+
"""Clear all session data"""
|
|
38
|
+
self._data.clear()
|