kalibr 1.0.17__py3-none-any.whl → 1.0.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kalibr/__init__.py +7 -0
- kalibr/__main__.py +672 -26
- kalibr/deployment.py +26 -0
- kalibr/kalibr.py +259 -0
- kalibr/kalibr_app.py +465 -34
- kalibr/schema_generators.py +212 -13
- kalibr/types.py +106 -0
- kalibr-1.0.20.data/data/examples/README.md +173 -0
- kalibr-1.0.20.data/data/examples/basic_kalibr_example.py +66 -0
- kalibr-1.0.20.data/data/examples/enhanced_kalibr_example.py +347 -0
- kalibr-1.0.20.dist-info/METADATA +302 -0
- kalibr-1.0.20.dist-info/RECORD +16 -0
- kalibr-1.0.17.dist-info/METADATA +0 -120
- kalibr-1.0.17.dist-info/RECORD +0 -10
- {kalibr-1.0.17.dist-info → kalibr-1.0.20.dist-info}/WHEEL +0 -0
- {kalibr-1.0.17.dist-info → kalibr-1.0.20.dist-info}/entry_points.txt +0 -0
- /kalibr-1.0.17.dist-info/licenses/LICENSE.txt → /kalibr-1.0.20.dist-info/licenses/LICENSE +0 -0
- {kalibr-1.0.17.dist-info → kalibr-1.0.20.dist-info}/top_level.txt +0 -0
kalibr/deployment.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kalibr Deployment Stub
|
|
3
|
+
-----------------------
|
|
4
|
+
This module provides a lightweight interface for future deployment providers.
|
|
5
|
+
|
|
6
|
+
Current supported strategy:
|
|
7
|
+
- Local (via Uvicorn)
|
|
8
|
+
|
|
9
|
+
Planned:
|
|
10
|
+
- Fly.io
|
|
11
|
+
- Render
|
|
12
|
+
- Railway
|
|
13
|
+
|
|
14
|
+
No AWS dependencies are required or used.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import subprocess
|
|
18
|
+
|
|
19
|
+
def deploy_local(file_path: str):
|
|
20
|
+
"""Serve the Kalibr app locally using Uvicorn."""
|
|
21
|
+
subprocess.run(["uvicorn", f"{file_path}:app", "--reload"], check=False)
|
|
22
|
+
|
|
23
|
+
def deploy_placeholder(file_path: str):
|
|
24
|
+
"""Placeholder for future remote deployment support."""
|
|
25
|
+
print(f"🚀 Deployment placeholder: {file_path}")
|
|
26
|
+
print("Coming soon: Fly.io and Render deployment support.")
|
kalibr/kalibr.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# kalibr/kalibr.py
|
|
2
|
+
|
|
3
|
+
from fastapi import FastAPI, Request
|
|
4
|
+
from fastapi.responses import JSONResponse
|
|
5
|
+
from typing import Callable, Dict, Any, get_type_hints
|
|
6
|
+
import inspect
|
|
7
|
+
|
|
8
|
+
class Kalibr:
|
|
9
|
+
"""
|
|
10
|
+
A framework for creating API endpoints that can be easily integrated with AI models.
|
|
11
|
+
Kalibr simplifies the process of exposing Python functions as API actions,
|
|
12
|
+
providing automatic documentation, request handling, and metadata generation
|
|
13
|
+
for services like Claude's MCP and OpenAI's function calling.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, title="Kalibr API", version="1.0.0", base_url="http://localhost:8000"):
|
|
16
|
+
"""
|
|
17
|
+
Initializes the Kalibr API.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
title (str): The title of the API. Defaults to "Kalibr API".
|
|
21
|
+
version (str): The version of the API. Defaults to "1.0.0".
|
|
22
|
+
base_url (str): The base URL of the API, used for generating tool URLs.
|
|
23
|
+
Defaults to "http://localhost:8000".
|
|
24
|
+
"""
|
|
25
|
+
self.app = FastAPI(title=title, version=version)
|
|
26
|
+
self.base_url = base_url
|
|
27
|
+
self.actions = {} # Stores registered actions and their metadata
|
|
28
|
+
self._setup_routes()
|
|
29
|
+
|
|
30
|
+
def action(self, name: str, description: str = ""):
|
|
31
|
+
"""
|
|
32
|
+
Decorator to register a Python function as an API action.
|
|
33
|
+
|
|
34
|
+
This decorator automatically handles request routing (both GET and POST),
|
|
35
|
+
parameter extraction, and response formatting. It also generates metadata
|
|
36
|
+
required by AI model integrations.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
name (str): The unique name of the action. This will be used as the
|
|
40
|
+
API endpoint path and in AI model tool definitions.
|
|
41
|
+
description (str): A human-readable description of what the action does.
|
|
42
|
+
This is used in AI model tool descriptions. Defaults to "".
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Callable: A decorator function.
|
|
46
|
+
"""
|
|
47
|
+
def decorator(func: Callable):
|
|
48
|
+
# Store the function and its metadata
|
|
49
|
+
self.actions[name] = {
|
|
50
|
+
"func": func,
|
|
51
|
+
"description": description,
|
|
52
|
+
"params": self._extract_params(func)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Define the endpoint path for this action
|
|
56
|
+
endpoint_path = f"/proxy/{name}"
|
|
57
|
+
|
|
58
|
+
# Create a unified handler that accepts both GET (query params) and POST (JSON body)
|
|
59
|
+
async def endpoint_handler(request: Request):
|
|
60
|
+
params = {}
|
|
61
|
+
if request.method == "POST":
|
|
62
|
+
# For POST requests, try to get parameters from the JSON body
|
|
63
|
+
try:
|
|
64
|
+
body = await request.json()
|
|
65
|
+
params = body if isinstance(body, dict) else {}
|
|
66
|
+
except Exception:
|
|
67
|
+
# If JSON parsing fails or body is not a dict, treat as empty params
|
|
68
|
+
params = {}
|
|
69
|
+
else:
|
|
70
|
+
# For GET requests, use query parameters
|
|
71
|
+
params = dict(request.query_params)
|
|
72
|
+
|
|
73
|
+
# Call the original registered function with extracted parameters
|
|
74
|
+
try:
|
|
75
|
+
result = func(**params)
|
|
76
|
+
# If the result is a coroutine, await it
|
|
77
|
+
if inspect.isawaitable(result):
|
|
78
|
+
result = await result
|
|
79
|
+
return JSONResponse(content=result)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
# Basic error handling for function execution
|
|
82
|
+
return JSONResponse(content={"error": str(e)}, status_code=500)
|
|
83
|
+
|
|
84
|
+
# Register both POST and GET endpoints for the same path
|
|
85
|
+
self.app.post(endpoint_path)(endpoint_handler)
|
|
86
|
+
self.app.get(endpoint_path)(endpoint_handler)
|
|
87
|
+
|
|
88
|
+
return func # Return the original function
|
|
89
|
+
return decorator
|
|
90
|
+
|
|
91
|
+
def _extract_params(self, func: Callable) -> Dict:
|
|
92
|
+
"""
|
|
93
|
+
Extracts parameter names, types, and requirements from a function's signature.
|
|
94
|
+
|
|
95
|
+
This method inspects the function's signature and type hints to generate
|
|
96
|
+
a schema representation of its parameters, suitable for API documentation
|
|
97
|
+
and AI model integrations.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
func (Callable): The function to inspect.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Dict: A dictionary where keys are parameter names and values are dictionaries
|
|
104
|
+
containing 'type' (JSON schema type) and 'required' (boolean) information.
|
|
105
|
+
"""
|
|
106
|
+
sig = inspect.signature(func)
|
|
107
|
+
params = {}
|
|
108
|
+
|
|
109
|
+
# Get type hints from annotations if available
|
|
110
|
+
type_hints = get_type_hints(func) if hasattr(func, '__annotations__') else {}
|
|
111
|
+
|
|
112
|
+
for param_name, param in sig.parameters.items():
|
|
113
|
+
param_type = "string" # Default type if none is inferred
|
|
114
|
+
|
|
115
|
+
# Determine the annotation for the parameter
|
|
116
|
+
if param_name in type_hints:
|
|
117
|
+
anno = type_hints[param_name]
|
|
118
|
+
elif param.annotation != inspect.Parameter.empty:
|
|
119
|
+
anno = param.annotation
|
|
120
|
+
else:
|
|
121
|
+
anno = str # Fallback to string if no annotation
|
|
122
|
+
|
|
123
|
+
# Map common Python types to their JSON schema equivalents
|
|
124
|
+
if anno == int:
|
|
125
|
+
param_type = "integer"
|
|
126
|
+
elif anno == bool:
|
|
127
|
+
param_type = "boolean"
|
|
128
|
+
elif anno == float:
|
|
129
|
+
param_type = "number"
|
|
130
|
+
elif anno == list or anno == dict:
|
|
131
|
+
# For lists and dicts, we can't automatically infer element/key types
|
|
132
|
+
# without more complex introspection or explicit type hints like List[str], Dict[str, int]
|
|
133
|
+
# For simplicity, we'll mark them as general objects/arrays.
|
|
134
|
+
# A more robust implementation might use a library like pydantic for schema generation.
|
|
135
|
+
if anno == list:
|
|
136
|
+
param_type = "array"
|
|
137
|
+
else:
|
|
138
|
+
param_type = "object"
|
|
139
|
+
|
|
140
|
+
# Determine if the parameter is required
|
|
141
|
+
is_required = param.default == inspect.Parameter.empty
|
|
142
|
+
|
|
143
|
+
params[param_name] = {
|
|
144
|
+
"type": param_type,
|
|
145
|
+
"required": is_required
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return params
|
|
149
|
+
|
|
150
|
+
def _setup_routes(self):
|
|
151
|
+
"""
|
|
152
|
+
Sets up the core routes for the Kalibr API.
|
|
153
|
+
|
|
154
|
+
This includes:
|
|
155
|
+
- A root endpoint ("/") for basic API status and available actions.
|
|
156
|
+
- Multi-model schema endpoints for all major AI platforms:
|
|
157
|
+
- /openapi.json (GPT Actions)
|
|
158
|
+
- /mcp.json (Claude MCP)
|
|
159
|
+
- /schemas/gemini (Google Gemini)
|
|
160
|
+
- /schemas/copilot (Microsoft Copilot)
|
|
161
|
+
"""
|
|
162
|
+
from kalibr.schema_generators import (
|
|
163
|
+
OpenAPISchemaGenerator,
|
|
164
|
+
MCPSchemaGenerator,
|
|
165
|
+
GeminiSchemaGenerator,
|
|
166
|
+
CopilotSchemaGenerator
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@self.app.get("/")
|
|
170
|
+
def root():
|
|
171
|
+
"""
|
|
172
|
+
Root endpoint providing API status and a list of available actions.
|
|
173
|
+
"""
|
|
174
|
+
return {
|
|
175
|
+
"message": "Kalibr API is running",
|
|
176
|
+
"actions": list(self.actions.keys()),
|
|
177
|
+
"schemas": {
|
|
178
|
+
"gpt_actions": f"{self.base_url}/gpt-actions.json",
|
|
179
|
+
"openapi_swagger": f"{self.base_url}/openapi.json",
|
|
180
|
+
"claude_mcp": f"{self.base_url}/mcp.json",
|
|
181
|
+
"gemini": f"{self.base_url}/schemas/gemini",
|
|
182
|
+
"copilot": f"{self.base_url}/schemas/copilot"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Initialize schema generators
|
|
187
|
+
openapi_gen = OpenAPISchemaGenerator()
|
|
188
|
+
mcp_gen = MCPSchemaGenerator()
|
|
189
|
+
gemini_gen = GeminiSchemaGenerator()
|
|
190
|
+
copilot_gen = CopilotSchemaGenerator()
|
|
191
|
+
|
|
192
|
+
@self.app.get("/gpt-actions.json")
|
|
193
|
+
def gpt_actions_schema():
|
|
194
|
+
"""
|
|
195
|
+
Generates OpenAPI 3.0 schema for GPT Actions integration.
|
|
196
|
+
(Alternative endpoint since /openapi.json is used by FastAPI)
|
|
197
|
+
"""
|
|
198
|
+
return openapi_gen.generate_schema(self.actions, self.base_url)
|
|
199
|
+
|
|
200
|
+
@self.app.get("/mcp.json")
|
|
201
|
+
def mcp_manifest():
|
|
202
|
+
"""
|
|
203
|
+
Generates Claude MCP manifest for AI model integration.
|
|
204
|
+
"""
|
|
205
|
+
return mcp_gen.generate_schema(self.actions, self.base_url)
|
|
206
|
+
|
|
207
|
+
@self.app.get("/schemas/gemini")
|
|
208
|
+
def gemini_schema():
|
|
209
|
+
"""
|
|
210
|
+
Generates Google Gemini Extensions schema.
|
|
211
|
+
"""
|
|
212
|
+
return gemini_gen.generate_schema(self.actions, self.base_url)
|
|
213
|
+
|
|
214
|
+
@self.app.get("/schemas/copilot")
|
|
215
|
+
def copilot_schema():
|
|
216
|
+
"""
|
|
217
|
+
Generates Microsoft Copilot plugin schema.
|
|
218
|
+
"""
|
|
219
|
+
return copilot_gen.generate_schema(self.actions, self.base_url)
|
|
220
|
+
|
|
221
|
+
# Override FastAPI's default OpenAPI generation to include servers configuration
|
|
222
|
+
def custom_openapi():
|
|
223
|
+
"""
|
|
224
|
+
Customizes the OpenAPI schema generation for Swagger UI.
|
|
225
|
+
"""
|
|
226
|
+
if self.app.openapi_schema:
|
|
227
|
+
return self.app.openapi_schema
|
|
228
|
+
|
|
229
|
+
from fastapi.openapi.utils import get_openapi
|
|
230
|
+
# Generate the default OpenAPI schema
|
|
231
|
+
openapi_schema = get_openapi(
|
|
232
|
+
title=self.app.title,
|
|
233
|
+
version=self.app.version,
|
|
234
|
+
routes=self.app.routes,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Add the 'servers' block to the schema
|
|
238
|
+
openapi_schema["servers"] = [{"url": self.base_url}]
|
|
239
|
+
|
|
240
|
+
self.app.openapi_schema = openapi_schema
|
|
241
|
+
return self.app.openapi_schema
|
|
242
|
+
|
|
243
|
+
# Assign the custom OpenAPI generator to the FastAPI app
|
|
244
|
+
self.app.openapi = custom_openapi
|
|
245
|
+
|
|
246
|
+
def get_app(self):
|
|
247
|
+
"""
|
|
248
|
+
Returns the FastAPI application instance.
|
|
249
|
+
|
|
250
|
+
This allows the Kalibr API to be run using standard ASGI servers like Uvicorn.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
FastAPI: The configured FastAPI application.
|
|
254
|
+
"""
|
|
255
|
+
return self.app
|
|
256
|
+
|
|
257
|
+
if __name__ == '__main__':
|
|
258
|
+
print("Kalibr SDK loaded. Use this class to build your API.")
|
|
259
|
+
print("See the __main__ block for example usage.")
|