Unified LLM service access layer — one API to access 325 AI providers
Shared reference — parameter tables, result shapes, factory functions, and the feature coverage matrix — lives in the API overview.
pip install arcships-aimuxfrom aimux import openai, generate_text
model = openai("sk-...", "gpt-4o")
result = generate_text(model, "What is Rust?")
print(result["text"])from aimux import provider, generate_text
# Key from the provider's env var (GROQ_API_KEY etc.):
model = provider("groq", None, "llama-3.3-70b")
# Explicit key + base URL override:
model = provider("groq", "sk-...", "llama-3.3-70b", "https://relay.example/v1")
# Full ProviderOptions via config dict:
model = provider("groq", "sk-...", "llama-3.3-70b",
config={"headers": {"X-Custom": "1"}, "max_retries": 0})
result = generate_text(model, "Hello")provider(name, api_key, model_id, base_url=None, config=None) covers all
251 built-in OpenAI-compatible providers. config takes the full
ProviderOptions shape (base_url / headers / organization / project /
max_retries / body_overrides); the base_url parameter wins over
config["base_url"]. openai / anthropic / deepseek factories
remain (deepseek is now registry-backed).
Scope:
provider(name)covers only the 251 registry OpenAI-compatible providers; Anthropic/Google/multimodal/local → typed factories (anthropic(api_key, model)); custom endpoints →base_urlparam. Full list: providers.md.
Non-streaming text generation; returns the complete result.
from aimux import openai, generate_text
model = openai("sk-...", "gpt-4o")
result = generate_text(model, "Explain Rust ownership.", {
"max_output_tokens": 100,
"temperature": 0.7,
})
print(result["text"])
print(result["usage"])
print(result["finish_reason"])Parameters, return value, and the
raw.contentvariants are documented in the API overview.
Returns generated content as a stream, output chunk by chunk.
from aimux import openai, stream_text
model = openai("sk-...", "gpt-4o")
for part in stream_text(model, "Write a haiku about Rust."):
if "TextDelta" in part:
print(part["TextDelta"]["delta"], end="")
if "Finish" in part:
print("\n[done]")Stream part variants are documented in the API overview.
Tool definitions are language-agnostic data descriptions (JSON Schema) that require no macros.
# Python — the same data shape via the options dict
tools = [{
"type": "function",
"name": "get_weather",
"description": "Get current weather",
"input_schema": {
"type": "object",
"properties": {"location": {"type": "string", "description": "City name"}},
"required": ["location"]
}
}]
result = generate_text(model, "What's the weather in Tokyo?", {"tools": tools})
if len(result["tool_calls"]) > 0:
call = result["tool_calls"][0]
print(call["tool_name"]) # get_weather
print(call["input"]) # {"location": "Tokyo"}Pass tool_choice through the options dict:
opts = {
"tools": tools,
"tool_choice": "auto" # "auto" | "none" | "required" | {"type": "tool", "toolName": "get_weather"}
}prompt accepts a message array to implement multi-turn conversation; roles support system / user / assistant / tool:
# Python — system + user multi-turn
result = generate_text(model, [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is Rust?"},
])Converts text into a vector representation.
from aimux import openai_embedding
import json
embedder = openai_embedding("sk-...", "text-embedding-3-small")
# embed() takes a JSON string, returns a JSON string
result = json.loads(embedder.embed(json.dumps(["hello", "world"])))
print(len(result["embeddings"])) # 2
print(len(result["embeddings"][0])) # 1536Converts text into speech audio.
from aimux import openai_speech
import json, base64
speaker = openai_speech("sk-...", "tts-1")
result = json.loads(speaker.generate(json.dumps({
"text": "Hello world!",
"voice": "alloy",
"output_format": "mp3",
})))
if "Base64" in result["audio"]:
audio_bytes = base64.b64decode(result["audio"]["Base64"])
with open("out.mp3", "wb") as f:
f.write(audio_bytes)Converts audio into text (non-streaming).
from aimux import openai_transcription
import base64, json
transcriber = openai_transcription("sk-...", "whisper-1")
audio_b64 = base64.b64encode(open("audio.mp3", "rb").read()).decode()
result = json.loads(transcriber.generate(audio_b64, "audio/mp3"))
print(result["text"])
print(result["segments"])from aimux import openai_image
import json, base64
imager = openai_image("sk-...", "dall-e-3")
result = json.loads(imager.generate(json.dumps({
"prompt": "A cute baby sea otter",
"n": 1,
})))
if "Base64" in result["images"]:
with open("out.png", "wb") as f:
f.write(base64.b64decode(result["images"]["Base64"][0]))Video generation typically returns a URL (not binary).
from aimux import google_video
import json
videor = google_video("sk-...", "veo-3.0")
result = json.loads(videor.generate(json.dumps({
"prompt": "A cat playing piano",
"n": 1,
})))
# result["videos"] is usually [{"Url": {"url": "...", "media_type": "..."}}]
if "Url" in result["videos"][0]:
print(result["videos"][0]["Url"]["url"])Reorders a document list by relevance.
from aimux import cohere_reranking
import json
reranker = cohere_reranking("sk-...", "rerank-v3.0")
result = json.loads(reranker.rerank(
"What is Rust?",
json.dumps([
{"text": "Rust is a systems programming language."},
{"text": "Rust is a chemical element."},
]),
json.dumps({"top_n": 3}),
))
# result["ranking"] sorted by relevance_score
for rank in result["ranking"]:
print(rank["index"], rank["relevance_score"])# Same as Node: the SearchModel class is exposed, but there is no factory
# function yet — use via the Rust core, the Go binding, or the C ABIUploads a file to the provider and returns a file ID.
from aimux import openai_files
import base64, json
files = openai_files("sk-...")
file_b64 = base64.b64encode(open("doc.pdf", "rb").read()).decode()
result = json.loads(files.upload_file(file_b64, "application/pdf"))
print(result["provider_reference"]) # {"openai": "file-xxx"}The aimux package has two layers:
| Layer | Source | Boundary |
|---|---|---|
| Native (PyO3) | bindings/python/src/lib.rs (aimux.abi3.so) |
JSON strings in / JSON strings out |
| Typed wrapper | bindings/python/python/aimux/wrapper.py |
pydantic models / Python dicts |
| Class | Factory functions | Methods |
|---|---|---|
Model |
openai / anthropic / deepseek |
generate_text(prompt_json, opts_json=None), stream_text(...) |
EmbeddingModel |
openai_embedding / cohere_embedding / google_embedding |
embed(values_json, opts_json=None) |
SpeechModel |
openai_speech |
generate(opts_json) |
TranscriptionModel |
openai_transcription |
generate(audio_base64, media_type, opts_json=None) |
ImageModel |
openai_image / google_image |
generate(opts_json) |
VideoModel |
google_video |
generate(opts_json) |
RerankingModel |
cohere_reranking |
rerank(query, docs_json, opts_json=None) |
SearchModel |
— (no factory yet) | search(query, opts_json=None) |
Files |
openai_files(api_key, base_url=None) |
upload_file(data_base64, media_type, opts_json=None) |
StreamIterator |
returned by Model.stream_text |
__iter__ / __next__ of StreamPart JSON strings |
All factories accept an optional base_url and return instances synchronously
(no await). The typed wrapper adds three functions: generate_text (returns a
pydantic GenerateTextResult), stream_text (yields parsed StreamPart
dicts), and parse_stream_part (validates a dict into a typed StreamPart).
The wrapper's types are pydantic models in bindings/python/python/aimux/wrapper.py:
from aimux.wrapper import (
# type aliases
Role, FinishReasonUnified, ReasoningEffort, MessageContent, ContentPart,
Tool, ToolChoice, ResponseFormat, StreamPart, GenerateContent,
FileData, FileBytes, Warning, AiMuxErrorValue,
# pydantic models
TokenUsage, Usage, FinishReason, ResponseMetadata, ToolCall,
ModelMessage, FunctionTool, ProviderTool, TextContentPart,
GenerateTextOptions, GenerateTextResult, GenerateResult,
# functions
generate_text, stream_text, parse_stream_part,
)Engine and binding failures raise an exception hierarchy (explicit types — OpenAI/Anthropic SDK style, same idea as Vercel AI SDK on JS):
Exception
└── AimuxError
├── ProviderError / HttpError / JsonError / StreamError / ToolError
├── InvalidArgumentError / InvalidPromptError
├── RateLimitError
├── AuthenticationError / TokenExpiredError
├── ModelNotFoundError / NoSuchModelError
├── UnsupportedError / UnknownProviderError
├── APICallError / APITimeoutError
├── RequestAbortedError
└── OtherError
Instances carry status (HTTP status or None), retry_ms (hint or None),
and error_value: str | None — the raw externally-tagged AiMuxError JSON
(e.g. '{"RateLimited":{"retry_after_ms":1500,"message":"..."}}'), the
machine-readable companion to str(e).
from aimux import (
generate_text,
AimuxError,
RateLimitError,
AuthenticationError,
)
try:
generate_text(model, "hi")
except RateLimitError as e:
... # e.retry_ms, e.status
except AuthenticationError:
...
except AimuxError:
... # any engine / binding failure(The pydantic wire type named AiMuxErrorValue in aimux.wrapper is only for
stream/payload shapes, not the raised exception.)
Key shapes (mirroring the shared JSON schema):
class GenerateTextResult(BaseModel):
text: str
tool_calls: list[ToolCall]
finish_reason: FinishReason
usage: Usage
warnings: list[Warning]
raw: GenerateResultStreamPart is a RootModel over the external-tagged union dict, e.g.
{"TextDelta": {"id": ..., "delta": ...}}. Iterate dicts with
if "TextDelta" in part: (as in Streaming Generation)
or validate them with parse_stream_part(part) for attribute access.