OpenAICompatibleDialect

org.llm4s.llmconnect.provider.OpenAICompatibleDialect
See theOpenAICompatibleDialect companion object
@Stable

The places where an OpenAI-compatible provider departs from the plain /chat/completions wire format.

OpenAICompatibleClient holds everything the providers share - request building, the HTTP round trip, SSE streaming, tool-call fan-out, error mapping, exchange logging - written once. A dialect answers the handful of questions on which they differ, and every answer defaults to the standard format, so a provider overrides only where it departs from it:

The generic openai-compatible provider uses OpenAICompatibleDialect.standard, which overrides only headers and streamed usage, both from config. DeepSeek, Z.ai, OpenRouter, Mistral, Cohere and xAI each override two to six members. A new OpenAI-compatible provider whose differences fit these members is a dialect and a descriptor, not a client; one whose differences do not should extend this trait rather than fork OpenAICompatibleClient (#1132).

Attributes

Companion
object
Graph
Supertypes
class Object
trait Matchable
class Any

Members list

Value members

Concrete methods

def addReasoning(body: Obj, model: String, options: CompletionOptions): Unit

Adds provider-specific reasoning fields to a request body, given the configured model and the caller's options. Standard: adds nothing, so CompletionOptions.reasoning is ignored.

Adds provider-specific reasoning fields to a request body, given the configured model and the caller's options. Standard: adds nothing, so CompletionOptions.reasoning is ignored.

Attributes

Whether an assistant message with no text still carries a content field ("" for empty text, null for none). Standard: false - the field is omitted, which is what most providers expect alongside tool_calls.

Whether an assistant message with no text still carries a content field ("" for empty text, null for none). Standard: false - the field is omitted, which is what most providers expect alongside tool_calls.

Attributes

def decodeContent(content: Value): Option[String]

Reads the text of a reply's content value - on a completion's message or a stream's delta. Standard: a JSON string, anything else is no text.

Reads the text of a reply's content value - on a completion's message or a stream's delta. Standard: a JSON string, anything else is no text.

Attributes

def decodeThinkingDetails(details: Seq[Value]): Seq[ThinkingBlock]

The thinking blocks for one reply's replay data: the items thinkingDetails read from its message, or from each of its stream's deltas concatenated in order - so a dialect whose provider streams an item in fragments joins them here. They follow the reply's thinking text on the returned message, and the client binds them to the request it answered (see ThinkingReplay), so they should be sealed - normally org.llm4s.llmconnect.model.ThinkingBlock.Opaque. Standard: none.

The thinking blocks for one reply's replay data: the items thinkingDetails read from its message, or from each of its stream's deltas concatenated in order - so a dialect whose provider streams an item in fragments joins them here. They follow the reply's thinking text on the returned message, and the client binds them to the request it answered (see ThinkingReplay), so they should be sealed - normally org.llm4s.llmconnect.model.ThinkingBlock.Opaque. Standard: none.

Attributes

def encodeContent(text: String): Value

Encodes the text of a user, system, assistant or tool message. Standard: a JSON string.

Encodes the text of a user, system, assistant or tool message. Standard: a JSON string.

Attributes

def encodeResponseFormat(format: ResponseFormat): Option[Value]

The response_format sent for CompletionOptions.responseFormat, or None to send none. Standard: OpenAI's shapes - {"type": "json_object"}, and {"type": "json_schema", "json_schema": {"name", "strict", "schema"}} for a schema.

The response_format sent for CompletionOptions.responseFormat, or None to send none. Standard: OpenAI's shapes - {"type": "json_object"}, and {"type": "json_schema", "json_schema": {"name", "strict", "schema"}} for a schema.

Attributes

def encodeThinking(message: Obj, thinking: Seq[ThinkingBlock]): Unit

Adds an assistant turn's thinking - its org.llm4s.llmconnect.model.AssistantMessage.thinking blocks, as they may be replayed - to that turn's encoded message, after its content and tool_calls are set. Called only when the turn has thinking. The blocks' text is ThinkingBlock.text(thinking).

Adds an assistant turn's thinking - its org.llm4s.llmconnect.model.AssistantMessage.thinking blocks, as they may be replayed - to that turn's encoded message, after its content and tool_calls are set. Called only when the turn has thinking. The blocks' text is ThinkingBlock.text(thinking).

By the time this is called the client has unsealed any turn whose sealed thinking no longer matches the conversation before it (see ThinkingReplay), so sealed blocks here - such as the org.llm4s.llmconnect.model.ThinkingBlock.Opaque blocks decodeThinkingDetails produced - can be sent back as they are. A dialect sends only the blocks that are its own and ignores the rest, which may have come from another provider.

Standard: adds nothing, so the thinking is dropped. The OpenAI format has no field for it, and an unknown field can fail a request. A provider that documents a field for a model's earlier reasoning overrides this: DeepSeek and Z.ai (reasoning_content, which both require back across a tool-calling turn), OpenRouter (reasoning, and its reasoning_details unchanged) and Mistral (a thinking chunk in content).

Attributes

def encodeToolCallId(id: String): String

The id sent for a tool call - on an assistant turn's tool_calls and on the tool message answering it. Standard: the id as the model produced it.

The id sent for a tool call - on an assistant turn's tool_calls and on the tool message answering it. Standard: the id as the model produced it.

A provider that accepts only its own id format overrides this. It must map the same id to the same result every time, since the call and its answer are encoded separately and must still match; ids that came from another provider in the same conversation go through it too.

Attributes

def encodeUserPart: PartialFunction[ContentPart, Value]

The content parts of a user message that has an image or a document, each as one element of the message's content array; a part it does not cover fails the request with an org.llm4s.error.UnsupportedContentPartError before it is sent. A user message of text only is sent as encodeContent of its text, and a tool result takes text only. Standard: OpenAICompatibleDialect.standardUserParts - text, and an image by URL or inline (image_url, inline as a data: URL).

The content parts of a user message that has an image or a document, each as one element of the message's content array; a part it does not cover fails the request with an org.llm4s.error.UnsupportedContentPartError before it is sent. A user message of text only is sent as encodeContent of its text, and a tool result takes text only. Standard: OpenAICompatibleDialect.standardUserParts - text, and an image by URL or inline (image_url, inline as a data: URL).

Attributes

def headers: Seq[(String, String)]

Headers sent on every request, after Content-Type and - when the provider has an API key - Authorization: Bearer <key>.

Headers sent on every request, after Content-Type and - when the provider has an API key - Authorization: Bearer <key>.

Attributes

def parseToolCalls(toolCalls: Value): Seq[ToolCall]

Parses the tool_calls array of a non-streaming reply.

Parses the tool_calls array of a non-streaming reply.

Standard: OpenAICompatibleDialect.lenientToolCalls, which fills in missing fields and turns unparseable arguments into {}. Streamed tool calls do not come here: their argument fragments are passed on verbatim and reassembled by the stream's accumulator.

Attributes

def reasoningTokens(usage: Value): Option[Int]

Reads the reasoning-token count from a usage object. Standard: none.

Reads the reasoning-token count from a usage object. Standard: none.

Attributes

Whether an assistant turn with neither text nor tool calls is sent at all. Standard: true - it goes out as {"role": "assistant"} (with content if alwaysSendAssistantContent). A provider that rejects a message carrying neither content nor tool_calls, as Mistral does, answers false and the turn is left out. A turn with thinking that encodeThinking encodes is not empty, and is sent either way.

Whether an assistant turn with neither text nor tool calls is sent at all. Standard: true - it goes out as {"role": "assistant"} (with content if alwaysSendAssistantContent). A provider that rejects a message carrying neither content nor tool_calls, as Mistral does, answers false and the turn is left out. A turn with thinking that encodeThinking encodes is not empty, and is sent either way.

Attributes

def streamUsageOption: Boolean

Whether a streaming request asks for token usage with "stream_options": {"include_usage": true}. Standard: true.

Whether a streaming request asks for token usage with "stream_options": {"include_usage": true}. Standard: true.

OpenAI streams usage only when asked this way, and servers that follow it - vLLM, Ollama's /v1, Perplexity Router - stream none otherwise. A provider that rejects the field (Mistral answers an unknown field with a 422), does not document it, or always reports usage without it answers false. Usage is read from whichever event carries it either way, including a final event with empty choices.

Attributes

def systemRole: String

The role a org.llm4s.llmconnect.model.SystemMessage is sent under. Standard: "system". Cohere's Compatibility API documents system instructions under "developer".

The role a org.llm4s.llmconnect.model.SystemMessage is sent under. Standard: "system". Cohere's Compatibility API documents system instructions under "developer".

Attributes

def thinking(obj: Value): Option[String]

Reads the model's thinking from a JSON object: a completion's message (then, if that has none, its enclosing choice), or a stream's delta. Standard: none.

Reads the model's thinking from a JSON object: a completion's message (then, if that has none, its enclosing choice), or a stream's delta. Standard: none.

Attributes

def thinkingDetails(obj: Value): Seq[Value]

The provider-specific replay data on a JSON object - a completion's message or a stream's delta - as raw items, for decodeThinkingDetails. Standard: none.

The provider-specific replay data on a JSON object - a completion's message or a stream's delta - as raw items, for decodeThinkingDetails. Standard: none.

For a provider that returns more than reasoning text and needs it back unchanged, as OpenRouter does with reasoning_details.

Attributes