Providers
The SDK ships with three built-in providers, all registered automatically by TLiteLLM.Create:
| Provider | Class | Model Prefix | API Base |
|---|---|---|---|
| OpenAI | TOpenAIProviderConfig |
openai/ |
https://api.openai.com/v1 |
| Anthropic | TAnthropicProviderConfig |
anthropic/ |
https://api.anthropic.com/v1 |
| Gemini | TGeminiProviderConfig |
gemini/ |
https://generativelanguage.googleapis.com/v1beta |
Provider Interface
Every provider implements ILiteLLMProviderConfig (defined in mormot.ai.litellm.provider.pas):
ILiteLLMProviderConfig = interface
function TransformRequest(const Model: RawUtf8;
const Messages: TLLMMessageDynArray;
const Tools: TLLMToolDynArray;
const Params: variant): variant;
function TransformResponse(const Model: RawUtf8;
const RawResponse: RawUtf8): TLLMResponse;
function GetCompleteUrl(const ApiBase, Model, ApiKey: RawUtf8;
Stream: Boolean): RawUtf8;
function GetAuthHeaders(const ApiKey: RawUtf8): variant;
function GetSupportedParams: TRawUtf8DynArray;
function MapParams(const OpenAIParams: variant): variant;
function GetProviderName: RawUtf8;
procedure ProcessSSEEvent(const EventType, Data: RawUtf8;
var Accumulator: TLLMResponse; OnDelta: TOnLLMStreamDelta);
end;
The handler calls these methods in sequence: TransformRequest -> HTTP POST -> TransformResponse (sync) or ProcessSSEEvent (streaming).
OpenAI
Unit: mormot.ai.litellm.provider.openai.pas
OpenAI is the canonical format for the SDK. Transformations are quasi pass-through.
Request Transformation
- Messages converted via
TLiteLLMProviderBase.MessagesToVariant(handlescontent=nullfor assistant messages with tool calls) - Tools converted via
TLiteLLMProviderBase.ToolsToVariant(OpenAI native format) - For reasoning models (
o1,o3):max_tokensis mapped tomax_completion_tokens - When streaming: adds
stream_options.include_usage = truefor token counts in the final chunk
Response Transformation
choices[0].message.content->TLLMResponse.Contentchoices[0].message.tool_calls[].function->TLLMResponse.ToolCalls[]usage.prompt_tokens/completion_tokens/total_tokens->TLLMUsagechoices[0].finish_reason-> mapped directly (already standard format)- If tool calls present but
finish_reason = 'stop', normalized to'tool_calls'
Streaming
OpenAI uses standard SSE with data: {...} lines. The [DONE] marker signals completion.
Each chunk contains choices[0].delta with incremental content and tool call fragments. Tool call arguments arrive as JSON string fragments across multiple chunks, accumulated in RawData and parsed when finish_reason is received.
Supported Parameters
temperature, max_tokens, top_p, frequency_penalty, presence_penalty, stop, n, stream, response_format, seed, tools, tool_choice
Note: logprobs is declared in GetSupportedParams but is not currently forwarded by TransformRequest.
Authentication
Standard Bearer token: Authorization: Bearer <api_key>
Endpoint
POST /chat/completions (relative to API base)
Anthropic
Unit: mormot.ai.litellm.provider.anthropic.pas
Anthropic's Messages API has significant format differences from OpenAI. The provider handles all transformations transparently.
Request Transformation
System messages:
- All
systemanddeveloperrole messages are extracted from the array and concatenated - The result is placed in the top-level
systemfield (Anthropic requirement) - The messages array is filtered to exclude system/developer messages
Content blocks:
- User messages become
[{"type": "text", "text": "..."}] - Assistant messages with tool calls become
[{"type": "tool_use", "id": "...", "name": "...", "input": {...}}]blocks - Tool results become
[{"type": "tool_result", "tool_use_id": "...", "content": "..."}] - Tool role is mapped to
userrole (tool results are user messages in Anthropic)
Consecutive same-role merging:
- Anthropic API rejects consecutive messages with the same role
- The provider automatically merges content blocks from consecutive same-role messages
Tool format:
- OpenAI's
parametersis renamed toinput_schema - Tool choice mapping:
"required"->{"type": "any"},"auto"->{"type": "auto"}
max_tokens is always required:
- If not provided, defaults based on model: opus-4 = 32768, sonnet-4 = 16384, opus = 4096, others = 8192
Response Transformation
content[]blocks parsed:text->Content,tool_use->ToolCalls[]stop_reasonmapping:end_turn->stop,tool_use->tool_calls,max_tokens->lengthusage.input_tokens->PromptTokens,usage.output_tokens->CompletionTokens- Cache tokens:
cache_creation_input_tokensandcache_read_input_tokenspreserved inTLLMUsage - Thinking blocks are ignored (accessible via
RawResponse)
Streaming
Anthropic uses event-typed SSE (not just data: lines). The provider implements a state machine:
| Event | Action |
|---|---|
message_start |
Extract id, model, input usage |
content_block_start |
If tool_use type: start new tool call entry |
content_block_delta |
text_delta: accumulate content + fire OnDelta. input_json_delta: accumulate tool args in RawData |
content_block_stop |
Parse accumulated tool call args from RawData |
message_delta |
Extract stop_reason and output usage |
error |
Raise ELiteLLM exception |
message_stop, ping |
Ignored |
Supported Parameters
temperature, max_tokens, top_p, stop (mapped to stop_sequences), stream, tools, tool_choice
Note: system is not a params field — system messages are extracted automatically from the messages array. top_k and metadata are not currently forwarded by TransformRequest.
Authentication
Custom headers (not Bearer token):
x-api-key: <api_key>
anthropic-version: 2023-06-01
content-type: application/json
Endpoint
POST /messages (relative to API base)
Gemini
Unit: mormot.ai.litellm.provider.gemini.pas
Google Gemini uses a completely different API structure. The provider handles all conversions.
Request Transformation
Message format:
- Messages become
contents[].parts[]withrolefield assistantrole is mapped tomodeltoolrole is mapped touser- Text content:
{"text": "..."} - Tool calls:
{"functionCall": {"name": "...", "args": {...}}} - Tool results:
{"functionResponse": {"name": "...", "response": {"result": "..."}}}
System prompt:
- Extracted to
systemInstruction.parts[].text(separate top-level field)
Consecutive same-role merging:
- Gemini API rejects consecutive turns with same role
- Parts from consecutive same-role messages are merged into a single turn
Tool format:
- OpenAI tools wrapped in:
tools[].functionDeclarations[] - Each declaration has
name,description,parameters - Tool choice mapping:
"required"/"any"->mode: "ANY","none"->mode: "NONE","auto"->mode: "AUTO" - Specific function:
{"type":"function","function":{"name":"foo"}}->mode: "ANY", allowedFunctionNames: ["foo"]
Generation config:
max_tokens->maxOutputTokenstemperature,topP,topKingenerationConfigobjectstop->stopSequences(string converted to single-element array)response_format.type = "json_object"->responseMimeType: "application/json"response_format.type = "json_schema"->responseMimeType: "application/json"+responseSchema
thoughtSignature preservation:
- When replaying tool calls, if the original response contained
thoughtSignature, it's stored inTLLMToolCall.RawDataas the complete Gemini Part object - The
BuildPartsmethod checks for_raw_fcin tool call data and uses it verbatim for round-trip fidelity
Response Transformation
candidates[0].content.parts[]parsed:text->Content,functionCall->ToolCalls[]finishReasonmapping:STOP->stop,MAX_TOKENS->length,SAFETY->content_filter- Tool call IDs are synthetic:
call_0,call_1, etc. (Gemini doesn't provide IDs) usageMetadata.promptTokenCount->PromptTokens,candidatesTokenCount->CompletionTokenspromptFeedback.blockReasonraisesELiteLLM(400) if present
Streaming
Gemini uses standard SSE with data: lines (when alt=sse query param is set). Each chunk contains a complete candidates[] structure. The provider processes text parts and function calls from each chunk incrementally.
Supported Parameters
temperature, max_tokens, top_p, top_k, stop (mapped to stopSequences), response_format (mapped to responseMimeType/responseSchema), stream, tools, tool_choice
Authentication
Query parameter auth (not headers):
- Sync:
/models/{model}:generateContent?key={api_key} - Stream:
/models/{model}:streamGenerateContent?alt=sse&key={api_key}
The GetAuthHeaders method returns only content-type: application/json (no auth headers).
Endpoint
Dynamic path based on model name and streaming flag:
- Sync:
/models/<model>:generateContent?key=<api_key> - Stream:
/models/<model>:streamGenerateContent?alt=sse&key=<api_key>
Custom Providers
You can register additional providers at runtime:
type
TMyProvider = class(TLiteLLMProviderBase)
public
function TransformRequest(const Model: RawUtf8;
const Messages: TLLMMessageDynArray;
const Tools: TLLMToolDynArray;
const Params: variant): variant; override;
function TransformResponse(const Model: RawUtf8;
const RawResponse: RawUtf8): TLLMResponse; override;
function GetCompleteUrl(const ApiBase, Model, ApiKey: RawUtf8;
Stream: Boolean): RawUtf8; override;
function GetSupportedParams: TRawUtf8DynArray; override;
function GetProviderName: RawUtf8; override;
// Override ProcessSSEEvent for streaming support
// Override GetAuthHeaders if not using Bearer token
end;
// Register
LLM.RegisterProvider('my-provider', TMyProvider.Create);
// Use
Response := LLM.Chat('my-provider/my-model', Messages);
TLiteLLMProviderBase provides default implementations:
GetAuthHeaders: returnsAuthorization: Bearer <key>(override for non-Bearer auth)MapParams: pass-through (available for external use but not called by the handler — the handler callsTransformRequestdirectly)ProcessSSEEvent: empty (override for streaming support)
And shared helper class functions:
MessagesToVariant: convertsTLLMMessageDynArrayto TDocVariant array (OpenAI format)ToolsToVariant: convertsTLLMToolDynArrayto TDocVariant array (OpenAI format)ExtractSystemMessages: removes system/developer messages from array, returns concatenated content
Custom API Base URLs
Override the API base URL for any provider (useful for self-hosted models, proxies, or OpenAI-compatible endpoints):
// OpenAI-compatible endpoint (e.g. vLLM, Ollama)
Response := LLM.Chat('openai/my-local-model', Messages,
'no-key-needed',
'http://localhost:8000/v1');
// Self-hosted Gemini proxy
Response := LLM.Chat('gemini/gemini-2.5-flash', Messages,
'my-key',
'https://my-proxy.example.com/v1beta');
Note: The OpenAI provider always appends /chat/completions to the base URL and uses Bearer auth. It does not support Azure OpenAI's deployment-based URL format or API key header — for Azure, use a custom provider or a local proxy.