Skip to main content

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

  1. Installation & Configuration
  2. Quick Start
  3. Architecture
  4. Multi-Provider Text Generation
  5. Streaming
  6. Function Calling
  7. Structured Outputs (JSON / CSV / XML)
  8. Stored Prompts & Variables
  9. Asynchronous Queue Execution (Redis)
  10. Image Generation
  11. Video Generation
  12. The Validator System
  13. The Exception Model
  14. The Wrapper (@provider_wrapper)
  15. The Error Normalizer
  16. End-to-End Error Flow
  17. Error Handling
  18. Provider Catalog
  19. Adding a New Provider
  20. Web Integration (Flask / FastAPI)
  21. Testing
  22. File Map
  23. 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 BaseValidator with ~90% of the validation logic
  • One subclass per provider that only overrides what differs
  • A VALIDATORS registry 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_codeint HTTP status (401, 429, 500, ...).
  • e.codestr machine-readable error code ("rate_limit_exceeded", "invalid_api_key", ...) or None when 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:

  1. Argument binding — binds the call against the function signature and applies declared defaults, so the validator sees the effective values (an omitted variation_count=1 validates as 1, not None). Mutable defaults (prompts=[], context=[]) are copied per call so state never leaks between invocations.
  2. Pre-validation — runs the validator. On failure, the validator raises BadRequestException; wrapper sees it's a category class and lets it propagate.
  3. 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.
  4. Exception transformation — typed exceptions pass through (with e.details filled 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

  1. @provider_wrapper runs the validator.
  2. Validator's _raise() constructs BadRequestException with full details.
  3. Exception propagates. Wrapper sees a category class → re-raises it, filling in any missing details keys.
  4. Caller catches BadRequestException. No HTTPS round-trip.

Scenario B — provider rate-limits

  1. Validation passes. Function calls provider SDK.
  2. Provider raises openai.RateLimitError.
  3. The raw exception propagates to the wrapper (no per-function try/except needed).
  4. Wrapper catches it, the normalizer reads status_code / code / "rate limit" → 429.
  5. Wrapper picks RateLimitException, attaches full details, raises from e.
  6. 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 callers except against.
  • 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 as e.details. Always present.
  • Error code (e.code) — semantic string like OUT_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 except ServerException which preserves the upstream value.
  • Category (e.category) — one of authentication_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 .... Render user_message to humans instead.
  • _CATEGORY_CLASSES — tuple in provider_wrapper.py of category classes. The wrapper checks isinstance(e, _CATEGORY_CLASSES) to decide whether to transform or pass through.

License

See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aibridgecore-1.6.0.dev2-py3-none-any.whl (127.6 kB view details)

Uploaded Python 3

File details

Details for the file aibridgecore-1.6.0.dev2-py3-none-any.whl.

File metadata

File hashes

Hashes for aibridgecore-1.6.0.dev2-py3-none-any.whl
Algorithm Hash digest
SHA256 fc972270199f9db3519bc3919ac1fe780d49d02daacabaa77a6fbc990dc813ee
MD5 e9f422752af70f781cf94f75370b58d3
BLAKE2b-256 beeb5c9d09d92d0908b92ef5e9de024b50e516f8d646da5281df9cbcbb093912

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page