Bridge for LLM"s
Project description
aibridgecore
A unified Python SDK for working with multiple AI providers through a consistent set of utilities — text generation (sync + streaming), prompt management, reusable variables, structured outputs, queue-backed async execution, image generation, video generation — all with client-side validation, typed exceptions, and provider-normalized error handling.
Overview
- Multi-provider text generation with a shared high-level interface — same call shape across OpenAI, Anthropic, Gemini, Cohere, Mistral, Grok, Deepseek, Kimi, Alibaba (Qwen), AI21, and self-hosted Ollama
- Streaming responses (
generate_stream) on every provider that supports it - Function calling (
execute_prompt_function_calling) for tool use across providers - Structured outputs — JSON, CSV, XML workflows with format validation
- Stored prompts and reusable variables — backed by SQL or MongoDB
- Redis-based async queue for fire-and-forget execution at scale
- Provider-specific image generation APIs (DALL·E, Stable Diffusion)
- Provider-specific video generation modules
- Client-side validation before any network call (model, prompts, temperature, output format, context, etc.)
- Provider-agnostic exception model — every error has the same shape with the same attributes regardless of which provider failed
- Automatic categorisation of errors into typed Python exceptions (
BadRequestException,AuthenticationException,RateLimitException,TimeoutException,ServerException) - Inheritance-based validator system — 90% of validation logic lives in
BaseValidator, providers override only what differs - OpenAI-style printed errors — uncaught exceptions render like
openai.BadRequestError: Error code: 400 - {'error': {...}}so they're immediately useful in tracebacks
Table of Contents
- Installation & Configuration
- Quick Start
- Architecture
- Multi-Provider Text Generation
- Streaming
- Function Calling
- Structured Outputs (JSON / CSV / XML)
- Stored Prompts & Variables
- Asynchronous Queue Execution (Redis)
- Image Generation
- Video Generation
- The Validator System
- The Exception Model
- The Wrapper (
@provider_wrapper) - The Error Normalizer
- End-to-End Error Flow
- Error Handling
- Provider Catalog
- Adding a New Provider
- Web Integration (Flask / FastAPI)
- Testing
- File Map
- Glossary
Installation & Configuration
pip install aibridgecore
Python 3.9+. Set the config file path:
export AIBRIDGE_CONFIG=/absolute/path/to/aibridge_config.yaml
Minimal aibridge_config.yaml:
open_ai:
equal:
- YOUR_OPENAI_API_KEY
kimi:
equal:
- YOUR_KIMI_API_KEY
anthropic:
equal:
- YOUR_ANTHROPIC_API_KEY
database: sql # or "mongo"
message_queue: redis
redis_host: localhost
redis_port: 6379
Each provider has its own top-level key. The equal list supports key rotation across multiple keys.
Quick Start
from aibridgecore.ai_services.openai_services import OpenAIService
from aibridgecore.exceptions import AIBridgeException
try:
response = OpenAIService.generate(
prompts=["Summarise this paragraph in 50 words..."],
model="gpt-3.5-turbo",
variation_count=1,
max_tokens=200,
temperature=0.5,
)
print(response)
except AIBridgeException as e:
print(f"{type(e).__name__}: {e.user_message}")
Every exception in the SDK is a subclass of AIBridgeException and carries the same attributes: e.status_code (int HTTP status), e.code (string error code), e.category, e.message, e.user_message, e.param, e.expected, e.received, e.provider, e.retryable, e.developer_message, and the full flat e.details dict (plus e.to_dict()).
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Caller (script / Flask / FastAPI / worker) │
└────────────────────┬────────────────────────────────────────┘
│ OpenAIService.generate(prompts=..., ...)
▼
┌─────────────────────────────────────────────────────────────┐
│ @provider_wrapper(validator=OpenAIValidator, ...) │
│ 1. OpenAIValidator.validate_generate(kwargs) │
│ 2. func(*args, **kwargs) if validation passes │
│ 3. On any exception → normalize → re-raise typed │
└────────────────────┬────────────────────────────────────────┘
│ HTTPS (only if validation passed)
▼
┌─────────────────────────────────────────────────────────────┐
│ Provider API (OpenAI, Anthropic, Kimi, ...) │
└─────────────────────────────────────────────────────────────┘
Three pieces do the heavy lifting:
| Piece | File | Responsibility |
|---|---|---|
| Validators | aibridgecore/validators/provider_validator.py |
Check kwargs before the call. Raise BadRequestException with rich details on bad input. |
| Wrapper | aibridgecore/wrappers/provider_wrapper.py |
Run the validator, run the function, catch any exception, transform it into the right category class. |
| Normalizer | aibridgecore/error_hanldling/error_normalizer.py |
Inspect any non-typed exception and produce a rich details dict. |
Multi-Provider Text Generation
Every provider exposes the same method signature for generate():
SomeService.generate(
prompts=[...], # or prompt_ids=[...] (from stored prompts)
prompt_data=[...], # variable substitutions
variables=[...],
output_format=["json"], # "json", "xml", "csv", "string"
format_strcture=["..."], # JSON schema / XML template / CSV header
model="...",
variation_count=1,
max_tokens=200,
temperature=0.5,
message_queue=False, # if True, enqueue and return response_id
api_key=None, # falls back to AIBRIDGE_CONFIG
output_format_parse=True,
context=[{"role": "system", "context": "..."}],
)
Swap the service class to switch providers:
from aibridgecore.ai_services.openai_services import OpenAIService
from aibridgecore.ai_services.anthropic_ai import AnthropicService
from aibridgecore.ai_services.geminin_services import GeminiAIService
from aibridgecore.ai_services.cohere_llm import CohereApi
from aibridgecore.ai_services.kimi_services import KimiService
from aibridgecore.ai_services.grok_services import GrokService
from aibridgecore.ai_services.mistral_services import MistralService
from aibridgecore.ai_services.deepseek_services import DeepseekService
from aibridgecore.ai_services.alibaba_services import AlibabaService
from aibridgecore.ai_services.ai21labs_text import AI21labsText
from aibridgecore.ai_services.ollama_services import OllamaService
The validation rules and exception types are uniform across all of them.
Streaming
from aibridgecore.ai_services.openai_services import OpenAIService
from aibridgecore.exceptions import AIBridgeException
try:
stream = OpenAIService.generate_stream(
prompts=["Tell me a 3-sentence story."],
model="gpt-3.5-turbo",
temperature=0.5,
max_tokens=200,
)
for chunk in stream:
if chunk["type"] == "content":
print(chunk["data"], end="", flush=True)
elif chunk["type"] == "usage":
print(f"\n[input={chunk['input_tokens']} output={chunk['output_tokens']}]")
except AIBridgeException as e:
print(f"\n{type(e).__name__}: {e}")
Chunks come in two shapes:
{"type": "content", "data": "<text fragment>"}{"type": "usage", "input_tokens": N, "output_tokens": M}(at the end)
Validation runs before the generator is created, so bad input raises a BadRequestException immediately (no API round-trip). Errors raised mid-stream are also caught by @provider_wrapper and normalized into the right typed exception, just like non-streaming methods. kimi_services.py and ollama_services.py do not expose generate_stream.
Function Calling
result = OpenAIService.execute_prompt_function_calling(
api_key="sk-...",
model="gpt-4",
messages=[{"role": "user", "content": "What's the weather in Paris?"}],
n=1,
functions_call=[{
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}],
max_tokens=200,
temperature=0.3,
)
The validator checks: api_key required, model required and string, messages must be a non-empty list of dicts, n is a positive integer, functions_call is a list of dicts.
Supported on every LLM provider except AI21.
Structured Outputs (JSON / CSV / XML)
Pass output_format and format_strcture to constrain provider responses to machine-parseable shapes:
response = OpenAIService.generate(
prompts=["List 3 fruits with their colors."],
model="gpt-4",
variation_count=1,
output_format=["json"],
format_strcture=['{"fruits": [{"name": "string", "color": "string"}]}'],
)
Allowed output_format values: "json", "xml", "csv", "string". Lengths of output_format and format_strcture must match len(prompts). JSON templates are checked for parse-ability; XML/CSV templates are sanity-checked at validation time.
Parsers live in aibridgecore/output_validation/.
Stored Prompts & Variables
Store frequently-used prompts in your database and reference them by ID, with placeholder substitution:
response = OpenAIService.generate(
prompt_ids=["product_description_v3"],
prompt_data=[{
"product_name": "Acme Widget",
"feature_list": ["A", "B", "C"],
}],
variables=[{"tone": "playful"}],
model="gpt-4",
)
Backed by SQL or MongoDB depending on database: in aibridge_config.yaml. Helpers in aibridgecore/prompts/.
Asynchronous Queue Execution (Redis)
For long jobs or fan-out workloads, enqueue the call and return a response ID immediately:
result = OpenAIService.generate(
prompts=["Long-running multi-step task..."],
model="gpt-4",
message_queue=True, # ← off by default
)
print(result) # {"response_id": "<uuid>"}
A worker (using aibridgecore.ai_services.process_mq) drains the Redis queue, executes the generate call, and stores the result for later retrieval. Configure Redis via redis_host / redis_port in the yaml.
Image Generation
from aibridgecore.ai_services.openai_images import OpenAIImage
result = OpenAIImage.generate(
prompts=["A cyberpunk samurai in Tokyo at night."],
model="dall-e-3",
size="1024x1024",
process_type="create", # "create", "variation", or "edit"
variation_count=1,
api_key="sk-...",
)
For process_type="variation" or "edit", also pass image_data=[...] (and mask_image=[...] for edits). Edits and variations require model="dall-e-2".
Stable Diffusion has the same shape — see aibridgecore.ai_services.stable_diffusion_image.StableDiffusion.
Video Generation
Provider-specific modules live in aibridgecore/video/. Same overall pattern as image generation — pass a prompt, get a generation response or queue ID back.
The Validator System
Design
A single file — validators/provider_validator.py — holds:
- One
BaseValidatorwith ~90% of the validation logic - One subclass per provider that only overrides what differs
- A
VALIDATORSregistry mapping provider name → class
BaseValidator
├── OpenAIValidator
├── KimiValidator (TEMP_MAX = 1.0)
├── AnthropicValidator
├── GeminiValidator
├── CohereValidator
├── GrokValidator
├── MistralValidator
├── DeepseekValidator
├── AlibabaValidator
├── OllamaValidator
└── Ai21Validator
from aibridgecore.validators.provider_validator import VALIDATORS
VALIDATORS["kimi"] # <class 'KimiValidator'>
VALIDATORS["openai"] # <class 'OpenAIValidator'>
What BaseValidator checks
Per-field methods on BaseValidator:
| Method | Validates |
|---|---|
validate_prompt_source(kwargs) |
exactly one of prompts / prompt_ids is provided |
validate_prompts(prompts) |
list of non-empty strings |
validate_prompt_ids(prompt_ids) |
list |
validate_prompt_data(prompt_data) |
list of dicts |
validate_variables(variables) |
list |
validate_model(model) |
required, must be a string |
validate_temperature(temperature) |
numeric, TEMP_MIN <= temp <= TEMP_MAX |
validate_max_tokens(max_tokens) |
positive integer |
validate_variation_count(variation_count) |
positive integer |
validate_output_format(output_format, prompts) |
list of "json" / "xml" / "csv" / "string", length matches prompts |
validate_format_structure(structure, output_format, prompts) |
structured format string is parseable; length matches prompts |
validate_context(context) |
list of {role, context} dicts; role is user / system / assistant |
validate_api_key(api_key) |
non-empty string when provided |
Class-level constants subclasses can override
class BaseValidator:
PROVIDER = "base"
ALLOWED_FORMATS = ["json", "xml", "csv", "string"]
ALLOWED_ROLES = ["user", "system", "assistant"]
TEMP_MIN = 0.0
TEMP_MAX = 2.0
Real overrides today:
class KimiValidator(BaseValidator):
PROVIDER = "kimi"
TEMP_MAX = 1.0
Method-level validators (full surface)
Every LLM service is wrapped on six entry points:
| Service method | validation_method classmethod |
|---|---|
generate |
validate_generate |
generate_stream |
validate_generate_stream |
execute_text_prompt |
validate_execute_text_prompt_method |
execute_prompt_function_calling |
validate_execute_prompt_function_calling_method |
get_prompt_context |
validate_get_prompt_context_method |
get_response |
validate_get_response_method |
Error codes the validator emits
e.code |
Meaning |
|---|---|
MISSING_REQUIRED_FIELD |
A required arg was None or absent |
EMPTY_VALUE |
A required string is empty / whitespace |
INVALID_TYPE |
Argument was of the wrong Python type |
INVALID_VALUE |
Value didn't match an enum (output_format, role, etc.) |
OUT_OF_RANGE |
Numeric value outside allowed range |
CONFLICTING_FIELDS |
Mutually exclusive args both provided |
LENGTH_MISMATCH |
List lengths don't match (e.g. output_format vs prompts) |
The Exception Model
Hierarchy
Exception
└── AIBridgeException (.code, .status_code, .message, .details, to_dict(), __str__, __repr__)
│
├── BadRequestException (HTTP 400 — generic bad request)
│ └── (ValidationException can be made to inherit — currently sibling)
├── AuthenticationException (HTTP 401)
├── TimeoutException (HTTP 408)
├── RateLimitException (HTTP 429)
├── ServerException (HTTP 5xx)
├── ValidationException (HTTP 400 — client-side validator caught it)
│
└── Provider-specific (internal layer, rarely surfaced):
OpenAIException, KimiException, AnthropicsException, GeminiException,
CohereException, Ai21Exception, GrokException, DeepseekException,
MistralException, AlibabaException, OllamaException
Per-category-class HTTP status
| Class | e.status_code |
|---|---|
ValidationException / BadRequestException |
400 |
AuthenticationException |
401, 403 |
TimeoutException |
408 |
RateLimitException |
429 |
ServerException |
500–599 (preserves upstream) |
e.code vs e.status_code
These are two different things, matching the OpenAI SDK:
e.status_code—intHTTP status (401,429,500, ...).e.code—strmachine-readable error code ("rate_limit_exceeded","invalid_api_key", ...) orNonewhen unknown.
Attributes on every exception
e.status_code # int — HTTP status (400, 401, 403, 429, ...)
e.code # str / None — semantic error code ("rate_limit_exceeded", ...)
e.category # str — error category (rate_limit_error, authentication_error, ...)
e.message # str — raw machine-friendly message
e.user_message # str — human-friendly version
e.param # str / None — which input parameter, if applicable
e.expected # str / None — what was expected
e.received # any / None — what was received
e.provider # str / None — which provider triggered
e.retryable # bool — whether retrying may succeed
e.developer_message # str — pre-formatted developer log line
e.details # dict — all of the above as a flat dict (always present)
e.to_dict() # method — returns e.details
Backward compatibility
AiBridgeValidationException is an alias of BadRequestException.
The Wrapper (@provider_wrapper)
Every public method on every service is decorated with @provider_wrapper(validator=..., validation_method=...). The wrapper:
- Argument binding — binds the call against the function signature and applies declared defaults, so the validator sees the effective values (an omitted
variation_count=1validates as1, notNone). Mutable defaults (prompts=[],context=[]) are copied per call so state never leaks between invocations. - Pre-validation — runs the validator. On failure, the validator raises
BadRequestException; wrapper sees it's a category class and lets it propagate. - Function execution — runs the wrapped function. Generator methods (
generate_stream) are detected automatically and their iteration is wrapped too, so errors raised mid-stream are caught the same way. - Exception transformation — typed exceptions pass through (with
e.detailsfilled in to the full normalized shape); anything else gets normalized and transformed into the right category class.
Because the wrapper covers every method shape — sync, generator, validation-time and mid-stream errors — service methods do not need their own try/except. It is the single error-handling layer.
_class_for_code() mapping
400 → BadRequestException
401, 403 → AuthenticationException
408 → TimeoutException
429 → RateLimitException
500-599 → ServerException
other → BadRequestException
Provider auto-injection
The wrapper reads PROVIDER from the validator class and stamps it onto e.details["provider"].
The Error Normalizer
ErrorNormalizer.normalize(e, provider=None) inspects any exception and returns a
structured details dict. Normalization is status-code-driven: _GENERIC_BY_STATUS
covers the 12 HTTP status codes below, and a small ERROR_CATALOG holds only the
handful of provider-specific errors that need a non-generic mapping.
Resolution order
| Step | Trigger | Result |
|---|---|---|
| 1 | isinstance(e, ValidationException / BadRequestException) |
validation_error / 400, passes param details through |
| 2 | provider known and e.code matches a catalog entry |
provider-specific entry |
| 3 | a provider catalog match_keywords entry is found in the message |
provider-specific entry |
| 4 | e.status_code is set |
_GENERIC_BY_STATUS entry for that status |
| 5 | nothing matched | internal_server_error / 500 |
Status codes covered (12)
400 invalid request · 401 authentication · 402 insufficient balance ·
403 permission / region blocked · 404 not found · 408 timeout ·
422 unprocessable entity · 429 rate limit · 500 server error ·
502 bad gateway · 503 service unavailable / overloaded · 529 overloaded (Anthropic).
ERROR_CATALOG — provider-specific overrides only (7)
Common errors (invalid_api_key, rate_limit_exceeded, model_not_found,
internal_server_error, ...) are not catalogued — they resolve straight from
_GENERIC_BY_STATUS by HTTP status. The catalog holds only entries whose behavior
differs from the generic mapping:
| Provider | Code | Why it overrides the generic |
|---|---|---|
| openai | insufficient_quota |
429 but retryable: False (billing, not rate) |
| openai | context_length_exceeded |
400 with param: messages + specific guidance |
| openai | engine_overloaded |
503 with provider-specific wording |
| anthropic | overloaded_error |
non-standard 529 |
| gemini | safety_blocked |
content-filter — no generic equivalent |
| ollama | out_of_memory |
500 but retryable: False |
| ollama | connection_refused |
local-daemon reachability |
Categories (6)
authentication_error · rate_limit_error · validation_error ·
provider_error · server_error · unknown_error.
ErrorNormalizer.status_codes() returns the sorted list of covered statuses;
ErrorNormalizer.lookup(provider, code) returns a single catalog entry.
End-to-End Error Flow
Scenario A — client-side validation fails
@provider_wrapperruns the validator.- Validator's
_raise()constructsBadRequestExceptionwith full details. - Exception propagates. Wrapper sees a category class → re-raises it, filling in any missing
detailskeys. - Caller catches
BadRequestException. No HTTPS round-trip.
Scenario B — provider rate-limits
- Validation passes. Function calls provider SDK.
- Provider raises
openai.RateLimitError. - The raw exception propagates to the wrapper (no per-function
try/exceptneeded). - Wrapper catches it, the normalizer reads
status_code/code/"rate limit"→ 429. - Wrapper picks
RateLimitException, attaches fulldetails, raisesfrom e. - Caller catches
RateLimitException.
Scenario C — typed exception inside function body
A service body may raise a typed exception directly:
raise OpenAIException("No prompts provided")
It propagates to the wrapper, which normalizes it into the right category class and fills in the full details. Provider methods do not need their own try/except — the wrapper is the single error layer.
Error Handling
The single most important section for SDK users.
Catching by category
from aibridgecore.exceptions import (
AIBridgeException,
ValidationException,
BadRequestException,
AuthenticationException,
RateLimitException,
TimeoutException,
ServerException,
)
try:
response = OpenAIService.generate(...)
except ValidationException as e:
pass
except AuthenticationException as e:
pass
except RateLimitException as e:
pass
except TimeoutException as e:
pass
except ServerException as e:
pass
except BadRequestException as e:
pass
except AIBridgeException as e:
pass
Catch most-specific first.
Catching everything
try:
response = OpenAIService.generate(...)
except AIBridgeException as e:
log.error(e.developer_message)
return {"error": e.to_dict()}, e.status_code
Accessing exception data
except AIBridgeException as e:
e.status_code # int — 400, 401, 403, 408, 429, 5xx
e.code # str / None — semantic error code ("rate_limit_exceeded")
e.category # str — error category
e.message
e.user_message
e.param
e.expected
e.received
e.provider
e.retryable
e.developer_message
e.details # flat dict (always present)
e.to_dict() # returns e.details
Example outputs
Pre-validation — temperature out of range:
try:
OpenAIService.generate(prompts=["hi"], variation_count=1, model="gpt-4", temperature=99)
except BadRequestException as e:
e.code # OUT_OF_RANGE (str — semantic error code)
e.status_code # 400 (int — HTTP status)
e.param # temperature
e.expected # 0.0 <= temperature <= 2.0
e.received # 99
e.user_message # Please use a temperature between 0.0 and 2.0.
Uncaught traceback:
aibridgecore.exceptions.BadRequestException: {'error': {'code': 'OUT_OF_RANGE',
'status_code': 400, 'category': 'validation_error',
'message': 'temperature must be between 0.0 and 2.0',
'user_message': 'Please use a temperature between 0.0 and 2.0.',
'param': 'temperature', 'expected': '0.0 <= temperature <= 2.0',
'received': 99, 'provider': 'openai',
'developer_message': '[openai] [OUT_OF_RANGE] param=temperature ...'}}
Provider rate limit:
except RateLimitException as e:
e.code # rate_limit_exceeded (str — semantic error code)
e.status_code # 429 (int — HTTP status)
e.category # rate_limit_error
e.retryable # True
e.user_message # Too many requests. Please slow down and try again shortly.
e.provider # openai
Provider auth failure:
except AuthenticationException as e:
e.code # invalid_api_key (str — semantic error code)
e.status_code # 401 (int — HTTP status)
e.category # authentication_error
e.user_message # Authentication failed. Please verify your API key.
Serializing for an API response or log line
import json
try:
OpenAIService.generate(...)
except AIBridgeException as e:
json.dumps(e.to_dict()) # safe over the wire
log.error(e.developer_message) # pre-formatted log line
Retry recipe
import time
from aibridgecore.exceptions import (
AIBridgeException, RateLimitException, TimeoutException, ServerException,
)
def call_with_retry(svc, **kwargs):
delay = 1
for attempt in range(5):
try:
return svc.generate(**kwargs)
except (RateLimitException, TimeoutException, ServerException):
time.sleep(delay)
delay *= 2
except AIBridgeException:
raise
raise
Cheat sheet — which class, when
| Class | When you'll see it |
|---|---|
ValidationException |
Bad input. Our validator caught it before any network call. Fix the input. |
BadRequestException |
Provider returned 400. Inspect e.message. |
AuthenticationException |
Provider returned 401. API key bad / missing / revoked. |
TimeoutException |
Provider returned 408 or the request timed out. Retry. |
RateLimitException |
Provider returned 429. Backoff and retry. |
ServerException |
Provider returned 5xx. Retry or fall back. |
Provider Catalog
| Provider | Service class | Validator | File |
|---|---|---|---|
| OpenAI | OpenAIService |
OpenAIValidator |
ai_services/openai_services.py |
| Anthropic (Claude) | AnthropicService |
AnthropicValidator |
ai_services/anthropic_ai.py |
| Google Gemini | GeminiAIService |
GeminiValidator |
ai_services/geminin_services.py |
| Cohere | CohereApi |
CohereValidator |
ai_services/cohere_llm.py |
| Grok | GrokService |
GrokValidator |
ai_services/grok_services.py |
| Mistral | MistralService |
MistralValidator |
ai_services/mistral_services.py |
| Deepseek | DeepseekService |
DeepseekValidator |
ai_services/deepseek_services.py |
| Kimi (Moonshot) | KimiService |
KimiValidator (TEMP_MAX=1.0) |
ai_services/kimi_services.py |
| Alibaba (Qwen) | AlibabaService |
AlibabaValidator |
ai_services/alibaba_services.py |
| AI21 | AI21labsText |
Ai21Validator |
ai_services/ai21labs_text.py |
| Ollama (self-hosted) | OllamaService |
OllamaValidator |
ai_services/ollama_services.py |
| OpenAI Images (DALL·E) | OpenAIImage |
not yet | ai_services/openai_images.py |
| Stable Diffusion | StableDiffusion |
not yet | ai_services/stable_diffusion_image.py |
Method coverage
| File | generate | stream | exec_txt | exec_fn | prompt_ctx | get_resp |
|---|---|---|---|---|---|---|
| openai_services | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| kimi_services | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| anthropic_ai | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| geminin_services | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| cohere_llm | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| grok_services | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| mistral_services | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| deepseek_services | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| alibaba_services | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| ollama_services | ✓ | — | ✓ | ✓ | ✓ | ✓ |
| ai21labs_text | ✓ | ✓ | — | — | ✓ | ✓ |
Adding a New Provider
Four steps.
1. Subclass BaseValidator
class NewProviderValidator(BaseValidator):
PROVIDER = "newprovider"
TEMP_MAX = 1.5
2. Register
VALIDATORS = {
...
"newprovider": NewProviderValidator,
}
3. Import in the service
from aibridgecore.validators.provider_validator import NewProviderValidator
from aibridgecore.wrappers.provider_wrapper import provider_wrapper
from aibridgecore.exceptions import AIBridgeException, NewProviderException
4. Wrap each public method
class NewProviderService(AIInterface):
@classmethod
@provider_wrapper(
validator=NewProviderValidator,
validation_method="validate_generate",
)
def generate(self, prompts=[], model="np-default", variation_count=1, ...):
try:
...
except AIBridgeException:
raise
except Exception as e:
raise NewProviderException(e)
Repeat for generate_stream, execute_text_prompt, execute_prompt_function_calling, get_prompt_context, get_response.
Web Integration (Flask / FastAPI)
FastAPI
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from aibridgecore.ai_services.openai_services import OpenAIService
from aibridgecore.exceptions import AIBridgeException
app = FastAPI()
@app.exception_handler(AIBridgeException)
def aibridge_handler(request, exc: AIBridgeException):
return JSONResponse(status_code=exc.status_code, content={"error": exc.to_dict()})
@app.post("/generate")
def generate(payload: dict):
return OpenAIService.generate(**payload)
Flask
from flask import Flask, jsonify, request
from aibridgecore.ai_services.openai_services import OpenAIService
from aibridgecore.exceptions import AIBridgeException
app = Flask(__name__)
@app.errorhandler(AIBridgeException)
def handle_aibridge(exc):
return jsonify({"error": exc.to_dict()}), exc.status_code
@app.post("/generate")
def generate():
return jsonify(OpenAIService.generate(**request.json))
A 429 from a provider, a 401 for a bad key, or a 400 from the validator all flow into the same handler.
Testing
python -m pytest tests/test_detailed_errors.py -v
The test file loads modules via importlib.util to exercise validator + normalizer + exception layers without importing every provider SDK.
Smoke-testing without an API key
from aibridgecore.validators.provider_validator import OpenAIValidator
from aibridgecore.exceptions import BadRequestException
try:
OpenAIValidator.validate_generate({
"prompts": ["hello"],
"variation_count": 1,
"model": "gpt-4",
"temperature": 99,
})
except BadRequestException as e:
assert e.code == "OUT_OF_RANGE"
assert e.param == "temperature"
File Map
aibridgecore/
├── ai_services/
│ ├── openai_services.py OpenAIService
│ ├── kimi_services.py KimiService
│ ├── anthropic_ai.py AnthropicService
│ ├── geminin_services.py GeminiAIService
│ ├── cohere_llm.py CohereApi
│ ├── grok_services.py GrokService
│ ├── mistral_services.py MistralService
│ ├── deepseek_services.py DeepseekService
│ ├── alibaba_services.py AlibabaService
│ ├── ai21labs_text.py AI21labsText
│ ├── ollama_services.py OllamaService
│ ├── openai_images.py OpenAIImage (DALL·E)
│ ├── stable_diffusion_image.py StableDiffusion
│ ├── image_optimisaton.py (internal helper)
│ ├── ai_services_response.py response store
│ ├── process_mq.py Redis queue worker
│ └── ai_abstraction.py AIInterface base contract
│
├── validators/
│ └── provider_validator.py BaseValidator + subclasses + VALIDATORS
│
├── wrappers/
│ └── provider_wrapper.py @provider_wrapper + _class_for_code
│
├── error_hanldling/
│ └── error_normalizer.py ErrorNormalizer + inline ERROR_CATALOG
│
├── output_validation/ JSON / XML / CSV parsers
├── prompts/ stored prompts + variables
├── queue_integration/ Redis integration
├── database/ SQL / MongoDB layer
├── constant/ provider URLs, allowed sizes, etc.
├── image/ image utilities
├── video/ video generation modules
├── exceptions.py exception hierarchy
└── setconfig.py config loader
Glossary
- Category class — one of
ValidationException,BadRequestException,AuthenticationException,TimeoutException,RateLimitException,ServerException. What callersexceptagainst. - Provider class — internal type like
OpenAIException,KimiException. The wrapper transforms these into category classes. - Details dict — flat dict built by
_raise()or_build_error()and attached ase.details. Always present. - Error code (
e.code) — semantic string likeOUT_OF_RANGE,rate_limit_exceeded,invalid_api_key. Independent of HTTP status. - HTTP status (
e.status_code) — numeric HTTP status (400,401,403,429, ...). Fixed per category class exceptServerExceptionwhich preserves the upstream value. - Category (
e.category) — one ofauthentication_error,rate_limit_error,validation_error,provider_error,server_error,unknown_error. - Developer message — pre-formatted log line like
[openai] [OUT_OF_RANGE] param=temperature .... Renderuser_messageto humans instead. _CATEGORY_CLASSES— tuple inprovider_wrapper.pyof category classes. The wrapper checksisinstance(e, _CATEGORY_CLASSES)to decide whether to transform or pass through.
License
See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aibridgecore-1.6.0.dev4-py3-none-any.whl.
File metadata
- Download URL: aibridgecore-1.6.0.dev4-py3-none-any.whl
- Upload date:
- Size: 127.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2187bc837c09a74bdf85312727968b1ba66ae54090f0ae0c601de83b6b39ed7b
|
|
| MD5 |
ac80f0223c03707e4dc1cec4a6d5aa07
|
|
| BLAKE2b-256 |
f938e28a593cf5426c45f16287188721bcfc98b07feef373060cdbd2d22c3d3a
|