OpenAI-Compatible Engine¶
The OpenAI-compatible engine provides inference using any server that implements the OpenAI API standard. This includes Ubuntu inference snaps, vLLM in OpenAI mode, LocalAI, LiteLLM, and more.
Status¶
Production - Fully implemented and tested.
Features¶
- Chat completions (streaming and non-streaming)
- Embedding generation
- Vision/multimodal models (Qwen-VL, LLaVA, etc.)
- Automatic model type detection
- Multiple named instances (run several servers simultaneously)
- Works with any OpenAI-compatible endpoint
Supported Servers¶
| Server | Description | Example base_url |
|---|---|---|
| Ubuntu Inference Snaps | Snap-packaged LLM servers | http://192.168.2.50:8326/v3 |
| vLLM (OpenAI mode) | High-performance inference | http://localhost:8000/v1 |
| LocalAI | Local OpenAI-compatible API | http://localhost:8080/v1 |
| LiteLLM | Proxy for 100+ LLM providers | http://localhost:4000/v1 |
| Ollama (OpenAI mode) | Ollama's OpenAI-compatible endpoint | http://localhost:11434/v1 |
| OpenAI | OpenAI cloud API | https://api.openai.com/v1 |
Requirements¶
- An OpenAI-compatible server reachable from the worker
- The
openaiPython package (pip install openai>=1.0.0)
Configuration¶
Single Instance¶
engine:
available:
- openai_compat
openai_compat:
base_url: ${OPENAI_COMPAT_BASE_URL:-http://localhost:8326/v3}
api_key: ${OPENAI_COMPAT_API_KEY:-} # optional for local servers
timeout: 600
Multiple Named Instances¶
You can run multiple OpenAI-compatible servers simultaneously using the openai_compat:<name> format. Each named instance gets its own config section under engine.<name>:
engine:
available:
- ollama
- openai_compat:qwen-snap # Ubuntu snap running Qwen-VL
- openai_compat:llama-vllm # vLLM serving Llama
- openai_compat:claude-proxy # LiteLLM proxy for Anthropic Claude
qwen-snap:
base_url: http://192.168.2.50:8326/v3
llama-vllm:
base_url: http://192.168.2.51:8000/v1
claude-proxy:
base_url: http://192.168.2.52:4000/v1
api_key: ${ANTHROPIC_API_KEY:-}
Each instance:
- Loads independently and has its own connection
- Reports its own model list in heartbeats
- Routes jobs via its platform name (e.g.,
"platform": "openai_compat:qwen-snap")
Environment Variables¶
| Variable | Default | Description |
|---|---|---|
OPENAI_COMPAT_BASE_URL |
http://localhost:8326/v3 |
Server URL (single instance) |
OPENAI_COMPAT_API_KEY |
(empty) | API key (optional for local servers) |
For named instances, configure via YAML or use custom env vars in the config.
Supported Model Types¶
Model type is auto-detected from the model ID:
| Pattern | Detected Type |
|---|---|
*embed*, *embedding* |
Embedding |
*vl*, *vision*, *llava*, *multimodal* |
Multimodal |
*dall-e*, *sdxl*, *stable-diffusion* |
Image |
*whisper*, *tts* |
Audio |
| Everything else | Chat |
Usage¶
Ubuntu Inference Snaps¶
Ubuntu inference snaps provide pre-packaged LLM servers with OpenAI-compatible APIs. After installing a snap, the server runs on a dedicated port:
# Install a snap (example: Qwen-VL)
sudo snap install qwen-vl
# Check the server status to find the port
snap info qwen-vl
# API endpoint: http://localhost:8326/v3
Configure the worker:
Job Examples¶
Text Generation:
{
"model_id": "qwen2.5:7b",
"platform": "openai_compat:qwen-snap",
"job_type": "llm",
"input_data": "Explain quantum computing in simple terms."
}
Chat Completion:
{
"model_id": "qwen2.5:7b",
"platform": "openai_compat:qwen-snap",
"job_type": "llm",
"llm_interaction_type": "chat",
"input_data": {
"system_prompt": "You are a helpful assistant.",
"messages": [
{"role": "user", "content": "What is the capital of France?"}
]
}
}
Vision/Multimodal (Qwen-VL, LLaVA, etc.):
{
"model_id": "qwen-vl:7b",
"platform": "openai_compat:qwen-snap",
"job_type": "llm",
"input_data": "Describe this image in detail.",
"attached_files": [
{"download_url": "/api/v1/files/abc123", "file_type": "image/png"}
]
}
Images are automatically base64-encoded and sent as OpenAI-format image content parts.
Embedding:
{
"model_id": "text-embedding-model",
"platform": "openai_compat:llama-vllm",
"job_type": "embed",
"input_data": {"texts": ["Hello world", "How are you?"]}
}
Streaming:
{
"model_id": "qwen2.5:7b",
"platform": "openai_compat:qwen-snap",
"job_type": "llm",
"stream": true,
"input_data": "Write a short story about a robot."
}
Using with LiteLLM for Anthropic Claude¶
LiteLLM provides an OpenAI-compatible proxy for 100+ LLM providers including Anthropic Claude:
engine:
available:
- openai_compat:claude-proxy
claude-proxy:
base_url: http://localhost:4000/v1
api_key: ${ANTHROPIC_API_KEY:-}
{
"model_id": "claude-sonnet-4-20250514",
"platform": "openai_compat:claude-proxy",
"job_type": "llm",
"input_data": "Explain the theory of relativity."
}
Model Aliases¶
Some providers use different model names than what you use internally. For example, Groq prefixes models with openai/, or appends context length suffixes. The model_aliases config maps your internal names to the provider's names:
engine:
available:
- openai_compat:groq
groq:
base_url: https://api.groq.com/openai/v1
api_key: ${GROQ_API_KEY:-}
model_aliases:
llama-3.3-70b: llama-3.3-70b-versatile
gpt-oss-20b: openai/gpt-oss-20b
mixtral-8x7b: mixtral-8x7b-32768
With this config:
- Jobs use the internal name:
"model_id": "gpt-oss-20b" - The engine translates to
openai/gpt-oss-20bwhen calling the Groq API - Model listings translate back: Groq's
openai/gpt-oss-20bappears asgpt-oss-20bin heartbeats
The mapping applies to all API calls (chat completions, embeddings, model info). Models without an alias pass through unchanged.
Example: Groq¶
groq:
base_url: https://api.groq.com/openai/v1
api_key: ${GROQ_API_KEY:-}
model_aliases:
llama-3.3-70b: llama-3.3-70b-versatile
gemma2-9b: gemma2-9b-it
Example: Azure OpenAI¶
azure:
base_url: https://my-resource.openai.azure.com/openai/deployments
api_key: ${AZURE_OPENAI_KEY:-}
model_aliases:
gpt-4: my-gpt4-deployment
gpt-4o: my-gpt4o-deployment
Parameter Mapping¶
Most parameters pass through directly to the OpenAI API:
| Generic | OpenAI | Description |
|---|---|---|
temperature |
temperature |
Sampling temperature |
max_tokens |
max_tokens |
Maximum tokens to generate |
top_p |
top_p |
Nucleus sampling |
frequency_penalty |
frequency_penalty |
Frequency penalty |
presence_penalty |
presence_penalty |
Presence penalty |
seed |
seed |
Random seed |
stop_sequences |
stop |
Stop sequences |
top_k |
extra_body.top_k |
Top-k sampling (via extra_body for non-standard support) |
API Endpoints Used¶
The engine uses these OpenAI API endpoints:
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/models |
List available models, health check |
POST |
/chat/completions |
Chat completions (streaming and non-streaming) |
POST |
/embeddings |
Embedding generation |
The base URL can use any API version prefix (e.g., /v1, /v3).
Model Management¶
Since models are managed server-side:
pull_model()- Logs a warning (manage models on the server directly)delete_model()- Logs a warning (manage models on the server directly)load_model()- Verifies the model exists on the serverunload_model()- Removes from local tracking only
Cost limiting (spending caps)¶
Because an openai_compat instance can point at a paid provider (Groq, Azure
OpenAI, Bedrock via LiteLLM, etc.), each instance can enforce a dollar budget so
a worker never spends past a real credit:
engine:
config:
openai_compat:
groq:
base_url: https://api.groq.com/openai/v1
api_key: ${GROQ_API_KEY}
cost_limit_usd: 50.0 # stop claiming once cumulative spend hits this
pricing: # USD per 1M tokens
llama-3.3-70b-versatile:
input: 0.59
output: 0.79
How it works:
- After each job the worker records the actual cost (from the provider's
reported token usage; an estimate when usage is omitted, e.g. streaming) into
a persistent ledger at
~/.microdc/spend.json— cumulative across restarts. - Before claiming a new job the worker estimates its cost and releases it back
to the pool if running it would push cumulative spend over
cost_limit_usd(reusing the same admission path as the VRAM/RAM check). - Models with no
pricingentry are treated as free. Delete an engine's entry inspend.jsonto start a new credit period.
See engine.<name>.cost_limit_usd / pricing in config/default.yaml, and
src/jobs/cost_ledger.py.
Implementation Files¶
src/engines/openai_compat.py- OpenAICompatEngine classsrc/engines/base.py- InferenceEngine base classtests/test_openai_compat.py- Unit tests (44 tests)
Troubleshooting¶
Connection refused¶
Ensure the server is running and the base_url is correct. For Ubuntu snaps, check the status with snap info <snap-name>.
No API key needed¶
Most local servers don't require an API key. The engine uses a placeholder (no-key) automatically. Only set api_key when connecting to servers that require authentication.
Model not found¶
The server must already have the model loaded/available. Use the server's management interface to add models.
Timeout errors¶
Increase timeout in config for large models or long generations:
Wrong instance used¶
Ensure jobs specify the correct platform with the instance name:
Not just: