Rig 0.42.0 is live! #2373
gold-silver-copper
announced in
Announcements
Replies: 1 comment
|
Great work!!! |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Rig v0.42.0 released!
77 PRs merged since 0.41 — +207k/−35k lines, and the replay-cassette corpus grew from 508 to 1,793 recordings. Big thank you to all our contributors 🫡
The Generics Are Gone
#2257
Agent<M>is nowAgent.CompletionResponse<T>is nowCompletionResponse.Extractor<M, T>isExtractor<T>. The provider type no longer leaks into your agent's type, which means an agent can now swap its model — or its whole provider — at runtime, per run, or per model call from a hook.Completion responses are normalized at the provider boundary, and the agent erases the model type at construction.
The provider-native payload didn't disappear — it moved to inherent
raw_completion/raw_streamon each provider model, and (see below) it now also travels on every hook event as serializedraw.Swap Models and Providers at Runtime
Because the agent stores a handle rather than a type, the model behind it can change without the agent's Rust type changing. Three ways to do it:
AgentHook::on_model_selectruns once per model-call boundary — including retries and post-tool calls — after the completion-call hooks, so it sees the mergedRequestPatch, the prompt and history, the run default, the candidate chosen by earlier hooks, and the model that executed the previous attempt.OneOrManyIs Gone, and Content Is TaggedOneOrMany<T>was a 743-line non-emptiness promise whoseis_empty()returned a hardcodedfalseand whose deserializer rejected[]. #2273 deletes it: every content list isVec<T>—Messagecontent,ToolResult::content,chat_history,CompletionResponse::choice,EmbeddingsBuilder::build's output,InsertDocuments. Serialization is byte-identical (zero cassette churn); the non-emptiness rule moves to a request-boundary validator (CompletionRequest::validate_message_content) on the way out andrequire_non_emptyguards on the way in. Seven production sites that mintedAssistantContent::text("")as padding are gone with it.#2277 then makes
AssistantContentinternally tagged ("type": "text" | "image" | …) and turns provider extras into a namedOption<AdditionalParams>field that is non-empty by construction ({}andnullcanonicalize toNone).Provider Consolidation and Architecture
The provider layer went through a dozen consolidation passes that deleted on the order of 8,000 lines of duplicated production code. The pattern is the same everywhere: one shared driver in
providers::internal, and per-provider code reduced to the constants and hooks that actually differ.send_completionowns every unary completion tail (send, status split, envelope decode, telemetry) at ten call sites;sse_frames/open_wire_streamreplace six hand-rolled SSE loops; shared model-listing (with one paginated loop and cursor-repeat / budget protection — #2339), transcription, image-generation, audio-generation, and error-envelope drivers.impl_capabilities!,impl_default_provider_builder!,impl_provider_client!,impl_model_lister!,provider_error_enum!— 49 hand-writtenProviderBuilder/ProviderClientimpls became invocations in one PR alone.AgentRunnerholds the agent'sAgentConfiginstead of a hand-copied mirror of 15 of its fields (#2327), and a 17-positional-argument request builder became 6.Two API-cleanup notes:
#[non_exhaustive]is removed from the whole workspace (#2335) — matches are exhaustive again, struct literals compile again. Basically I really don't like#[non_exhaustive]and AI agents always freak out when they see it, so bye bye.1,000+ New Cassettes
The replay corpus went from 508 to 1,793 recorded provider interactions — 350 OpenAI, 336 Anthropic, 222 Gemini, 222 OpenRouter, 177 Mistral, 125 DeepSeek, 82 Doubleword, and coverage for xAI, Venice, Cohere, Ollama, Copilot, Bedrock, Groq, Perplexity, llamafile, and ChatGPT. Every one is recorded against the live API and replayed byte-for-byte in CI, with request-body assertions, and a
cassette_cache_prefixguard that walks the whole corpus and asserts each multi-turn conversation's earlier request prefixes the later one.We did this because it finds bugs. A partial list of what live recording caught this cycle:
temperatureormax_tokens(#2283) —Option::mapon aNonegeneration_configsilently dropped both — and separately injected a hardcodedmaxOutputTokens: 4096andtemperature: 1.0into requests that set neither (#2324). Both are fixed. If you set these before and saw no effect, they apply now; if you relied on the 4096 cap, set one.input_tokensfrom Anthropic-compatible gateways were read from the wrong frame (#2279, reported by @shlim33); Anthropic and Gemini model listing could loop forever on a cursorlesshas_more/ emptynextPageToken.ndims() == 0, and truncated tool calls destroying the whole response (#2337).max_tokensinstead ofmax_completion_tokens; image/TTSadditional_paramswere being ignored.finish_reason: "length"turn cut mid-JSON in a tool call failed the entire blocking decode on OpenAI, DeepSeek, Mistral, and OpenRouter — text, usage, and complete sibling calls with it (#2359, 450 cassettes on its own).max_tokens, wrongtool_choicespelling (#2263 by @rleisti, #2302).includeThoughts, and dropped trailing thought signatures (#2328). Anthropic loststop_sequenceon the streamed terminal and rejected a legitimately empty stop-sequence turn (#2329).ndims() == 0for its only embedding model (#2356).Observability: Identity, Headers, Raw Payloads, Finish Reasons
A cluster of features makes what a provider actually said reachable from an agent run without reaching into provider types:
CompletionResponseandStreamFinalcarryprovider_request_id— the transport request id read off response headers, the one provider support asks you for — alongsideresponse_id/message_id, bundled asResponseIdentityand threaded to every agent hook event andcompletion_callsentry. It survives onto failed calls too (#2315), and every capability error gains aprovider_request_id()accessor.Retry-Afterand friends now reachprovider_response_headers()on completion, transcription, image, audio, andverify()errors — including 2xx error envelopes from gateways.CompletionResponse::raw(and the streamed terminal) carry the serialized valueraw_completion/raw_streamwould have returned, and hook events expose it asraw: &serde_json::Value.openai::CompletionResponse::deserialize(event.raw)gets you the typed provider response back from inside a hook. See the newraw_response_hookexample.ModelTurnFinishedcarries the normalizedfinish_reasonand the attempt's ownmax_tokens, which is what you need to write retry-on-truncation as a hook — see theretry_on_truncationexample.AgentHook::on_reasoning_delta, with a stable per-part correlator.Length/ContentFilterwith no text, tool call, or image returns anErrnaming the remedy instead ofOk(""). On the OpenAI-compatible wire the underlyingcompletion()call succeeds with an emptychoiceand the finish reason (#2332), so direct callers should checkfinish_reason().truncated_output().Providers
Venice joins as a new provider (#2306) on the shared OpenAI path with native image generation, TTS, and ASR, and 33 cassettes from day one.
Anthropic gains opt-in strict tool use (#2296, requested by @kanyesthaker) with a schema-subset transformer that rewrites schemars output into what Anthropic's strict mode accepts (84 cassettes pin exactly where the API 400s), and per-breakpoint cache TTL (#2312, requested by @domenic-donato) so a 1h static prefix no longer drags the volatile conversation tail to 1h with it; usage now parses the per-TTL cache-write breakdown and
reasoning_tokens.Cohere gains image embeddings via a new
ImageEmbeddingModeltrait (#2304). Voyage AI exposesinput_type/truncation/output_dimension(#2343 by @sergiomeneses). Groq, Moonshot, and MiniMax gain model listing. OpenAI mergesadditional_paramstools into the typed tool list instead of clobbering it (#2294), and rejected Responses websocket upgrades keep their status, body, and request id (#2338).EmbeddingsBuilderreturned documents inHashMaporder — arbitrary per process — and a document straddling a batch boundary could get its own embeddings shuffled. Both are fixed (#2344 by @sergiomeneses, #2348); output is now in input order at both levels, and a provider returning fewer embeddings than sent is an error rather than a silent truncation.Dependencies, Packaging, CI
cargo updateon unrelated crates. A nightly job builds against the declared floors so they stay true.lopdfmoved to 0.44 (#2297 by @mccormickt), which also lets--all-featuresbuild for browser wasm (#2319).--all-featuressweep, dropped every API key from CI (cassettes make them unnecessary), and finally runsrig-derive's test suite, which had been running in no job at all (#2268, #2271, #2275).Upgrading
Please read
MIGRATING.md. The0.41 → 0.42section is long because we tried to write down every observable change, including the ones that only show up as different bytes on the wire.Huge thank you to everyone who made 0.42 happen:
And thank you to everyone who opened issues, reviewed the large architectural changes, and tested providers.
We build Rig, Rig builds us!
Full changelog: v0.41.0...v0.42.0
All reactions