llm_vllm 0.4.0
llm_vllm: ^0.4.0 copied to clipboard
VLLM backend implementation for LLM interactions. Provides streaming chat, embeddings, tool calling, vision support, and model management.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased] #
0.4.0 - 2026-08-30 #
Added #
- Streaming tool calls surface as they arrive on
LLMChunkMessage.toolCallDeltas, instead of being withheld until the call finishes. Against a live server the tool name became visible 697ms before the completed call, on a two-field argument object. extraHeadersonVLLMChatRepository,VLLMRepository,VLLMInstanceConfigand the builder — arbitrary headers on every request, including embeddings and pool health checks. Protocol headers andauthorizationalways take precedence.
Fixed #
- Parallel tool calls stay separate when a server or proxy emits all of them with
index: 0. A fragment whoseiddiffers from the call open at that index now starts a new call instead of being merged into it. VLLMRepository.fetchSupportedParamsomittedauthorization, so against a server started with--api-keythe/openapi.jsonprobe returned 401 and the method silently fell back to the built-in parameter snapshot.
Changed #
- The empty priming delta (
{"role":"assistant","content":""}) is no longer yielded. It carried no output and told consumers the model had started producing text before it had.
0.3.2 - 2026-08-18 #
Changed #
- Version bumped to
0.3.2in lockstep with the other packages, and thellm_coreconstraint raised to^0.3.2. No other changes to this package.
0.3.1 - 2026-08-18 #
Added #
example/concurrency_stall_repro.dart— concurrency soak harness that fails when a request exceeds 4x the run median, with request tracing viaLLM_VLLM_TRACE=1.example/raw_socket_burst_probe.dart— raw-Socketrepro of the macOS write-event loss.
Fixed #
- A stream read timeout no longer kills the isolate. It is pushed into the sink instead of escaping from
Stream.timeout's callback. - Sustained concurrent requests through one shared repository no longer stall permanently, via
llm_core's write gate. Full investigation indocs/concurrent-send-stall.md.
Changed #
- Dependency floors raised: Dart SDK
^3.12.0(was^3.8.0),http ^1.6.0,lints ^6.1.0,test ^1.31.0.
0.3.0 - 2026-08-13 #
Correctness sweep over parameter handling, the stream converter, embeddings, and the pool, plus test hardening across all of them.
Fixed #
chatResponsereturned empty content against a live server. vLLM (like OpenAI) sendsrole: "assistant"only on the first SSE delta; later content deltas omit it.VLLMChunkmapped the missing role tonull, and the shared stream-folding logic only accumulates assistant chunks — so everything after the first (empty) delta was dropped. Content-bearing deltas now default to the assistant role.- camelCase
toolChoicewas silently dropped. The alias passed validation but the request builder read the raw map by wire name, so it never reached the body.backendOptionsis now normalized once (normalizeVllmParams) after validation and every read goes through the normalized map. - Caller-supplied
chat_template_kwargsdiscardedenable_thinking— and with it thethink:flag. The map now merges key-by-key; caller entries win, so an explicitenable_thinkingstill overridesthink:. tool_choicewithout tools was silently dropped.none/autonow pass through (vLLM accepts them);requiredor a named function without tools throwsArgumentErrorinstead of a server-side 400.- Malformed 200 responses threw raw
TypeError.embedandVLLMRepository.models()now translate JSON-shape failures intoLLMApiExceptioncarrying the response body. - Stream-converter edges: text held back as a potential partial
<think>tag is flushed at end of stream instead of dropped; in-streamerrorevents carry theircodeasstatusCodeso retry classification works; the malformed-event guard now throws on the third event, matching its message. embedoptions were sent unvalidated while chat options were strictly checked. Embedding options are now validated against the embeddings schema (knownVllmEmbeddingParams), aliases normalize, client-side keys (batch_size,timeout) are stripped from the wire, empty input throws, andoptions['timeout'](aDuration) is honored per request.
Added #
- Thinking budget:
reasoningBudgetnow maps to vLLM's server-enforcedthinking_token_budget(requires--reasoning-parser). reasoningEffortmaps to vLLM'sreasoning_effort;nonedisables thinking via the chat template.- Reasoning-token usage surfaced as
LLMUsage.reasoningTokens. VLLMRepository.describe()— one-call deployment discovery returningVLLMDeploymentInfo: served models with their context window (VLLMModel.maxModelLen, newly parsed from/v1/models), probed capabilities per model, and the server's accepted request parameters. An unreachable server yieldsreachable: falseinstead of throwing, so sweeping candidate ports degrades gracefully. Seeexample/discover_example.dart.VLLMPoolgains pool-levelresponseCacheandmetrics(viaLLMRepositoryFeatures), acapabilitiesForModeloverride that OR-folds what healthy eligible instances offer,toolAttemptsforwarding, and abatchEmbedthat actually batches through the selected instance.VLLMInstanceConfiggains per-instancerateLimiter,supportedParams,capabilities, andhttpClient(caller-supplied clients are not closed bydispose()).VLLMChatRepositoryBuilder.capabilities(...)and.supportedParams(...), so the probe-then-configure workflow works through the builder.normalizeVllmParams,knownVllmEmbeddingParams, andreservedVllmEmbeddingParamsare exported.backendOptions['n'] != 1is rejected: the stream surfaces onlychoices[0], so extra candidates would cost tokens and be discarded.
Changed #
- The pool's
maxQueueDepthguard is enforced with a synchronous admission counter instead of sampling semaphore state, closing a race where a burst of concurrent requests could all slip past the depth check. - Integration suite:
concurrency_test.dartis now part ofall_integration_tests.dart(it never ran in CI), and astructured_output_test.dartsuite was added to match the other providers.
Removed #
- Breaking: the dead non-streaming DTOs
VLLMResponse,VLLMChoice,VLLMMessage, andVLLMMessageToLLMMessageExt. No code path ever produced them — the repository is streaming-only (chatResponsefolds the stream) — andtoLLMMessagethrewStateErroron unknown roles. - Breaking:
VLLMChatRepositoryBuilderExtension. An extensionstaticis unreachable through instances; useVLLMChatRepository.builder()orVLLMChatRepositoryBuilder().
0.2.0 - 2026-08-12 #
Initial release of the vLLM backend for the dart-llm ecosystem.
Added #
VLLMChatRepository— streaming chat against a vLLM OpenAI-compatible server (/v1/chat/completions), with tool calling, vision, and automatic tool-loop execution.- Optional API key support for servers started with
--api-key(sent asAuthorization: Bearer). - Embeddings via
/v1/embeddings; model listing via/v1/models. - Structured output through the OpenAI-compatible
response_formatfield (json_object/json_schema). VLLMStructuredOutputs— vLLM-native guided decoding via the top-levelstructured_outputsfield, with named constructors forjson,regex,choice,grammar, andstructural_tag.VLLMRepository.resolveCapabilities()— probes the connected deployment and returns an [LLMCapabilities].VLLMChatRepository.capabilitiesForModelreports what the backend implements (vLLM serves one model per process, so tool calling, vision and embeddings vary per deployment); pass the probe result as thecapabilitiesconstructor argument to report what the server actually offers.VLLMRepository.supportsEmbeddings()added alongside it.- Retries are on by default (
VLLMChatRepository.defaultRetryConfig: three attempts on429/5xx). A vLLM server answers503while loading weights, which was previously a first-attempt failure. Opt out withRetryConfig(maxAttempts: 0). TimeoutConfig.totalTimeoutis now applied to streaming responses. It was documented as "maximum total time for entire request" but never enforced, so a stream that trickled data indefinitely never timed out —readTimeoutonly measures the gap between chunks.batchEmbedsplits large inputs into batches ofdefaultEmbeddingBatchSize(32), preserving order. Override withoptions['batch_size'], or pass0to send everything in one request.tool_choicesupport, acceptingauto/none/requiredor a tool name (wrapped in the named-function form). Note bothautoandrequiredrequire the server to run with--tool-call-parser.VLLMSamplingOptions— typed access to the vLLM-only sampling knobs (min_p,repetition_penalty,min_tokens,seed,stop_token_ids,bad_words, …) plusvllm_xargs, vLLM's own escape hatch for custom-extension parameters.backendOptionsvalidation against vLLM's request schema. An unrecognized key throws with the closest match ("repitition_penalty"→ did you mean "repetition_penalty"?) instead of being silently dropped by the server. camelCase spellings are accepted and normalized (minP→min_p), matchingllm_ollama.VLLMRepository.supportsToolCalling()andVLLMRepository.supportsReasoningParser()— server-configuration probes in the style ofllm_ollama'ssupportsStructuredOutput, returningfalserather than throwing when the server is unreachable.VLLMRepository.fetchSupportedParams()reads the running server's/openapi.json, sobackendOptionscan be validated against that server's vLLM version rather than the bundled snapshot. Pass it toVLLMChatRepository(supportedParams: ...).- vLLM's terse configuration errors are translated into actionable exceptions:
a missing
--tool-call-parserbecomes aToolsNotSupportedExceptionnaming the flag and suggesting a parser for the model family, and a missing--reasoning-parserbecomes aThinkingNotSupportedExceptionexplaining that thinking still works without it. - Reasoning control through
chat_template_kwargs.enable_thinking, sent for bothtrueandfalsebecause Qwen3-family models think by default. Note this is a different knob from vLLM'sinclude_reasoning, which defaults totrueand controls only whether reasoning is surfaced — setting it tofalsediscards the reasoning while the model still spends tokens producing it. Reasoning text is read from the server'sreasoningfield (aliasesreasoning_content,thinking), with a<think>-tag splitter as a fallback for servers started without--reasoning-parser. - Base URLs are accepted with or without a
/v1suffix and a trailing slash; all spellings resolve to the same endpoint (normalizeVllmBaseUrl,vllmEndpoint). VLLMPool— multi-instance routing with per-instance and per-model concurrency limits, queue limits, health checks, and stats.VLLMChatRepository.builder()with retry, timeout, rate limiting, response cache, and metrics support.- Unit and integration test suites, including coverage for 16 concurrent streams through a single repository.
Fixed #
RetryConfig.retryableStatusCodesnever applied to streaming requests. A non-2xx response is returned rather than thrown, and the status check ran after the retry wrapper — so retries only ever fired on transport errors, and a retryable429/503failed on the first attempt regardless of configuration. Retryable statuses are now raised inside the retried operation; non-retryable ones still flow through to the specific exception types.- Embedding errors are routed through
VLLMErrorHandlerandhandleHttpError, so a request against a chat-only model reports the server's explanation instead of a generic'Error generating embedding'. Embeddings also use the same retry policy as chat.
Notes on vLLM behavior #
vLLM silently ignores unknown request fields — it returns 200 and drops
them rather than reporting an error, so a misspelled or obsolete parameter is
indistinguishable from a working one. Two guards exist because of this:
- The
guided_*parameter names removed in vLLM 0.12 raise anArgumentErrorrather than yielding unconstrained output. UseVLLMStructuredOutputs. extra_bodyis an OpenAI Python SDK wrapper, not a wire field. Its contents are flattened onto the request body so they actually reach the server.
thinking_token_budget is opt-in via backendOptions: vLLM rejects it with a
400 unless the server was started with --reasoning-parser /
--reasoning-config.
Tool calling requires the server to run with --enable-auto-tool-choice and a
--tool-call-parser matching the model's output format.