kalibr 1.0.28__tar.gz → 1.1.0a0__tar.gz

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 (71) hide show
  1. kalibr-1.1.0a0/LICENSE +21 -0
  2. kalibr-1.1.0a0/PKG-INFO +97 -0
  3. kalibr-1.1.0a0/README.md +54 -0
  4. kalibr-1.1.0a0/kalibr/__init__.py +172 -0
  5. kalibr-1.1.0a0/kalibr/__main__.py +6 -0
  6. kalibr-1.1.0a0/kalibr/capsule_middleware.py +108 -0
  7. kalibr-1.1.0a0/kalibr/cli/__init__.py +5 -0
  8. kalibr-1.1.0a0/kalibr/cli/capsule_cmd.py +174 -0
  9. kalibr-1.1.0a0/kalibr/cli/deploy_cmd.py +114 -0
  10. kalibr-1.1.0a0/kalibr/cli/main.py +67 -0
  11. kalibr-1.1.0a0/kalibr/cli/run.py +200 -0
  12. kalibr-1.1.0a0/kalibr/cli/serve.py +59 -0
  13. kalibr-1.1.0a0/kalibr/client.py +293 -0
  14. kalibr-1.1.0a0/kalibr/collector.py +173 -0
  15. kalibr-1.1.0a0/kalibr/context.py +132 -0
  16. kalibr-1.1.0a0/kalibr/cost_adapter.py +222 -0
  17. kalibr-1.1.0a0/kalibr/decorators.py +140 -0
  18. kalibr-1.1.0a0/kalibr/instrumentation/__init__.py +13 -0
  19. kalibr-1.1.0a0/kalibr/instrumentation/anthropic_instr.py +282 -0
  20. kalibr-1.1.0a0/kalibr/instrumentation/base.py +108 -0
  21. kalibr-1.1.0a0/kalibr/instrumentation/google_instr.py +281 -0
  22. kalibr-1.1.0a0/kalibr/instrumentation/openai_instr.py +265 -0
  23. kalibr-1.1.0a0/kalibr/instrumentation/registry.py +153 -0
  24. kalibr-1.1.0a0/kalibr/kalibr.py +173 -0
  25. kalibr-1.1.0a0/kalibr/kalibr_app.py +82 -0
  26. kalibr-1.1.0a0/kalibr/middleware/__init__.py +5 -0
  27. kalibr-1.1.0a0/kalibr/middleware/auto_tracer.py +356 -0
  28. kalibr-1.1.0a0/kalibr/models.py +41 -0
  29. kalibr-1.1.0a0/kalibr/redaction.py +44 -0
  30. kalibr-1.1.0a0/kalibr/schemas.py +116 -0
  31. kalibr-1.1.0a0/kalibr/simple_tracer.py +255 -0
  32. kalibr-1.1.0a0/kalibr/tokens.py +52 -0
  33. kalibr-1.1.0a0/kalibr/trace_capsule.py +296 -0
  34. kalibr-1.1.0a0/kalibr/trace_models.py +201 -0
  35. kalibr-1.1.0a0/kalibr/tracer.py +354 -0
  36. kalibr-1.1.0a0/kalibr/types.py +38 -0
  37. kalibr-1.1.0a0/kalibr/utils.py +198 -0
  38. kalibr-1.1.0a0/kalibr.egg-info/PKG-INFO +97 -0
  39. kalibr-1.1.0a0/kalibr.egg-info/SOURCES.txt +45 -0
  40. kalibr-1.1.0a0/kalibr.egg-info/entry_points.txt +2 -0
  41. kalibr-1.1.0a0/kalibr.egg-info/requires.txt +17 -0
  42. kalibr-1.1.0a0/pyproject.toml +75 -0
  43. kalibr-1.1.0a0/tests/test_capsule_builder.py +326 -0
  44. kalibr-1.1.0a0/tests/test_instrumentation.py +235 -0
  45. kalibr-1.0.28/LICENSE +0 -11
  46. kalibr-1.0.28/MANIFEST.in +0 -3
  47. kalibr-1.0.28/PKG-INFO +0 -175
  48. kalibr-1.0.28/README.md +0 -152
  49. kalibr-1.0.28/examples/README.md +0 -173
  50. kalibr-1.0.28/examples/__init__.py +0 -1
  51. kalibr-1.0.28/examples/basic_kalibr_example.py +0 -66
  52. kalibr-1.0.28/examples/enhanced_kalibr_example.py +0 -347
  53. kalibr-1.0.28/kalibr/__init__.py +0 -5
  54. kalibr-1.0.28/kalibr/__main__.py +0 -206
  55. kalibr-1.0.28/kalibr/deployment.py +0 -41
  56. kalibr-1.0.28/kalibr/kalibr.py +0 -259
  57. kalibr-1.0.28/kalibr/kalibr_app.py +0 -343
  58. kalibr-1.0.28/kalibr/packager.py +0 -43
  59. kalibr-1.0.28/kalibr/runtime_router.py +0 -138
  60. kalibr-1.0.28/kalibr/schema_generators.py +0 -159
  61. kalibr-1.0.28/kalibr/types.py +0 -106
  62. kalibr-1.0.28/kalibr/validator.py +0 -70
  63. kalibr-1.0.28/kalibr.egg-info/PKG-INFO +0 -175
  64. kalibr-1.0.28/kalibr.egg-info/SOURCES.txt +0 -25
  65. kalibr-1.0.28/kalibr.egg-info/entry_points.txt +0 -2
  66. kalibr-1.0.28/kalibr.egg-info/requires.txt +0 -7
  67. kalibr-1.0.28/pyproject.toml +0 -22
  68. kalibr-1.0.28/setup.py +0 -42
  69. {kalibr-1.0.28 → kalibr-1.1.0a0}/kalibr.egg-info/dependency_links.txt +0 -0
  70. {kalibr-1.0.28 → kalibr-1.1.0a0}/kalibr.egg-info/top_level.txt +0 -0
  71. {kalibr-1.0.28 → kalibr-1.1.0a0}/setup.cfg +0 -0
kalibr-1.1.0a0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Kalibr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: kalibr
3
+ Version: 1.1.0a0
4
+ Summary: Unified LLM Observability & Multi-Model AI Integration Framework - Deploy to GPT, Claude, Gemini, Copilot with full telemetry
5
+ Author-email: Kalibr Team <team@kalibr.dev>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/devonakelley/kalibr-sdk
8
+ Project-URL: Documentation, https://github.com/devonakelley/kalibr-sdk#readme
9
+ Project-URL: Repository, https://github.com/devonakelley/kalibr-sdk
10
+ Project-URL: Issues, https://github.com/devonakelley/kalibr-sdk/issues
11
+ Keywords: ai,mcp,gpt,claude,gemini,copilot,openai,anthropic,google,microsoft,observability,telemetry,tracing,llm,schema-generation,api,multi-model
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: httpx>=0.27.0
27
+ Requires-Dist: tiktoken>=0.8.0
28
+ Requires-Dist: fastapi>=0.110.1
29
+ Requires-Dist: uvicorn>=0.25.0
30
+ Requires-Dist: pydantic>=2.6.4
31
+ Requires-Dist: typer>=0.9.0
32
+ Requires-Dist: python-multipart>=0.0.9
33
+ Requires-Dist: rich>=10.0.0
34
+ Requires-Dist: requests>=2.31.0
35
+ Requires-Dist: opentelemetry-api>=1.20.0
36
+ Requires-Dist: opentelemetry-sdk>=1.20.0
37
+ Requires-Dist: opentelemetry-exporter-otlp>=1.20.0
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
40
+ Requires-Dist: black>=23.0.0; extra == "dev"
41
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
42
+ Dynamic: license-file
43
+
44
+ # Kalibr Python SDK
45
+
46
+ Production-grade observability for LLM applications.
47
+
48
+ ## Installation
49
+ ```bash
50
+ pip install kalibr
51
+ ```
52
+
53
+ ## Quickstart
54
+ ```python
55
+ from kalibr import trace
56
+ import openai
57
+
58
+ @trace(api_key="your-kalibr-api-key")
59
+ def my_agent():
60
+ response = openai.chat.completions.create(
61
+ model="gpt-4",
62
+ messages=[{"role": "user", "content": "Hello!"}]
63
+ )
64
+ return response
65
+ ```
66
+
67
+ ## Features
68
+
69
+ - ✅ Zero-code instrumentation for OpenAI, Anthropic, Google AI
70
+ - ✅ Automatic parent-child trace relationships
71
+ - ✅ Real-time cost tracking
72
+ - ✅ Token usage monitoring
73
+ - ✅ Performance metrics
74
+
75
+ ## CLI Tools
76
+ ```bash
77
+ # Run your app locally
78
+ kalibr serve myapp.py
79
+
80
+ # Deploy to Fly.io
81
+ kalibr deploy myapp.py
82
+
83
+ # Fetch trace data
84
+ kalibr capsule <trace-id>
85
+ ```
86
+
87
+ ## Examples
88
+
89
+ See `examples/` directory for complete examples.
90
+
91
+ ## Documentation
92
+
93
+ Full docs at https://docs.kalibr.systems
94
+
95
+ ## License
96
+
97
+ MIT
@@ -0,0 +1,54 @@
1
+ # Kalibr Python SDK
2
+
3
+ Production-grade observability for LLM applications.
4
+
5
+ ## Installation
6
+ ```bash
7
+ pip install kalibr
8
+ ```
9
+
10
+ ## Quickstart
11
+ ```python
12
+ from kalibr import trace
13
+ import openai
14
+
15
+ @trace(api_key="your-kalibr-api-key")
16
+ def my_agent():
17
+ response = openai.chat.completions.create(
18
+ model="gpt-4",
19
+ messages=[{"role": "user", "content": "Hello!"}]
20
+ )
21
+ return response
22
+ ```
23
+
24
+ ## Features
25
+
26
+ - ✅ Zero-code instrumentation for OpenAI, Anthropic, Google AI
27
+ - ✅ Automatic parent-child trace relationships
28
+ - ✅ Real-time cost tracking
29
+ - ✅ Token usage monitoring
30
+ - ✅ Performance metrics
31
+
32
+ ## CLI Tools
33
+ ```bash
34
+ # Run your app locally
35
+ kalibr serve myapp.py
36
+
37
+ # Deploy to Fly.io
38
+ kalibr deploy myapp.py
39
+
40
+ # Fetch trace data
41
+ kalibr capsule <trace-id>
42
+ ```
43
+
44
+ ## Examples
45
+
46
+ See `examples/` directory for complete examples.
47
+
48
+ ## Documentation
49
+
50
+ Full docs at https://docs.kalibr.systems
51
+
52
+ ## License
53
+
54
+ MIT
@@ -0,0 +1,172 @@
1
+ """Kalibr SDK v1.1.0 - Unified LLM Observability & Multi-Model AI Integration Framework
2
+
3
+ This SDK combines:
4
+ 1. Full LLM Observability with tracing, cost tracking, and analytics
5
+ 2. Multi-Model AI Integration (GPT, Claude, Gemini, Copilot)
6
+ 3. One-line deployment with Docker and runtime router
7
+ 4. Schema generation for all major AI platforms
8
+ 5. **NEW in 1.1.0**: Auto-instrumentation of LLM SDKs (OpenAI, Anthropic, Google)
9
+
10
+ Features:
11
+ - **Auto-Instrumentation**: Zero-config tracing of OpenAI, Anthropic, Google SDK calls
12
+ - **OpenTelemetry**: OTel-compatible spans with OTLP export
13
+ - **Tracing**: Complete telemetry with @trace decorator
14
+ - **Cost Tracking**: Multi-vendor cost calculation (OpenAI, Anthropic, etc.)
15
+ - **Deployment**: One-command deployment to Fly.io, Render, or local
16
+ - **Schema Generation**: Auto-generate schemas for GPT Actions, Claude MCP, Gemini, Copilot
17
+ - **Error Handling**: Automatic error capture with stack traces
18
+ - **Analytics**: ClickHouse-backed analytics and alerting
19
+
20
+ Usage - Auto-Instrumentation (NEW):
21
+ from kalibr import Kalibr
22
+ import openai # Automatically instrumented!
23
+
24
+ app = Kalibr(title="My API")
25
+
26
+ @app.action("chat", "Chat with GPT")
27
+ def chat(message: str):
28
+ # This OpenAI call is automatically traced!
29
+ response = openai.chat.completions.create(
30
+ model="gpt-4",
31
+ messages=[{"role": "user", "content": message}]
32
+ )
33
+ return response.choices[0].message.content
34
+
35
+ Usage - Manual Tracing:
36
+ from kalibr import trace
37
+
38
+ @trace(operation="chat_completion", vendor="openai", model="gpt-4")
39
+ def call_openai(prompt):
40
+ response = openai.chat.completions.create(
41
+ model="gpt-4",
42
+ messages=[{"role": "user", "content": prompt}]
43
+ )
44
+ return response
45
+
46
+ CLI Usage:
47
+ kalibr serve my_app.py # Run locally
48
+ kalibr deploy my_app.py --runtime fly # Deploy to Fly.io
49
+ kalibr run my_app.py # Run with auto-tracing
50
+ kalibr version # Show version
51
+ """
52
+
53
+ __version__ = "1.1.0-alpha"
54
+
55
+ # Auto-instrument LLM SDKs on import (can be disabled via env var)
56
+ import os
57
+
58
+ # ============================================================================
59
+ # OBSERVABILITY & TRACING (from 1023)
60
+ # ============================================================================
61
+ from .client import KalibrClient
62
+
63
+ # ============================================================================
64
+ # PHASE 1: SDK INSTRUMENTATION & OPENTELEMETRY (v1.1.0)
65
+ # ============================================================================
66
+ from .collector import (
67
+ get_tracer_provider,
68
+ )
69
+ from .collector import is_configured as is_collector_configured
70
+ from .collector import (
71
+ setup_collector,
72
+ )
73
+ from .context import get_parent_span_id, get_trace_id, new_trace_id, trace_context
74
+ from .cost_adapter import (
75
+ AnthropicCostAdapter,
76
+ BaseCostAdapter,
77
+ CostAdapterFactory,
78
+ OpenAICostAdapter,
79
+ )
80
+ from .instrumentation import auto_instrument, get_instrumented_providers
81
+
82
+ # ============================================================================
83
+ # SDK & DEPLOYMENT (from 1.0.30)
84
+ # ============================================================================
85
+ from .kalibr import Kalibr
86
+ from .kalibr_app import KalibrApp
87
+ from .models import EventData, TraceConfig
88
+ from .schemas import (
89
+ generate_copilot_schema,
90
+ generate_gemini_schema,
91
+ generate_mcp_schema,
92
+ get_base_url,
93
+ get_supported_models,
94
+ )
95
+ from .simple_tracer import trace
96
+ from .trace_capsule import TraceCapsule, get_or_create_capsule
97
+ from .tracer import SpanContext, Tracer
98
+ from .types import FileUpload, Session
99
+ from .utils import load_config_from_env
100
+
101
+ if os.getenv("KALIBR_AUTO_INSTRUMENT", "true").lower() == "true":
102
+ # Setup OpenTelemetry collector
103
+ try:
104
+ setup_collector(
105
+ service_name=os.getenv("KALIBR_SERVICE_NAME", "kalibr"),
106
+ file_export=True,
107
+ console_export=os.getenv("KALIBR_CONSOLE_EXPORT", "false").lower() == "true",
108
+ )
109
+ except Exception as e:
110
+ print(f"⚠️ Failed to setup OpenTelemetry collector: {e}")
111
+
112
+ # Auto-instrument available SDKs
113
+ try:
114
+ auto_instrument(["openai", "anthropic", "google"])
115
+ except Exception as e:
116
+ print(f"⚠️ Failed to auto-instrument SDKs: {e}")
117
+
118
+ __all__ = [
119
+ # ========================================================================
120
+ # OBSERVABILITY & TRACING
121
+ # ========================================================================
122
+ # Simple tracing API (recommended)
123
+ "trace",
124
+ # Capsule propagation (Phase 6)
125
+ "TraceCapsule",
126
+ "get_or_create_capsule",
127
+ # Client
128
+ "KalibrClient",
129
+ # Context
130
+ "trace_context",
131
+ "get_trace_id",
132
+ "get_parent_span_id",
133
+ "new_trace_id",
134
+ # Tracer
135
+ "Tracer",
136
+ "SpanContext",
137
+ # Cost Adapters
138
+ "BaseCostAdapter",
139
+ "OpenAICostAdapter",
140
+ "AnthropicCostAdapter",
141
+ "CostAdapterFactory",
142
+ # Models
143
+ "TraceConfig",
144
+ "EventData",
145
+ # Utils
146
+ "load_config_from_env",
147
+ # ========================================================================
148
+ # SDK & DEPLOYMENT
149
+ # ========================================================================
150
+ # SDK Classes
151
+ "Kalibr",
152
+ "KalibrApp",
153
+ # Types
154
+ "FileUpload",
155
+ "Session",
156
+ # Schema Generation
157
+ "get_base_url",
158
+ "generate_mcp_schema",
159
+ "generate_gemini_schema",
160
+ "generate_copilot_schema",
161
+ "get_supported_models",
162
+ # ========================================================================
163
+ # PHASE 1: SDK INSTRUMENTATION & OPENTELEMETRY (v1.1.0)
164
+ # ========================================================================
165
+ # Auto-instrumentation
166
+ "auto_instrument",
167
+ "get_instrumented_providers",
168
+ # OpenTelemetry collector
169
+ "setup_collector",
170
+ "get_tracer_provider",
171
+ "is_collector_configured",
172
+ ]
@@ -0,0 +1,6 @@
1
+ """Entry point for python -m kalibr"""
2
+
3
+ from kalibr.cli.main import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,108 @@
1
+ """
2
+ Kalibr Capsule Middleware for FastAPI.
3
+
4
+ Automatically extracts, propagates, and injects trace capsules in HTTP requests.
5
+
6
+ Usage in FastAPI app:
7
+ from kalibr.capsule_middleware import add_capsule_middleware
8
+
9
+ app = FastAPI()
10
+ add_capsule_middleware(app)
11
+
12
+ # Now all requests have request.state.capsule available
13
+ @app.get("/")
14
+ def endpoint(request: Request):
15
+ capsule = request.state.capsule
16
+ # Use capsule...
17
+ """
18
+
19
+ import logging
20
+
21
+ from fastapi import FastAPI, Request
22
+ from starlette.middleware.base import BaseHTTPMiddleware
23
+ from starlette.responses import Response
24
+
25
+ from .trace_capsule import TraceCapsule, get_or_create_capsule
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ CAPSULE_HEADER = "X-Kalibr-Capsule"
30
+
31
+
32
+ class CapsuleMiddleware(BaseHTTPMiddleware):
33
+ """Middleware that extracts and propagates Kalibr trace capsules.
34
+
35
+ This middleware:
36
+ 1. Extracts capsule from incoming X-Kalibr-Capsule header
37
+ 2. Attaches capsule to request.state for access in endpoints
38
+ 3. Automatically injects updated capsule in response headers
39
+ """
40
+
41
+ async def dispatch(self, request: Request, call_next):
42
+ """Process request and response with capsule handling.
43
+
44
+ Args:
45
+ request: Incoming HTTP request
46
+ call_next: Next middleware/endpoint in chain
47
+
48
+ Returns:
49
+ Response with X-Kalibr-Capsule header attached
50
+ """
51
+ # Extract capsule from header (or create new one)
52
+ capsule_header = request.headers.get(CAPSULE_HEADER)
53
+
54
+ if capsule_header:
55
+ capsule = TraceCapsule.from_json(capsule_header)
56
+ logger.debug(f"📦 Received capsule: {capsule}")
57
+ else:
58
+ capsule = TraceCapsule()
59
+ logger.debug(f"📦 Created new capsule: {capsule.trace_id}")
60
+
61
+ # Attach capsule to request state
62
+ request.state.capsule = capsule
63
+
64
+ # Process request
65
+ response = await call_next(request)
66
+
67
+ # Inject updated capsule in response headers
68
+ try:
69
+ capsule_json = capsule.to_json()
70
+ response.headers[CAPSULE_HEADER] = capsule_json
71
+ logger.debug(f"📦 Sending capsule: {capsule}")
72
+ except Exception as e:
73
+ logger.warning(f"⚠️ Failed to serialize capsule: {e}")
74
+
75
+ return response
76
+
77
+
78
+ def add_capsule_middleware(app: FastAPI) -> None:
79
+ """Add capsule middleware to FastAPI application.
80
+
81
+ Args:
82
+ app: FastAPI application instance
83
+
84
+ Example:
85
+ app = FastAPI()
86
+ add_capsule_middleware(app)
87
+ """
88
+ app.add_middleware(CapsuleMiddleware)
89
+ logger.info("✅ Kalibr Capsule middleware added")
90
+
91
+
92
+ def get_capsule(request: Request) -> TraceCapsule:
93
+ """Get capsule from request state (convenience function).
94
+
95
+ Args:
96
+ request: FastAPI Request object
97
+
98
+ Returns:
99
+ TraceCapsule attached to request
100
+
101
+ Raises:
102
+ AttributeError: If middleware not installed
103
+ """
104
+ if not hasattr(request.state, "capsule"):
105
+ logger.warning("⚠️ Capsule middleware not installed, creating new capsule")
106
+ request.state.capsule = TraceCapsule()
107
+
108
+ return request.state.capsule
@@ -0,0 +1,5 @@
1
+ """Kalibr CLI package"""
2
+
3
+ from kalibr.cli.main import app
4
+
5
+ __all__ = ["app"]
@@ -0,0 +1,174 @@
1
+ """
2
+ Capsule Reconstruction CLI Command
3
+ Fetch and export trace capsules from Kalibr Platform
4
+ """
5
+
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import requests
12
+ import typer
13
+ from rich import print as rprint
14
+ from rich.console import Console
15
+ from rich.table import Table
16
+
17
+ console = Console()
18
+
19
+
20
+ def capsule(
21
+ trace_id: str = typer.Argument(..., help="Trace ID to reconstruct capsule for"),
22
+ api_url: Optional[str] = typer.Option(
23
+ None,
24
+ "--api-url",
25
+ "-u",
26
+ help="Kalibr API base URL (default: from env KALIBR_API_URL or http://localhost:8001)",
27
+ envvar="KALIBR_API_URL",
28
+ ),
29
+ output: Optional[Path] = typer.Option(
30
+ None,
31
+ "--output",
32
+ "-o",
33
+ help="Output file path (JSON format). If not specified, prints to stdout.",
34
+ ),
35
+ export: bool = typer.Option(
36
+ False,
37
+ "--export",
38
+ "-e",
39
+ help="Use export endpoint to download as file",
40
+ ),
41
+ pretty: bool = typer.Option(
42
+ True,
43
+ "--pretty/--no-pretty",
44
+ "-p/-np",
45
+ help="Pretty print JSON output",
46
+ ),
47
+ ):
48
+ """
49
+ Reconstruct and fetch a trace capsule by trace_id.
50
+
51
+ The capsule contains all linked trace events, aggregated metrics,
52
+ and metadata for a complete execution chain.
53
+
54
+ Examples:
55
+
56
+ # Fetch capsule and display in terminal
57
+ kalibr capsule abc-123-def
58
+
59
+ # Save capsule to file
60
+ kalibr capsule abc-123-def --output capsule.json
61
+
62
+ # Use export endpoint
63
+ kalibr capsule abc-123-def --export --output capsule.json
64
+
65
+ # Specify custom API URL
66
+ kalibr capsule abc-123-def -u https://api.kalibr.io
67
+ """
68
+ # Determine API base URL
69
+ base_url = api_url or "http://localhost:8001"
70
+ base_url = base_url.rstrip("/")
71
+
72
+ # Build endpoint URL
73
+ if export:
74
+ endpoint = f"{base_url}/api/capsule/{trace_id}/export"
75
+ else:
76
+ endpoint = f"{base_url}/api/capsule/{trace_id}"
77
+
78
+ console.print(f"[cyan]Fetching capsule for trace_id:[/cyan] [bold]{trace_id}[/bold]")
79
+ console.print(f"[dim]Endpoint: {endpoint}[/dim]")
80
+
81
+ try:
82
+ # Make API request
83
+ response = requests.get(endpoint, timeout=30)
84
+ response.raise_for_status()
85
+
86
+ # Parse response
87
+ capsule_data = response.json()
88
+
89
+ # Display summary
90
+ console.print("\n[green]✓ Capsule reconstructed successfully[/green]\n")
91
+
92
+ # Create summary table
93
+ table = Table(title="Capsule Summary", show_header=False)
94
+ table.add_column("Field", style="cyan", no_wrap=True)
95
+ table.add_column("Value", style="white")
96
+
97
+ table.add_row("Capsule ID", capsule_data.get("capsule_id", "N/A"))
98
+ table.add_row("Total Cost (USD)", f"${capsule_data.get('total_cost_usd', 0):.6f}")
99
+ table.add_row("Total Latency (ms)", str(capsule_data.get("total_latency_ms", 0)))
100
+ table.add_row("Hop Count", str(capsule_data.get("hop_count", 0)))
101
+ table.add_row("Providers", ", ".join(capsule_data.get("providers", [])))
102
+ table.add_row("Reconstructed At", capsule_data.get("reconstructed_at", "N/A"))
103
+
104
+ console.print(table)
105
+
106
+ # Output to file or stdout
107
+ if output:
108
+ output_path = Path(output)
109
+ output_path.parent.mkdir(parents=True, exist_ok=True)
110
+
111
+ with open(output_path, "w") as f:
112
+ if pretty:
113
+ json.dump(capsule_data, f, indent=2, sort_keys=True)
114
+ else:
115
+ json.dump(capsule_data, f)
116
+
117
+ console.print(f"\n[green]✓ Capsule saved to:[/green] {output_path}")
118
+ else:
119
+ # Print to stdout
120
+ console.print("\n[bold]Capsule Data:[/bold]")
121
+ if pretty:
122
+ rprint(json.dumps(capsule_data, indent=2, sort_keys=True))
123
+ else:
124
+ print(json.dumps(capsule_data))
125
+
126
+ # Display event details
127
+ events = capsule_data.get("events", [])
128
+ if events:
129
+ console.print(f"\n[bold]Events ({len(events)}):[/bold]")
130
+ event_table = Table()
131
+ event_table.add_column("Trace ID", style="cyan")
132
+ event_table.add_column("Provider", style="yellow")
133
+ event_table.add_column("Model", style="magenta")
134
+ event_table.add_column("Operation", style="blue")
135
+ event_table.add_column("Duration (ms)", justify="right", style="green")
136
+ event_table.add_column("Cost (USD)", justify="right", style="green")
137
+ event_table.add_column("Status", style="white")
138
+
139
+ for event in events:
140
+ event_table.add_row(
141
+ event.get("trace_id", "")[:12] + "...",
142
+ event.get("provider", "N/A"),
143
+ event.get("model_id", "N/A")[:20],
144
+ event.get("operation", "N/A"),
145
+ str(event.get("duration_ms", 0)),
146
+ f"${event.get('cost_usd', 0):.6f}",
147
+ event.get("status", "N/A"),
148
+ )
149
+
150
+ console.print(event_table)
151
+
152
+ return 0
153
+
154
+ except requests.exceptions.HTTPError as e:
155
+ if e.response.status_code == 404:
156
+ console.print(f"[red]✗ Capsule not found for trace_id: {trace_id}[/red]")
157
+ console.print("[yellow]Make sure the trace_id exists and has been ingested.[/yellow]")
158
+ else:
159
+ console.print(f"[red]✗ API Error ({e.response.status_code}):[/red] {e.response.text}")
160
+ return 1
161
+
162
+ except requests.exceptions.ConnectionError:
163
+ console.print(f"[red]✗ Connection Error:[/red] Unable to connect to {base_url}")
164
+ console.print("[yellow]Make sure the Kalibr backend is running and accessible.[/yellow]")
165
+ return 1
166
+
167
+ except requests.exceptions.Timeout:
168
+ console.print("[red]✗ Timeout:[/red] Request took too long to complete")
169
+ return 1
170
+
171
+ except Exception as e:
172
+ console.print(f"[red]✗ Unexpected Error:[/red] {str(e)}")
173
+ console.print("[yellow]Run with --help for usage information[/yellow]")
174
+ return 1