Skip to content

Chat & Streaming

Send a message and get a response:

Basic chat completion with a single user message

Python
import asyncio
import os
from liter_llm import create_client
from liter_llm._internal_bindings import ChatCompletionRequest
async def main() -> None:
client = create_client(api_key=os.environ["API_KEY"])
req = ChatCompletionRequest.from_json("{\"messages\":[{\"content\":\"Say hello\",\"role\":\"user\"}],\"model\":\"gpt-4\",\"temperature\":0}")
result = await client.chat(req)
print(result.choices)
print(result.choices[0].message.content)
print(result.choices[0].finish_reason)
print((result.usage.total_tokens if result.usage else None))
print(result.model)
asyncio.run(main())

Liter-llm uses a provider/model prefix convention. The prefix determines which API endpoint, auth header, and parameter mappings to use:

openai/gpt-4o -> OpenAI
anthropic/claude-sonnet-4-20250514 -> Anthropic
groq/llama3-70b -> Groq
google/gemini-2.0-flash -> Google AI
mistral/mistral-large -> Mistral
bedrock/anthropic.claude-v2 -> AWS Bedrock

Switch providers by changing the model string – no other code changes needed.

Role Purpose
system Sets the assistant’s behavior. Sent once at the start.
user User input – questions, instructions, data.
assistant Previous assistant responses for multi-turn context.
tool Results from tool calls.
developer Developer-level instructions (some providers).

Append the assistant’s response and the next user message, then call chat again:

Multi-turn conversation with system, user, assistant, and follow-up user messages

Python
import asyncio
import os
from liter_llm import create_client
from liter_llm._internal_bindings import ChatCompletionRequest
async def main() -> None:
client = create_client(api_key=os.environ["API_KEY"])
req = ChatCompletionRequest.from_json("{\"messages\":[{\"content\":\"You are a helpful assistant.\",\"role\":\"system\"},{\"content\":\"What is 2 + 2?\",\"role\":\"user\"},{\"content\":\"2 + 2 equals 4.\",\"role\":\"assistant\"},{\"content\":\"And what is 4 + 4?\",\"role\":\"user\"}],\"model\":\"gpt-4\"}")
result = await client.chat(req)
print(result.choices)
print(result.choices[0].message.content)
print(result.choices[0].finish_reason)
asyncio.run(main())

Stream tokens as they arrive instead of waiting for the full response:

Streaming chat completion that produces content across multiple SSE chunks

Python
import asyncio
import os
from liter_llm import create_client
from liter_llm._internal_bindings import ChatCompletionRequest
async def main() -> None:
client = create_client(api_key=os.environ["API_KEY"])
req = ChatCompletionRequest.from_json("{\"messages\":[{\"content\":\"Count to 3\",\"role\":\"user\"}],\"model\":\"gpt-4\",\"stream\":true}")
result = client.chat_stream(req)
chunks = []
async for chunk in result:
chunks.append(chunk)
print(result)
asyncio.run(main())

Each chunk contains choices[].delta.content with incremental text. The final chunk includes finish_reason: "stop".

Accumulate deltas to get both real-time output and the complete text:

Streaming chat completion that includes a usage summary in the final chunk

Python
import asyncio
import os
from liter_llm import create_client
from liter_llm._internal_bindings import ChatCompletionRequest
async def main() -> None:
client = create_client(api_key=os.environ["API_KEY"])
req = ChatCompletionRequest.from_json("{\"messages\":[{\"content\":\"Say hi\",\"role\":\"user\"}],\"model\":\"gpt-4\",\"stream\":true,\"stream_options\":{\"include_usage\":true}}")
result = client.chat_stream(req)
chunks = []
async for chunk in result:
chunks.append(chunk)
for result_chunk in chunks:
if result_chunk.usage is not None:
print((result_chunk.usage.total_tokens if result_chunk.usage else None))
asyncio.run(main())

Define tools as JSON schema functions. The model can request tool calls, which you execute and return results for:

Chat request with a tool definition; assistant responds with a tool call

Python
import asyncio
import os
from liter_llm import create_client
from liter_llm._internal_bindings import ChatCompletionRequest
async def main() -> None:
client = create_client(api_key=os.environ["API_KEY"])
req = ChatCompletionRequest.from_json("{\"messages\":[{\"content\":\"What is the weather in San Francisco?\",\"role\":\"user\"}],\"model\":\"gpt-4\",\"tool_choice\":\"auto\",\"tools\":[{\"function\":{\"description\":\"Get the current weather for a given location\",\"name\":\"get_weather\",\"parameters\":{\"properties\":{\"location\":{\"description\":\"The city and state, e.g. San Francisco, CA\",\"type\":\"string\"},\"unit\":{\"description\":\"The temperature unit to use\",\"enum\":[\"celsius\",\"fahrenheit\"],\"type\":\"string\"}},\"required\":[\"location\"],\"type\":\"object\"}},\"type\":\"function\"}]}")
result = await client.chat(req)
print(result.choices)
print(result.choices[0].message.tool_calls)
print((result.choices[0].message.tool_calls[0].function.name if result.choices[0].message.tool_calls else None))
print(result.choices[0].finish_reason)
asyncio.run(main())

All chat parameters work with both chat and chat_stream:

Parameter Type Description
model string Provider/model identifier (e.g. "openai/gpt-4o")
messages array Conversation messages
temperature float Sampling temperature (0.0-2.0)
max_tokens int Maximum tokens to generate
top_p float Nucleus sampling threshold
n int Number of completions to generate
stop string/array Stop sequences
tools array Tool/function definitions
tool_choice string/object Tool selection strategy
response_format object Force JSON output ({"type": "json_object"})
seed int Deterministic sampling seed
presence_penalty float Penalize new topics (-2.0 to 2.0)
frequency_penalty float Penalize repetition (-2.0 to 2.0)
reasoning_effort string Reasoning budget for o-series and extended-thinking models.
extra_body object Provider-specific fields passed through verbatim.

OpenAI o-series models and Anthropic extended-thinking models accept a reasoning_effort parameter that controls how much compute the model spends on internal reasoning before producing the final response.

response = client.chat({
"model": "openai/o3-mini",
"messages": [{"role": "user", "content": "Prove the Pythagorean theorem."}],
"reasoning_effort": "high",
})

Accepted values for OpenAI o-series: "low", "medium", "high". Anthropic extended thinking uses a budget_tokens integer instead, which maps to reasoning_effort when the binding converts the field.

Pass a JSON Schema to response_format to constrain the model output to a specific structure. Use "type": "json_schema" instead of "type": "json_object" for schema-validated output.

schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
},
"required": ["name", "age"],
"additionalProperties": False,
}
response = client.chat({
"model": "openai/gpt-4o",
"messages": [{"role": "user", "content": "Extract: Alice is 30 years old."}],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "person",
"strict": True,
"schema": schema,
},
},
})

Structured output availability depends on provider support. OpenAI gpt-4o and later support json_schema. Providers that do not support it fall back to json_object or return EndpointNotSupported.

Pass provider-specific parameters that liter-llm does not model natively via extra_body. Fields in extra_body are merged into the top-level request JSON before it is sent to the provider.

response = client.chat({
"model": "openai/gpt-4o",
"messages": [{"role": "user", "content": "Hello"}],
"extra_body": {
"store": True, # OpenAI conversation store
"metadata": {"user": "alice"},
},
})

extra_body fields take lower precedence than named fields. If a named field and an extra_body key conflict, the named field wins.

Send audio inline in a user message using the input_audio content part type. The audio must be base64-encoded.

import base64
with open("audio.wav", "rb") as f:
audio_b64 = base64.b64encode(f.read()).decode()
response = client.chat({
"model": "openai/gpt-4o-audio-preview",
"messages": [{
"role": "user",
"content": [
{
"type": "input_audio",
"input_audio": {
"data": audio_b64,
"format": "wav",
},
},
{"type": "text", "text": "Transcribe and summarize this audio."},
],
}],
})

Supported formats depend on the provider. OpenAI gpt-4o-audio-preview accepts wav, mp3, ogg, flac, m4a.

When routing to Bedrock providers, responses arrive in AWS EventStream framing rather than SSE. Liter-llm handles the framing transparently. chat_stream works the same way regardless of provider.

// EventStream framing is transparent to the caller.
let stream = client.chat_stream(ChatCompletionRequest {
model: "bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0".into(),
messages: vec![/* ... */],
..Default::default()
}).await?;
// Consume exactly like any other stream.
pin_mut!(stream);
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
if let Some(content) = chunk.choices[0].delta.content.as_deref() {
print!("{content}");
}
}