kalibr 1.0.28__py3-none-any.whl → 1.1.3a0__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 (61) hide show
  1. kalibr/__init__.py +129 -4
  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 +203 -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 +258 -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.3a0.dist-info/METADATA +236 -0
  36. kalibr-1.1.3a0.dist-info/RECORD +48 -0
  37. kalibr-1.1.3a0.dist-info/entry_points.txt +2 -0
  38. kalibr-1.1.3a0.dist-info/licenses/LICENSE +21 -0
  39. kalibr-1.1.3a0.dist-info/top_level.txt +4 -0
  40. kalibr_crewai/__init__.py +65 -0
  41. kalibr_crewai/callbacks.py +539 -0
  42. kalibr_crewai/instrumentor.py +513 -0
  43. kalibr_langchain/__init__.py +47 -0
  44. kalibr_langchain/async_callback.py +850 -0
  45. kalibr_langchain/callback.py +1064 -0
  46. kalibr_openai_agents/__init__.py +43 -0
  47. kalibr_openai_agents/processor.py +554 -0
  48. kalibr/deployment.py +0 -41
  49. kalibr/packager.py +0 -43
  50. kalibr/runtime_router.py +0 -138
  51. kalibr/schema_generators.py +0 -159
  52. kalibr/validator.py +0 -70
  53. kalibr-1.0.28.data/data/examples/README.md +0 -173
  54. kalibr-1.0.28.data/data/examples/basic_kalibr_example.py +0 -66
  55. kalibr-1.0.28.data/data/examples/enhanced_kalibr_example.py +0 -347
  56. kalibr-1.0.28.dist-info/METADATA +0 -175
  57. kalibr-1.0.28.dist-info/RECORD +0 -19
  58. kalibr-1.0.28.dist-info/entry_points.txt +0 -2
  59. kalibr-1.0.28.dist-info/licenses/LICENSE +0 -11
  60. kalibr-1.0.28.dist-info/top_level.txt +0 -1
  61. {kalibr-1.0.28.dist-info → kalibr-1.1.3a0.dist-info}/WHEEL +0 -0
kalibr/kalibr_app.py CHANGED
@@ -1,343 +1,82 @@
1
- from fastapi import FastAPI, Request, UploadFile, File
2
- from fastapi.responses import JSONResponse, StreamingResponse as FastAPIStreamingResponse
3
- from typing import Callable, Dict, Any, List, Optional, get_type_hints
4
- import inspect
5
- import asyncio
6
- from datetime import datetime
7
- import uuid
8
- import os
1
+ """KalibrApp - Advanced app framework with async, file uploads, sessions"""
9
2
 
10
- from kalibr.types import FileUpload, Session, WorkflowState
3
+ from typing import Callable, List, Optional
11
4
 
12
- class KalibrApp:
13
- """
14
- Enhanced app-level Kalibr framework with advanced capabilities:
15
- - File upload handling
16
- - Session management
17
- - Streaming responses
18
- - Complex workflows
19
- - Multi-model schema generation
20
- """
5
+ import uvicorn
6
+ from fastapi import FastAPI, File, UploadFile
7
+ from fastapi.responses import StreamingResponse
8
+ from kalibr.kalibr import Kalibr
9
+ from kalibr.types import FileUpload, Session
21
10
 
22
- def __init__(self, title="Kalibr Enhanced API", version="2.0.0", base_url: Optional[str] = None, models: Optional[List[str]] = None):
23
- """
24
- Initialize the Kalibr enhanced app.
25
- Automatically determines correct base URL for deployed environments.
26
11
 
27
- Priority:
28
- 1. Explicit `base_url` passed by user
29
- 2. Env var `KALIBR_BASE_URL`
30
- 3. Env var `FLY_APP_NAME` -> https://<fly_app_name>.fly.dev
31
- 4. Default localhost for dev
12
+ class KalibrApp(Kalibr):
13
+ """Advanced Kalibr app with file uploads, sessions, and streaming"""
32
14
 
33
- `models`: optional list like ["mcp"] or ["mcp","gpt-actions","gemini","copilot"]
34
- controls which schema endpoints are advertised.
35
- """
36
- self.app = FastAPI(title=title, version=version)
15
+ def __init__(self, title: str = "Kalibr API", version: str = "1.0.0"):
16
+ super().__init__(title=title, version=version)
17
+ self.sessions = {} # In-memory session store
37
18
 
38
- if base_url:
39
- self.base_url = base_url
40
- elif os.getenv("KALIBR_BASE_URL"):
41
- self.base_url = os.getenv("KALIBR_BASE_URL")
42
- elif os.getenv("FLY_APP_NAME"):
43
- self.base_url = f"https://{os.getenv('FLY_APP_NAME')}.fly.dev"
44
- else:
45
- self.base_url = "http://localhost:8000"
19
+ def file_handler(self, name: str, allowed_extensions: List[str]):
20
+ """Decorator for file upload handlers"""
46
21
 
47
- self.models_supported = models or ["mcp", "gpt-actions", "gemini", "copilot"]
48
-
49
- # Storage for different action types
50
- self.actions: Dict[str, Any] = {}
51
- self.file_handlers: Dict[str, Any] = {}
52
- self.session_actions: Dict[str, Any] = {}
53
- self.stream_actions: Dict[str, Any] = {}
54
- self.workflows: Dict[str, Any] = {}
55
-
56
- # Session and workflow memory
57
- self.sessions: Dict[str, Session] = {}
58
- self.workflow_states: Dict[str, WorkflowState] = {}
59
-
60
- self._setup_routes()
61
-
62
- # -------------------------------------------------------------------------
63
- # Action registration decorators
64
- # -------------------------------------------------------------------------
65
-
66
- def action(self, name: str, description: str = ""):
67
22
  def decorator(func: Callable):
68
- self.actions[name] = {
69
- "func": func,
70
- "description": description,
71
- "params": self._extract_params(func),
72
- }
73
-
74
- endpoint_path = f"/proxy/{name}"
75
-
76
- async def endpoint_handler(request: Request):
77
- params = {}
78
- if request.method == "POST":
79
- try:
80
- body = await request.json()
81
- params = body if isinstance(body, dict) else {}
82
- except Exception:
83
- params = {}
84
- else:
85
- params = dict(request.query_params)
86
-
87
- try:
88
- result = func(**params)
89
- if inspect.isawaitable(result):
90
- result = await result
91
- return JSONResponse(content=result)
92
- except Exception as e:
93
- return JSONResponse(content={"error": str(e)}, status_code=500)
94
-
95
- self.app.post(endpoint_path)(endpoint_handler)
96
- self.app.get(endpoint_path)(endpoint_handler)
97
- return func
98
-
99
- return decorator
100
-
101
- def file_handler(self, name: str, allowed_extensions: List[str] = None, description: str = ""):
102
- def decorator(func: Callable):
103
- self.file_handlers[name] = {
104
- "func": func,
105
- "description": description,
106
- "allowed_extensions": allowed_extensions or [],
107
- "params": self._extract_params(func),
108
- }
109
-
110
- endpoint_path = f"/files/{name}"
111
-
23
+ @self.app.post(f"/proxy/{name}")
112
24
  async def file_endpoint(file: UploadFile = File(...)):
113
- try:
114
- if allowed_extensions:
115
- file_ext = "." + file.filename.split(".")[-1] if "." in file.filename else ""
116
- if file_ext not in allowed_extensions:
117
- return JSONResponse(
118
- content={"error": f"File type {file_ext} not allowed. Allowed: {allowed_extensions}"},
119
- status_code=400,
120
- )
121
-
122
- content = await file.read()
123
- file_upload = FileUpload(
124
- filename=file.filename,
125
- content_type=file.content_type or "application/octet-stream",
126
- size=len(content),
127
- content=content,
128
- )
129
-
130
- result = func(file_upload)
131
- if inspect.isawaitable(result):
132
- result = await result
133
- return JSONResponse(content=result)
134
- except Exception as e:
135
- return JSONResponse(content={"error": str(e)}, status_code=500)
25
+ # Validate file extension
26
+ if allowed_extensions:
27
+ ext = f".{file.filename.split('.')[-1]}"
28
+ if ext not in allowed_extensions:
29
+ return {"error": f"File type not allowed. Allowed: {allowed_extensions}"}
30
+
31
+ # Create FileUpload object
32
+ content = await file.read()
33
+ file_obj = FileUpload(
34
+ filename=file.filename,
35
+ content_type=file.content_type,
36
+ size=len(content),
37
+ content=content,
38
+ )
39
+
40
+ # Call handler
41
+ result = await func(file_obj)
42
+ return result
136
43
 
137
- self.app.post(endpoint_path)(file_endpoint)
138
44
  return func
139
45
 
140
46
  return decorator
141
47
 
142
48
  def session_action(self, name: str, description: str = ""):
143
- def decorator(func: Callable):
144
- self.session_actions[name] = {
145
- "func": func,
146
- "description": description,
147
- "params": self._extract_params(func),
148
- }
149
-
150
- endpoint_path = f"/session/{name}"
151
-
152
- async def session_endpoint(request: Request):
153
- try:
154
- session_id = request.headers.get("X-Session-ID") or request.cookies.get("session_id")
155
- if not session_id or session_id not in self.sessions:
156
- session_id = str(uuid.uuid4())
157
- session = Session(session_id=session_id)
158
- self.sessions[session_id] = session
159
- else:
160
- session = self.sessions[session_id]
161
- session.last_accessed = datetime.now()
49
+ """Decorator for session-based actions"""
162
50
 
163
- body = await request.json() if request.method == "POST" else {}
164
-
165
- sig = inspect.signature(func)
166
- if "session" in sig.parameters:
167
- func_params = {k: v for k, v in body.items() if k != "session"}
168
- result = func(session=session, **func_params)
169
- else:
170
- result = func(**body)
51
+ def decorator(func: Callable):
52
+ @self.app.post(f"/proxy/{name}")
53
+ async def session_endpoint(session_id: str, **kwargs):
54
+ # Get or create session
55
+ if session_id not in self.sessions:
56
+ self.sessions[session_id] = Session(session_id)
171
57
 
172
- if inspect.isawaitable(result):
173
- result = await result
58
+ session = self.sessions[session_id]
174
59
 
175
- response = JSONResponse(content=result)
176
- response.set_cookie("session_id", session_id)
177
- response.headers["X-Session-ID"] = session_id
178
- return response
179
- except Exception as e:
180
- return JSONResponse(content={"error": str(e)}, status_code=500)
60
+ # Call handler
61
+ result = await func(session, **kwargs)
62
+ return result
181
63
 
182
- self.app.post(endpoint_path)(session_endpoint)
183
64
  return func
184
65
 
185
66
  return decorator
186
67
 
187
- def stream_action(self, name: str, description: str = ""):
188
- def decorator(func: Callable):
189
- self.stream_actions[name] = {
190
- "func": func,
191
- "description": description,
192
- "params": self._extract_params(func),
193
- }
194
-
195
- endpoint_path = f"/stream/{name}"
196
-
197
- async def stream_endpoint(request: Request):
198
- try:
199
- params = dict(request.query_params) if request.method == "GET" else {}
200
- if request.method == "POST":
201
- body = await request.json()
202
- params.update(body)
68
+ def stream_action(self, name: str):
69
+ """Decorator for streaming responses"""
203
70
 
204
- sig = inspect.signature(func)
205
- type_hints = get_type_hints(func) if hasattr(func, "__annotations__") else {}
206
- converted_params = {}
207
- for key, value in params.items():
208
- if key in sig.parameters:
209
- param_type = type_hints.get(key, str)
210
- try:
211
- if param_type == int:
212
- converted_params[key] = int(value)
213
- elif param_type == float:
214
- converted_params[key] = float(value)
215
- elif param_type == bool:
216
- converted_params[key] = value.lower() in ("true", "1", "yes")
217
- else:
218
- converted_params[key] = value
219
- except Exception:
220
- converted_params[key] = value
221
-
222
- result = func(**converted_params)
223
-
224
- async def generate():
225
- import json
226
- if inspect.isasyncgen(result):
227
- async for item in result:
228
- yield json.dumps(item) + "\\n"
229
- elif inspect.isgenerator(result):
230
- for item in result:
231
- yield json.dumps(item) + "\\n"
71
+ def decorator(func: Callable):
72
+ @self.app.post(f"/proxy/{name}")
73
+ async def stream_endpoint(**kwargs):
74
+ async def generate():
75
+ async for chunk in func(**kwargs):
76
+ yield chunk
232
77
 
233
- return FastAPIStreamingResponse(generate(), media_type="application/x-ndjson")
234
- except Exception as e:
235
- return JSONResponse(content={"error": str(e)}, status_code=500)
78
+ return StreamingResponse(generate(), media_type="text/event-stream")
236
79
 
237
- self.app.get(endpoint_path)(stream_endpoint)
238
- self.app.post(endpoint_path)(stream_endpoint)
239
80
  return func
240
81
 
241
82
  return decorator
242
-
243
- # -------------------------------------------------------------------------
244
- # Schema generation routes
245
- # -------------------------------------------------------------------------
246
-
247
- def _setup_routes(self):
248
- from kalibr.schema_generators import (
249
- OpenAPISchemaGenerator,
250
- MCPSchemaGenerator,
251
- GeminiSchemaGenerator,
252
- CopilotSchemaGenerator,
253
- )
254
-
255
- openapi_gen = OpenAPISchemaGenerator()
256
- mcp_gen = MCPSchemaGenerator()
257
- gemini_gen = GeminiSchemaGenerator()
258
- copilot_gen = CopilotSchemaGenerator()
259
-
260
- @self.app.get("/")
261
- def root():
262
- schemas = {}
263
- if "gpt-actions" in self.models_supported:
264
- schemas["gpt_actions"] = f"{self.base_url}/gpt-actions.json"
265
- schemas["claude_mcp"] = f"{self.base_url}/mcp.json" # MCP is the default lingua franca
266
- if "gemini" in self.models_supported:
267
- schemas["gemini"] = f"{self.base_url}/schemas/gemini"
268
- if "copilot" in self.models_supported:
269
- schemas["copilot"] = f"{self.base_url}/schemas/copilot"
270
-
271
- return {
272
- "message": "Kalibr Enhanced API is running",
273
- "actions": list(self.actions.keys()),
274
- "schemas": schemas,
275
- }
276
-
277
- @self.app.get("/models/supported")
278
- def supported_models():
279
- return {"models": self.models_supported}
280
-
281
- @self.app.get("/gpt-actions.json")
282
- def gpt_actions_schema():
283
- all_actions = {**self.actions, **self.file_handlers, **self.session_actions}
284
- return openapi_gen.generate_schema(all_actions, self.base_url)
285
-
286
- @self.app.get("/mcp.json")
287
- def mcp_manifest():
288
- all_actions = {**self.actions, **self.file_handlers, **self.session_actions}
289
- return mcp_gen.generate_schema(all_actions, self.base_url)
290
-
291
- @self.app.get("/schemas/gemini")
292
- def gemini_schema():
293
- all_actions = {**self.actions, **self.file_handlers, **self.session_actions}
294
- return gemini_gen.generate_schema(all_actions, self.base_url)
295
-
296
- @self.app.get("/schemas/copilot")
297
- def copilot_schema():
298
- all_actions = {**self.actions, **self.file_handlers, **self.session_actions}
299
- return copilot_gen.generate_schema(all_actions, self.base_url)
300
-
301
- @self.app.get("/health")
302
- def health_check():
303
- return {
304
- "status": "healthy",
305
- "service": "Kalibr Enhanced API",
306
- "version": self.app.version,
307
- "features": {
308
- "file_handlers": bool(self.file_handlers),
309
- "sessions": True,
310
- "streams": bool(self.stream_actions),
311
- },
312
- }
313
-
314
- # -------------------------------------------------------------------------
315
- # Helpers
316
- # -------------------------------------------------------------------------
317
-
318
- def _extract_params(self, func: Callable) -> Dict[str, Any]:
319
- sig = inspect.signature(func)
320
- params = {}
321
- type_hints = get_type_hints(func) if hasattr(func, "__annotations__") else {}
322
-
323
- for param_name, param in sig.parameters.items():
324
- if param_name in ["session", "workflow_state", "file"]:
325
- continue
326
-
327
- param_type = "string"
328
- anno = type_hints.get(param_name, param.annotation)
329
- if anno == int:
330
- param_type = "integer"
331
- elif anno == bool:
332
- param_type = "boolean"
333
- elif anno == float:
334
- param_type = "number"
335
- elif anno == list:
336
- param_type = "array"
337
- elif anno == dict:
338
- param_type = "object"
339
-
340
- is_required = param.default == inspect.Parameter.empty
341
- params[param_name] = {"type": param_type, "required": is_required}
342
-
343
- return params
@@ -0,0 +1,5 @@
1
+ """Kalibr Middleware Package"""
2
+
3
+ from .auto_tracer import AutoTracerMiddleware
4
+
5
+ __all__ = ["AutoTracerMiddleware"]