Proxy Server
TLiteLLMProxy is an OpenAI-compatible HTTP proxy that accepts standard OpenAI API requests and routes them to any configured provider. Use it to give OpenAI-only applications access to Anthropic, Gemini, or multiple providers.
Quick Start
program MyProxy;
{$I mormot.defines.inc}
{$APPTYPE CONSOLE}
uses
sysutils,
mormot.core.base,
mormot.core.log,
mormot.ai.litellm.proxy;
var
Proxy: TLiteLLMProxy;
begin
with TSynLog.Family do
begin
Level := LOG_VERBOSE;
EchoToConsole := LOG_VERBOSE;
end;
Proxy := TLiteLLMProxy.Create; // reads port from .env, defaults to 4000
try
Proxy.Start;
WriteLn('Proxy running on port ', Proxy.Port);
WriteLn('Press ENTER to stop...');
ReadLn;
Proxy.Stop;
finally
Proxy.Free;
end;
end.
Configuration
Configuration can be done via a .env file next to the executable, or programmatically via the Port, ApiKey, DefaultModel properties and AddModelMapping method:
# Proxy port (default: 4000)
PROXY_PORT=4000
# Optional: require Bearer token auth on proxy requests
PROXY_API_KEY=my-secret-proxy-key
# Optional: default model when request has no provider prefix
PROXY_DEFAULT_MODEL=gemini/gemini-2.5-flash
# Optional: model name mapping (JSON object)
MODEL_MAP={"gpt-4o":"gemini/gemini-2.5-flash","gpt-3.5-turbo":"gemini/gemini-2.5-flash","claude-3":"anthropic/claude-sonnet-4-20250514"}
# Provider API keys (used by the underlying TLiteLLM)
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GEMINI_API_KEY=AIza...
Endpoints
POST /v1/chat/completions
Accepts standard OpenAI chat completion requests:
curl -X POST http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer my-secret-proxy-key" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 100,
"temperature": 0.7
}'
The proxy:
- Validates Bearer token (if
PROXY_API_KEYis set) - Maps the model name via
MODEL_MAPor auto-detection - Extracts messages, tools, and parameters from the request
- Routes to the appropriate provider via
TLiteLLM.ChatRaw - Returns an OpenAI-format response
Also available as POST /chat/completions (without /v1 prefix).
GET /v1/models
Also available as GET /models (without /v1 prefix).
Returns the list of available models (from MODEL_MAP or default model):
curl http://localhost:4000/v1/models \
-H "Authorization: Bearer my-secret-proxy-key"
{
"object": "list",
"data": [
{"id": "gpt-4o", "object": "model", "owned_by": "litellm-proxy"},
{"id": "gpt-3.5-turbo", "object": "model", "owned_by": "litellm-proxy"}
]
}
GET /health
Returns {"status":"ok"}. No authentication required.
Model Mapping
The proxy resolves incoming model names in this order:
- Explicit
MODEL_MAP— if the incoming model name is a key in the map, use the mapped value - Provider prefix — if the model already contains
/(e.g.gemini/gemini-2.5-flash), pass through as-is - Default model — if
PROXY_DEFAULT_MODELis set, use it - Auto-detection — based on model name prefix:
gemini-*->gemini/<model>claude-*->anthropic/<model>- Everything else ->
openai(default, no prefix added)
Programmatic Mapping
Proxy := TLiteLLMProxy.Create;
Proxy.AddModelMapping('gpt-4o', 'gemini/gemini-2.5-flash');
Proxy.AddModelMapping('gpt-3.5-turbo', 'gemini/gemini-2.5-flash');
Proxy.AddModelMapping('claude-3', 'anthropic/claude-sonnet-4-20250514');
Proxy.Start;
Streaming
The proxy supports SSE streaming in buffered mode:
- Client sends
"stream": truein the request - Proxy calls
TLiteLLM.ChatRawwith streaming enabled TProxyStreamAccumulator.OnDeltareceives plain text deltas and wraps each into an OpenAI-format SSE chunk- After streaming completes, the proxy sends all buffered chunks at once
This means the response is not truly real-time (tokens don't arrive one-by-one at the client), but the response format is correct SSE that any OpenAI-compatible client can parse.
Limitation: Buffered SSE only emits delta.content chunks. Tool calls are not streamed — they appear only in the non-streaming response format. Example streaming output:
data: {"id":"chatcmpl-proxy-...","object":"chat.completion.chunk","created":...,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-proxy-...","object":"chat.completion.chunk","created":...,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}
data: {"id":"chatcmpl-proxy-...","object":"chat.completion.chunk","created":...,"model":"gpt-4o","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15}}
data: [DONE]
Supported Request Parameters
The proxy extracts and forwards these OpenAI parameters:
| Parameter | Type | Notes |
|---|---|---|
model |
string | Mapped via model resolution |
messages |
array | Full message format with roles, content, tool_calls, tool_call_id |
tools |
array | OpenAI function calling format |
max_tokens |
integer | Also accepts max_completion_tokens |
temperature |
number | |
top_p |
number | |
frequency_penalty |
number | |
presence_penalty |
number | |
stop |
string/array | |
n |
integer | |
seed |
integer | |
response_format |
object | |
tool_choice |
string/object | |
stream |
boolean | Buffered SSE mode |
Error Handling
Errors are returned in OpenAI format:
{
"error": {
"message": "Invalid API key",
"type": "proxy_error",
"code": 401
}
}
HTTP status codes are passed through from the underlying provider:
| Scenario | Status | Error Type |
|---|---|---|
| Invalid proxy auth | 401 | proxy_error |
| Invalid provider API key | 401 | proxy_error |
| Rate limited by provider | 429 | proxy_error |
| Model not found | 404 | proxy_error |
| Bad request | 400 | proxy_error |
| Provider unavailable | 503 | proxy_error |
| Internal error | 500 | proxy_error |
All errors use "type": "proxy_error" regardless of origin. The status code and message from the underlying provider are preserved in the code and message fields.
CORS
The proxy includes CORS headers on all responses:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
OPTIONS preflight requests return 200 immediately.
Response Format
Non-streaming responses follow the OpenAI chat completion format:
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1234567890,
"model": "gpt-4o",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello!"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 5,
"total_tokens": 15
}
}
Note: tool_calls is omitted from the message when no tool calls are present (not set to null).
When tool calls are present and content is empty, content is set to null and tool_calls contains the array:
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris\"}"
}
}]
}
Note: arguments in the response is a JSON string (matching OpenAI format), not an object.
Using with OpenAI-Compatible Clients
Any application that speaks OpenAI API can use this proxy. Just point it at http://localhost:4000:
# Python example with openai library
import openai
client = openai.OpenAI(
base_url="http://localhost:4000/v1",
api_key="my-secret-proxy-key"
)
response = client.chat.completions.create(
model="gpt-4o", # mapped to gemini/gemini-2.5-flash via MODEL_MAP
messages=[{"role": "user", "content": "Hello"}]
)
// TypeScript with @anthropic-ai/sdk or any OpenAI-compatible library
const openai = new OpenAI({
baseURL: 'http://localhost:4000/v1',
apiKey: 'my-secret-proxy-key'
});