Skip to content

Releases

lm15 2.4.0 for Rust

Choose a tag to compare

@MaximeRivest MaximeRivest released this 12 Nov 20:24

Google Cloud from AWS, with no key (lm15-contract f67699c, AUTH-1 gcp-chain; 4,271 of 4,271 checks, the same in every lm15 language; the amendment waits for ratification).

  • New: a Google external_account credential with an AWS source, what gcloud iam workload-identity-pools create-cred-config … --aws writes. Vertex (vertex, vertex-anthropic) can now be reached from AWS through workload identity federation, for organizations that forbid API keys. Every lm15 language used to refuse it as a stated gap. lm15 signs an AWS STS GetCallerIdentity request with the AWS identity, Google's STS exchanges it, then the configuration's service account is impersonated. The subject token is google-auth's, byte for byte: two new contract vectors were computed by google-auth itself.
  • Where the AWS identity comes from: AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY (Lambda), then the container endpoint (ECS and EKS Pod Identity: the task's role), then EC2 metadata at the file's url. IMDSv2 is used when the file names its token URL, with one session token per acquisition. The container endpoint is an lm15 addition: Google's own libraries skip it, so on ECS they sign as the host's role or find nothing. The region comes from AWS_REGION, then AWS_DEFAULT_REGION, else the file's region_url.
  • A source that is not aws1, or that lacks regional_cred_verification_url, is refused before any request. When no AWS identity is found, the error says what was looked for.
  • Checked live (2026-11-12): Gemini on Vertex answered through a federated AWS identity, from the environment's keys and from a container endpoint. A service account with only aiplatform.endpoints.predict is enough for Gemini.

Use it: point GOOGLE_APPLICATION_CREDENTIALS at the file and name the credential workload (Vertex, below), or leave the chain to find it.

In Rust: RouterConfig::new().credential("vertex", "workload"), then "vertex:gemini-2.5-flash" as the model. New public function lm15::cloud::chains::gcp_aws_subject_token.

lm15 2.3.1 for Rust

Choose a tag to compare

@MaximeRivest MaximeRivest released this 11 Nov 03:06

A conversation that changes provider keeps working (lm15-contract 81b519f, new rule MAP-24, issue lm15-dev/lm15-contract#3; 4,269 of 4,269 checks, the same in every lm15 language; the rule is decided and waits for ratification).

  • Fixed: a conversation answered by an OpenAI Responses model and continued on Claude failed on every request with messages: text content blocks must be non-empty. OpenAI's hidden reasoning is kept in the history as a thinking part with empty text and OpenAI's own state. lm15 wrote it to Claude as an empty text block, which Claude refuses, and since the history is stored every retry failed the same way. A part with nothing for the provider it is sent to now writes nothing there, and a message left with nothing is not sent.
  • Your messages are not changed. Only the request leaves those parts out. Send the same history back to OpenAI and its reasoning goes with it. A stored conversation that was stuck works again as it is.
  • The same on every wire, for every such part: an empty text part (what lm15 reads for a reply with no parts), a citation that quotes nothing, an empty system prompt given as parts. Anthropic, Gemini, OpenAI Responses and Chat Completions requests no longer carry an empty text block or an empty message. Checked live on 14 servers: Moonshot refuses an empty text block on all three of its endpoints, DeepSeek and Moonshot refuse an assistant row with nothing in it, Gemini refuses a message with no parts; all 14 accept the message being left out.
  • What must be sent still is. Claude's own thinking with display omitted (empty text and its signature) goes back as a thinking block. A tool result is always sent; an empty output is the empty string.
  • Nothing is recorded as an adaptation for this: no word anyone wrote is withheld.
  • Rust already left another provider's hidden thinking out of a request to Claude, and then sent that message with content: [], which DeepSeek's Anthropic-compatible endpoint refuses; the message is now left out.
  • A citation in an assistant message is sent to Claude and Gemini as the text it quotes, as in the reference; it was title — url — text.

Small wire changes for requests that worked before: text joined into one string has no blank line for an empty part; a message that is only an empty string is not sent, so a request whose only message is empty now reaches the provider with no message and is refused there (13 of 14 servers); a stored conversation that carried empty blocks may miss the prompt cache once.

Not changed, and still refused by the provider when it happens (each has an issue with its receipts): thinking signed by another server of the same API family, for example DeepSeek's or Meta's Anthropic-compatible endpoint sent to Anthropic, or Azure's reasoning items sent to OpenAI (lm15-dev/lm15-contract#5); a tool loop moved to Gemini 3 or DeepSeek between a call and its result (lm15-dev/lm15-contract#6); a message whose only text is whitespace sent to Anthropic (lm15-dev/lm15-contract#4). Change model between turns, not inside one.

Nothing is removed and no API changes.

lm15 2.3.0 for Rust

Choose a tag to compare

@MaximeRivest MaximeRivest released this 10 Nov 02:21

In Rust: base_models on RouterConfig (and on LmBuilder), then "tinker:tinker://…" as the model.

Tinker (Thinking Machines): base models and the models you trained there (lm15-contract 18b3788; 2,266 of 2,266 checks, the same in every lm15 language; the new rules MAP-21, MAP-22 and MAP-23 are decided and wait for ratification).

  • Two new providers, one key (TINKER_API_KEY). tinker speaks Chat Completions (also spelled thinkingmachines); tinker-anthropic speaks the Anthropic Messages wire. Both serve Tinker's base models (tinker:Qwen/Qwen3.5-4B) and any sampler checkpoint by its path.
  • A URI is one model name. tinker://<run>:train:0/sampler_weights/<n> routes to Tinker whole. Before, any name://… string was read as provider name with model //….
  • Reasoning is switched the way each model family takes it. On tinker, Qwen 3/3.5/3.6, Kimi K2.6 and Nemotron take an on/off switch (an effort word turns reasoning on, and the substitution is recorded); Inkling takes the effort words. A checkpoint follows the family of the model it was trained from when you name that model (the line above). Listing models on tinker shows your checkpoints as fine-tunes with their base model.
  • Error messages from FastAPI servers are kept. An error body that is {"detail": "…"} used to raise with an empty message on every OpenAI-family and Anthropic-family adapter; the detail is now the message (MAP-21).
  • stop means the same everywhere. Tinker returns the stop sequence it stopped at; lm15 cuts it off (MAP-22).
  • New compat values: thinking format reasoning_effort_switch, reasoning off switch, JSON schema none (a server that applies no response_format: dropped with a record), and stop_in_text. Registry entries may carry aliases.
  • Known, not changed in this release: the reader for a Chat Completions response body that did not come through lm15 (response_from_openai_chat in Python; the same door under each language's spelling) is given no request, so a Tinker body that stopped at a stop sequence keeps the sequence at its end. Ordinary calls and streams cut it. On the Messages door, Tinker itself refuses reasoning off and effort words for the on/off families; use tinker for those.

Nothing is removed and no field changes meaning. Tinker calls both of its doors beta.

Rust source compatibility. The chat core covered by the 2.x promise is unchanged. Outside it, this release adds public fields and enum variants, which cargo semver-checks reports as breaking for code that lists every field or every variant:

  • new field stop_in_text on OpenAIChatCompat, ResolvedOpenAIChatCompat, ChatModelOverride, AnthropicCompat and ResolvedAnthropicCompat; aliases on registry::ProviderDefinition; base_model on wire::BuildContext. A struct literal that names every field needs the new one (or ..Default::default() where the type has a default).
  • new variants OpenAIChatThinkingFormat::ReasoningEffortSwitch, OpenAIChatReasoningOff::Switch and OpenAIChatJsonSchema::None. A match over one of these enums without a wildcard arm needs a new arm.

lm15 2.2.0 for Rust

Choose a tag to compare

@MaximeRivest MaximeRivest released this 07 Nov 01:23

In Rust: Reasoning { summary: Some(ReasoningSummary::Auto), .. }.

Claude shows its thinking when you ask for it (lm15-contract 9d8e1ce, MAP-7 rule 7 amended; 2,140 of 2,140 checks, the same in every lm15 language).

  • A reasoning summary on Claude sends thinking.display: "summarized". Since Opus 4.7, Anthropic returns a thinking block with empty text unless the request asks for the summary. On Opus 4.7, Opus 4.8 and every Claude 5 model, asking lm15 for reasoning with summary: "auto" gave an empty thinking part (checked live on 14 Claude models). Any summary now adds the setting, with adaptive thinking and with a thinking budget alike; every Claude model accepts it. Without a summary nothing changes: each model's own default.
  • DeepSeek, Meta and Moonshot over the Anthropic Messages wire are unchanged: they return their thinking without the setting.
  • Known, not changed in this release: effort: "off" sends no thinking object to Claude, and Claude 5 thinks anyway. See the contract's change entry, changes/2026-11-07-anthropic-thinking-display.md.

Nothing is removed and no field changes meaning.

lm15 2.1.0 for Rust

Choose a tag to compare

@MaximeRivest MaximeRivest released this 05 Nov 16:39

In Rust: Dialect::stream_reader() (default None) and wire::StreamReader; StreamDecoder (and so stream, replay_stream, parse_stream) reads through it. The vet shim now builds the Codex door with the harness account id for build_request too.

Streams now yield what the provider said, all of it, once (MAP-20). Nothing is removed and no field changes meaning. Contract 77f22f4, 2,132 of 2,132 checks, the same in all six lm15 languages.

  • Parallel tool calls on the Codex subscription keep their inputs. The Codex backend sends the arguments of parallel calls only in each call's final response.output_item.done, never in pieces, and lm15 read only the pieces, so every call arrived with input {} (from stream and from complete alike). A stream's final copy of an item now completes whatever its pieces did not deliver: arguments, message text, a reasoning summary. A piece that contradicts the final copy is refused (StreamAssemblyError, code stream_assembly, with the part's index) instead of either value being chosen.
  • A streamed reasoning summary reads like the complete one: its parts are joined with a newline, as the complete call joins them. They used to run together (**First idea****Second idea**).
  • Gemini parallel calls sent one per chunk stay separate calls. gemini-2.5-pro and gemini-3-flash-preview send each parallel call in a chunk of its own. Every call landed in the same slot and their arguments ran into one unreadable input.
  • A Responses stream cut short by the output limit ends with length. It ends with response.incomplete, which was ignored: on OpenAI, Azure, Codex and Moonshot the stream then failed with StreamAssemblyError; on Meta it ended stop without its usage.

How this is checked from now on: the contract harness compares every OpenAI Responses stream with the stream's own final copy of the whole response (no hand-written answer needed), replays legal re-shapings of every recorded stream (fragments cut elsewhere, joined, re-chunked, dropped where a final copy restates them, contradicted), and every kind of event a recorded stream carries must be classified (mapping/stream-events.json). See lm15-contract/changes/2026-11-05-stream-final-copy.md.

lm15 2.0.0 for Rust

Choose a tag to compare

@MaximeRivest MaximeRivest released this 01 Nov 11:59

The first stable release of lm15 for Rust. The code is 2.0.0-rc.1's, now marked stable: within 2.x the chat core (requests, responses, streaming, tools, structured output, media in messages, reasoning, errors, credentials, model listing) keeps working as documented. Files, batches, media generation, stored caches, realtime sessions, Chat Completions ingest and sign-in stay provisional; see the README's Stability section.

What 2.0 changed from the 1.0 release candidates (a usage counter means one thing on every provider; cost estimates exact or None; major version 2 in every lm15 language): see the 2.0.0-rc.1 notes.

cargo add lm15

Contract cfc3edc: 1,968 of 1,968 checks, like Python 2.0.0, TypeScript 2.0.0, Go v2.0.0, Julia 2.0.0 and R 2.0.0.

lm15 2.0.0-rc.1 for Rust

Pre-release

Choose a tag to compare

@MaximeRivest MaximeRivest released this 01 Nov 10:58

A release candidate.

In Rust: InferencePricing::estimate(&usage) returns Option<f64>; PricingTier for long prompts; reasoning_usage compat. Rust goes from 1.0.0 release candidates straight to 2.0.0 ones; no stable 1.0 was released.

A usage counter means one thing on every provider (MAP-19). Breaking for code that reads token counts or estimates cost.

  • input_tokens is every token the model read, cached tokens included; output_tokens is every token it wrote, reasoning included. The cache, reasoning and audio counts are parts of one of those two, and total_tokens is input + output. In 1.x each counter was the provider's own, so the same field meant different things.
  • What changes: Anthropic input (and DeepSeek, Meta and Moonshot on the Anthropic wire) now includes cache reads and writes; a call that read 4,873 cached tokens and 20 new ones reports 4,893, not 20. Gemini and Vertex output now includes the thinking, and input includes the tokens Gemini's own tools added. xAI output now includes reasoning. OpenAI and other Chat Completions servers: unchanged. The provider's own numbers stay in provider_data, for checking a bill.
  • A counter is exact or empty, never a partial sum. When a provider's numbers do not add up the way its wire is documented to count, input and/or output are left empty rather than wrong.
  • Building a usage record checks that parts fit inside wholes and that the total is input + output. A usage record saved by 1.x from Anthropic, Gemini or xAI can fail to load for this reason; the message says so.

Cost estimates are exact or unknown. The estimate takes a usage record and returns the list price, or nothing when an unknown count could change it (and for calls with audio tokens). 1.x charged cached tokens twice on OpenAI, Gemini and xAI (a call with 90,000 of its 100,000 input tokens cached cost $0.039 and was estimated at $0.219) and missed Gemini's and xAI's reasoning. Pricing tiers reprice a whole call above a prompt length, as OpenAI does above 272,000 input tokens and Claude Haiku 5.5 above 100,000; an absent price is unknown, never zero. A reasoning_usage compat setting covers servers that count reasoning beside the completion (the xai preset).

Major version 2 in every language. The major version is now the contract's generation, the same in every lm15 language (lm15-contract playbooks/api-family.md, rule 8); generation 2 begins with this change.

Contract cfc3edc (ratified on 2026-11-01 in 6b801f9, wording only): 1,968 of 1,968 checks, the same commit as Python 2.0.0, TypeScript 2.0.0, Rust 2.0.0-rc.1, Go v2.0.0-rc.1, Julia 2.0.0 (LanguageModel15, joining Julia's registry) and R 2.0.0. Rules in lm15-contract changes/2026-10-31-usage-one-meaning.md.

cargo add lm15@2.0.0-rc.1

lm15 1.0.0-rc.9

lm15 1.0.0-rc.9 Pre-release
Pre-release

Choose a tag to compare

@MaximeRivest MaximeRivest released this 15 Oct 00:28

A release candidate. Includes rc.8, which was tagged but never published.

Jev reads pictures. Put a PNG or JPEG image in the one user message sent to TypeSafe's Jev, on its own or beside text and data (sent as an ordered list), and ask judgments about it ("Is there a person in the image?", "Do the two images show the same product?"). lm15 sends the bytes where TypeSafe expects them and adds its TypeSafe-Danger-Zone-Images: true opt-in header for you. TypeSafe calls images beta: check answers on pictures you know before relying on them. A picture given by URL or file id, another format (WebP, GIF), audio and video are refused before anything is sent. Box questions are not supported yet. Tested live on jev-1.13.0; rules in lm15-contract changes/2026-10-14-jev-images.md.

Also new since the last published version: the Meta login reaches the Muse Code subscription, Muse Wonder Full included. Signing in with Meta (method device) sends requests to the address Meta names when it hands out the key, and only to an https meta.ai address. Read before using it: the method stays unverified (allow_unverified), because Meta's Model API terms (2026-10-09, § 3.2) reserve this key for Muse Code; using it from lm15 puts the Meta account at risk, and subscription traffic is in Contributor mode by default (Meta may train on it).

Contract 10645e5: 1,911 of 1,911 checks, the same commit as Python 1.4.0, TypeScript and Rust 1.0.0-rc.9, Go v1.1.0-rc.8, Julia 1.0.0 and R 1.3.0.

lm15 1.0.0-rc.11

lm15 1.0.0-rc.11 Pre-release
Pre-release

Choose a tag to compare

@MaximeRivest MaximeRivest released this 15 Oct 23:16

A release candidate.

Bounding boxes: ask where something is in a picture. A new judgment helper (bounding_box in Python, Rust, Julia and R; boundingBox in TypeScript; BoundingBox in Go) declares a question whose answer is four numbers, top, bottom, left and right, each a fraction of the picture measured from its top-left corner. Multiply by the picture's size in pixels to draw or crop. On TypeSafe it is Jev 1.14's new Box question (use jev-preview or jev-1.14.0; jev-latest is still 1.13 and answers "Invalid request."), and lm15 sends TypeSafe's TypeSafe-Danger-Zone-Images: true opt-in header for you. On OpenAI, Anthropic, Gemini and the others the same request is ordinary structured output, so one program runs everywhere; how close the box is depends on the model (on one test picture: Jev 1.14 and GPT-5.6 Luna within 0.04, Claude Sonnet 4.5 and Gemini 2.5 Flash about 0.15, Claude Haiku 4.5 off by 0.3 to 0.4). A box is always four edges, even when the thing is absent: ask a yes/no beside it. A box has no probabilities, so probabilities concerns only the other judgments. TypeSafe calls images and boxes beta. Rules in lm15-contract changes/2026-10-15-bounding-box.md.

Contract 6dc1c5c: 1,921 of 1,921 checks, the same commit as Python 1.5.0, TypeScript and Rust 1.0.0-rc.11, Go v1.1.0-rc.10, Julia 1.0.0 and R 1.4.0.

lm15 1.0.0-rc.10

lm15 1.0.0-rc.10 Pre-release
Pre-release

Choose a tag to compare

@MaximeRivest MaximeRivest released this 15 Oct 16:28

A release candidate.

A saved cloud login's settings are used. A cloud login saved with its host settings (for example configure("vertex-anthropic", method="cloud", answers={"named": "cli"}, settings={"project": "my-project", "location": "us-east5"})) now sends that project and location. Before, the router ignored them and fell back to the environment and the defaults, so a Vertex login saved with its project failed with "setting 'project' is required" or quietly used GOOGLE_CLOUD_PROJECT and the global location. A router settings entry for the same provider changes only the settings it names; an explicit key or named identity uses no saved login, so none of its settings apply. Found and fixed in lm15-python by Rolf Fredheim (lm15-dev/lm15-python#21). Rules in lm15-contract changes/2026-10-15-saved-cloud-settings.md.

Contract 1cbdc12: 1,912 of 1,912 checks, the same commit as Python 1.4.1, TypeScript and Rust 1.0.0-rc.10, Go v1.1.0-rc.9, Julia 1.0.0 and R 1.3.1.