Webscout Python Client (webscout/client.py)

March 31, 2026 · View on GitHub

Last updated: 2026-03-31
Maintained by: OEvortex

webscout.client.Client is the unified Python entry point for Webscout providers. It exposes an OpenAI-style surface for chat completions, image generation, and audio speech synthesis while handling dynamic provider discovery, intelligent model resolution, automatic failover, and per-process provider instance caching.

Table of Contents


Architecture

The client is built as a layered system that mirrors the OpenAI Python client shape while adding automatic provider management:

Client
├── chat (ClientChat)
│   └── completions (ClientCompletions)
│       ├── _resolve_provider_and_model()
│       ├── _fuzzy_resolve_provider_and_model()
│       ├── _get_available_providers()
│       └── create()  ← main entry point
├── images (ClientImages)
│   ├── _resolve_provider_and_model()
│   ├── _fuzzy_resolve_provider_and_model()
│   ├── _get_available_providers()
│   ├── generate()
│   └── create()  ← alias for generate()
└── audio (ClientAudio)
    └── speech (ClientAudioSpeech)
        ├── _resolve_provider_and_model()
        ├── _fuzzy_resolve_provider_and_model()
        ├── _get_available_providers()
        └── create()  ← main entry point

Each namespace (chat, images, audio) maintains its own provider registry, resolution logic, and fallback queue. The parent Client instance coordinates shared configuration (API keys, proxies, exclusions) and caches provider instances across all namespaces.


Quick Start

from webscout.client import Client

client = Client(print_provider_info=True)

# Chat completion with auto provider/model selection
chat = client.chat.completions.create(
    model="auto",
    messages=[{"role": "user", "content": "Summarize Webscout in one sentence."}],
)
print(chat.choices[0].message.content)

# Image generation
image = client.images.generate(
    prompt="A neon owl in a futuristic city",
    model="auto",
)
print(image.data[0].url)

# Audio speech generation
audio_path = client.audio.speech.create(
    input_text="Hello from Webscout.",
    model="auto",
    voice="coral",
)
print(audio_path)

Client Initialization

Client(
    provider=None,           # Optional[Type[OpenAICompatibleProvider]]
    image_provider=None,     # Optional[Type[TTICompatibleProvider]]
    api_key=None,            # Optional[str]
    proxies=None,            # Optional[dict]
    exclude=None,            # Optional[List[str]]
    exclude_images=None,     # Optional[List[str]]
    exclude_tts=None,        # Optional[List[str]]
    print_provider_info=False,  # bool
    **kwargs,
)

Parameters

ParameterTypeDefaultDescription
providerType[OpenAICompatibleProvider]NoneDefault provider class for chat completions. When specified, this provider is prioritized during resolution.
image_providerType[TTICompatibleProvider]NoneDefault provider class for image generation. Prioritized during image resolution.
api_keystrNoneAPI key for authenticated providers. When provided, unlocks providers where required_auth = True.
proxiesdictNoneHTTP proxy configuration dictionary (e.g., {"http": "http://proxy:8080", "https": "http://proxy:8080"}). Applied to all provider requests.
excludeList[str]NoneCase-insensitive list of provider names to exclude from chat completions.
exclude_imagesList[str]NoneCase-insensitive list of provider names to exclude from image generation.
exclude_ttsList[str]NoneCase-insensitive list of provider names to exclude from audio/TTS generation.
print_provider_infoboolFalseWhen True, prints color-formatted provider name and model to stdout as requests succeed. Useful for debugging resolution and failover.
**kwargsAnyAdditional keyword arguments stored for future use.

Examples

# Minimal setup — uses all free providers
client = Client()

# With authentication and debugging
client = Client(
    api_key="sk-your-api-key",
    proxies={"http": "http://proxy.example.com:8080"},
    exclude=["UnreliableProvider"],
    print_provider_info=True,
)

# With specific default providers
from webscout.Provider.Openai_comp.groq import Groq
from webscout.Provider.TTI.stable import StableDiffusion

client = Client(
    provider=Groq,
    image_provider=StableDiffusion,
)

Chat Completions

Access via client.chat.completions.create(...).

Method Signature

client.chat.completions.create(
    model="auto",              # str — required
    messages,                  # List[Dict[str, Any]] — required
    max_tokens=None,           # Optional[int]
    stream=False,              # bool
    temperature=None,          # Optional[float]
    top_p=None,                # Optional[float]
    tools=None,                # Optional[List[Union[Tool, Dict[str, Any]]]]
    tool_choice=None,          # Optional[Union[str, Dict[str, Any]]]
    timeout=None,              # Optional[int]
    proxies=None,              # Optional[dict]
    provider=None,             # Optional[Type[OpenAICompatibleProvider]]
    **kwargs,
) -> Union[ChatCompletion, Generator[ChatCompletionChunk, None, None]]

Parameters

ParameterTypeDefaultDescription
modelstr"auto"Model identifier. Supports "auto", "ProviderName/model-name", or plain model name.
messagesList[Dict[str, Any]]List of message dicts with role and content keys. Required.
max_tokensintNoneMaximum tokens in the response.
streamboolFalseWhether to stream the response as chunks.
temperaturefloatNoneSampling temperature (0–2). Controls response randomness.
top_pfloatNoneNucleus sampling parameter (0–1).
toolsList[Tool | Dict]NoneTools or tool definitions for function calling.
tool_choicestr | DictNoneWhich tool to use or how to select tools.
timeoutintNoneRequest timeout in seconds.
proxiesdictNoneHTTP proxy configuration for this specific call.
providerType[OpenAICompatibleProvider]NoneSpecific provider class to use, bypassing auto resolution.

Return Types

  • Non-streaming: ChatCompletion object with choices[0].message.content
  • Streaming: Generator[ChatCompletionChunk, None, None] yielding chunks with choices[0].delta.content

Examples

# Basic chat
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)

# Streaming
for chunk in client.chat.completions.create(
    model="auto",
    messages=[{"role": "user", "content": "Write a poem about Python."}],
    stream=True,
):
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

# With specific provider
from webscout.Provider.Openai_comp.groq import Groq
response = client.chat.completions.create(
    model="llama-3-70b",
    messages=[{"role": "user", "content": "Explain recursion."}],
    provider=Groq,
)

Tracking Last Provider

completions = client.chat.completions
response = completions.create(model="auto", messages=[...])
print(f"Used provider: {completions.last_provider}")

Image Generation

Access via client.images.generate(...) or client.images.create(...) (alias).

Method Signature

client.images.generate(
    prompt,                  # str — required
    model="auto",            # str
    n=1,                     # int
    size="1024x1024",        # str
    response_format="url",   # str — "url" or "b64_json"
    provider=None,           # Optional[Type[TTICompatibleProvider]]
    **kwargs,
) -> ImageResponse

Parameters

ParameterTypeDefaultDescription
promptstrText description of the image(s) to generate. Required.
modelstr"auto"Model identifier. Same resolution strategy as chat.
nint1Number of images to generate.
sizestr"1024x1024"Image size specification (e.g., "1024x1024", "512x512").
response_formatstr"url"Response format: "url" for image URLs or "b64_json" for base64 data.
providerType[TTICompatibleProvider]NoneSpecific TTI provider class to use.

Return Type

ImageResponse object containing generated images with:

  • data[0].url — URL to the generated image (when response_format="url")
  • data[0].b64_json — Base64-encoded image data (when response_format="b64_json")

Examples

# Basic image generation
response = client.images.generate(
    prompt="A beautiful sunset over mountains",
    model="auto",
    n=1,
    size="1024x1024",
)
print(response.data[0].url)

# Using specific provider
from webscout.Provider.TTI.stable import StableDiffusion
response = client.images.generate(
    prompt="A cat wearing sunglasses",
    provider=StableDiffusion,
)

# Using create() alias (OpenAI-style)
response = client.images.create(
    prompt="A robot painting a picture",
    model="auto",
)

Tracking Last Provider

images = client.images
response = images.generate(prompt="...", model="auto")
print(f"Used provider: {images.last_provider}")

Audio Speech Synthesis

Access via client.audio.speech.create(...).

Method Signature

client.audio.speech.create(
    input_text=None,           # str — required
    model="auto",              # str
    voice=None,                # Optional[str]
    response_format="mp3",     # str
    instructions=None,         # Optional[str]
    stream=False,              # bool
    chunk_size=1024,           # int
    provider=None,             # Optional[Type[BaseTTSProvider]]
    verbose=False,             # bool
    **kwargs,
) -> Union[str, Generator[bytes, None, None]]

Parameters

ParameterTypeDefaultDescription
input_textstrText to synthesize into speech. Required. Also accepts input as kwarg.
modelstr"auto"TTS model identifier. Same resolution strategy as chat/images.
voicestrNoneVoice identifier for the TTS provider.
response_formatstr"mp3"Audio format (e.g., "mp3", "wav", "ogg").
instructionsstrNoneAdditional instructions for speech generation (provider-specific).
streamboolFalseWhen True, returns a generator yielding audio bytes from the generated file.
chunk_sizeint1024Size of audio chunks (bytes) when streaming.
providerType[BaseTTSProvider]NoneSpecific TTS provider class to use.
verboseboolFalseProvider-specific verbose output flag.

Return Types

  • Non-streaming: str — Path to the generated audio file on disk
  • Streaming: Generator[bytes, None, None] — Iterator yielding audio bytes

Examples

# Basic speech
audio_path = client.audio.speech.create(
    input_text="Hello from Webscout.",
    model="auto",
    voice="coral",
)
print(f"Audio saved to: {audio_path}")

# Streaming audio
for chunk in client.audio.speech.create(
    input_text="This is a streaming speech example.",
    model="auto",
    stream=True,
):
    # Process audio chunks (e.g., write to file or play)
    pass

# Using specific provider
from webscout.Provider.TTS.elevenlabs import ElevenlabsTTS
audio_path = client.audio.speech.create(
    input_text="Hello world",
    provider=ElevenlabsTTS,
    voice="alloy",
)

Tracking Last Provider

speech = client.audio.speech
audio_path = speech.create(input_text="Hello", model="auto")
print(f"Used provider: {speech.last_provider}")

Provider Resolution Engine

The client uses a multi-stage resolution strategy to map model identifiers to concrete provider/model pairs. This engine is implemented independently for chat (ClientCompletions), images (ClientImages), and audio (ClientAudioSpeech).

Input Formats

The model parameter accepts three formats:

  1. "auto" — Randomly selects an available provider and model
  2. "ProviderName/model-name" — Forces exact provider and model
  3. Plain model name — Searches across all providers for exact or fuzzy match

Resolution Strategy

The resolution follows this priority order:

  1. Provider/model format: If the model string contains /, it is parsed as provider/model. The provider is looked up by name (case-insensitive) in the appropriate registry. If found, the exact provider and model are returned.

  2. Explicit provider: If a provider class is passed directly, it is used. If model="auto", a random model is selected from that provider's available models.

  3. Auto selection: If model="auto" with no explicit provider, the client:

    • Filters available providers based on exclusions and API key availability
    • Collects all providers that have at least one model
    • Randomly selects one provider and one model from its available models
  4. Exact match: For a plain model name, the client searches across all available providers for an exact case-insensitive match.

  5. Substring match: If no exact match is found, the client looks for substring containment (either the query is contained in a model name or vice versa).

  6. Fuzzy match: Uses difflib.get_close_matches() with a 50% cutoff to find the closest model name.

  7. Random fallback: If all matching fails, a random available provider is selected with the original model string.

Fuzzy Matching Details

# Three levels of matching are attempted:
# 1. Exact case-insensitive match
for m_name in model_to_provider:
    if m_name.lower() == model.lower():
        return model_to_provider[m_name], m_name

# 2. Substring match
for m_name in model_to_provider:
    if model.lower() in m_name.lower() or m_name.lower() in model.lower():
        return model_to_provider[m_name], m_name

# 3. Fuzzy match with difflib (50% cutoff)
matches = difflib.get_close_matches(model, model_to_provider.keys(), n=1, cutoff=0.5)
if matches:
    return model_to_provider[matches[0]], matches[0]

When print_provider_info=True, substring and fuzzy matches are printed in yellow:

Substring match: 'gpt-4' -> 'gpt-4o-mini'
Fuzzy match: 'claude-3' -> 'claude-3-sonnet'

Fallback Tiers and Failover

If the initially resolved provider fails, the client automatically falls back to other providers using a tiered priority queue:

Tier Classification

Providers are classified into three tiers based on model compatibility:

  • Tier 1: Providers with an exact model match for the requested model
  • Tier 2: Providers with a fuzzy model match (via difflib.get_close_matches())
  • Tier 3: Providers with any available model (random model assigned)

Failover Process

  1. The initially resolved provider is attempted first
  2. If it fails, the client builds a fallback queue:
    • All Tier 1 providers (shuffled randomly within the tier)
    • All Tier 2 providers (shuffled randomly within the tier)
    • All Tier 3 providers (shuffled randomly within the tier)
  3. Providers are tried in order: Tier 1 → Tier 2 → Tier 3
  4. The first provider to return a valid response with non-empty content succeeds
  5. If all providers fail, a RuntimeError is raised with error summaries

Streaming Failover

For streaming responses, the failover logic attempts to consume the first chunk:

  • If the first chunk is received successfully, the provider is marked as successful
  • A chained generator yields the first chunk followed by the rest of the stream
  • If the first chunk fails, the next provider in the fallback queue is tried

Error Reporting

When all providers fail, the error message includes up to 3 error summaries:

RuntimeError: All chat providers failed. Errors: ProviderA: Connection timeout; ProviderB: Rate limit exceeded; ProviderC: Invalid response

Provider Caching

The client maintains a per-process _provider_cache dictionary to avoid redundant provider instantiations:

self._provider_cache = {}  # Dict[str, ProviderInstance]

How Caching Works

  1. When a provider is first requested, it is instantiated with the client's configuration (proxies, API key)
  2. The instance is stored in _provider_cache keyed by provider class name
  3. Subsequent requests for the same provider return the cached instance
  4. Caching applies across all namespaces (chat, images, audio)

Cache Initialization

Provider instances are initialized with client-level configuration:

init_kwargs = {}
if self._client.proxies:
    init_kwargs["proxies"] = self._client.proxies
if self._client.api_key:
    init_kwargs["api_key"] = self._client.api_key
instance = provider_class(**init_kwargs)
self._client._provider_cache[p_name] = instance

For TTS providers, additional proxy handling attempts multiple proxy parameter names:

# TTS providers may accept "proxies" or "proxy"
proxy_candidates = [
    {**init_kwargs, "proxies": self._client.proxies},
    {**init_kwargs, "proxy": proxy_value},
    init_kwargs,
]
for candidate in proxy_candidates:
    try:
        instance = provider_class(**candidate)
        # Cache and return
    except Exception:
        continue

Provider Helper Methods

Static methods on Client for discovering available providers:

# Chat providers
Client.get_chat_providers()       # List[str] — all chat providers
Client.get_free_chat_providers()  # List[str] — chat providers without auth

# Image providers
Client.get_image_providers()       # List[str] — all image providers
Client.get_free_image_providers()  # List[str] — image providers without auth

# TTS/Audio providers
Client.get_tts_providers()         # List[str] — all TTS providers
Client.get_free_tts_providers()    # List[str] — TTS providers without auth
Client.get_audio_providers()       # Alias for get_tts_providers()
Client.get_free_audio_providers()  # Alias for get_free_tts_providers()

Examples

# List all available chat providers
providers = Client.get_chat_providers()
print(f"Total chat providers: {len(providers)}")
print(providers)

# List free providers only
free = Client.get_free_chat_providers()
print(f"Free chat providers: {len(free)}")

# With API key, more providers become available
client = Client(api_key="sk-your-key")
# Now authenticated providers are included in resolution

Error Handling

Common Exceptions

ExceptionWhen Raised
RuntimeErrorNo providers available or all providers fail
ValueErrorProvider returned empty content (chat)
FileNotFoundErrorTTS provider did not generate an audio file
ImportErrorServer module not available (for run_api/start_server)

Validation

  • input_text for speech must be a non-empty string; otherwise ValueError is raised
  • Chat responses with empty content raise ValueError and trigger fallback
  • TTS responses that don't produce a file raise FileNotFoundError and trigger fallback

Graceful Degradation

The client is designed to be resilient:

  • Provider loading failures are silently handled during module initialization
  • Individual provider failures trigger automatic fallback to the next provider
  • Only when all providers fail is an exception raised

Advanced Usage Patterns

Excluding Unstable Providers

client = Client(
    exclude=["FlakyProvider", "SlowProvider"],
    exclude_images=["UnreliableImageProvider"],
    exclude_tts=["BadTTSProvider"],
)

Using Proxies

client = Client(
    proxies={
        "http": "http://proxy.example.com:8080",
        "https": "http://proxy.example.com:8080",
    }
)

Per-Call Provider Override

from webscout.Provider.Openai_comp.groq import Groq

# Use Groq for this specific call regardless of auto resolution
response = client.chat.completions.create(
    model="llama-3-70b",
    messages=[{"role": "user", "content": "Hello"}],
    provider=Groq,
)

Mixed Auth and Free Providers

# Without API key — only free providers are used
client = Client()

# With API key — both free and authenticated providers are available
client = Client(api_key="sk-your-key")

Server Functions

The client also exposes server startup functions (requires webscout[api]):

from webscout.client import run_api, start_server

# Start the OpenAI-compatible API server
run_api(host="0.0.0.0", port=8000)

# Or use start_server for production deployment
start_server()

If the API dependencies are not installed, these functions raise ImportError.


Debug Tips

  1. Enable provider info printing:

    client = Client(print_provider_info=True)
    

    This prints color-formatted provider:model pairs as requests succeed:

    GPT4Free:gpt-4o-mini
    StableDiffusion:flux (Fallback)
    
  2. Check which provider was used:

    client.chat.completions.last_provider
    client.images.last_provider
    client.audio.speech.last_provider
    
  3. Exclude problematic providers:

    client = Client(exclude=["ProblematicProvider"])
    
  4. Test with specific provider first:

    from webscout.Provider.Openai_comp.gpt4free import GPT4Free
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[...],
        provider=GPT4Free,
    )
    
  5. List available providers:

    print(Client.get_chat_providers())
    print(Client.get_free_chat_providers())
    

Internal Functions

These module-level functions support the client's provider discovery:

FunctionDescription
load_openai_providers()Dynamically loads all OpenAI-compatible provider classes from webscout.Provider.Openai_comp. Returns (provider_map, auth_required_set).
load_tti_providers()Dynamically loads all TTI provider classes from webscout.Provider.TTI. Returns (provider_map, auth_required_set).
load_tts_providers()Dynamically loads all TTS provider classes from webscout.Provider.TTS. Returns (provider_map, auth_required_set).
_get_models_safely(provider_cls, client)Safely retrieves models from a provider class, handling all exceptions. Uses client cache if available.
_get_tts_models_safely(provider_cls)Retrieves TTS models from SUPPORTED_MODELS or AVAILABLE_MODELS class attributes. Defaults to ["gpt-4o-mini-tts"].
_normalized_name_set(values)Converts a list of provider names to a case-normalized set for exclusion matching.

Global Registries

After module load, these global variables are populated:

OPENAI_PROVIDERS, OPENAI_AUTH_REQUIRED = load_openai_providers()
TTI_PROVIDERS, TTI_AUTH_REQUIRED = load_tti_providers()
TTS_PROVIDERS, TTS_AUTH_REQUIRED = load_tts_providers()

These registries are used by the client's resolution and filtering logic.