Custom Chat Processors
A custom chat processor replaces Dynamo’s chat preprocessing and response building with your own
Python code, as the built-in vllm and sglang processors do. Dynamo still serves HTTP, routes each
request to a worker, and streams the result back. Use one when your workers need a request shape or
a rendering step that no built-in processor provides.
The Frontend loads the processor named by --dyn-chat-processor (env var DYN_CHAT_PROCESSOR).
Any value other than dynamo, vllm or sglang names a custom processor:
- A name registered under the
dynamo.frontend.chat_processorsentry-point group, recommended for packaged processors. - A direct
module:functionpath for development.
Write a processor
A processor is a provider function. The Frontend calls it once at startup, inside its event loop,
with the parsed Frontend configuration and the --dyn-chat-processor-config JSON object (empty when
unset). It returns a chat engine factory.
The Frontend calls the factory for each token-input model when its first worker registers, and
again if the model’s card changes. The factory returns a PythonAsyncEngine that serves
/v1/chat/completions, /v1/responses and /v1/messages for that model. /v1/completions keeps
Dynamo’s own preprocessing. If the factory raises, the Frontend retries with backoff and serves the
model once it succeeds. A model whose workers register with text input preprocesses requests
itself, so its chat requests go straight to a worker and skip the processor.
The factory receives:
mdc, the model’s deployment card.mdc.runtime_config()returns what the worker published when it registered, itsruntime_dataincluded, andmdc.local_dir()the model’s downloaded files.routed_engine, which sends a preprocessed request to a worker through Dynamo’s router:await routed_engine.generate(request, context=context).
Register and run it
For an installable package, register the provider under the dynamo.frontend.chat_processors
entry-point group. The entry-point name is the value passed to the Frontend.
Start the Frontend with the processor and its settings:
During development, pass the module path instead of a registered name:
The Frontend rejects a name that resolves to nothing when it parses its arguments, and lists the
registered processors. A custom processor runs only with the HTTP Frontend, not with
--interactive or --kserve-grpc-server.
Errors
An exception your engine raises becomes the HTTP error response:
- Raise
dynamo.llm.exceptions.InvalidArgument(message)to reject a request with HTTP 400 and showmessageto the client. - Raise
dynamo.llm.HttpError(code, message)for another status. The client gets that status for a 4xx, 503 or 529, and 500 for any other 5xx, with the status’s standard text;messageis logged, not returned. - A worker’s error reaches your engine from
routed_engineasInvalidArgumentfor a rejected request, and asdynamo.llm.exceptions.RoutedEngineError, aValueError, for any other classified error. Re-raise it, or let it escape, and the client gets the status the worker’s error maps to.
Response headers
To set an HTTP response header, call context.set_response_header(name, value) before your
engine’s first yield, or before it raises:
The header is on the response, error responses included, when the Frontend reads your engine’s
stream before it commits the response. That is every non-streaming /v1/chat/completions request,
and a streaming one only when DYN_HTTP_PRE_COMMIT_ERROR_PEEK_MS is set.
Otherwise the call returns False and sets nothing. That covers a streaming request without that
setting, a call made after a streaming response has started, and every /v1/responses and
/v1/messages request.
When the response can carry the header, the call raises ValueError for a name or value that is
not a valid header, and for a header that the HTTP layer owns (content-type, content-length,
content-encoding, x-request-id and hop-by-hop headers).