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.
- kalibr/__init__.py +129 -4
- kalibr/__main__.py +3 -203
- kalibr/capsule_middleware.py +108 -0
- kalibr/cli/__init__.py +5 -0
- kalibr/cli/capsule_cmd.py +174 -0
- kalibr/cli/deploy_cmd.py +114 -0
- kalibr/cli/main.py +67 -0
- kalibr/cli/run.py +203 -0
- kalibr/cli/serve.py +59 -0
- kalibr/client.py +293 -0
- kalibr/collector.py +173 -0
- kalibr/context.py +132 -0
- kalibr/cost_adapter.py +222 -0
- kalibr/decorators.py +140 -0
- kalibr/instrumentation/__init__.py +13 -0
- kalibr/instrumentation/anthropic_instr.py +282 -0
- kalibr/instrumentation/base.py +108 -0
- kalibr/instrumentation/google_instr.py +281 -0
- kalibr/instrumentation/openai_instr.py +265 -0
- kalibr/instrumentation/registry.py +153 -0
- kalibr/kalibr.py +144 -230
- kalibr/kalibr_app.py +53 -314
- kalibr/middleware/__init__.py +5 -0
- kalibr/middleware/auto_tracer.py +356 -0
- kalibr/models.py +41 -0
- kalibr/redaction.py +44 -0
- kalibr/schemas.py +116 -0
- kalibr/simple_tracer.py +258 -0
- kalibr/tokens.py +52 -0
- kalibr/trace_capsule.py +296 -0
- kalibr/trace_models.py +201 -0
- kalibr/tracer.py +354 -0
- kalibr/types.py +25 -93
- kalibr/utils.py +198 -0
- kalibr-1.1.3a0.dist-info/METADATA +236 -0
- kalibr-1.1.3a0.dist-info/RECORD +48 -0
- kalibr-1.1.3a0.dist-info/entry_points.txt +2 -0
- kalibr-1.1.3a0.dist-info/licenses/LICENSE +21 -0
- kalibr-1.1.3a0.dist-info/top_level.txt +4 -0
- kalibr_crewai/__init__.py +65 -0
- kalibr_crewai/callbacks.py +539 -0
- kalibr_crewai/instrumentor.py +513 -0
- kalibr_langchain/__init__.py +47 -0
- kalibr_langchain/async_callback.py +850 -0
- kalibr_langchain/callback.py +1064 -0
- kalibr_openai_agents/__init__.py +43 -0
- kalibr_openai_agents/processor.py +554 -0
- kalibr/deployment.py +0 -41
- kalibr/packager.py +0 -43
- kalibr/runtime_router.py +0 -138
- kalibr/schema_generators.py +0 -159
- kalibr/validator.py +0 -70
- kalibr-1.0.28.data/data/examples/README.md +0 -173
- kalibr-1.0.28.data/data/examples/basic_kalibr_example.py +0 -66
- kalibr-1.0.28.data/data/examples/enhanced_kalibr_example.py +0 -347
- kalibr-1.0.28.dist-info/METADATA +0 -175
- kalibr-1.0.28.dist-info/RECORD +0 -19
- kalibr-1.0.28.dist-info/entry_points.txt +0 -2
- kalibr-1.0.28.dist-info/licenses/LICENSE +0 -11
- kalibr-1.0.28.dist-info/top_level.txt +0 -1
- {kalibr-1.0.28.dist-info → kalibr-1.1.3a0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instrumentation Registry
|
|
3
|
+
|
|
4
|
+
Handles auto-discovery and registration of LLM SDK instrumentations.
|
|
5
|
+
Provides a central place to manage which SDKs are instrumented.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import Dict, List, Set
|
|
10
|
+
|
|
11
|
+
# Track which providers have been instrumented
|
|
12
|
+
_instrumented_providers: Set[str] = set()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def auto_instrument(providers: List[str] = None) -> Dict[str, bool]:
|
|
16
|
+
"""
|
|
17
|
+
Auto-discover and instrument LLM SDKs
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
providers: List of provider names to instrument.
|
|
21
|
+
If None, attempts to instrument all supported providers.
|
|
22
|
+
Supported: ["openai", "anthropic", "google"]
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Dictionary mapping provider names to instrumentation success status
|
|
26
|
+
"""
|
|
27
|
+
global _instrumented_providers
|
|
28
|
+
|
|
29
|
+
# Default to all providers if none specified
|
|
30
|
+
if providers is None:
|
|
31
|
+
providers = ["openai", "anthropic", "google"]
|
|
32
|
+
|
|
33
|
+
results = {}
|
|
34
|
+
|
|
35
|
+
for provider in providers:
|
|
36
|
+
provider_lower = provider.lower()
|
|
37
|
+
|
|
38
|
+
# Skip if already instrumented
|
|
39
|
+
if provider_lower in _instrumented_providers:
|
|
40
|
+
results[provider_lower] = True
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
if provider_lower == "openai":
|
|
45
|
+
from . import openai_instr
|
|
46
|
+
|
|
47
|
+
success = openai_instr.instrument()
|
|
48
|
+
results[provider_lower] = success
|
|
49
|
+
if success:
|
|
50
|
+
_instrumented_providers.add(provider_lower)
|
|
51
|
+
print(f"✅ Instrumented OpenAI SDK")
|
|
52
|
+
|
|
53
|
+
elif provider_lower == "anthropic":
|
|
54
|
+
from . import anthropic_instr
|
|
55
|
+
|
|
56
|
+
success = anthropic_instr.instrument()
|
|
57
|
+
results[provider_lower] = success
|
|
58
|
+
if success:
|
|
59
|
+
_instrumented_providers.add(provider_lower)
|
|
60
|
+
print(f"✅ Instrumented Anthropic SDK")
|
|
61
|
+
|
|
62
|
+
elif provider_lower == "google":
|
|
63
|
+
from . import google_instr
|
|
64
|
+
|
|
65
|
+
success = google_instr.instrument()
|
|
66
|
+
results[provider_lower] = success
|
|
67
|
+
if success:
|
|
68
|
+
_instrumented_providers.add(provider_lower)
|
|
69
|
+
print(f"✅ Instrumented Google Generative AI SDK")
|
|
70
|
+
|
|
71
|
+
else:
|
|
72
|
+
print(f"⚠️ Unknown provider: {provider}")
|
|
73
|
+
results[provider_lower] = False
|
|
74
|
+
|
|
75
|
+
except ImportError as e:
|
|
76
|
+
print(f"⚠️ {provider} SDK not installed, skipping instrumentation")
|
|
77
|
+
results[provider_lower] = False
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"❌ Failed to instrument {provider}: {e}")
|
|
80
|
+
results[provider_lower] = False
|
|
81
|
+
|
|
82
|
+
return results
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def uninstrument_all() -> Dict[str, bool]:
|
|
86
|
+
"""
|
|
87
|
+
Remove instrumentation from all previously instrumented SDKs
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dictionary mapping provider names to uninstrumentation success status
|
|
91
|
+
"""
|
|
92
|
+
global _instrumented_providers
|
|
93
|
+
|
|
94
|
+
results = {}
|
|
95
|
+
providers_to_uninstrument = list(_instrumented_providers)
|
|
96
|
+
|
|
97
|
+
for provider in providers_to_uninstrument:
|
|
98
|
+
try:
|
|
99
|
+
if provider == "openai":
|
|
100
|
+
from . import openai_instr
|
|
101
|
+
|
|
102
|
+
success = openai_instr.uninstrument()
|
|
103
|
+
results[provider] = success
|
|
104
|
+
if success:
|
|
105
|
+
_instrumented_providers.discard(provider)
|
|
106
|
+
print(f"✅ Uninstrumented OpenAI SDK")
|
|
107
|
+
|
|
108
|
+
elif provider == "anthropic":
|
|
109
|
+
from . import anthropic_instr
|
|
110
|
+
|
|
111
|
+
success = anthropic_instr.uninstrument()
|
|
112
|
+
results[provider] = success
|
|
113
|
+
if success:
|
|
114
|
+
_instrumented_providers.discard(provider)
|
|
115
|
+
print(f"✅ Uninstrumented Anthropic SDK")
|
|
116
|
+
|
|
117
|
+
elif provider == "google":
|
|
118
|
+
from . import google_instr
|
|
119
|
+
|
|
120
|
+
success = google_instr.uninstrument()
|
|
121
|
+
results[provider] = success
|
|
122
|
+
if success:
|
|
123
|
+
_instrumented_providers.discard(provider)
|
|
124
|
+
print(f"✅ Uninstrumented Google Generative AI SDK")
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
print(f"❌ Failed to uninstrument {provider}: {e}")
|
|
128
|
+
results[provider] = False
|
|
129
|
+
|
|
130
|
+
return results
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_instrumented_providers() -> List[str]:
|
|
134
|
+
"""
|
|
135
|
+
Get list of currently instrumented providers
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of provider names that are currently instrumented
|
|
139
|
+
"""
|
|
140
|
+
return list(_instrumented_providers)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def is_instrumented(provider: str) -> bool:
|
|
144
|
+
"""
|
|
145
|
+
Check if a specific provider is instrumented
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
provider: Provider name to check
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
True if provider is instrumented, False otherwise
|
|
152
|
+
"""
|
|
153
|
+
return provider.lower() in _instrumented_providers
|
kalibr/kalibr.py
CHANGED
|
@@ -1,259 +1,173 @@
|
|
|
1
|
-
|
|
1
|
+
"""Kalibr - Simple function-level API builder"""
|
|
2
2
|
|
|
3
|
+
import inspect
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import uvicorn
|
|
3
8
|
from fastapi import FastAPI, Request
|
|
4
9
|
from fastapi.responses import JSONResponse
|
|
5
|
-
from
|
|
6
|
-
import
|
|
10
|
+
from kalibr.middleware.auto_tracer import AutoTracerMiddleware
|
|
11
|
+
from kalibr.schemas import (
|
|
12
|
+
generate_copilot_schema,
|
|
13
|
+
generate_gemini_schema,
|
|
14
|
+
generate_mcp_schema,
|
|
15
|
+
get_base_url,
|
|
16
|
+
get_supported_models,
|
|
17
|
+
)
|
|
18
|
+
from pydantic import BaseModel, create_model
|
|
19
|
+
|
|
7
20
|
|
|
8
21
|
class Kalibr:
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
"""
|
|
22
|
+
"""Simple function-level API builder for multi-model integration"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
title: str = "Kalibr API",
|
|
27
|
+
version: str = "1.0.0",
|
|
28
|
+
auto_trace: bool = True,
|
|
29
|
+
agent_name: str = None,
|
|
30
|
+
):
|
|
25
31
|
self.app = FastAPI(title=title, version=version)
|
|
26
|
-
self.
|
|
27
|
-
self.
|
|
32
|
+
self.actions: List[Dict[str, Any]] = []
|
|
33
|
+
self.base_url = get_base_url()
|
|
34
|
+
self.agent_name = agent_name or title
|
|
35
|
+
|
|
36
|
+
# Phase 3B: Auto-attach tracing middleware
|
|
37
|
+
if auto_trace and os.getenv("KALIBR_TRACE_ENABLED", "true").lower() == "true":
|
|
38
|
+
self.app.add_middleware(
|
|
39
|
+
AutoTracerMiddleware,
|
|
40
|
+
agent_name=self.agent_name,
|
|
41
|
+
)
|
|
42
|
+
|
|
28
43
|
self._setup_routes()
|
|
29
44
|
|
|
30
45
|
def action(self, name: str, description: str = ""):
|
|
31
|
-
"""
|
|
32
|
-
|
|
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
|
-
"""
|
|
46
|
+
"""Decorator to register a function as an action"""
|
|
47
|
+
|
|
47
48
|
def decorator(func: Callable):
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
49
|
+
# Extract function signature
|
|
50
|
+
sig = inspect.signature(func)
|
|
51
|
+
schema = self._generate_schema(sig)
|
|
52
|
+
|
|
53
|
+
# Store action metadata
|
|
54
|
+
self.actions.append(
|
|
55
|
+
{
|
|
56
|
+
"name": name,
|
|
57
|
+
"description": description or func.__doc__ or "",
|
|
58
|
+
"function": func,
|
|
59
|
+
"schema": schema,
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Register proxy endpoint
|
|
64
|
+
self._register_proxy_endpoint(name, func, schema)
|
|
65
|
+
|
|
66
|
+
return func
|
|
67
|
+
|
|
89
68
|
return decorator
|
|
90
|
-
|
|
91
|
-
def
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
69
|
+
|
|
70
|
+
def _generate_schema(self, sig: inspect.Signature) -> Dict[str, Any]:
|
|
71
|
+
"""Generate JSON schema from function signature"""
|
|
72
|
+
properties = {}
|
|
73
|
+
required = []
|
|
74
|
+
|
|
112
75
|
for param_name, param in sig.parameters.items():
|
|
113
|
-
param_type = "string" # Default type
|
|
114
|
-
|
|
115
|
-
#
|
|
116
|
-
if
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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:
|
|
76
|
+
param_type = "string" # Default type
|
|
77
|
+
|
|
78
|
+
# Map Python types to JSON schema types
|
|
79
|
+
if param.annotation != inspect.Parameter.empty:
|
|
80
|
+
if param.annotation == int:
|
|
81
|
+
param_type = "integer"
|
|
82
|
+
elif param.annotation == float:
|
|
83
|
+
param_type = "number"
|
|
84
|
+
elif param.annotation == bool:
|
|
85
|
+
param_type = "boolean"
|
|
86
|
+
elif param.annotation == list:
|
|
136
87
|
param_type = "array"
|
|
137
|
-
|
|
88
|
+
elif param.annotation == dict:
|
|
138
89
|
param_type = "object"
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
90
|
+
|
|
91
|
+
properties[param_name] = {"type": param_type}
|
|
92
|
+
|
|
93
|
+
# Check if parameter is required (no default value)
|
|
94
|
+
if param.default == inspect.Parameter.empty:
|
|
95
|
+
required.append(param_name)
|
|
96
|
+
|
|
97
|
+
return {"type": "object", "properties": properties, "required": required}
|
|
98
|
+
|
|
99
|
+
def _register_proxy_endpoint(self, name: str, func: Callable, schema: Dict[str, Any]):
|
|
100
|
+
"""Register a proxy endpoint for the action"""
|
|
101
|
+
# Create Pydantic model for request validation
|
|
102
|
+
fields = {}
|
|
103
|
+
for prop_name, prop_schema in schema["properties"].items():
|
|
104
|
+
field_type = str # Default
|
|
105
|
+
if prop_schema["type"] == "integer":
|
|
106
|
+
field_type = int
|
|
107
|
+
elif prop_schema["type"] == "number":
|
|
108
|
+
field_type = float
|
|
109
|
+
elif prop_schema["type"] == "boolean":
|
|
110
|
+
field_type = bool
|
|
111
|
+
|
|
112
|
+
# Check if required
|
|
113
|
+
if prop_name in schema.get("required", []):
|
|
114
|
+
fields[prop_name] = (field_type, ...)
|
|
115
|
+
else:
|
|
116
|
+
fields[prop_name] = (Optional[field_type], None)
|
|
117
|
+
|
|
118
|
+
RequestModel = create_model(f"{name}_Request", **fields)
|
|
119
|
+
|
|
120
|
+
@self.app.post(f"/proxy/{name}")
|
|
121
|
+
async def proxy_endpoint(request: RequestModel):
|
|
122
|
+
try:
|
|
123
|
+
result = func(**request.dict(exclude_none=True))
|
|
124
|
+
return result
|
|
125
|
+
except Exception as e:
|
|
126
|
+
return JSONResponse(status_code=500, content={"error": str(e)})
|
|
127
|
+
|
|
150
128
|
def _setup_routes(self):
|
|
151
|
-
"""
|
|
152
|
-
|
|
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
|
-
|
|
129
|
+
"""Setup built-in routes"""
|
|
130
|
+
|
|
169
131
|
@self.app.get("/")
|
|
170
|
-
def root():
|
|
171
|
-
"""
|
|
172
|
-
Root endpoint providing API status and a list of available actions.
|
|
173
|
-
"""
|
|
132
|
+
async def root():
|
|
174
133
|
return {
|
|
175
|
-
"message": "Kalibr API is running",
|
|
176
|
-
"actions":
|
|
134
|
+
"message": "Kalibr API is running",
|
|
135
|
+
"actions": [action["name"] for action in self.actions],
|
|
177
136
|
"schemas": {
|
|
178
137
|
"gpt_actions": f"{self.base_url}/gpt-actions.json",
|
|
179
138
|
"openapi_swagger": f"{self.base_url}/openapi.json",
|
|
180
139
|
"claude_mcp": f"{self.base_url}/mcp.json",
|
|
181
140
|
"gemini": f"{self.base_url}/schemas/gemini",
|
|
182
|
-
"copilot": f"{self.base_url}/schemas/copilot"
|
|
183
|
-
}
|
|
141
|
+
"copilot": f"{self.base_url}/schemas/copilot",
|
|
142
|
+
},
|
|
184
143
|
}
|
|
185
|
-
|
|
186
|
-
# Initialize schema generators
|
|
187
|
-
openapi_gen = OpenAPISchemaGenerator()
|
|
188
|
-
mcp_gen = MCPSchemaGenerator()
|
|
189
|
-
gemini_gen = GeminiSchemaGenerator()
|
|
190
|
-
copilot_gen = CopilotSchemaGenerator()
|
|
191
|
-
|
|
144
|
+
|
|
192
145
|
@self.app.get("/gpt-actions.json")
|
|
193
|
-
def gpt_actions_schema():
|
|
194
|
-
"""
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return openapi_gen.generate_schema(self.actions, self.base_url)
|
|
199
|
-
|
|
146
|
+
async def gpt_actions_schema():
|
|
147
|
+
"""Generates OpenAPI 3.0 schema for GPT Actions integration.
|
|
148
|
+
(Alternative endpoint since /openapi.json is used by FastAPI)"""
|
|
149
|
+
return self.app.openapi()
|
|
150
|
+
|
|
200
151
|
@self.app.get("/mcp.json")
|
|
201
|
-
def
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
"""
|
|
205
|
-
return mcp_gen.generate_schema(self.actions, self.base_url)
|
|
206
|
-
|
|
152
|
+
async def mcp_schema():
|
|
153
|
+
return generate_mcp_schema(self.actions, self.base_url)
|
|
154
|
+
|
|
207
155
|
@self.app.get("/schemas/gemini")
|
|
208
|
-
def gemini_schema():
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
"""
|
|
212
|
-
return gemini_gen.generate_schema(self.actions, self.base_url)
|
|
213
|
-
|
|
156
|
+
async def gemini_schema():
|
|
157
|
+
return generate_gemini_schema(self.actions, self.base_url)
|
|
158
|
+
|
|
214
159
|
@self.app.get("/schemas/copilot")
|
|
215
|
-
def copilot_schema():
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
"""
|
|
160
|
+
async def copilot_schema():
|
|
161
|
+
return generate_copilot_schema(self.actions, self.base_url)
|
|
162
|
+
|
|
163
|
+
@self.app.get("/models/supported")
|
|
164
|
+
async def models_supported():
|
|
165
|
+
return get_supported_models()
|
|
166
|
+
|
|
167
|
+
def get_app(self) -> FastAPI:
|
|
168
|
+
"""Get the FastAPI app instance"""
|
|
255
169
|
return self.app
|
|
256
170
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
171
|
+
def run(self, host: str = "0.0.0.0", port: int = 8000):
|
|
172
|
+
"""Run the Kalibr server"""
|
|
173
|
+
uvicorn.run(self.app, host=host, port=port)
|