kalibr 1.0.28__py3-none-any.whl → 1.1.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.
Files changed (52) hide show
  1. kalibr/__init__.py +170 -3
  2. kalibr/__main__.py +3 -203
  3. kalibr/capsule_middleware.py +108 -0
  4. kalibr/cli/__init__.py +5 -0
  5. kalibr/cli/capsule_cmd.py +174 -0
  6. kalibr/cli/deploy_cmd.py +114 -0
  7. kalibr/cli/main.py +67 -0
  8. kalibr/cli/run.py +200 -0
  9. kalibr/cli/serve.py +59 -0
  10. kalibr/client.py +293 -0
  11. kalibr/collector.py +173 -0
  12. kalibr/context.py +132 -0
  13. kalibr/cost_adapter.py +222 -0
  14. kalibr/decorators.py +140 -0
  15. kalibr/instrumentation/__init__.py +13 -0
  16. kalibr/instrumentation/anthropic_instr.py +282 -0
  17. kalibr/instrumentation/base.py +108 -0
  18. kalibr/instrumentation/google_instr.py +281 -0
  19. kalibr/instrumentation/openai_instr.py +265 -0
  20. kalibr/instrumentation/registry.py +153 -0
  21. kalibr/kalibr.py +144 -230
  22. kalibr/kalibr_app.py +53 -314
  23. kalibr/middleware/__init__.py +5 -0
  24. kalibr/middleware/auto_tracer.py +356 -0
  25. kalibr/models.py +41 -0
  26. kalibr/redaction.py +44 -0
  27. kalibr/schemas.py +116 -0
  28. kalibr/simple_tracer.py +255 -0
  29. kalibr/tokens.py +52 -0
  30. kalibr/trace_capsule.py +296 -0
  31. kalibr/trace_models.py +201 -0
  32. kalibr/tracer.py +354 -0
  33. kalibr/types.py +25 -93
  34. kalibr/utils.py +198 -0
  35. kalibr-1.1.0.dist-info/METADATA +97 -0
  36. kalibr-1.1.0.dist-info/RECORD +40 -0
  37. kalibr-1.1.0.dist-info/entry_points.txt +2 -0
  38. kalibr-1.1.0.dist-info/licenses/LICENSE +21 -0
  39. kalibr/deployment.py +0 -41
  40. kalibr/packager.py +0 -43
  41. kalibr/runtime_router.py +0 -138
  42. kalibr/schema_generators.py +0 -159
  43. kalibr/validator.py +0 -70
  44. kalibr-1.0.28.data/data/examples/README.md +0 -173
  45. kalibr-1.0.28.data/data/examples/basic_kalibr_example.py +0 -66
  46. kalibr-1.0.28.data/data/examples/enhanced_kalibr_example.py +0 -347
  47. kalibr-1.0.28.dist-info/METADATA +0 -175
  48. kalibr-1.0.28.dist-info/RECORD +0 -19
  49. kalibr-1.0.28.dist-info/entry_points.txt +0 -2
  50. kalibr-1.0.28.dist-info/licenses/LICENSE +0 -11
  51. {kalibr-1.0.28.dist-info → kalibr-1.1.0.dist-info}/WHEEL +0 -0
  52. {kalibr-1.0.28.dist-info → kalibr-1.1.0.dist-info}/top_level.txt +0 -0
kalibr/utils.py ADDED
@@ -0,0 +1,198 @@
1
+ """Utility functions for Kalibr SDK.
2
+
3
+ Helper functions for:
4
+ - Environment configuration
5
+ - Event validation
6
+ - JSON serialization
7
+ - Logging
8
+ """
9
+
10
+ import json
11
+ import os
12
+ from datetime import date, datetime, time
13
+ from typing import Any, Dict, Optional
14
+
15
+
16
+ def load_config_from_env() -> Dict[str, str]:
17
+ """Load Kalibr configuration from environment variables.
18
+
19
+ Returns:
20
+ Configuration dict with keys:
21
+ - clickhouse_url: ClickHouse connection URL
22
+ - project_name: Project/service name
23
+ - auth_token: API authentication token
24
+ - environment: Environment (prod/staging/dev)
25
+ - tenant_id: Tenant identifier
26
+ - workflow_id: Workflow identifier
27
+ - sandbox_id: Sandbox/VM identifier
28
+ - runtime_env: Runtime environment
29
+ - collector_url: Collector endpoint URL
30
+ """
31
+ config = {
32
+ "clickhouse_url": os.getenv("CLICKHOUSE_URL", "http://localhost:8123"),
33
+ "project_name": os.getenv("KALIBR_PROJECT_NAME", "kalibr-app"),
34
+ "auth_token": os.getenv("KALIBR_AUTH_TOKEN", ""),
35
+ "api_key": os.getenv("KALIBR_API_KEY", ""),
36
+ "environment": os.getenv("KALIBR_ENVIRONMENT", "prod"),
37
+ "tenant_id": os.getenv("KALIBR_TENANT_ID", "default"),
38
+ "workflow_id": os.getenv("KALIBR_WORKFLOW_ID", "default-workflow"),
39
+ "sandbox_id": os.getenv("SANDBOX_ID", "local"),
40
+ "runtime_env": os.getenv("RUNTIME_ENV", "local"),
41
+ "api_endpoint": os.getenv("KALIBR_API_ENDPOINT", "http://localhost:8001/api/v1/traces"),
42
+ "collector_url": os.getenv("KALIBR_COLLECTOR_URL", "http://localhost:8080/api/ingest"),
43
+ }
44
+ return config
45
+
46
+
47
+ def validate_event(event: Dict[str, Any]) -> bool:
48
+ """Validate that event has required fields.
49
+
50
+ Args:
51
+ event: Event dictionary
52
+
53
+ Returns:
54
+ True if valid, False otherwise
55
+ """
56
+ required_fields = [
57
+ "trace_id",
58
+ "span_id",
59
+ "timestamp",
60
+ "service",
61
+ "vendor",
62
+ "operation",
63
+ "latency_ms",
64
+ "status",
65
+ ]
66
+
67
+ for field in required_fields:
68
+ if field not in event:
69
+ return False
70
+
71
+ return True
72
+
73
+
74
+ def serialize_event(event: Dict[str, Any]) -> str:
75
+ """Serialize event to JSON string.
76
+
77
+ Handles special types like datetime, date, time.
78
+
79
+ Args:
80
+ event: Event dictionary
81
+
82
+ Returns:
83
+ JSON string
84
+ """
85
+
86
+ def default_handler(obj):
87
+ """Handle non-serializable types."""
88
+ if isinstance(obj, (datetime, date, time)):
89
+ return obj.isoformat()
90
+ raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
91
+
92
+ return json.dumps(event, default=default_handler)
93
+
94
+
95
+ def safe_get_nested(data: Dict, keys: list, default: Any = None) -> Any:
96
+ """Safely get nested dictionary value.
97
+
98
+ Args:
99
+ data: Dictionary to query
100
+ keys: List of nested keys
101
+ default: Default value if key path doesn't exist
102
+
103
+ Returns:
104
+ Value at nested key path, or default
105
+
106
+ Example:
107
+ >>> data = {"a": {"b": {"c": 123}}}
108
+ >>> safe_get_nested(data, ["a", "b", "c"])
109
+ 123
110
+ >>> safe_get_nested(data, ["a", "x", "y"], default=0)
111
+ 0
112
+ """
113
+ current = data
114
+ for key in keys:
115
+ if isinstance(current, dict) and key in current:
116
+ current = current[key]
117
+ else:
118
+ return default
119
+ return current
120
+
121
+
122
+ def truncate_string(s: str, max_length: int = 1000) -> str:
123
+ """Truncate string to max length with ellipsis.
124
+
125
+ Args:
126
+ s: String to truncate
127
+ max_length: Maximum length
128
+
129
+ Returns:
130
+ Truncated string
131
+ """
132
+ if len(s) <= max_length:
133
+ return s
134
+ return s[: max_length - 3] + "..."
135
+
136
+
137
+ def format_cost(cost_usd: float) -> str:
138
+ """Format cost in USD for display.
139
+
140
+ Args:
141
+ cost_usd: Cost in USD
142
+
143
+ Returns:
144
+ Formatted string (e.g., "$0.0123")
145
+ """
146
+ if cost_usd < 0.0001:
147
+ return "$0.0000"
148
+ elif cost_usd < 0.01:
149
+ return f"${cost_usd:.4f}"
150
+ elif cost_usd < 1.0:
151
+ return f"${cost_usd:.3f}"
152
+ else:
153
+ return f"${cost_usd:.2f}"
154
+
155
+
156
+ def format_latency(latency_ms: int) -> str:
157
+ """Format latency for display.
158
+
159
+ Args:
160
+ latency_ms: Latency in milliseconds
161
+
162
+ Returns:
163
+ Formatted string (e.g., "420ms" or "2.3s")
164
+ """
165
+ if latency_ms < 1000:
166
+ return f"{latency_ms}ms"
167
+ else:
168
+ seconds = latency_ms / 1000
169
+ return f"{seconds:.1f}s"
170
+
171
+
172
+ def get_log_prefix() -> str:
173
+ """Get log prefix for Kalibr SDK messages.
174
+
175
+ Returns:
176
+ Log prefix string
177
+ """
178
+ return "[Kalibr SDK]"
179
+
180
+
181
+ def log_info(message: str):
182
+ """Log info message."""
183
+ print(f"{get_log_prefix()} ℹ️ {message}")
184
+
185
+
186
+ def log_warning(message: str):
187
+ """Log warning message."""
188
+ print(f"{get_log_prefix()} ⚠️ {message}")
189
+
190
+
191
+ def log_error(message: str):
192
+ """Log error message."""
193
+ print(f"{get_log_prefix()} ❌ {message}")
194
+
195
+
196
+ def log_success(message: str):
197
+ """Log success message."""
198
+ print(f"{get_log_prefix()} ✅ {message}")
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: kalibr
3
+ Version: 1.1.0
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,40 @@
1
+ kalibr/__init__.py,sha256=fNOjaXe1sJ9fFgqTsO6GQY4Yf4TCr_T659lxr3FB35Q,5906
2
+ kalibr/__main__.py,sha256=jO96I4pqinwHg7ONRvNVKbySBh5pSIhOAiNrgSQrNlY,110
3
+ kalibr/capsule_middleware.py,sha256=pXG_wORgCqo3wHjtkn_zY4doLyiDmTwJtB7XiZNnbPk,3163
4
+ kalibr/client.py,sha256=t6_vZn_juHrsCg-7QFrkl-pzlqV7ZesQLCh8fnF808Q,9727
5
+ kalibr/collector.py,sha256=rtTKQLe6NkDSblBIfFooQ-ESFcP0Q1HUp4Bcqqg8JFo,5818
6
+ kalibr/context.py,sha256=hBxWXZx0gcmeGqDMS1rstke_DmrujoRBIsfrG26WKUY,3755
7
+ kalibr/cost_adapter.py,sha256=NerJ7ywaJjBn97gVFr7qKX7318e3Kmy2qqeNlGl9nPE,6439
8
+ kalibr/decorators.py,sha256=m-XBXxWMDVrzaNsljACiGmeGhgiHj_MqSfj6OGK3L5I,4380
9
+ kalibr/kalibr.py,sha256=cNXC3W_TX5SvGsy1lRopkwFqsHOpyd1kkVjEMOz1Yr4,6084
10
+ kalibr/kalibr_app.py,sha256=ItZwEh0FZPx9_BE-zPQajC2yxI2y9IHYwJD0k9tbHvY,2773
11
+ kalibr/models.py,sha256=HwD_-iysZMSnCzMQYO1Qcf0aeXySupY7yJeBwl_dLS0,1024
12
+ kalibr/redaction.py,sha256=XibxX4Lv1Ci0opE6Tb5ZI2GLbO0a8E9U66MAg60llnc,1139
13
+ kalibr/schemas.py,sha256=XLZNLkXca6jbj9AF6gDIyGVnIcr1SVOsNYaKvW-wbgE,3669
14
+ kalibr/simple_tracer.py,sha256=LPc56PfGZQT8YXS2Y1eAiOu0CsOEi4-UmWVlq03cZpM,9665
15
+ kalibr/tokens.py,sha256=istjgaxi9S4dMddjuGtoQaTnZYcWLCqdnxRjV86yNXA,1297
16
+ kalibr/trace_capsule.py,sha256=CPMUz5D-fVfao-MozNtSDbgOQKdDAJxTN5KQL6w2Xp8,10154
17
+ kalibr/trace_models.py,sha256=9o7VJQk3gCrvdfXPrNh3Ptkq5sRgA9_qrLLE3jNkSBg,7304
18
+ kalibr/tracer.py,sha256=jwWBpZbGXn6fEv4pw25BLFCH-22QUbyzofPWp1Iwdkk,11911
19
+ kalibr/types.py,sha256=cna4-akpdwfHXfOJCtVIq5lO_jaoG2Am3BRrXi0Vo34,895
20
+ kalibr/utils.py,sha256=IbAgw-Bwxdy9Kc0fm2yIDt2RxWU0gRCzffuz3GRUSnE,5042
21
+ kalibr/cli/__init__.py,sha256=FmRGaDMhM9DhrKg1ONkF0emIrJcjFWjlFBl_oenvpsk,77
22
+ kalibr/cli/capsule_cmd.py,sha256=htxUtRpos_dLs-LnJm6WeGtLYCCc1h7H3WXz7i6DVWc,6082
23
+ kalibr/cli/deploy_cmd.py,sha256=kV4uqCN2IdQev1vPBY5qqIHsEhjGBZ7y_rLx8RGAL_4,5178
24
+ kalibr/cli/main.py,sha256=Ob0Vpg8KPnHluP_23pgbEZpDb_iKnjoN7mW52-2Qr44,1939
25
+ kalibr/cli/run.py,sha256=DrH0Pjc_gcrvvi0cSiQCf_WJNtOh3xd1EWncuNloNCM,6293
26
+ kalibr/cli/serve.py,sha256=71Xha35qrBNkcQxuUkwC-ixbOriHGUIEgxl7C_qERQo,2085
27
+ kalibr/instrumentation/__init__.py,sha256=YnUJ4gUH8WNxdVv5t1amn0l2WUULJG2MuQIL2ZZhn04,354
28
+ kalibr/instrumentation/anthropic_instr.py,sha256=ozXHr8BPMafIbvgaxunskQi9YX5_Gpoiekye77oRc2E,10058
29
+ kalibr/instrumentation/base.py,sha256=eMFBTIQXtG2bZD5st6vzN72ooeHCANZ3SapYzrdijgk,3109
30
+ kalibr/instrumentation/google_instr.py,sha256=f2um7MB2QCT2u9CFV4-vKke-8M0dSXSpZHTcbdxMZyI,10476
31
+ kalibr/instrumentation/openai_instr.py,sha256=UU0Pi1Gq1FqgetYWDacQhNFdjemuPrc0hRTKd-LIDHI,9250
32
+ kalibr/instrumentation/registry.py,sha256=sfQnXhbPOI5LVon2kFhe8KcXQwWmuKW1XUe50B2AaBc,4749
33
+ kalibr/middleware/__init__.py,sha256=qyDUn_irAX67MS-IkuDVxg4RmFnJHDf_BfIT3qfGoBI,115
34
+ kalibr/middleware/auto_tracer.py,sha256=oevrhWBAM4el_F5jFc28ZBFLEklx7XGd40dNFe5VQ8Q,13032
35
+ kalibr-1.1.0.dist-info/licenses/LICENSE,sha256=BYlEPoDdYD3iHuAjt2JYGoxDYQaI1gxab2pR4acoz04,1063
36
+ kalibr-1.1.0.dist-info/METADATA,sha256=U1E1RTfkpgHZSOVSpfP6Sw-GxMf4ilmczPK6VvuZfW4,2841
37
+ kalibr-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
+ kalibr-1.1.0.dist-info/entry_points.txt,sha256=Kojlc6WRX8V1qS9lOMdDPZpTUVHCtzGtHqXusErgmLY,47
39
+ kalibr-1.1.0.dist-info/top_level.txt,sha256=OkloC5_IfpE4-QwI30aLIYbFZk_-ChABWF7aBGddy28,7
40
+ kalibr-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kalibr = kalibr.cli.main:app
@@ -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.
kalibr/deployment.py DELETED
@@ -1,41 +0,0 @@
1
- """
2
- Kalibr Deployment
3
- -----------------
4
- Thin wrapper that forwards to the runtime router.
5
- Keeps a simple API surface for backwards-compat commands.
6
- """
7
-
8
- from __future__ import annotations
9
- from dataclasses import dataclass, field
10
- from typing import Dict, Any
11
- from kalibr.runtime_router import deploy as router_deploy
12
-
13
- @dataclass
14
- class DeploymentConfig:
15
- app_name: str
16
- memory_mb: int = 512
17
- timeout_seconds: int = 30
18
- environment_vars: Dict[str, str] = field(default_factory=dict)
19
-
20
- def deploy_app(file_path: str, config: DeploymentConfig, platform: str = "local") -> Dict[str, Any]:
21
- # Map older "platform" to runtime names used by router
22
- runtime = {
23
- "local": "local",
24
- "fly": "fly",
25
- "aws-lambda": "local", # not supported; punt to local
26
- "render": "render",
27
- }.get(platform, platform)
28
-
29
- result = router_deploy(runtime=runtime, app_name=config.app_name, app_file=file_path)
30
- if result.get("status") in ("success", "started"):
31
- eps = result.get("endpoints", {})
32
- return {
33
- "status": "success",
34
- "endpoints": {
35
- "root": eps.get("root", ""),
36
- "mcp": eps.get("mcp", ""),
37
- "openapi": eps.get("openapi", ""),
38
- "health": eps.get("health", ""),
39
- }
40
- }
41
- return {"status": "error", "error": "unknown deploy outcome", "raw": result}
kalibr/packager.py DELETED
@@ -1,43 +0,0 @@
1
- """
2
- Packager
3
- --------
4
- Create a deployable MCP bundle (code + manifests + metadata).
5
- This does not host anything; it prepares artifacts for any runtime.
6
- """
7
-
8
- from __future__ import annotations
9
- from pathlib import Path
10
- import shutil
11
- import json
12
- import tempfile
13
- from typing import Dict, Any, Optional
14
-
15
- DEFAULT_BUNDLE = "kalibr_bundle.zip"
16
-
17
- def package_app(app_dir: str = ".", output: str = DEFAULT_BUNDLE, models_supported: Optional[list] = None, kalibr_version: str = "unknown") -> str:
18
- app_dir = Path(app_dir).resolve()
19
- out_path = Path(output).resolve()
20
-
21
- # Assemble temp dir with metadata
22
- with tempfile.TemporaryDirectory() as tmp:
23
- tmpdir = Path(tmp)
24
- # Copy source tree
25
- for item in app_dir.iterdir():
26
- if item.name == out_path.name:
27
- continue
28
- dest = tmpdir / item.name
29
- if item.is_dir():
30
- shutil.copytree(item, dest)
31
- else:
32
- shutil.copy2(item, dest)
33
-
34
- # Write bundle metadata
35
- (tmpdir / "kalibr_manifest.json").write_text(json.dumps({
36
- "kalibr_version": kalibr_version,
37
- "models_supported": models_supported or ["mcp", "gpt-actions", "gemini", "copilot"],
38
- }, indent=2))
39
-
40
- # Zip
41
- shutil.make_archive(out_path.with_suffix(""), "zip", tmpdir)
42
-
43
- return str(out_path)
kalibr/runtime_router.py DELETED
@@ -1,138 +0,0 @@
1
- """
2
- Runtime Router
3
- --------------
4
- Abstraction over deployment targets without hosting them ourselves.
5
- Generates minimal configs and invokes the target's CLI/API where possible.
6
-
7
- Supported:
8
- - local (uvicorn)
9
- - fly (fly.io) -> generates fly.toml and basic Dockerfile
10
- - render -> generates render.yaml
11
-
12
- Note: We do not ship vendor SDKs. We shell out to their CLIs if present.
13
- """
14
-
15
- from __future__ import annotations
16
- from pathlib import Path
17
- import subprocess
18
- import shutil
19
- import os
20
- import json
21
- from typing import Dict, Any, Optional, Tuple
22
-
23
- HERE = Path(__file__).parent
24
-
25
- def which(cmd: str) -> Optional[str]:
26
- return shutil.which(cmd)
27
-
28
- def ensure_file(path: Path, content: str):
29
- path.parent.mkdir(parents=True, exist_ok=True)
30
- if not path.exists():
31
- path.write_text(content)
32
-
33
- def generate_fly_files(app_name: str) -> Tuple[Path, Path]:
34
- fly_toml = Path("fly.toml")
35
- dockerfile = Path("Dockerfile")
36
- ensure_file(fly_toml, f"""# fly.toml
37
- app = "{app_name}"
38
- primary_region = "iad"
39
-
40
- [build]
41
- dockerfile = "Dockerfile"
42
-
43
- [http_service]
44
- internal_port = 8000
45
- force_https = true
46
- auto_stop_machines = "off"
47
- auto_start_machines = true
48
- min_machines_running = 1
49
- """)
50
- ensure_file(dockerfile, """# Dockerfile
51
- FROM python:3.11-slim
52
- WORKDIR /app
53
- COPY . /app
54
- RUN pip install --no-cache-dir -U pip && \
55
- pip install --no-cache-dir fastapi uvicorn typer pydantic requests
56
- EXPOSE 8000
57
- CMD ["python", "-m", "kalibr", "serve", "kalibr_app.py", "--host", "0.0.0.0", "--port", "8000", "--base-url", "http://0.0.0.0:8000"]
58
- """)
59
- return fly_toml, dockerfile
60
-
61
- def generate_render_file(service_name: str) -> Path:
62
- render_yaml = Path("render.yaml")
63
- ensure_file(render_yaml, f"""# render.yaml
64
- services:
65
- - type: web
66
- name: {service_name}
67
- env: docker
68
- plan: free
69
- dockerfilePath: ./Dockerfile
70
- autoDeploy: true
71
- """)
72
- return render_yaml
73
-
74
- def deploy_local(app_file: str, host: str = "0.0.0.0", port: int = 8000, base_url: str = "http://localhost:8000") -> Dict[str, Any]:
75
- # Run uvicorn inline (non-blocking not handled here - CLI uses this to print guidance)
76
- cmd = ["python", "-m", "kalibr", "serve", app_file, "--host", host, "--port", str(port), "--base-url", base_url]
77
- print("▶︎", " ".join(cmd))
78
- subprocess.run(cmd, check=False)
79
- return {
80
- "status": "started",
81
- "endpoints": {
82
- "root": f"{base_url}/",
83
- "mcp": f"{base_url}/mcp.json",
84
- "openapi": f"{base_url}/openapi.json",
85
- "health": f"{base_url}/health"
86
- }
87
- }
88
-
89
- def deploy_fly(app_name: str) -> Dict[str, Any]:
90
- if not which("flyctl"):
91
- raise RuntimeError("flyctl is not installed. See https://fly.io/docs/flyctl/install/")
92
- # Ensure files exist
93
- generate_fly_files(app_name)
94
- # Launch or deploy
95
- print("▶︎ flyctl apps list")
96
- subprocess.run(["flyctl", "apps", "list"], check=False)
97
- print(f"▶︎ flyctl deploy --app {app_name}")
98
- subprocess.run(["flyctl", "deploy", "--app", app_name], check=False)
99
- url = f"https://{app_name}.fly.dev"
100
- return {
101
- "status": "success",
102
- "endpoints": {
103
- "root": f"{url}/",
104
- "mcp": f"{url}/mcp.json",
105
- "openapi": f"{url}/openapi.json",
106
- "health": f"{url}/health"
107
- }
108
- }
109
-
110
- def deploy_render(service_name: str) -> Dict[str, Any]:
111
- # We just generate render.yaml and Dockerfile. User connects repo in Render UI.
112
- generate_render_file(service_name)
113
- ensure_file(Path("Dockerfile"), """# Dockerfile for Render
114
- FROM python:3.11-slim
115
- WORKDIR /app
116
- COPY . /app
117
- RUN pip install --no-cache-dir -U pip && \
118
- pip install --no-cache-dir fastapi uvicorn typer pydantic requests
119
- EXPOSE 8000
120
- CMD ["python", "-m", "kalibr", "serve", "kalibr_app.py", "--host", "0.0.0.0", "--port", "8000", "--base-url", "https://$RENDER_EXTERNAL_URL"]
121
- """)
122
- print("📄 Generated render.yaml and Dockerfile. Connect your repo in Render.com and auto-deploy.")
123
- return {
124
- "status": "success",
125
- "endpoints": {},
126
- "note": "Connect this repository to Render; it will build from render.yaml."
127
- }
128
-
129
- def deploy(runtime: str, app_name: str, app_file: str, host: str = "0.0.0.0", port: int = 8000, base_url: str = "http://localhost:8000") -> Dict[str, Any]:
130
- runtime = runtime.lower()
131
- if runtime in ("local", "dev"):
132
- return deploy_local(app_file, host, port, base_url)
133
- if runtime in ("fly", "flyio"):
134
- return deploy_fly(app_name)
135
- if runtime == "render":
136
- return deploy_render(app_name)
137
- raise ValueError(f"Unknown runtime: {runtime}")
138
-