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
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced Kalibr App Example - App-level capabilities
|
|
3
|
+
This demonstrates the new enhanced capabilities including file uploads,
|
|
4
|
+
sessions, streaming, workflows, and multi-model schema generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from kalibr import KalibrApp
|
|
8
|
+
from kalibr.types import FileUpload, Session, StreamingResponse, WorkflowState, AuthenticatedUser
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
# Create an enhanced KalibrApp instance
|
|
15
|
+
app = KalibrApp(title="Enhanced Kalibr Demo", base_url="http://localhost:8000")
|
|
16
|
+
|
|
17
|
+
# Basic action (compatible with original Kalibr)
|
|
18
|
+
@app.action("hello", "Say hello with enhanced capabilities")
|
|
19
|
+
def hello_enhanced(name: str = "World", include_timestamp: bool = False):
|
|
20
|
+
"""Enhanced hello function with optional timestamp"""
|
|
21
|
+
message = f"Hello, {name}! This is Enhanced Kalibr v2.0"
|
|
22
|
+
|
|
23
|
+
response = {"message": message}
|
|
24
|
+
if include_timestamp:
|
|
25
|
+
response["timestamp"] = datetime.now().isoformat()
|
|
26
|
+
|
|
27
|
+
return response
|
|
28
|
+
|
|
29
|
+
# File upload handler
|
|
30
|
+
@app.file_handler("analyze_document", [".txt", ".md", ".py", ".js", ".json"])
|
|
31
|
+
async def analyze_document(file: FileUpload):
|
|
32
|
+
"""Analyze uploaded document and return insights"""
|
|
33
|
+
try:
|
|
34
|
+
# Decode file content
|
|
35
|
+
content = file.content.decode('utf-8')
|
|
36
|
+
|
|
37
|
+
# Basic analysis
|
|
38
|
+
lines = content.split('\n')
|
|
39
|
+
words = content.split()
|
|
40
|
+
|
|
41
|
+
# Language detection based on file extension
|
|
42
|
+
language = "text"
|
|
43
|
+
if file.filename.endswith('.py'):
|
|
44
|
+
language = "python"
|
|
45
|
+
elif file.filename.endswith('.js'):
|
|
46
|
+
language = "javascript"
|
|
47
|
+
elif file.filename.endswith('.json'):
|
|
48
|
+
language = "json"
|
|
49
|
+
try:
|
|
50
|
+
json_data = json.loads(content)
|
|
51
|
+
return {
|
|
52
|
+
"upload_id": file.upload_id,
|
|
53
|
+
"filename": file.filename,
|
|
54
|
+
"analysis": {
|
|
55
|
+
"type": "json",
|
|
56
|
+
"valid_json": True,
|
|
57
|
+
"keys": list(json_data.keys()) if isinstance(json_data, dict) else None,
|
|
58
|
+
"size_bytes": file.size
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
except json.JSONDecodeError:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"upload_id": file.upload_id,
|
|
66
|
+
"filename": file.filename,
|
|
67
|
+
"analysis": {
|
|
68
|
+
"language": language,
|
|
69
|
+
"line_count": len(lines),
|
|
70
|
+
"word_count": len(words),
|
|
71
|
+
"character_count": len(content),
|
|
72
|
+
"size_bytes": file.size,
|
|
73
|
+
"non_empty_lines": len([line for line in lines if line.strip()]),
|
|
74
|
+
"estimated_reading_time_minutes": len(words) / 200 # Average reading speed
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
except UnicodeDecodeError:
|
|
78
|
+
return {
|
|
79
|
+
"upload_id": file.upload_id,
|
|
80
|
+
"filename": file.filename,
|
|
81
|
+
"error": "File is not text-readable (binary file)",
|
|
82
|
+
"size_bytes": file.size
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Session-aware action
|
|
86
|
+
@app.session_action("save_note", "Save a note to user session")
|
|
87
|
+
async def save_note(session: Session, note_title: str, note_content: str):
|
|
88
|
+
"""Save a note to the user's session"""
|
|
89
|
+
|
|
90
|
+
# Initialize notes if not exists
|
|
91
|
+
if 'notes' not in session.data:
|
|
92
|
+
session.data['notes'] = []
|
|
93
|
+
|
|
94
|
+
# Create note object
|
|
95
|
+
note = {
|
|
96
|
+
"id": len(session.data['notes']) + 1,
|
|
97
|
+
"title": note_title,
|
|
98
|
+
"content": note_content,
|
|
99
|
+
"created_at": datetime.now().isoformat(),
|
|
100
|
+
"updated_at": datetime.now().isoformat()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
session.data['notes'].append(note)
|
|
104
|
+
session.set('last_note_id', note['id'])
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
"status": "saved",
|
|
108
|
+
"note": note,
|
|
109
|
+
"total_notes": len(session.data['notes']),
|
|
110
|
+
"session_id": session.session_id
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@app.session_action("get_notes", "Retrieve all notes from session")
|
|
114
|
+
async def get_notes(session: Session):
|
|
115
|
+
"""Get all notes from the user's session"""
|
|
116
|
+
notes = session.get('notes', [])
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
"notes": notes,
|
|
120
|
+
"count": len(notes),
|
|
121
|
+
"session_id": session.session_id,
|
|
122
|
+
"last_note_id": session.get('last_note_id')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Streaming action
|
|
126
|
+
@app.stream_action("count_with_progress", "Stream counting with progress updates")
|
|
127
|
+
async def count_with_progress(max_count: int = 10, delay_seconds: float = 1.0):
|
|
128
|
+
"""Stream counting numbers with progress indication"""
|
|
129
|
+
|
|
130
|
+
for i in range(max_count + 1):
|
|
131
|
+
progress_percent = (i / max_count) * 100
|
|
132
|
+
|
|
133
|
+
yield {
|
|
134
|
+
"count": i,
|
|
135
|
+
"max_count": max_count,
|
|
136
|
+
"progress_percent": progress_percent,
|
|
137
|
+
"message": f"Counting: {i}/{max_count}",
|
|
138
|
+
"timestamp": datetime.now().isoformat(),
|
|
139
|
+
"is_complete": (i == max_count)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if i < max_count: # Don't delay after the last item
|
|
143
|
+
await asyncio.sleep(delay_seconds)
|
|
144
|
+
|
|
145
|
+
@app.stream_action("generate_fibonacci", "Stream Fibonacci sequence")
|
|
146
|
+
async def generate_fibonacci(count: int = 20, delay_seconds: float = 0.5):
|
|
147
|
+
"""Generate Fibonacci sequence as a stream"""
|
|
148
|
+
|
|
149
|
+
a, b = 0, 1
|
|
150
|
+
for i in range(count):
|
|
151
|
+
yield {
|
|
152
|
+
"position": i + 1,
|
|
153
|
+
"fibonacci_number": a,
|
|
154
|
+
"sequence_so_far": f"F({i+1}) = {a}",
|
|
155
|
+
"timestamp": datetime.now().isoformat()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
a, b = b, a + b
|
|
159
|
+
await asyncio.sleep(delay_seconds)
|
|
160
|
+
|
|
161
|
+
# Complex workflow
|
|
162
|
+
@app.workflow("process_text_analysis", "Complete text analysis workflow")
|
|
163
|
+
async def text_analysis_workflow(text: str, workflow_state: WorkflowState):
|
|
164
|
+
"""Multi-step text analysis workflow"""
|
|
165
|
+
|
|
166
|
+
# Step 1: Validation
|
|
167
|
+
workflow_state.step = "validation"
|
|
168
|
+
workflow_state.status = "processing"
|
|
169
|
+
|
|
170
|
+
if not text or len(text.strip()) < 10:
|
|
171
|
+
workflow_state.status = "error"
|
|
172
|
+
return {"error": "Text must be at least 10 characters long"}
|
|
173
|
+
|
|
174
|
+
await asyncio.sleep(1) # Simulate processing time
|
|
175
|
+
|
|
176
|
+
# Step 2: Basic analysis
|
|
177
|
+
workflow_state.step = "basic_analysis"
|
|
178
|
+
workflow_state.data["validation_passed"] = True
|
|
179
|
+
|
|
180
|
+
words = text.split()
|
|
181
|
+
sentences = [s.strip() for s in text.replace('!', '.').replace('?', '.').split('.') if s.strip()]
|
|
182
|
+
|
|
183
|
+
basic_stats = {
|
|
184
|
+
"character_count": len(text),
|
|
185
|
+
"word_count": len(words),
|
|
186
|
+
"sentence_count": len(sentences),
|
|
187
|
+
"paragraph_count": len([p for p in text.split('\n\n') if p.strip()])
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
workflow_state.data["basic_stats"] = basic_stats
|
|
191
|
+
await asyncio.sleep(1)
|
|
192
|
+
|
|
193
|
+
# Step 3: Advanced analysis
|
|
194
|
+
workflow_state.step = "advanced_analysis"
|
|
195
|
+
|
|
196
|
+
# Word frequency
|
|
197
|
+
word_freq = {}
|
|
198
|
+
for word in words:
|
|
199
|
+
clean_word = word.lower().strip('.,!?";:')
|
|
200
|
+
word_freq[clean_word] = word_freq.get(clean_word, 0) + 1
|
|
201
|
+
|
|
202
|
+
# Top words
|
|
203
|
+
top_words = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:5]
|
|
204
|
+
|
|
205
|
+
advanced_stats = {
|
|
206
|
+
"unique_words": len(word_freq),
|
|
207
|
+
"average_word_length": sum(len(word) for word in words) / len(words) if words else 0,
|
|
208
|
+
"longest_word": max(words, key=len) if words else None,
|
|
209
|
+
"top_words": top_words,
|
|
210
|
+
"readability_score": min(100, max(0, 100 - (len(words) / len(sentences) if sentences else 1) * 2))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
workflow_state.data["advanced_stats"] = advanced_stats
|
|
214
|
+
await asyncio.sleep(1)
|
|
215
|
+
|
|
216
|
+
# Step 4: Final compilation
|
|
217
|
+
workflow_state.step = "compilation"
|
|
218
|
+
|
|
219
|
+
result = {
|
|
220
|
+
"workflow_id": workflow_state.workflow_id,
|
|
221
|
+
"analysis_type": "complete_text_analysis",
|
|
222
|
+
"input_text_preview": text[:100] + "..." if len(text) > 100 else text,
|
|
223
|
+
"basic_statistics": basic_stats,
|
|
224
|
+
"advanced_statistics": advanced_stats,
|
|
225
|
+
"processing_steps": ["validation", "basic_analysis", "advanced_analysis", "compilation"],
|
|
226
|
+
"completed_at": datetime.now().isoformat()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
workflow_state.step = "completed"
|
|
230
|
+
workflow_state.status = "success"
|
|
231
|
+
workflow_state.data["final_result"] = result
|
|
232
|
+
|
|
233
|
+
return result
|
|
234
|
+
|
|
235
|
+
# Data processing workflow
|
|
236
|
+
@app.workflow("batch_text_processor", "Process multiple texts in batch")
|
|
237
|
+
async def batch_text_processor(texts: List[str], workflow_state: WorkflowState):
|
|
238
|
+
"""Process multiple texts as a batch workflow"""
|
|
239
|
+
|
|
240
|
+
workflow_state.step = "initialization"
|
|
241
|
+
workflow_state.status = "processing"
|
|
242
|
+
|
|
243
|
+
if not texts or len(texts) == 0:
|
|
244
|
+
workflow_state.status = "error"
|
|
245
|
+
return {"error": "No texts provided for processing"}
|
|
246
|
+
|
|
247
|
+
results = []
|
|
248
|
+
workflow_state.data["total_texts"] = len(texts)
|
|
249
|
+
|
|
250
|
+
for i, text in enumerate(texts):
|
|
251
|
+
workflow_state.step = f"processing_text_{i+1}"
|
|
252
|
+
workflow_state.data["current_text"] = i + 1
|
|
253
|
+
workflow_state.data["progress_percent"] = ((i + 1) / len(texts)) * 100
|
|
254
|
+
|
|
255
|
+
# Process each text
|
|
256
|
+
words = text.split()
|
|
257
|
+
analysis = {
|
|
258
|
+
"text_id": i + 1,
|
|
259
|
+
"text_preview": text[:50] + "..." if len(text) > 50 else text,
|
|
260
|
+
"word_count": len(words),
|
|
261
|
+
"character_count": len(text),
|
|
262
|
+
"sentence_count": len([s for s in text.split('.') if s.strip()])
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
results.append(analysis)
|
|
266
|
+
await asyncio.sleep(0.5) # Simulate processing time
|
|
267
|
+
|
|
268
|
+
# Final aggregation
|
|
269
|
+
workflow_state.step = "aggregation"
|
|
270
|
+
|
|
271
|
+
total_words = sum(r["word_count"] for r in results)
|
|
272
|
+
total_chars = sum(r["character_count"] for r in results)
|
|
273
|
+
|
|
274
|
+
final_result = {
|
|
275
|
+
"workflow_id": workflow_state.workflow_id,
|
|
276
|
+
"batch_summary": {
|
|
277
|
+
"total_texts_processed": len(results),
|
|
278
|
+
"total_words": total_words,
|
|
279
|
+
"total_characters": total_chars,
|
|
280
|
+
"average_words_per_text": total_words / len(results) if results else 0,
|
|
281
|
+
"average_chars_per_text": total_chars / len(results) if results else 0
|
|
282
|
+
},
|
|
283
|
+
"individual_results": results,
|
|
284
|
+
"completed_at": datetime.now().isoformat()
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
workflow_state.step = "completed"
|
|
288
|
+
workflow_state.status = "success"
|
|
289
|
+
workflow_state.data["final_result"] = final_result
|
|
290
|
+
|
|
291
|
+
return final_result
|
|
292
|
+
|
|
293
|
+
# Advanced action with multiple parameters
|
|
294
|
+
@app.action("advanced_search", "Perform advanced search with multiple filters")
|
|
295
|
+
def advanced_search(
|
|
296
|
+
query: str,
|
|
297
|
+
category: str = "all",
|
|
298
|
+
min_score: float = 0.0,
|
|
299
|
+
max_results: int = 10,
|
|
300
|
+
include_metadata: bool = False,
|
|
301
|
+
sort_by: str = "relevance"
|
|
302
|
+
):
|
|
303
|
+
"""Advanced search function demonstrating complex parameter handling"""
|
|
304
|
+
|
|
305
|
+
# Simulate search results
|
|
306
|
+
mock_results = [
|
|
307
|
+
{"id": 1, "title": f"Result matching '{query}'", "score": 0.95, "category": category},
|
|
308
|
+
{"id": 2, "title": f"Another match for '{query}'", "score": 0.87, "category": category},
|
|
309
|
+
{"id": 3, "title": f"Related to '{query}'", "score": 0.73, "category": category},
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
# Filter by score
|
|
313
|
+
filtered_results = [r for r in mock_results if r["score"] >= min_score]
|
|
314
|
+
|
|
315
|
+
# Limit results
|
|
316
|
+
filtered_results = filtered_results[:max_results]
|
|
317
|
+
|
|
318
|
+
# Sort results
|
|
319
|
+
if sort_by == "score":
|
|
320
|
+
filtered_results.sort(key=lambda x: x["score"], reverse=True)
|
|
321
|
+
|
|
322
|
+
response = {
|
|
323
|
+
"query": query,
|
|
324
|
+
"filters": {
|
|
325
|
+
"category": category,
|
|
326
|
+
"min_score": min_score,
|
|
327
|
+
"max_results": max_results,
|
|
328
|
+
"sort_by": sort_by
|
|
329
|
+
},
|
|
330
|
+
"results": filtered_results,
|
|
331
|
+
"result_count": len(filtered_results)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if include_metadata:
|
|
335
|
+
response["metadata"] = {
|
|
336
|
+
"search_performed_at": datetime.now().isoformat(),
|
|
337
|
+
"processing_time_ms": 45,
|
|
338
|
+
"total_available": len(mock_results)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return response
|
|
342
|
+
|
|
343
|
+
# Enable authentication (optional)
|
|
344
|
+
# app.enable_auth("your-secret-jwt-key-here")
|
|
345
|
+
|
|
346
|
+
# The app instance is automatically discovered by the Kalibr CLI
|
|
347
|
+
# To run this: kalibr serve enhanced_kalibr_example.py --app-mode
|
|
@@ -0,0 +1,302 @@
|
|
|
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
|
+
[](https://badge.fury.io/py/kalibr)
|
|
59
|
+
[](https://www.python.org/downloads/)
|
|
60
|
+
[](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! 🚀
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
kalibr/__init__.py,sha256=1IIl43EB3tAsP1anJhi63PyD9Cx-cOGYPGJ9eKbAB0s,188
|
|
2
|
+
kalibr/__main__.py,sha256=Hl_-_vfimqHWZEwAUl4_OjrUWb1l7pM4q_13wbDI6gY,25584
|
|
3
|
+
kalibr/deployment.py,sha256=B-2ePPCMF2UcnkM2YKkAoaNrOjpUKUGfNO0IxyNOJMI,670
|
|
4
|
+
kalibr/kalibr.py,sha256=yrgXVlTgadBbpnX_l7fAxxjxGp9oxcZhzGjaQPiIcpo,10469
|
|
5
|
+
kalibr/kalibr_app.py,sha256=gNQprofiDk8CUk0oxC8kwRiOt32VkoRhKc2t6cgD3IA,19307
|
|
6
|
+
kalibr/schema_generators.py,sha256=nIgoYaO0FGC6arHdUHG0XaGDpJDycJeWDDC1-zAHzfI,7528
|
|
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,,
|