aisbf 0.2.2__py3-none-any.whl → 0.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. aisbf/config.py +57 -1
  2. aisbf/handlers.py +314 -33
  3. aisbf/providers.py +164 -9
  4. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/aisbf/config.py +57 -1
  5. aisbf-0.2.4.data/data/share/aisbf/aisbf/handlers.py +664 -0
  6. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/aisbf/providers.py +164 -9
  7. aisbf-0.2.4.data/data/share/aisbf/main.py +421 -0
  8. {aisbf-0.2.2.dist-info → aisbf-0.2.4.dist-info}/METADATA +1 -1
  9. aisbf-0.2.4.dist-info/RECORD +24 -0
  10. aisbf-0.2.2.data/data/share/aisbf/aisbf/handlers.py +0 -383
  11. aisbf-0.2.2.data/data/share/aisbf/main.py +0 -214
  12. aisbf-0.2.2.dist-info/RECORD +0 -24
  13. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/aisbf/__init__.py +0 -0
  14. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/aisbf/models.py +0 -0
  15. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/aisbf.sh +0 -0
  16. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/autoselect.json +0 -0
  17. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/autoselect.md +0 -0
  18. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/providers.json +0 -0
  19. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/requirements.txt +0 -0
  20. {aisbf-0.2.2.data → aisbf-0.2.4.data}/data/share/aisbf/rotations.json +0 -0
  21. {aisbf-0.2.2.dist-info → aisbf-0.2.4.dist-info}/WHEEL +0 -0
  22. {aisbf-0.2.2.dist-info → aisbf-0.2.4.dist-info}/entry_points.txt +0 -0
  23. {aisbf-0.2.2.dist-info → aisbf-0.2.4.dist-info}/licenses/LICENSE.txt +0 -0
  24. {aisbf-0.2.2.dist-info → aisbf-0.2.4.dist-info}/top_level.txt +0 -0
@@ -1,383 +0,0 @@
1
- """
2
- Copyleft (C) 2026 Stefy Lanza <stefy@nexlab.net>
3
-
4
- AISBF - AI Service Broker Framework || AI Should Be Free
5
-
6
- Request handlers for AISBF.
7
-
8
- This program is free software: you can redistribute it and/or modify
9
- it under the terms of the GNU General Public License as published by
10
- the Free Software Foundation, either version 3 of the License, or
11
- (at your option) any later version.
12
-
13
- This program is distributed in the hope that it will be useful,
14
- but WITHOUT ANY WARRANTY; without even the implied warranty of
15
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
- GNU General Public License for more details.
17
-
18
- You should have received a copy of the GNU General Public License
19
- along with this program. If not, see <https://www.gnu.org/licenses/>.
20
-
21
- Why did the programmer quit his job? Because he didn't get arrays!
22
-
23
- Request handlers for AISBF.
24
- """
25
- import asyncio
26
- import re
27
- from typing import Dict, List, Optional
28
- from pathlib import Path
29
- from fastapi import HTTPException, Request
30
- from fastapi.responses import JSONResponse, StreamingResponse
31
- from .models import ChatCompletionRequest, ChatCompletionResponse
32
- from .providers import get_provider_handler
33
- from .config import config
34
-
35
- class RequestHandler:
36
- def __init__(self):
37
- self.config = config
38
-
39
- async def handle_chat_completion(self, request: Request, provider_id: str, request_data: Dict) -> Dict:
40
- provider_config = self.config.get_provider(provider_id)
41
-
42
- if provider_config.api_key_required:
43
- api_key = request_data.get('api_key') or request.headers.get('Authorization', '').replace('Bearer ', '')
44
- if not api_key:
45
- raise HTTPException(status_code=401, detail="API key required")
46
- else:
47
- api_key = None
48
-
49
- handler = get_provider_handler(provider_id, api_key)
50
-
51
- if handler.is_rate_limited():
52
- raise HTTPException(status_code=503, detail="Provider temporarily unavailable")
53
-
54
- try:
55
- # Apply rate limiting
56
- await handler.apply_rate_limit()
57
-
58
- response = await handler.handle_request(
59
- model=request_data['model'],
60
- messages=request_data['messages'],
61
- max_tokens=request_data.get('max_tokens'),
62
- temperature=request_data.get('temperature', 1.0),
63
- stream=request_data.get('stream', False)
64
- )
65
- handler.record_success()
66
- return response
67
- except Exception as e:
68
- handler.record_failure()
69
- raise HTTPException(status_code=500, detail=str(e))
70
-
71
- async def handle_streaming_chat_completion(self, request: Request, provider_id: str, request_data: Dict):
72
- provider_config = self.config.get_provider(provider_id)
73
-
74
- if provider_config.api_key_required:
75
- api_key = request_data.get('api_key') or request.headers.get('Authorization', '').replace('Bearer ', '')
76
- if not api_key:
77
- raise HTTPException(status_code=401, detail="API key required")
78
- else:
79
- api_key = None
80
-
81
- handler = get_provider_handler(provider_id, api_key)
82
-
83
- if handler.is_rate_limited():
84
- raise HTTPException(status_code=503, detail="Provider temporarily unavailable")
85
-
86
- async def stream_generator():
87
- try:
88
- # Apply rate limiting
89
- await handler.apply_rate_limit()
90
-
91
- response = await handler.handle_request(
92
- model=request_data['model'],
93
- messages=request_data['messages'],
94
- max_tokens=request_data.get('max_tokens'),
95
- temperature=request_data.get('temperature', 1.0),
96
- stream=True
97
- )
98
- for chunk in response:
99
- yield f"data: {chunk}\n\n".encode('utf-8')
100
- handler.record_success()
101
- except Exception as e:
102
- handler.record_failure()
103
- yield f"data: {str(e)}\n\n".encode('utf-8')
104
-
105
- return StreamingResponse(stream_generator(), media_type="text/event-stream")
106
-
107
- async def handle_model_list(self, request: Request, provider_id: str) -> List[Dict]:
108
- provider_config = self.config.get_provider(provider_id)
109
-
110
- if provider_config.api_key_required:
111
- api_key = request.headers.get('Authorization', '').replace('Bearer ', '')
112
- if not api_key:
113
- raise HTTPException(status_code=401, detail="API key required")
114
- else:
115
- api_key = None
116
-
117
- handler = get_provider_handler(provider_id, api_key)
118
- try:
119
- # Apply rate limiting
120
- await handler.apply_rate_limit()
121
-
122
- models = await handler.get_models()
123
- return [model.dict() for model in models]
124
- except Exception as e:
125
- raise HTTPException(status_code=500, detail=str(e))
126
-
127
- class RotationHandler:
128
- def __init__(self):
129
- self.config = config
130
-
131
- async def handle_rotation_request(self, rotation_id: str, request_data: Dict) -> Dict:
132
- rotation_config = self.config.get_rotation(rotation_id)
133
- if not rotation_config:
134
- raise HTTPException(status_code=400, detail=f"Rotation {rotation_id} not found")
135
-
136
- providers = rotation_config.providers
137
- weighted_models = []
138
-
139
- for provider in providers:
140
- for model in provider['models']:
141
- weighted_models.extend([model] * model['weight'])
142
-
143
- if not weighted_models:
144
- raise HTTPException(status_code=400, detail="No models available in rotation")
145
-
146
- import random
147
- selected_model = random.choice(weighted_models)
148
-
149
- provider_id = selected_model['provider_id']
150
- api_key = selected_model.get('api_key')
151
- model_name = selected_model['name']
152
-
153
- handler = get_provider_handler(provider_id, api_key)
154
-
155
- if handler.is_rate_limited():
156
- raise HTTPException(status_code=503, detail="All providers temporarily unavailable")
157
-
158
- try:
159
- # Apply rate limiting with model-specific rate limit if available
160
- rate_limit = selected_model.get('rate_limit')
161
- await handler.apply_rate_limit(rate_limit)
162
-
163
- response = await handler.handle_request(
164
- model=model_name,
165
- messages=request_data['messages'],
166
- max_tokens=request_data.get('max_tokens'),
167
- temperature=request_data.get('temperature', 1.0),
168
- stream=request_data.get('stream', False)
169
- )
170
- handler.record_success()
171
- return response
172
- except Exception as e:
173
- handler.record_failure()
174
- raise HTTPException(status_code=500, detail=str(e))
175
-
176
- async def handle_rotation_model_list(self, rotation_id: str) -> List[Dict]:
177
- rotation_config = self.config.get_rotation(rotation_id)
178
- if not rotation_config:
179
- raise HTTPException(status_code=400, detail=f"Rotation {rotation_id} not found")
180
-
181
- all_models = []
182
- for provider in rotation_config.providers:
183
- for model in provider['models']:
184
- all_models.append({
185
- "id": f"{provider['provider_id']}/{model['name']}",
186
- "name": model['name'],
187
- "provider_id": provider['provider_id'],
188
- "weight": model['weight'],
189
- "rate_limit": model.get('rate_limit')
190
- })
191
-
192
- return all_models
193
-
194
- class AutoselectHandler:
195
- def __init__(self):
196
- self.config = config
197
- self._skill_file_content = None
198
-
199
- def _get_skill_file_content(self) -> str:
200
- """Load the autoselect.md skill file content"""
201
- if self._skill_file_content is None:
202
- # Try installed locations first
203
- installed_dirs = [
204
- Path('/usr/share/aisbf'),
205
- Path.home() / '.local' / 'share' / 'aisbf',
206
- ]
207
-
208
- for installed_dir in installed_dirs:
209
- skill_file = installed_dir / 'autoselect.md'
210
- if skill_file.exists():
211
- with open(skill_file) as f:
212
- self._skill_file_content = f.read()
213
- return self._skill_file_content
214
-
215
- # Fallback to source tree config directory
216
- source_dir = Path(__file__).parent.parent / 'config'
217
- skill_file = source_dir / 'autoselect.md'
218
- if skill_file.exists():
219
- with open(skill_file) as f:
220
- self._skill_file_content = f.read()
221
- return self._skill_file_content
222
-
223
- raise FileNotFoundError("Could not find autoselect.md skill file")
224
-
225
- return self._skill_file_content
226
-
227
- def _build_autoselect_prompt(self, user_prompt: str, autoselect_config) -> str:
228
- """Build the prompt for model selection"""
229
- skill_content = self._get_skill_file_content()
230
-
231
- # Build the available models list
232
- models_list = ""
233
- for model_info in autoselect_config.available_models:
234
- models_list += f"<model><model_id>{model_info.model_id}</model_id><model_description>{model_info.description}</model_description></model>\n"
235
-
236
- # Build the complete prompt
237
- prompt = f"""{skill_content}
238
-
239
- <aisbf_user_prompt>{user_prompt}</aisbf_user_prompt>
240
- <aisbf_autoselect_list>
241
- {models_list}
242
- </aisbf_autoselect_list>
243
- <aisbf_autoselect_fallback>{autoselect_config.fallback}</aisbf_autoselect_fallback>
244
- """
245
- return prompt
246
-
247
- def _extract_model_selection(self, response: str) -> Optional[str]:
248
- """Extract the model_id from the autoselection response"""
249
- match = re.search(r'<aisbf_model_autoselection>(.*?)</aisbf_model_autoselection>', response, re.DOTALL)
250
- if match:
251
- return match.group(1).strip()
252
- return None
253
-
254
- async def _get_model_selection(self, prompt: str) -> str:
255
- """Send the autoselect prompt to a model and get the selection"""
256
- # Use the first available provider/model for the selection
257
- # This is a simple implementation - could be enhanced to use a specific selection model
258
- rotation_handler = RotationHandler()
259
-
260
- # Create a minimal request for model selection
261
- selection_request = {
262
- "messages": [{"role": "user", "content": prompt}],
263
- "temperature": 0.1, # Low temperature for more deterministic selection
264
- "max_tokens": 100, # We only need a short response
265
- "stream": False
266
- }
267
-
268
- # Use the fallback rotation for the selection
269
- try:
270
- response = await rotation_handler.handle_rotation_request("general", selection_request)
271
- content = response.get('choices', [{}])[0].get('message', {}).get('content', '')
272
- model_id = self._extract_model_selection(content)
273
- return model_id
274
- except Exception as e:
275
- # If selection fails, we'll handle it in the main handler
276
- return None
277
-
278
- async def handle_autoselect_request(self, autoselect_id: str, request_data: Dict) -> Dict:
279
- """Handle an autoselect request"""
280
- autoselect_config = self.config.get_autoselect(autoselect_id)
281
- if not autoselect_config:
282
- raise HTTPException(status_code=400, detail=f"Autoselect {autoselect_id} not found")
283
-
284
- # Extract the user prompt from the request
285
- user_messages = request_data.get('messages', [])
286
- if not user_messages:
287
- raise HTTPException(status_code=400, detail="No messages provided")
288
-
289
- # Build a string representation of the user prompt
290
- user_prompt = ""
291
- for msg in user_messages:
292
- role = msg.get('role', 'user')
293
- content = msg.get('content', '')
294
- if isinstance(content, list):
295
- # Handle complex content (e.g., with images)
296
- content = str(content)
297
- user_prompt += f"{role}: {content}\n"
298
-
299
- # Build the autoselect prompt
300
- autoselect_prompt = self._build_autoselect_prompt(user_prompt, autoselect_config)
301
-
302
- # Get the model selection
303
- selected_model_id = await self._get_model_selection(autoselect_prompt)
304
-
305
- # Validate the selected model
306
- if not selected_model_id:
307
- # Fallback to the configured fallback model
308
- selected_model_id = autoselect_config.fallback
309
- else:
310
- # Check if the selected model is in the available models list
311
- available_ids = [m.model_id for m in autoselect_config.available_models]
312
- if selected_model_id not in available_ids:
313
- selected_model_id = autoselect_config.fallback
314
-
315
- # Now proxy the actual request to the selected rotation
316
- rotation_handler = RotationHandler()
317
- return await rotation_handler.handle_rotation_request(selected_model_id, request_data)
318
-
319
- async def handle_autoselect_streaming_request(self, autoselect_id: str, request_data: Dict):
320
- """Handle an autoselect streaming request"""
321
- autoselect_config = self.config.get_autoselect(autoselect_id)
322
- if not autoselect_config:
323
- raise HTTPException(status_code=400, detail=f"Autoselect {autoselect_id} not found")
324
-
325
- # Extract the user prompt from the request
326
- user_messages = request_data.get('messages', [])
327
- if not user_messages:
328
- raise HTTPException(status_code=400, detail="No messages provided")
329
-
330
- # Build a string representation of the user prompt
331
- user_prompt = ""
332
- for msg in user_messages:
333
- role = msg.get('role', 'user')
334
- content = msg.get('content', '')
335
- if isinstance(content, list):
336
- content = str(content)
337
- user_prompt += f"{role}: {content}\n"
338
-
339
- # Build the autoselect prompt
340
- autoselect_prompt = self._build_autoselect_prompt(user_prompt, autoselect_config)
341
-
342
- # Get the model selection
343
- selected_model_id = await self._get_model_selection(autoselect_prompt)
344
-
345
- # Validate the selected model
346
- if not selected_model_id:
347
- selected_model_id = autoselect_config.fallback
348
- else:
349
- available_ids = [m.model_id for m in autoselect_config.available_models]
350
- if selected_model_id not in available_ids:
351
- selected_model_id = autoselect_config.fallback
352
-
353
- # Now proxy the actual streaming request to the selected rotation
354
- rotation_handler = RotationHandler()
355
-
356
- async def stream_generator():
357
- try:
358
- response = await rotation_handler.handle_rotation_request(
359
- selected_model_id,
360
- {**request_data, "stream": True}
361
- )
362
- for chunk in response:
363
- yield f"data: {chunk}\n\n".encode('utf-8')
364
- except Exception as e:
365
- yield f"data: {str(e)}\n\n".encode('utf-8')
366
-
367
- return StreamingResponse(stream_generator(), media_type="text/event-stream")
368
-
369
- async def handle_autoselect_model_list(self, autoselect_id: str) -> List[Dict]:
370
- """List available models for an autoselect endpoint"""
371
- autoselect_config = self.config.get_autoselect(autoselect_id)
372
- if not autoselect_config:
373
- raise HTTPException(status_code=400, detail=f"Autoselect {autoselect_id} not found")
374
-
375
- # Return the available models that can be selected
376
- return [
377
- {
378
- "id": model_info.model_id,
379
- "name": model_info.model_id,
380
- "description": model_info.description
381
- }
382
- for model_info in autoselect_config.available_models
383
- ]
@@ -1,214 +0,0 @@
1
- """
2
- Copyleft (C) 2026 Stefy Lanza <stefy@nexlab.net>
3
-
4
- AISBF - AI Service Broker Framework || AI Should Be Free
5
-
6
- Main application for AISBF.
7
-
8
- This program is free software: you can redistribute it and/or modify
9
- it under the terms of the GNU General Public License as published by
10
- the Free Software Foundation, either version 3 of the License, or
11
- (at your option) any later version.
12
-
13
- This program is distributed in the hope that it will be useful,
14
- but WITHOUT ANY WARRANTY; without even the implied warranty of
15
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
- GNU General Public License for more details.
17
-
18
- You should have received a copy of the GNU General Public License
19
- along with this program. If not, see <https://www.gnu.org/licenses/>.
20
-
21
- Why did the programmer quit his job? Because he didn't get arrays!
22
-
23
- Main application for AISBF.
24
- """
25
- from fastapi import FastAPI, HTTPException, Request, status
26
- from fastapi.responses import JSONResponse, StreamingResponse
27
- from fastapi.middleware.cors import CORSMiddleware
28
- from aisbf.models import ChatCompletionRequest, ChatCompletionResponse
29
- from aisbf.handlers import RequestHandler, RotationHandler, AutoselectHandler
30
- from aisbf.config import config
31
- import time
32
- import logging
33
- import sys
34
- import os
35
- from logging.handlers import RotatingFileHandler
36
- from datetime import datetime, timedelta
37
- from collections import defaultdict
38
- from pathlib import Path
39
-
40
- def setup_logging():
41
- """Setup logging with rotating file handlers"""
42
- # Determine log directory based on user
43
- if os.geteuid() == 0:
44
- # Running as root - use /var/log/aisbf
45
- log_dir = Path('/var/log/aisbf')
46
- else:
47
- # Running as user - use ~/.local/var/log/aisbf
48
- log_dir = Path.home() / '.local' / 'var' / 'log' / 'aisbf'
49
-
50
- # Create log directory if it doesn't exist
51
- log_dir.mkdir(parents=True, exist_ok=True)
52
-
53
- # Setup rotating file handler for general logs
54
- log_file = log_dir / 'aisbf.log'
55
- file_handler = RotatingFileHandler(
56
- log_file,
57
- maxBytes=50*1024*1024, # 50 MB
58
- backupCount=5,
59
- encoding='utf-8'
60
- )
61
- file_handler.setLevel(logging.DEBUG)
62
- file_formatter = logging.Formatter(
63
- '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
64
- )
65
- file_handler.setFormatter(file_formatter)
66
-
67
- # Setup rotating file handler for error logs
68
- error_log_file = log_dir / 'aisbf_error.log'
69
- error_handler = RotatingFileHandler(
70
- error_log_file,
71
- maxBytes=50*1024*1024, # 50 MB
72
- backupCount=5,
73
- encoding='utf-8'
74
- )
75
- error_handler.setLevel(logging.ERROR)
76
- error_handler.setFormatter(file_formatter)
77
-
78
- # Setup console handler
79
- console_handler = logging.StreamHandler(sys.stdout)
80
- console_handler.setLevel(logging.INFO)
81
- console_formatter = logging.Formatter(
82
- '%(asctime)s - %(levelname)s - %(message)s'
83
- )
84
- console_handler.setFormatter(console_formatter)
85
-
86
- # Configure root logger
87
- root_logger = logging.getLogger()
88
- root_logger.setLevel(logging.DEBUG)
89
- root_logger.addHandler(file_handler)
90
- root_logger.addHandler(error_handler)
91
- root_logger.addHandler(console_handler)
92
-
93
- # Redirect stderr to error log
94
- sys.stderr = open(log_dir / 'aisbf_stderr.log', 'a')
95
-
96
- return logging.getLogger(__name__)
97
-
98
- # Configure logging
99
- logger = setup_logging()
100
-
101
- # Initialize handlers
102
- request_handler = RequestHandler()
103
- rotation_handler = RotationHandler()
104
- autoselect_handler = AutoselectHandler()
105
-
106
- app = FastAPI(title="AI Proxy Server")
107
-
108
- # CORS middleware
109
- app.add_middleware(
110
- CORSMiddleware,
111
- allow_origins=["*"],
112
- allow_credentials=True,
113
- allow_methods=["*"],
114
- allow_headers=["*"],
115
- )
116
-
117
- @app.get("/")
118
- async def root():
119
- return {"message": "AI Proxy Server is running", "providers": list(config.providers.keys())}
120
-
121
- @app.post("/api/{provider_id}/chat/completions")
122
- async def chat_completions(provider_id: str, request: Request, body: ChatCompletionRequest):
123
- logger.debug(f"Received chat_completions request for provider: {provider_id}")
124
- logger.debug(f"Request headers: {dict(request.headers)}")
125
- logger.debug(f"Request body: {body}")
126
-
127
- body_dict = body.model_dump()
128
-
129
- # Check if it's an autoselect
130
- if provider_id in config.autoselect:
131
- logger.debug("Handling autoselect request")
132
- try:
133
- if body.stream:
134
- logger.debug("Handling streaming autoselect request")
135
- return await autoselect_handler.handle_autoselect_streaming_request(provider_id, body_dict)
136
- else:
137
- logger.debug("Handling non-streaming autoselect request")
138
- result = await autoselect_handler.handle_autoselect_request(provider_id, body_dict)
139
- logger.debug(f"Autoselect response result: {result}")
140
- return result
141
- except Exception as e:
142
- logger.error(f"Error handling autoselect: {str(e)}", exc_info=True)
143
- raise
144
-
145
- # Check if it's a rotation
146
- if provider_id in config.rotations:
147
- logger.debug("Handling rotation request")
148
- return await rotation_handler.handle_rotation_request(provider_id, body_dict)
149
-
150
- # Check if it's a provider
151
- if provider_id not in config.providers:
152
- logger.error(f"Provider {provider_id} not found")
153
- raise HTTPException(status_code=400, detail=f"Provider {provider_id} not found")
154
-
155
- provider_config = config.get_provider(provider_id)
156
- logger.debug(f"Provider config: {provider_config}")
157
-
158
- try:
159
- if body.stream:
160
- logger.debug("Handling streaming chat completion")
161
- return await request_handler.handle_streaming_chat_completion(request, provider_id, body_dict)
162
- else:
163
- logger.debug("Handling non-streaming chat completion")
164
- result = await request_handler.handle_chat_completion(request, provider_id, body_dict)
165
- logger.debug(f"Response result: {result}")
166
- return result
167
- except Exception as e:
168
- logger.error(f"Error handling chat_completions: {str(e)}", exc_info=True)
169
- raise
170
-
171
- @app.get("/api/{provider_id}/models")
172
- async def list_models(request: Request, provider_id: str):
173
- logger.debug(f"Received list_models request for provider: {provider_id}")
174
-
175
- # Check if it's an autoselect
176
- if provider_id in config.autoselect:
177
- logger.debug("Handling autoselect model list request")
178
- try:
179
- result = await autoselect_handler.handle_autoselect_model_list(provider_id)
180
- logger.debug(f"Autoselect models result: {result}")
181
- return result
182
- except Exception as e:
183
- logger.error(f"Error handling autoselect model list: {str(e)}", exc_info=True)
184
- raise
185
-
186
- # Check if it's a rotation
187
- if provider_id in config.rotations:
188
- logger.debug("Handling rotation model list request")
189
- return await rotation_handler.handle_rotation_model_list(provider_id)
190
-
191
- # Check if it's a provider
192
- if provider_id not in config.providers:
193
- logger.error(f"Provider {provider_id} not found")
194
- raise HTTPException(status_code=400, detail=f"Provider {provider_id} not found")
195
-
196
- provider_config = config.get_provider(provider_id)
197
-
198
- try:
199
- logger.debug("Handling model list request")
200
- result = await request_handler.handle_model_list(request, provider_id)
201
- logger.debug(f"Models result: {result}")
202
- return result
203
- except Exception as e:
204
- logger.error(f"Error handling list_models: {str(e)}", exc_info=True)
205
- raise
206
-
207
- def main():
208
- """Main entry point for the AISBF server"""
209
- import uvicorn
210
- logger.info("Starting AI Proxy Server on http://localhost:8000")
211
- uvicorn.run(app, host="0.0.0.0", port=8000)
212
-
213
- if __name__ == "__main__":
214
- main()
@@ -1,24 +0,0 @@
1
- cli.py,sha256=SpjVC1iBdDhQXuhJcjVFkODu4BH-nj_1hNFD_d8wPbw,2503
2
- aisbf/__init__.py,sha256=D3-tZRWCu31CltN_pjx8IikwPl0OGEJkvoASm8QjvcQ,2156
3
- aisbf/config.py,sha256=-pJa69vw_d_VuGPYGgKNm0hhMEgbVYSg2ZdWNl8LubY,6349
4
- aisbf/handlers.py,sha256=exBfP1oLHYxNvq_xqVbWCl1NPxZgn-tlZL7j7acE558,15828
5
- aisbf/models.py,sha256=LT1NaQVAw0VWXL-j3hdfNlXCA9HeiET_O3GDj3t9XC4,1883
6
- aisbf/providers.py,sha256=jQGKYqrb-1RB3s4XrKK7AbX-7W5Cl3HG7Yc_riMkcyA,11942
7
- aisbf-0.2.2.data/data/share/aisbf/aisbf.sh,sha256=ntI4UPefBtU2jrTwMR3hddHEPG_pDyJyO0J3SD7e5PA,4574
8
- aisbf-0.2.2.data/data/share/aisbf/autoselect.json,sha256=Anud0hTE1mehonmMmhOTPK2ANUxfruE2yMdLqiEkEUA,659
9
- aisbf-0.2.2.data/data/share/aisbf/autoselect.md,sha256=F8PilhaYBs0qdpIxIkokrjtIOdhAx5Bi1tA0hyfnqps,4301
10
- aisbf-0.2.2.data/data/share/aisbf/main.py,sha256=ikv0-3KVHbawExNy6X4juE_pjZKaggkv-qAcUrqCw_s,7809
11
- aisbf-0.2.2.data/data/share/aisbf/providers.json,sha256=9L5GO6sQ2Z6zndGed0AckvYNV1DMr9r7tSdZ9fJxYlA,3934
12
- aisbf-0.2.2.data/data/share/aisbf/requirements.txt,sha256=lp6cPakAO3lpTCwQ27THf-PNz_HIpzCELrtpdgo6-2o,133
13
- aisbf-0.2.2.data/data/share/aisbf/rotations.json,sha256=SzbmMeTRR0vVTrYTMwxSPxjXLVr8zxjaI4HYRxjyExQ,2123
14
- aisbf-0.2.2.data/data/share/aisbf/aisbf/__init__.py,sha256=D3-tZRWCu31CltN_pjx8IikwPl0OGEJkvoASm8QjvcQ,2156
15
- aisbf-0.2.2.data/data/share/aisbf/aisbf/config.py,sha256=-pJa69vw_d_VuGPYGgKNm0hhMEgbVYSg2ZdWNl8LubY,6349
16
- aisbf-0.2.2.data/data/share/aisbf/aisbf/handlers.py,sha256=exBfP1oLHYxNvq_xqVbWCl1NPxZgn-tlZL7j7acE558,15828
17
- aisbf-0.2.2.data/data/share/aisbf/aisbf/models.py,sha256=LT1NaQVAw0VWXL-j3hdfNlXCA9HeiET_O3GDj3t9XC4,1883
18
- aisbf-0.2.2.data/data/share/aisbf/aisbf/providers.py,sha256=jQGKYqrb-1RB3s4XrKK7AbX-7W5Cl3HG7Yc_riMkcyA,11942
19
- aisbf-0.2.2.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
20
- aisbf-0.2.2.dist-info/METADATA,sha256=gi0VlbVGDAR2oAwVMmJaOU-8xIksAY3vKMI2i6UGXbE,4190
21
- aisbf-0.2.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
22
- aisbf-0.2.2.dist-info/entry_points.txt,sha256=dV_E5f6UvgSe9AoyPTzGxBK8IYaIeLR8yTe7EwBZ3F8,35
23
- aisbf-0.2.2.dist-info/top_level.txt,sha256=odXp1LYymu31EdVSmMGCg3ZYAI5HeB8tZkaXh9Pw3kE,10
24
- aisbf-0.2.2.dist-info/RECORD,,
File without changes