kalibr 1.0.20__py3-none-any.whl → 1.0.22__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/kalibr_app.py CHANGED
@@ -1,12 +1,11 @@
1
- # kalibr/kalibr_app.py - Full App-Level Implementation
2
-
3
- from fastapi import FastAPI, Request, UploadFile, File, Depends
1
+ from fastapi import FastAPI, Request, UploadFile, File
4
2
  from fastapi.responses import JSONResponse, StreamingResponse as FastAPIStreamingResponse
5
3
  from typing import Callable, Dict, Any, List, Optional, get_type_hints
6
4
  import inspect
7
5
  import asyncio
8
6
  from datetime import datetime
9
7
  import uuid
8
+ import os
10
9
 
11
10
  from kalibr.types import FileUpload, Session, WorkflowState
12
11
 
@@ -20,63 +19,67 @@ class KalibrApp:
20
19
  - Complex workflows
21
20
  - Multi-model schema generation
22
21
  """
23
-
24
- def __init__(self, title="Kalibr Enhanced API", version="2.0.0", base_url="http://localhost:8000"):
22
+
23
+ def __init__(self, title="Kalibr Enhanced API", version="2.0.0", base_url: Optional[str] = None):
25
24
  """
26
25
  Initialize the Kalibr enhanced app.
27
-
28
- Args:
29
- title: API title
30
- version: API version
31
- base_url: Base URL for schema generation
26
+ Automatically determines correct base URL for deployed environments.
27
+
28
+ Priority:
29
+ 1. Explicit `base_url` passed by user
30
+ 2. Env var `KALIBR_BASE_URL`
31
+ 3. Env var `FLY_APP_NAME` -> https://<fly_app_name>.fly.dev
32
+ 4. Default localhost for dev
32
33
  """
33
34
  self.app = FastAPI(title=title, version=version)
34
- self.base_url = base_url
35
-
35
+
36
+ if base_url:
37
+ self.base_url = base_url
38
+ elif os.getenv("KALIBR_BASE_URL"):
39
+ self.base_url = os.getenv("KALIBR_BASE_URL")
40
+ elif os.getenv("FLY_APP_NAME"):
41
+ self.base_url = f"https://{os.getenv('FLY_APP_NAME')}.fly.dev"
42
+ else:
43
+ self.base_url = "http://localhost:8000"
44
+
36
45
  # Storage for different action types
37
- self.actions = {} # Regular actions
38
- self.file_handlers = {} # File upload handlers
39
- self.session_actions = {} # Session-aware actions
40
- self.stream_actions = {} # Streaming actions
41
- self.workflows = {} # Workflow handlers
42
-
43
- # Session storage (in-memory for simplicity)
46
+ self.actions: Dict[str, Any] = {}
47
+ self.file_handlers: Dict[str, Any] = {}
48
+ self.session_actions: Dict[str, Any] = {}
49
+ self.stream_actions: Dict[str, Any] = {}
50
+ self.workflows: Dict[str, Any] = {}
51
+
52
+ # Session and workflow memory
44
53
  self.sessions: Dict[str, Session] = {}
45
-
46
- # Workflow state storage
47
54
  self.workflow_states: Dict[str, WorkflowState] = {}
48
-
55
+
49
56
  self._setup_routes()
50
-
57
+
58
+ # -------------------------------------------------------------------------
59
+ # Action registration decorators
60
+ # -------------------------------------------------------------------------
61
+
51
62
  def action(self, name: str, description: str = ""):
52
- """
53
- Decorator to register a regular action.
54
-
55
- Usage:
56
- @app.action("greet", "Greet someone")
57
- def greet(name: str):
58
- return {"message": f"Hello, {name}!"}
59
- """
60
63
  def decorator(func: Callable):
61
64
  self.actions[name] = {
62
65
  "func": func,
63
66
  "description": description,
64
- "params": self._extract_params(func)
67
+ "params": self._extract_params(func),
65
68
  }
66
-
69
+
67
70
  endpoint_path = f"/proxy/{name}"
68
-
71
+
69
72
  async def endpoint_handler(request: Request):
70
73
  params = {}
71
74
  if request.method == "POST":
72
75
  try:
73
76
  body = await request.json()
74
77
  params = body if isinstance(body, dict) else {}
75
- except:
78
+ except Exception:
76
79
  params = {}
77
80
  else:
78
81
  params = dict(request.query_params)
79
-
82
+
80
83
  try:
81
84
  result = func(**params)
82
85
  if inspect.isawaitable(result):
@@ -84,92 +87,67 @@ class KalibrApp:
84
87
  return JSONResponse(content=result)
85
88
  except Exception as e:
86
89
  return JSONResponse(content={"error": str(e)}, status_code=500)
87
-
90
+
88
91
  self.app.post(endpoint_path)(endpoint_handler)
89
92
  self.app.get(endpoint_path)(endpoint_handler)
90
-
91
93
  return func
94
+
92
95
  return decorator
93
-
96
+
94
97
  def file_handler(self, name: str, allowed_extensions: List[str] = None, description: str = ""):
95
- """
96
- Decorator to register a file upload handler.
97
-
98
- Usage:
99
- @app.file_handler("process_document", [".pdf", ".docx"])
100
- async def process_document(file: FileUpload):
101
- return {"filename": file.filename, "size": file.size}
102
- """
103
98
  def decorator(func: Callable):
104
99
  self.file_handlers[name] = {
105
100
  "func": func,
106
101
  "description": description,
107
102
  "allowed_extensions": allowed_extensions or [],
108
- "params": self._extract_params(func)
103
+ "params": self._extract_params(func),
109
104
  }
110
-
105
+
111
106
  endpoint_path = f"/files/{name}"
112
-
107
+
113
108
  async def file_endpoint(file: UploadFile = File(...)):
114
109
  try:
115
- # Validate file extension
116
110
  if allowed_extensions:
117
111
  file_ext = "." + file.filename.split(".")[-1] if "." in file.filename else ""
118
112
  if file_ext not in allowed_extensions:
119
113
  return JSONResponse(
120
114
  content={"error": f"File type {file_ext} not allowed. Allowed: {allowed_extensions}"},
121
- status_code=400
115
+ status_code=400,
122
116
  )
123
-
124
- # Read file content
117
+
125
118
  content = await file.read()
126
-
127
- # Create FileUpload object
128
119
  file_upload = FileUpload(
129
120
  filename=file.filename,
130
121
  content_type=file.content_type or "application/octet-stream",
131
122
  size=len(content),
132
- content=content
123
+ content=content,
133
124
  )
134
-
135
- # Call handler
125
+
136
126
  result = func(file_upload)
137
127
  if inspect.isawaitable(result):
138
128
  result = await result
139
-
140
129
  return JSONResponse(content=result)
141
130
  except Exception as e:
142
131
  return JSONResponse(content={"error": str(e)}, status_code=500)
143
-
132
+
144
133
  self.app.post(endpoint_path)(file_endpoint)
145
-
146
134
  return func
135
+
147
136
  return decorator
148
-
137
+
149
138
  def session_action(self, name: str, description: str = ""):
150
- """
151
- Decorator to register a session-aware action.
152
-
153
- Usage:
154
- @app.session_action("save_data", "Save data to session")
155
- async def save_data(session: Session, data: dict):
156
- session.set("my_data", data)
157
- return {"saved": True}
158
- """
159
139
  def decorator(func: Callable):
160
140
  self.session_actions[name] = {
161
141
  "func": func,
162
142
  "description": description,
163
- "params": self._extract_params(func)
143
+ "params": self._extract_params(func),
164
144
  }
165
-
145
+
166
146
  endpoint_path = f"/session/{name}"
167
-
147
+
168
148
  async def session_endpoint(request: Request):
169
149
  try:
170
- # Get or create session
171
150
  session_id = request.headers.get("X-Session-ID") or request.cookies.get("session_id")
172
-
173
151
  if not session_id or session_id not in self.sessions:
174
152
  session_id = str(uuid.uuid4())
175
153
  session = Session(session_id=session_id)
@@ -177,67 +155,50 @@ class KalibrApp:
177
155
  else:
178
156
  session = self.sessions[session_id]
179
157
  session.last_accessed = datetime.now()
180
-
181
- # Get request parameters
158
+
182
159
  body = await request.json() if request.method == "POST" else {}
183
-
184
- # Call function with session
160
+
185
161
  sig = inspect.signature(func)
186
- if 'session' in sig.parameters:
187
- # Remove 'session' from params, pass separately
188
- func_params = {k: v for k, v in body.items() if k != 'session'}
162
+ if "session" in sig.parameters:
163
+ func_params = {k: v for k, v in body.items() if k != "session"}
189
164
  result = func(session=session, **func_params)
190
165
  else:
191
166
  result = func(**body)
192
-
167
+
193
168
  if inspect.isawaitable(result):
194
169
  result = await result
195
-
196
- # Return result with session ID
170
+
197
171
  response = JSONResponse(content=result)
198
172
  response.set_cookie("session_id", session_id)
199
173
  response.headers["X-Session-ID"] = session_id
200
-
201
174
  return response
202
175
  except Exception as e:
203
176
  return JSONResponse(content={"error": str(e)}, status_code=500)
204
-
177
+
205
178
  self.app.post(endpoint_path)(session_endpoint)
206
-
207
179
  return func
180
+
208
181
  return decorator
209
-
182
+
210
183
  def stream_action(self, name: str, description: str = ""):
211
- """
212
- Decorator to register a streaming action.
213
-
214
- Usage:
215
- @app.stream_action("live_feed", "Stream live data")
216
- async def live_feed(count: int = 10):
217
- for i in range(count):
218
- yield {"item": i, "timestamp": datetime.now().isoformat()}
219
- await asyncio.sleep(0.5)
220
- """
221
184
  def decorator(func: Callable):
222
185
  self.stream_actions[name] = {
223
186
  "func": func,
224
187
  "description": description,
225
- "params": self._extract_params(func)
188
+ "params": self._extract_params(func),
226
189
  }
227
-
190
+
228
191
  endpoint_path = f"/stream/{name}"
229
-
192
+
230
193
  async def stream_endpoint(request: Request):
231
194
  try:
232
- # Get parameters
233
195
  params = dict(request.query_params) if request.method == "GET" else {}
234
196
  if request.method == "POST":
235
197
  body = await request.json()
236
198
  params.update(body)
237
-
238
- # Convert parameter types based on function signature
199
+
239
200
  sig = inspect.signature(func)
240
- type_hints = get_type_hints(func) if hasattr(func, '__annotations__') else {}
201
+ type_hints = get_type_hints(func) if hasattr(func, "__annotations__") else {}
241
202
  converted_params = {}
242
203
  for key, value in params.items():
243
204
  if key in sig.parameters:
@@ -248,222 +209,114 @@ class KalibrApp:
248
209
  elif param_type == float:
249
210
  converted_params[key] = float(value)
250
211
  elif param_type == bool:
251
- converted_params[key] = value.lower() in ('true', '1', 'yes')
212
+ converted_params[key] = value.lower() in ("true", "1", "yes")
252
213
  else:
253
214
  converted_params[key] = value
254
- except (ValueError, AttributeError):
215
+ except Exception:
255
216
  converted_params[key] = value
256
-
257
- # Call generator function
217
+
258
218
  result = func(**converted_params)
259
-
260
- # Create streaming generator
219
+
261
220
  async def generate():
221
+ import json
262
222
  if inspect.isasyncgen(result):
263
223
  async for item in result:
264
- import json
265
224
  yield json.dumps(item) + "\n"
266
225
  elif inspect.isgenerator(result):
267
226
  for item in result:
268
- import json
269
227
  yield json.dumps(item) + "\n"
270
-
228
+
271
229
  return FastAPIStreamingResponse(generate(), media_type="application/x-ndjson")
272
230
  except Exception as e:
273
231
  return JSONResponse(content={"error": str(e)}, status_code=500)
274
-
232
+
275
233
  self.app.get(endpoint_path)(stream_endpoint)
276
234
  self.app.post(endpoint_path)(stream_endpoint)
277
-
278
- return func
279
- return decorator
280
-
281
- def workflow(self, name: str, description: str = ""):
282
- """
283
- Decorator to register a workflow.
284
-
285
- Usage:
286
- @app.workflow("process_order", "Process customer order")
287
- async def process_order(order_data: dict, workflow_state: WorkflowState):
288
- workflow_state.step = "validation"
289
- # ... process steps
290
- return {"workflow_id": workflow_state.workflow_id}
291
- """
292
- def decorator(func: Callable):
293
- self.workflows[name] = {
294
- "func": func,
295
- "description": description,
296
- "params": self._extract_params(func)
297
- }
298
-
299
- endpoint_path = f"/workflow/{name}"
300
-
301
- async def workflow_endpoint(request: Request):
302
- try:
303
- # Get workflow ID from headers or create new
304
- workflow_id = request.headers.get("X-Workflow-ID")
305
-
306
- if not workflow_id or workflow_id not in self.workflow_states:
307
- workflow_id = str(uuid.uuid4())
308
- workflow_state = WorkflowState(
309
- workflow_id=workflow_id,
310
- step="init",
311
- status="running"
312
- )
313
- self.workflow_states[workflow_id] = workflow_state
314
- else:
315
- workflow_state = self.workflow_states[workflow_id]
316
- workflow_state.updated_at = datetime.now()
317
-
318
- # Get request data
319
- body = await request.json() if request.method == "POST" else {}
320
-
321
- # Call workflow function
322
- sig = inspect.signature(func)
323
- if 'workflow_state' in sig.parameters:
324
- func_params = {k: v for k, v in body.items() if k != 'workflow_state'}
325
- result = func(workflow_state=workflow_state, **func_params)
326
- else:
327
- result = func(**body)
328
-
329
- if inspect.isawaitable(result):
330
- result = await result
331
-
332
- # Return result with workflow ID
333
- response = JSONResponse(content=result)
334
- response.headers["X-Workflow-ID"] = workflow_id
335
-
336
- return response
337
- except Exception as e:
338
- return JSONResponse(content={"error": str(e)}, status_code=500)
339
-
340
- self.app.post(endpoint_path)(workflow_endpoint)
341
-
342
235
  return func
236
+
343
237
  return decorator
344
-
345
- def _extract_params(self, func: Callable) -> Dict:
346
- """Extract parameter information from function signature."""
347
- sig = inspect.signature(func)
348
- params = {}
349
- type_hints = get_type_hints(func) if hasattr(func, '__annotations__') else {}
350
-
351
- for param_name, param in sig.parameters.items():
352
- # Skip special parameters
353
- if param_name in ['session', 'workflow_state', 'file']:
354
- continue
355
-
356
- param_type = "string"
357
-
358
- if param_name in type_hints:
359
- anno = type_hints[param_name]
360
- elif param.annotation != inspect.Parameter.empty:
361
- anno = param.annotation
362
- else:
363
- anno = str
364
-
365
- # Map types
366
- if anno == int:
367
- param_type = "integer"
368
- elif anno == bool:
369
- param_type = "boolean"
370
- elif anno == float:
371
- param_type = "number"
372
- elif anno == list:
373
- param_type = "array"
374
- elif anno == dict:
375
- param_type = "object"
376
-
377
- is_required = param.default == inspect.Parameter.empty
378
-
379
- params[param_name] = {
380
- "type": param_type,
381
- "required": is_required
382
- }
383
-
384
- return params
385
-
238
+
239
+ # -------------------------------------------------------------------------
240
+ # Schema generation routes
241
+ # -------------------------------------------------------------------------
242
+
386
243
  def _setup_routes(self):
387
- """Setup core API routes."""
388
244
  from kalibr.schema_generators import (
389
245
  OpenAPISchemaGenerator,
390
246
  MCPSchemaGenerator,
391
247
  GeminiSchemaGenerator,
392
- CopilotSchemaGenerator
248
+ CopilotSchemaGenerator,
393
249
  )
394
-
395
- # Initialize schema generators
250
+
396
251
  openapi_gen = OpenAPISchemaGenerator()
397
252
  mcp_gen = MCPSchemaGenerator()
398
253
  gemini_gen = GeminiSchemaGenerator()
399
254
  copilot_gen = CopilotSchemaGenerator()
400
-
255
+
401
256
  @self.app.get("/")
402
257
  def root():
403
- """Root endpoint with API information."""
404
258
  return {
405
259
  "message": "Kalibr Enhanced API is running",
406
260
  "actions": list(self.actions.keys()),
407
- "file_handlers": list(self.file_handlers.keys()),
408
- "session_actions": list(self.session_actions.keys()),
409
- "stream_actions": list(self.stream_actions.keys()),
410
- "workflows": list(self.workflows.keys()),
411
261
  "schemas": {
412
262
  "gpt_actions": f"{self.base_url}/gpt-actions.json",
413
- "openapi_swagger": f"{self.base_url}/openapi.json",
414
263
  "claude_mcp": f"{self.base_url}/mcp.json",
415
264
  "gemini": f"{self.base_url}/schemas/gemini",
416
- "copilot": f"{self.base_url}/schemas/copilot"
417
- }
265
+ "copilot": f"{self.base_url}/schemas/copilot",
266
+ },
418
267
  }
419
-
268
+
420
269
  @self.app.get("/gpt-actions.json")
421
270
  def gpt_actions_schema():
422
- """Generate GPT Actions schema from all registered actions."""
423
- # Combine all action types for schema generation
424
271
  all_actions = {**self.actions, **self.file_handlers, **self.session_actions}
425
272
  return openapi_gen.generate_schema(all_actions, self.base_url)
426
-
273
+
427
274
  @self.app.get("/mcp.json")
428
275
  def mcp_manifest():
429
- """Generate Claude MCP manifest."""
430
276
  all_actions = {**self.actions, **self.file_handlers, **self.session_actions}
431
277
  return mcp_gen.generate_schema(all_actions, self.base_url)
432
-
278
+
433
279
  @self.app.get("/schemas/gemini")
434
280
  def gemini_schema():
435
- """Generate Gemini Extensions schema."""
436
281
  all_actions = {**self.actions, **self.file_handlers, **self.session_actions}
437
282
  return gemini_gen.generate_schema(all_actions, self.base_url)
438
-
283
+
439
284
  @self.app.get("/schemas/copilot")
440
285
  def copilot_schema():
441
- """Generate Microsoft Copilot schema."""
442
286
  all_actions = {**self.actions, **self.file_handlers, **self.session_actions}
443
287
  return copilot_gen.generate_schema(all_actions, self.base_url)
444
-
445
- # Health check
288
+
446
289
  @self.app.get("/health")
447
290
  def health_check():
448
- return {
449
- "status": "healthy",
450
- "service": "Kalibr Enhanced API",
451
- "features": ["actions", "file_uploads", "sessions", "streaming", "workflows"]
452
- }
453
-
454
- # Override FastAPI OpenAPI for Swagger UI
455
- def custom_openapi():
456
- if self.app.openapi_schema:
457
- return self.app.openapi_schema
458
-
459
- from fastapi.openapi.utils import get_openapi
460
- openapi_schema = get_openapi(
461
- title=self.app.title,
462
- version=self.app.version,
463
- routes=self.app.routes,
464
- )
465
- openapi_schema["servers"] = [{"url": self.base_url}]
466
- self.app.openapi_schema = openapi_schema
467
- return self.app.openapi_schema
468
-
469
- self.app.openapi = custom_openapi
291
+ return {"status": "healthy", "service": "Kalibr Enhanced API"}
292
+
293
+ # -------------------------------------------------------------------------
294
+ # Helpers
295
+ # -------------------------------------------------------------------------
296
+
297
+ def _extract_params(self, func: Callable) -> Dict[str, Any]:
298
+ sig = inspect.signature(func)
299
+ params = {}
300
+ type_hints = get_type_hints(func) if hasattr(func, "__annotations__") else {}
301
+
302
+ for param_name, param in sig.parameters.items():
303
+ if param_name in ["session", "workflow_state", "file"]:
304
+ continue
305
+
306
+ param_type = "string"
307
+ anno = type_hints.get(param_name, param.annotation)
308
+ if anno == int:
309
+ param_type = "integer"
310
+ elif anno == bool:
311
+ param_type = "boolean"
312
+ elif anno == float:
313
+ param_type = "number"
314
+ elif anno == list:
315
+ param_type = "array"
316
+ elif anno == dict:
317
+ param_type = "object"
318
+
319
+ is_required = param.default == inspect.Parameter.empty
320
+ params[param_name] = {"type": param_type, "required": is_required}
321
+
322
+ return params
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.4
2
+ Name: kalibr
3
+ Version: 1.0.22
4
+ Summary: Multi-Model AI Integration Framework
5
+ Home-page: https://github.com/devonakelley/kalibr-sdk
6
+ Author: Kalibr Team
7
+ Author-email: Kalibr Team <team@kalibr.dev>
8
+ License: MIT
9
+ Project-URL: Homepage, https://kalibr.dev
10
+ Project-URL: Documentation, https://kalibr.dev/docs
11
+ Project-URL: Repository, https://github.com/devonakelley/kalibr-sdk
12
+ Project-URL: Bug Reports, https://github.com/devonakelley/kalibr-sdk/issues
13
+ Keywords: ai,api,framework,gpt,claude,gemini,copilot,multi-model,sdk
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
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 :: Internet :: WWW/HTTP :: WSGI :: Application
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: fastapi>=0.110.1
28
+ Requires-Dist: uvicorn>=0.25.0
29
+ Requires-Dist: pydantic>=2.6.4
30
+ Requires-Dist: typer>=0.9.0
31
+ Requires-Dist: requests>=2.31.0
32
+ Requires-Dist: python-jose[cryptography]>=3.3.0
33
+ Requires-Dist: passlib[bcrypt]>=1.7.4
34
+ Requires-Dist: python-multipart>=0.0.9
35
+ Requires-Dist: motor>=3.3.1
36
+ Requires-Dist: pymongo>=4.5.0
37
+ Requires-Dist: boto3>=1.34.129
38
+ Requires-Dist: aiofiles>=23.2.1
39
+ Provides-Extra: dev
40
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
41
+ Requires-Dist: black>=24.1.1; extra == "dev"
42
+ Requires-Dist: isort>=5.13.2; extra == "dev"
43
+ Requires-Dist: flake8>=7.0.0; extra == "dev"
44
+ Requires-Dist: mypy>=1.8.0; extra == "dev"
45
+ Dynamic: author
46
+ Dynamic: home-page
47
+ Dynamic: license-file
48
+ Dynamic: requires-python
49
+
50
+ # Kalibr SDK
51
+ ### Multi-Model AI Integration Framework
52
+
53
+ **Write once. Deploy anywhere. Connect to any AI model.**
54
+
55
+ Kalibr turns Python functions into APIs that work seamlessly with GPT, Claude, Gemini, and Copilot — automatically generating model-specific schemas and endpoints.
56
+
57
+ ---
58
+
59
+ ## 🚀 Quick Start (2 minutes)
60
+
61
+ ### 1. Install
62
+ ```bash
63
+ pip install kalibr
64
+ ```
65
+
66
+ ### 2. Get Examples
67
+ ```bash
68
+ kalibr-connect examples
69
+ ```
70
+ This copies example files to `./kalibr_examples/` in your current directory.
71
+
72
+ ### 3. Run Demo
73
+ ```bash
74
+ kalibr-connect serve kalibr_examples/basic_kalibr_example.py
75
+ ```
76
+
77
+ ### 4. See All Schemas
78
+ Kalibr now **auto-detects your environment** and generates the correct base URLs.
79
+
80
+ | Environment | Example Base URL |
81
+ |--------------|------------------|
82
+ | Local Dev | `http://localhost:8000` |
83
+ | Fly.io | `https://<app-name>.fly.dev` |
84
+ | Custom Host | Use `KALIBR_BASE_URL` env var |
85
+
86
+ Then open:
87
+ ```
88
+ <your-base-url>/gpt-actions.json # ChatGPT
89
+ <your-base-url>/mcp.json # Claude
90
+ <your-base-url>/schemas/gemini # Gemini
91
+ <your-base-url>/schemas/copilot # Copilot
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 🧠 What Kalibr Does
97
+
98
+ Kalibr turns your Python functions into production-ready multi-model APIs.
99
+
100
+ ```python
101
+ from kalibr import Kalibr
102
+
103
+ app = Kalibr(title="Inventory API")
104
+
105
+ @app.action("get_inventory", "Fetch inventory data")
106
+ def get_inventory(product_id: str):
107
+ return {"product_id": product_id, "stock": 42}
108
+ ```
109
+
110
+ Result:
111
+ ChatGPT, Claude, Gemini, and Copilot can all call `get_inventory()` using their native protocols — no schema work required.
112
+
113
+ ---
114
+
115
+ ## 💪 Two Modes
116
+
117
+ ### **Function-Level (Simple)**
118
+ Ideal for one-off APIs or scripts.
119
+
120
+ ```python
121
+ from kalibr import Kalibr
122
+
123
+ app = Kalibr(title="My API")
124
+
125
+ @app.action("calculate_price", "Calculate price total")
126
+ def calculate_price(product_id: str, quantity: int):
127
+ return {"total": quantity * 19.99}
128
+ ```
129
+
130
+ ### **App-Level (Advanced)**
131
+ Use `KalibrApp` for complete control — file uploads, sessions, streaming, and workflows.
132
+
133
+ ```python
134
+ from kalibr import KalibrApp
135
+ from kalibr.types import FileUpload, Session
136
+
137
+ app = KalibrApp(title="Advanced API")
138
+
139
+ @app.file_handler("analyze_doc", [".pdf", ".docx"])
140
+ async def analyze_doc(file: FileUpload):
141
+ return {"filename": file.filename, "analysis": "..."}
142
+
143
+ @app.session_action("save_data", "Save session data")
144
+ async def save_data(session: Session, data: dict):
145
+ session.set("my_data", data)
146
+ return {"saved": True}
147
+ ```
148
+
149
+ ---
150
+
151
+ ## 📚 Examples Included
152
+
153
+ After running `kalibr-connect examples`, you’ll get:
154
+
155
+ - `basic_kalibr_example.py` – simple function-level demo
156
+ - `enhanced_kalibr_example.py` – full app with sessions, uploads, and streaming
157
+
158
+ ---
159
+
160
+ ## 🤖 AI Platform Integration
161
+
162
+ ### ChatGPT (GPT Actions)
163
+ 1. Copy schema URL:
164
+ `https://<your-domain>/gpt-actions.json`
165
+ 2. In GPT Builder → *Actions* → *Import from URL*
166
+ 3. Done — ChatGPT can call your endpoints.
167
+
168
+ ### Claude (MCP)
169
+ Add to Claude Desktop config:
170
+ ```json
171
+ {
172
+ "mcp": {
173
+ "servers": {
174
+ "my-api": {
175
+ "url": "https://<your-domain>/mcp.json"
176
+ }
177
+ }
178
+ }
179
+ }
180
+ ```
181
+
182
+ ### Gemini / Copilot
183
+ Use:
184
+ ```
185
+ https://<your-domain>/schemas/gemini
186
+ https://<your-domain>/schemas/copilot
187
+ ```
188
+
189
+ ---
190
+
191
+ ## 🎯 Common Use Cases
192
+
193
+ - **Customer Service APIs** — let AI handle orders or refunds
194
+ - **Data Analysis** — query your analytics through AI
195
+ - **Document Processing** — parse or summarize uploaded docs
196
+ - **Business Automation** — trigger internal workflows
197
+ - **Internal Tools** — expose secure internal logic to assistants
198
+
199
+ ---
200
+
201
+ ## 🔧 CLI Reference
202
+
203
+ ```bash
204
+ kalibr-connect examples # Copy examples
205
+ kalibr-connect serve my_app.py # Run locally
206
+ kalibr-connect version # Show version
207
+ kalibr-connect --help # Full CLI
208
+ ```
209
+
210
+ ---
211
+
212
+ ## ⚡ Key Features
213
+
214
+ ✅ Multi-Model Support — GPT, Claude, Gemini, Copilot
215
+ ✅ Automatic Schema Generation
216
+ ✅ Environment-Aware Base URLs (v1.0.21+)
217
+ ✅ File Uploads
218
+ ✅ Session Management
219
+ ✅ Streaming Responses
220
+ ✅ Workflow Support
221
+ ✅ Type-Safe API Generation
222
+ ✅ Async / Await Ready
223
+
224
+ ---
225
+
226
+ ## 🔥 Why Kalibr?
227
+
228
+ Without Kalibr:
229
+ - Learn 4 model specs
230
+ - Maintain 4 codebases
231
+ - Duplicate effort
232
+
233
+ With Kalibr:
234
+ - One Python function
235
+ - Four schemas generated automatically
236
+ - Deploy anywhere
237
+
238
+ ---
239
+
240
+ ## 🆕 Version 1.0.21+
241
+
242
+ - **Automatic Base-URL Detection**
243
+ - Works with `KALIBR_BASE_URL` or `FLY_APP_NAME`
244
+ - Fixes all localhost references in deployed schemas
245
+ - Ready for **MCP ecosystem production use**
246
+ - Drop-in backwards compatibility
247
+
248
+ ---
249
+
250
+ ## 🧩 License
251
+
252
+ MIT License — see `LICENSE` file for details.
253
+
254
+ ---
255
+
256
+ **Kalibr SDK — the unified layer between AI models and the real world.**
257
+ Write once. Deploy anywhere. Integrate everything.
@@ -2,15 +2,15 @@ kalibr/__init__.py,sha256=1IIl43EB3tAsP1anJhi63PyD9Cx-cOGYPGJ9eKbAB0s,188
2
2
  kalibr/__main__.py,sha256=Hl_-_vfimqHWZEwAUl4_OjrUWb1l7pM4q_13wbDI6gY,25584
3
3
  kalibr/deployment.py,sha256=B-2ePPCMF2UcnkM2YKkAoaNrOjpUKUGfNO0IxyNOJMI,670
4
4
  kalibr/kalibr.py,sha256=yrgXVlTgadBbpnX_l7fAxxjxGp9oxcZhzGjaQPiIcpo,10469
5
- kalibr/kalibr_app.py,sha256=gNQprofiDk8CUk0oxC8kwRiOt32VkoRhKc2t6cgD3IA,19307
5
+ kalibr/kalibr_app.py,sha256=bA-YOxEq2MyXVrjPQG-b8k_66gY5Ci7hxTOtg9rZuh0,12878
6
6
  kalibr/schema_generators.py,sha256=nIgoYaO0FGC6arHdUHG0XaGDpJDycJeWDDC1-zAHzfI,7528
7
7
  kalibr/types.py,sha256=bNmf_cOWXBmhaMVAPEp3_EdRCcdXY2pbOgOxZ1dZ0Mc,3476
8
- kalibr-1.0.20.data/data/examples/README.md,sha256=loo2nm6yfT-pqGb5uNg1VeEdOKflYzHISUHTuSltfY0,4875
9
- kalibr-1.0.20.data/data/examples/basic_kalibr_example.py,sha256=Kfrh-XZuJ0vwFLB_xBpdqpgpMJw2NpIx0yBsqrAqBnE,2188
10
- kalibr-1.0.20.data/data/examples/enhanced_kalibr_example.py,sha256=AuhTpyRUNVAJuZKRy9iydXusNkBgQ84eKNiXxsr4iUQ,11994
11
- kalibr-1.0.20.dist-info/licenses/LICENSE,sha256=1WLJDkrueNpHCROy9zANrK2Ar2weqZ_z88hw90UKDoc,451
12
- kalibr-1.0.20.dist-info/METADATA,sha256=mKCIVFrYPVFhl2XUidHMI21O8_-L8VNVvarWmshJO8E,7965
13
- kalibr-1.0.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- kalibr-1.0.20.dist-info/entry_points.txt,sha256=T-DOrFEZb0fZxA9H8sSCh-2zKxdjnmpzIRmm5TY_f6s,56
15
- kalibr-1.0.20.dist-info/top_level.txt,sha256=OkloC5_IfpE4-QwI30aLIYbFZk_-ChABWF7aBGddy28,7
16
- kalibr-1.0.20.dist-info/RECORD,,
8
+ kalibr-1.0.22.data/data/examples/README.md,sha256=loo2nm6yfT-pqGb5uNg1VeEdOKflYzHISUHTuSltfY0,4875
9
+ kalibr-1.0.22.data/data/examples/basic_kalibr_example.py,sha256=Kfrh-XZuJ0vwFLB_xBpdqpgpMJw2NpIx0yBsqrAqBnE,2188
10
+ kalibr-1.0.22.data/data/examples/enhanced_kalibr_example.py,sha256=AuhTpyRUNVAJuZKRy9iydXusNkBgQ84eKNiXxsr4iUQ,11994
11
+ kalibr-1.0.22.dist-info/licenses/LICENSE,sha256=1WLJDkrueNpHCROy9zANrK2Ar2weqZ_z88hw90UKDoc,451
12
+ kalibr-1.0.22.dist-info/METADATA,sha256=nuXv558HbymTNBsuh7WircA-uV5Q3neiZ0DGM4mZCnM,6658
13
+ kalibr-1.0.22.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ kalibr-1.0.22.dist-info/entry_points.txt,sha256=T-DOrFEZb0fZxA9H8sSCh-2zKxdjnmpzIRmm5TY_f6s,56
15
+ kalibr-1.0.22.dist-info/top_level.txt,sha256=OkloC5_IfpE4-QwI30aLIYbFZk_-ChABWF7aBGddy28,7
16
+ kalibr-1.0.22.dist-info/RECORD,,
@@ -1,302 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: kalibr
3
- Version: 1.0.20
4
- Summary: Multi-Model AI Integration Framework
5
- Home-page: https://github.com/devonakelley/kalibr-sdk
6
- Author: Kalibr Team
7
- Author-email: Kalibr Team <team@kalibr.dev>
8
- License: MIT
9
- Project-URL: Homepage, https://kalibr.dev
10
- Project-URL: Documentation, https://kalibr.dev/docs
11
- Project-URL: Repository, https://github.com/devonakelley/kalibr-sdk
12
- Project-URL: Bug Reports, https://github.com/devonakelley/kalibr-sdk/issues
13
- Keywords: ai,api,framework,gpt,claude,gemini,copilot,multi-model,sdk
14
- Classifier: Development Status :: 4 - Beta
15
- Classifier: Intended Audience :: Developers
16
- Classifier: License :: OSI Approved :: MIT License
17
- Classifier: Operating System :: OS Independent
18
- Classifier: Programming Language :: Python :: 3
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 :: Internet :: WWW/HTTP :: WSGI :: Application
23
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
- Requires-Python: >=3.11
25
- Description-Content-Type: text/markdown
26
- License-File: LICENSE
27
- Requires-Dist: fastapi>=0.110.1
28
- Requires-Dist: uvicorn>=0.25.0
29
- Requires-Dist: pydantic>=2.6.4
30
- Requires-Dist: typer>=0.9.0
31
- Requires-Dist: requests>=2.31.0
32
- Requires-Dist: python-jose[cryptography]>=3.3.0
33
- Requires-Dist: passlib[bcrypt]>=1.7.4
34
- Requires-Dist: python-multipart>=0.0.9
35
- Requires-Dist: motor>=3.3.1
36
- Requires-Dist: pymongo>=4.5.0
37
- Requires-Dist: boto3>=1.34.129
38
- Requires-Dist: aiofiles>=23.2.1
39
- Provides-Extra: dev
40
- Requires-Dist: pytest>=8.0.0; extra == "dev"
41
- Requires-Dist: black>=24.1.1; extra == "dev"
42
- Requires-Dist: isort>=5.13.2; extra == "dev"
43
- Requires-Dist: flake8>=7.0.0; extra == "dev"
44
- Requires-Dist: mypy>=1.8.0; extra == "dev"
45
- Dynamic: author
46
- Dynamic: home-page
47
- Dynamic: license-file
48
- Dynamic: requires-python
49
-
50
- # Kalibr SDK
51
-
52
- **Multi-Model AI Integration Framework**
53
-
54
- Write once. Deploy anywhere. Connect to any AI model.
55
-
56
- Kalibr lets you expose Python functions as APIs that work with **GPT, Claude, Gemini, and Copilot** automatically.
57
-
58
- [![PyPI version](https://badge.fury.io/py/kalibr.svg)](https://badge.fury.io/py/kalibr)
59
- [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
60
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
61
-
62
- ---
63
-
64
- ## 🚀 Quick Start (2 minutes)
65
-
66
- ### 1. Install
67
- ```bash
68
- pip install kalibr
69
- ```
70
-
71
- ### 2. Get Examples
72
- ```bash
73
- kalibr-connect examples
74
- ```
75
-
76
- This copies example files to `./kalibr_examples/` in your current directory.
77
-
78
- ### 3. Run Demo
79
- ```bash
80
- kalibr-connect serve kalibr_examples/basic_kalibr_example.py
81
- ```
82
-
83
- ### 4. See Multi-Model Schemas
84
-
85
- Open your browser to see all 4 AI model schemas auto-generated:
86
-
87
- ```
88
- http://localhost:8000/ # API info
89
- http://localhost:8000/gpt-actions.json # For ChatGPT
90
- http://localhost:8000/mcp.json # For Claude
91
- http://localhost:8000/schemas/gemini # For Gemini
92
- http://localhost:8000/schemas/copilot # For Copilot
93
- http://localhost:8000/docs # Interactive docs
94
- ```
95
-
96
- **That's it!** One Python file, four AI platform schemas. 🎯
97
-
98
- ---
99
-
100
- ## 🎯 What Does This Do?
101
-
102
- Kalibr turns your Python functions into APIs that AI assistants can call:
103
-
104
- ```python
105
- from kalibr import Kalibr
106
-
107
- app = Kalibr(title="My Business API")
108
-
109
- @app.action("get_inventory", "Check product stock")
110
- def get_inventory(product_id: str):
111
- # Your business logic
112
- return {"product_id": product_id, "stock": 42}
113
- ```
114
-
115
- **Result:** ChatGPT, Claude, Gemini, and Copilot can all call your `get_inventory` function!
116
-
117
- ---
118
-
119
- ## 💪 Two Modes
120
-
121
- ### Function-Level (Simple)
122
- Perfect for exposing business logic:
123
-
124
- ```python
125
- from kalibr import Kalibr
126
-
127
- app = Kalibr(title="My API")
128
-
129
- @app.action("calculate_price", "Calculate product price")
130
- def calculate_price(product_id: str, quantity: int):
131
- return {"total": quantity * get_price(product_id)}
132
- ```
133
-
134
- ### App-Level (Advanced)
135
- Full framework with file uploads, sessions, streaming, workflows:
136
-
137
- ```python
138
- from kalibr import KalibrApp
139
- from kalibr.types import FileUpload, Session
140
-
141
- app = KalibrApp(title="Advanced API")
142
-
143
- @app.file_handler("analyze_doc", [".pdf", ".docx"])
144
- async def analyze_doc(file: FileUpload):
145
- return {"filename": file.filename, "analysis": "..."}
146
-
147
- @app.session_action("save_data", "Save to session")
148
- async def save_data(session: Session, data: dict):
149
- session.set("my_data", data)
150
- return {"saved": True}
151
-
152
- @app.stream_action("live_feed", "Stream real-time data")
153
- async def live_feed(count: int = 10):
154
- for i in range(count):
155
- yield {"item": i, "timestamp": "..."}
156
- ```
157
-
158
- ---
159
-
160
- ## 📚 Examples Included
161
-
162
- After running `kalibr-connect examples`, you get:
163
-
164
- - **`basic_kalibr_example.py`** - Simple function-level example
165
- - **`enhanced_kalibr_example.py`** - Advanced app-level example with all features
166
-
167
- ---
168
-
169
- ## 🤖 AI Platform Integration
170
-
171
- Once your Kalibr app is running, integrate with AI platforms:
172
-
173
- ### ChatGPT (GPT Actions)
174
- 1. Copy schema from `http://localhost:8000/gpt-actions.json`
175
- 2. Go to GPT Builder → Actions → Import from URL
176
- 3. Done! ChatGPT can now call your functions
177
-
178
- ### Claude (MCP)
179
- 1. Add to Claude Desktop config:
180
- ```json
181
- {
182
- "mcp": {
183
- "servers": {
184
- "my-api": {
185
- "url": "http://localhost:8000/mcp.json"
186
- }
187
- }
188
- }
189
- }
190
- ```
191
- 2. Done! Claude can now call your functions
192
-
193
- ### Gemini & Copilot
194
- Similar simple setup using their respective schema endpoints.
195
-
196
- ---
197
-
198
- ## 🎯 Use Cases
199
-
200
- - **Customer Service APIs** - Let AI assistants look up orders, process refunds
201
- - **Data Analysis APIs** - Let AI query your analytics and generate insights
202
- - **Document Processing** - Let AI analyze uploaded documents
203
- - **Business Automation** - Let AI trigger workflows in your systems
204
- - **Internal Tools** - Give your team AI-powered access to internal systems
205
-
206
- ---
207
-
208
- ## 📖 Documentation
209
-
210
- - **Quick Start**: You're reading it!
211
- - **Full Docs**: See `KALIBR_SDK_COMPLETE.md` in the package
212
- - **Examples**: Run `kalibr-connect examples`
213
- - **CLI Help**: `kalibr-connect --help`
214
-
215
- ---
216
-
217
- ## 🔧 CLI Commands
218
-
219
- ```bash
220
- kalibr-connect examples # Copy example files to current dir
221
- kalibr-connect serve my_app.py # Run your app locally
222
- kalibr-connect version # Show version info
223
- kalibr-connect --help # Show all commands
224
- ```
225
-
226
- ---
227
-
228
- ## ⚡ Key Features
229
-
230
- - ✅ **Multi-Model Support** - Works with GPT, Claude, Gemini, Copilot
231
- - ✅ **Automatic Schemas** - No manual schema writing
232
- - ✅ **File Uploads** - Handle document uploads
233
- - ✅ **Sessions** - Stateful conversations
234
- - ✅ **Streaming** - Real-time data streaming
235
- - ✅ **Workflows** - Multi-step processes
236
- - ✅ **Type Safe** - Full Python type hints
237
- - ✅ **Fast** - Async/await support
238
-
239
- ---
240
-
241
- ## 🔥 Why Kalibr?
242
-
243
- **Without Kalibr:**
244
- - Learn 4 different API specs
245
- - Write 4 different schemas
246
- - Maintain 4 codebases
247
- - = Weeks of work
248
-
249
- **With Kalibr:**
250
- - Write Python functions once
251
- - Kalibr generates all 4 schemas
252
- - Single codebase
253
- - = One day of work
254
-
255
- ---
256
-
257
- ## 💡 Simple Example
258
-
259
- ```python
260
- # my_app.py
261
- from kalibr import Kalibr
262
-
263
- app = Kalibr(title="Weather API")
264
-
265
- @app.action("get_weather", "Get current weather")
266
- def get_weather(city: str):
267
- # Your logic here
268
- return {"city": city, "temp": 72, "condition": "sunny"}
269
- ```
270
-
271
- ```bash
272
- # Run it
273
- kalibr-connect serve my_app.py
274
-
275
- # Now ALL these work:
276
- # ✅ ChatGPT can call get_weather()
277
- # ✅ Claude can call get_weather()
278
- # ✅ Gemini can call get_weather()
279
- # ✅ Copilot can call get_weather()
280
- ```
281
-
282
- ---
283
-
284
- ## 🚀 Get Started Now
285
-
286
- ```bash
287
- pip install kalibr
288
- kalibr-connect examples
289
- kalibr-connect serve kalibr_examples/basic_kalibr_example.py
290
- ```
291
-
292
- Open http://localhost:8000 and see your multi-model API in action! 🎯
293
-
294
- ---
295
-
296
- ## 📝 License
297
-
298
- MIT License - see LICENSE file for details.
299
-
300
- ---
301
-
302
- **Kalibr SDK** - Transform how you build AI-integrated applications! 🚀