df49b9cd/llm-dialect

Pure sans-I/O LLM dialect translation: Anthropic Messages / OpenAI Chat Completions / OpenAI Responses wire formats ↔ a canonical model, plus SSE framers. No runtime, no I/O, no wall clock. MIT

Rust

1

30 commits

updated Sep 17, 2026

See the code

See what people are saying (1)

SourceMessageScoreDate

llm-dialect for all your llm-proxy needs (r/rust)

Hello there, I’ve released my take on a lib for translating between llm provider dialects. I currently use it with my own proxy to glue claude-desktop together with a kimi-k3 openai chat/completions provider.

0

Sep 17, 2026

README

llm-dialect

ci publish crates.io docs.rs rust version license dependency status crates.io downloads GitHub stars

llm-dialect = "0.1.1" # MSRV 1.88

Pure sans-I/O LLM dialect translation: Anthropic Messages, OpenAI Chat Completions, and OpenAI Responses wire formats ↔ a canonical request/response model, plus SSE framer state machines for streamed turns. No async runtime, no HTTP client/server types in the API surface, no wall clock, no randomness — id minting and timestamps are supplied by the caller.

The mapping is the translation core of an LLM gateway (a LiteLLM-proxy-class service): parse whatever dialect your client speaks into one ItemRequest, translate to the ChatRequest your engine consumes, render the engine's canonical chunks back to the caller's dialect.

What it gives an embedder

Put an Anthropic /v1/messages (or OpenAI Responses) surface in front of any chat/completions-class backend without rewriting the translation yourself:

use llm_dialect::{
    canonical::ChatRequest,
    dialect::{anthropic::{req, stream::{chunk_to_sse_events, finalize_stream, StreamState}}, deflate},
    items::ItemRequest,
};

// 1. parse an Anthropic Messages wire body into the canonical model
let items: ItemRequest = req::from_anthropic(&body)?;

// 2. flatten to the internal chat request your engine speaks
let req: ChatRequest = deflate::items_to_chat_request(&items)?;

// 3. per-chunk: canonical chunks back to Anthropic SSE frames
let mut st = StreamState::with_stop_sequences(stops);
for chunk in engine_chunks {
    for (event, data) in chunk_to_sse_events(&chunk, &model, &mut st, &msg_id) {
        write!(out, "event: {event}\ndata: {data}\n\n")?;
    }
}
// one guaranteed terminal pair (message_delta + message_stop)
for (event, data) in finalize_stream(&mut st) {
    write!(out, "event: {event}\ndata: {data}\n\n")?;
}

Enable the axum feature for the shared SSE pump and per-dialect *_stream_response shells (anthropic_stream_response(&stream, …) -> Response), if you're already an axum service and want drop-in handlers.

Crate shape

modulecontents
itemsthe canonical request model (ItemRequest, Item, ContentItem, …)
canonicalthe flattened ChatRequest/ChatResponse/CanonChunk the engine speaks
dialect/{anthropic,openai_chat,openai_responses}wire parsers (*/req.rs), response renderers (*/out.rs), SSE framers (*/stream.rs)
dialect::deflateItemRequestChatRequest
dialect::sse (feature axum)the pump that drives a framer over a stream
errorthe shared ProxyError and its HTTP error envelope

See ARCHITECTURE.md for the module dataflow, the purity boundary and how it's enforced, and the cross-dialect invariants (usage arithmetic, thinking, tool-call linkage, streaming terminal frames).

Guarantees the crate holds

The dialect translation files are pure data transformation. A lint in the integration tests enforces no tokio/reqwest/axum/sqlx/rand/ clock calls outside the sanctioned shell/factory regions, so the boundary above is enforced rather than aspirational.

License

MIT.

Contributors

df49b9cd

29 commits

awd

1 commits

df49b9cd/llm-dialect

Pure sans-I/O LLM dialect translation: Anthropic Messages / OpenAI Chat Completions / OpenAI Responses wire formats ↔ a canonical model, plus SSE framers. No runtime, no I/O, no wall clock. MIT

Rust

1

30 commits

updated Sep 17, 2026

See the code

See what people are saying (1)

SourceMessageScoreDate

llm-dialect for all your llm-proxy needs (r/rust)

Hello there, I’ve released my take on a lib for translating between llm provider dialects. I currently use it with my own proxy to glue claude-desktop together with a kimi-k3 openai chat/completions provider.

0

Sep 17, 2026

README

llm-dialect

ci publish crates.io docs.rs rust version license dependency status crates.io downloads GitHub stars

llm-dialect = "0.1.1" # MSRV 1.88

Pure sans-I/O LLM dialect translation: Anthropic Messages, OpenAI Chat Completions, and OpenAI Responses wire formats ↔ a canonical request/response model, plus SSE framer state machines for streamed turns. No async runtime, no HTTP client/server types in the API surface, no wall clock, no randomness — id minting and timestamps are supplied by the caller.

The mapping is the translation core of an LLM gateway (a LiteLLM-proxy-class service): parse whatever dialect your client speaks into one ItemRequest, translate to the ChatRequest your engine consumes, render the engine's canonical chunks back to the caller's dialect.

What it gives an embedder

Put an Anthropic /v1/messages (or OpenAI Responses) surface in front of any chat/completions-class backend without rewriting the translation yourself:

use llm_dialect::{
    canonical::ChatRequest,
    dialect::{anthropic::{req, stream::{chunk_to_sse_events, finalize_stream, StreamState}}, deflate},
    items::ItemRequest,
};

// 1. parse an Anthropic Messages wire body into the canonical model
let items: ItemRequest = req::from_anthropic(&body)?;

// 2. flatten to the internal chat request your engine speaks
let req: ChatRequest = deflate::items_to_chat_request(&items)?;

// 3. per-chunk: canonical chunks back to Anthropic SSE frames
let mut st = StreamState::with_stop_sequences(stops);
for chunk in engine_chunks {
    for (event, data) in chunk_to_sse_events(&chunk, &model, &mut st, &msg_id) {
        write!(out, "event: {event}\ndata: {data}\n\n")?;
    }
}
// one guaranteed terminal pair (message_delta + message_stop)
for (event, data) in finalize_stream(&mut st) {
    write!(out, "event: {event}\ndata: {data}\n\n")?;
}

Enable the axum feature for the shared SSE pump and per-dialect *_stream_response shells (anthropic_stream_response(&stream, …) -> Response), if you're already an axum service and want drop-in handlers.

Crate shape

modulecontents
itemsthe canonical request model (ItemRequest, Item, ContentItem, …)
canonicalthe flattened ChatRequest/ChatResponse/CanonChunk the engine speaks
dialect/{anthropic,openai_chat,openai_responses}wire parsers (*/req.rs), response renderers (*/out.rs), SSE framers (*/stream.rs)
dialect::deflateItemRequestChatRequest
dialect::sse (feature axum)the pump that drives a framer over a stream
errorthe shared ProxyError and its HTTP error envelope

See ARCHITECTURE.md for the module dataflow, the purity boundary and how it's enforced, and the cross-dialect invariants (usage arithmetic, thinking, tool-call linkage, streaming terminal frames).

Guarantees the crate holds

The dialect translation files are pure data transformation. A lint in the integration tests enforces no tokio/reqwest/axum/sqlx/rand/ clock calls outside the sanctioned shell/factory regions, so the boundary above is enforced rather than aspirational.

License

MIT.

Contributors

df49b9cd

29 commits

awd

1 commits

Languages

Rust

94.0%

Shell

6.0%