Skip to main content
ai-query already ships with built-in providers, but you can also add your own. This is useful when you want to:
  • integrate a provider that ai-query does not support yet
  • wrap an internal gateway or model router
  • target an OpenAI-compatible API with custom auth or routing
  • run ai-query in a custom runtime with a custom transport

The Extension Points

The provider system is built from a small set of primitives:
  • BaseProvider: implement provider behavior
  • LanguageModel: wrap a provider + model ID for text generation
  • EmbeddingModel: wrap a provider + model ID for embeddings
  • HTTPTransport: optionally replace the networking layer
  • GenerateTextResult, StreamChunk, Usage, ToolCall: the result types your provider returns

Two Common Patterns

Most custom providers fit one of these patterns:
  1. OpenAI-compatible wrapper
  2. Full custom provider
If your API already supports OpenAI-style /chat/completions and /embeddings, prefer the first pattern. It is much smaller.

Pattern 1: OpenAI-Compatible Wrapper

This is the simplest approach. Subclass OpenAIProvider, then customize auth and base URL.
Use it exactly like a built-in provider:

Pattern 2: Full Custom Provider

If the API is not OpenAI-compatible, subclass BaseProvider directly. You must implement:
  • generate()
  • stream()
You can optionally implement:
  • embed()
  • embed_many()

Minimal Example

Provider Contracts

generate()

generate() returns a GenerateTextResult. At minimum, set:
  • text
  • finish_reason
  • response
Set usage when the upstream API exposes token information. If the model requests tools, place parsed ToolCall values in result.response["tool_calls"].

stream()

stream() yields StreamChunk objects. Typical shape:
  1. yield chunks with text=...
  2. accumulate any tool call state if the API streams it incrementally
  3. yield one final chunk with:
    • is_final=True
    • usage=...
    • finish_reason=...
    • tool_calls=[...] when present

Tool Calling

If your provider supports tool calling:
  • accept tools: ToolSet | None
  • convert ToolSet into the provider’s tool schema
  • parse the provider’s tool-call response into ai-query ToolCall objects
  • return them in response["tool_calls"] for generate() or in the final StreamChunk.tool_calls for stream()
The execution loop in generate_text() / stream_text() handles the rest.

Provider Options

ai-query passes all provider-specific options through provider_options. Inside your provider, use:
This extracts the entry matching self.name. Example:

Embeddings

If your provider supports embeddings, implement embed() and optionally embed_many(). Factory pattern:

Custom Transports

If you need a custom runtime or HTTP stack, pass a custom HTTPTransport to your provider.
This is how ai-query supports both standard Python and Cloudflare Workers.
  1. Start with an OpenAI-compatible wrapper if possible.
  2. Only subclass BaseProvider directly when the upstream API shape is meaningfully different.
  3. Implement generate() first.
  4. Add stream() once non-streaming works.
  5. Add embeddings only if the provider supports them.
  6. Add tests that validate message conversion, tool call parsing, and usage extraction.