llama.cpp
- In one line
- Dispatches unified requests to a
llama-serverover its OpenAI-compatible chat route or its native completion route. - You need
- The
provider-llamacppfeature and a runningllama-server. No API key is required. - Read this if
- You route requests through
ProviderEndpoint::LlamaCpp, or choose between the chat and completion routes.
The default route, Chat, runs the code on the OpenAI page. Its own differences:
- The default base URL is
http://127.0.0.1:8080, and the adapter appends/v1for the chat route. - No API key is needed. A key is sent as a bearer token only when one is set.
LlamaCppConfigselects the route and carriesllama-serverbody parameters.- The
Completionroute is a separate protocol with its own body, prompt format and frames.
Endpoint
| Fact | Chat route (default) | Completion route |
|---|---|---|
| Feature flag | provider-llamacpp | provider-llamacpp |
ProviderEndpoint variant | LlamaCpp | LlamaCpp |
| Default base URL | http://127.0.0.1:8080 | http://127.0.0.1:8080 |
| Route | POST {base}/v1/chat/completions | POST {base}/completion |
| Wire protocol | OpenAI-compatible | native, one token per frame |
- The base URL is the first non-empty of
LlamaCppConfig::base_url, the client's base URL, then the default. - The chat route trims a trailing
/and appends/v1unless the base URL already ends in/v1. - The completion route trims a trailing
/and appends/completion, with no/v1handling.
Authentication
Authorization: Bearer <api_key>is sent when a key is set.LlamaCppConfig::api_keywins over the client's API key.- With no key anywhere, no auth header is sent and dispatch does not fail.
Configuration
CucaClientBuilder::with_llamacpp_config sets a LlamaCppConfig. With none set, the defaults below apply.
| Field | Type | Default | Used by |
|---|---|---|---|
base_url | String | empty, defers to the client | both routes |
api_key | Option<String> | None, defers to the client | both routes |
model | String | empty, defers to the request's model | both routes |
route | LlamaRoute | Chat | selects the route |
n_threads | Option<u32> | None | Completion only |
n_gpu_layers | Option<u32> | None | Completion only |
flash_attn | bool | false | Completion only |
LlamaCppConfig and LlamaRoute are defined in the crate-private provider module, and the crate root does not re-export them.
Request translation
Chat route: the body is the one described under OpenAI request translation. The model is LlamaCppConfig::model when non-empty, otherwise the request's model. The three llama-server parameters are not sent.
Completion route: the body is a JSON object with these keys:
promptis assembled from the conversation.Systemmessage text comes first as bare context.UserandAssistantmessages are marked with### User:and### Assistant:prefixes.- Only
Textblocks contribute. Images, thinking and tool blocks are dropped, andTool-role messages are skipped. streamis alwaystrue.temperatureis included only when set.n_predictcarriesmax_tokens, or 128 when the request leaves it unset.n_threadsandn_gpu_layersare included only when configured, andflash_attnonly whentrue.modelisLlamaCppConfig::model, or the request's model when that is empty.llama-serverignores it.
The completion route has no tool protocol, so UnifiedRequest::tools is not sent.
Response stream
- Chat route: the stream is the one described under OpenAI response stream, ending at
data: [DONE]. - Completion route: each frame is
{"content": "tok", "stop": false}. A non-emptycontentbecomesMessageContentBlock::Text. A frame with empty or missingcontent, including the final{"content": "", "stop": true}, produces no block. - The completion route emits only
Textblocks, and the stream ends when the byte stream ends.
Token usage
- Chat route: the rules on the OpenAI page apply, because the request sets
stream_options.include_usageandllama-serverhonours it. - Completion route: no token counts are read.
UnifiedResponse::usageisNone,prompt_tokensis0, andcompletion_tokenscounts one perTextblock.
Thinking
- Chat route:
req.thinkingmaps toreasoning_effortas on the OpenAI page, andreasoning_contentdeltas becomeThinkingblocks. - Completion route:
req.thinkingis ignored, and noThinkingblock is produced.
Errors
| Condition | Error |
|---|---|
| Feature not compiled | CucaError::ProviderNotEnabled("provider-llamacpp") |
| Non-2xx response | CucaError::Http { status, body } with the captured body |
| Connection or body-read failure | CucaError::Transport { message } |
| Frame is not valid JSON | CucaError::Json |
Chat frame carries {"error": {"message": ...}} | CucaError::Provider with provider: OpenAi |
Completion frame has an error field, as a string or {"message": ...} | CucaError::Provider with provider: LlamaCpp |
| Frame bytes are not valid UTF-8 | CucaError::SseParse |
A malformed frame or an error frame ends the stream after the error.
Limits
- No cap on request or response size, and the client sets no request timeout.
- The SSE parser keeps one reusable buffer that starts at 8192 bytes. Its high-water mark is the largest single frame plus one partial line.
- Completion route:
n_predictdefaults to 128 when the request leavesmax_tokensunset. Chat route:max_tokensis sent only when the request sets it. - Chat route: in-flight tool calls hold their argument fragments until each call completes.
Example
The stream below uses the chat route against a server on port 1234. Setting CUCA_BASE_URL and CUCA_MODEL is optional.
cargo add cuca --path /path/to/cuca --features provider-llamacpp
cargo add tokio --features rt,macros
cargo add tokio-streamexport CUCA_BASE_URL="http://127.0.0.1:1234/v1"
export CUCA_MODEL="google/gemma-4-e4b"$env:CUCA_BASE_URL = "http://127.0.0.1:1234/v1"
$env:CUCA_MODEL = "google/gemma-4-e4b"use std::io::{Write, stdout};
use cuca::types::{MessageContentBlock, ProviderEndpoint};
use cuca::{CucaClient, UnifiedRequest};
use tokio_stream::StreamExt;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base_url =
std::env::var("CUCA_BASE_URL").unwrap_or_else(|_| "http://127.0.0.1:1234/v1".to_string());
let model = std::env::var("CUCA_MODEL").unwrap_or_else(|_| "google/gemma-4-e4b".to_string());
let client = CucaClient::builder()
.with_provider(ProviderEndpoint::LlamaCpp)
.with_base_url(base_url)
.build()?;
let request = UnifiedRequest::new(model)
.add_system_message("You are concise.")
.add_user_message("Say hello.")
.set_max_tokens(128);
let mut stream = client.generate_stream(request).await?;
let mut text_blocks = 0usize;
let mut thinking_blocks = 0usize;
while let Some(block) = stream.next().await {
match block? {
MessageContentBlock::Text(text) => {
print!("{text}");
stdout().flush()?;
text_blocks += 1;
}
MessageContentBlock::Thinking { .. } => thinking_blocks += 1,
_ => {}
}
}
println!("\nblocks: {text_blocks} text, {thinking_blocks} thinking");
Ok(())
}cargo run
The provider-llamacpp feature comes from the cargo add line above.
Hello.
blocks: 2 text, 18 thinking
google/gemma-4-12b-qat, served by a local server on port 1234, produced this reply.