# llm-dialect
[](https://github.com/df49b9cd/llm-dialect/actions/workflows/ci.yml)
[](https://crates.io/crates/llm-dialect)
[](https://docs.rs/llm-dialect)
```toml
llm-dialect = "0.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:
```rust
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
| `items` | the canonical request model (`ItemRequest`, `Item`, `ContentItem`, …) |
| `canonical` | the 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::deflate` | `ItemRequest` → `ChatRequest` |
| `dialect::sse` *(feature `axum`)* | the pump that drives a framer over a stream |
| `error` | the shared `ProxyError` and its HTTP error envelope |
## 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.