diff --git a/CHANGELOG.md b/CHANGELOG.md index 12116089..833086c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ User-visible changes to Imp are recorded here. ## Unreleased +## 0.16.1 — 2026-10-17 + ### Fixed - ReActV2 under `finish: :submit` or `finish: :tools` replays a stored turn diff --git a/README.md b/README.md index 70571e93..c030afa1 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ large-scale benchmarking. Bug reports and pull requests are welcome. - [Getting started](docs/getting-started/index.md) builds one program step by step, from the first call to a supervised server, with real scores. - [Coming from DSPy](docs/coming-from-dspy.md) maps DSPy's names to Imp's. -- [Tutorials](https://github.com/deepfates/imp/tree/v0.16.0/livebooks) are Livebook notebooks +- [Tutorials](https://github.com/deepfates/imp/tree/v0.16.1/livebooks) are Livebook notebooks you can run offline or with a key. - The [cheatsheet](docs/cheatsheet.cheatmd) has the common calls on one page. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e0a2d4f1..f9d336e2 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,13 +1,13 @@ -# Imp v0.16.0 +# Imp v0.16.1 Imp is a framework for typed, optimizable language-model programs on the BEAM. -This release adds `request_retry:` to `Imp.Run.start/3`, which sends a model -request the provider failed in a way that may pass again in the same run, after -a wait. It also changes two things a caller may rely on: an LM client other -than ReqLLM reports its charge as metadata `:cost`, because `Imp.Core` no longer -reads it from the usage map; and a ReActV2 forced `submit` with invalid -arguments goes to the extractor. A new option and changed behaviour make this -a minor release under Imp's versioning rule. +This release fixes how ReActV2 replays a history turn that a host stored with +the task's inputs and its answer, and no step of the loop's own. Under +`finish: :submit` and `finish: :tools`, since 0.14.0, such a turn replayed only +its inputs: the model read the event and not the answer stored with it. It now +replays as the model's message holding that answer, as it always has under +`finish: :auto`. It adds no options and changes no public API, so under Imp's +versioning rule this is a patch release. ## Install @@ -29,44 +29,29 @@ in its release definition; see [releases that use MCP or ACP](docs/production.md#releases-that-use-mcp-or-acp). Ordinary Imp startup starts no protocol endpoint. -## Changes and upgrading from 0.15 +## Changes and upgrading from 0.16.0 -Change the Imp dependency to `{:imp, "~> 0.16.0"}`, run `mix deps.update imp`, -and commit `mix.lock`. No data migration is needed. Check the second item if -you use an LM client of your own, or build response envelopes by hand. +Under `{:imp, "~> 0.16.0"}`, run `mix deps.update imp` and commit `mix.lock`. +No code or data migration is needed. -- **Added: `request_retry:` on `Imp.Run.start/3`.** `request_retry: [waits: - [ms, ...], reserve: ms]` sends a model request that failed with an error - `Imp.Errors.retryable?/1` accepts (408, 425, 429, 5xx, a failed connection) - again inside `Imp.LM.request/2`, after each wait in turn. Under a deadline - the last wait repeats while it leaves `reserve` (60 s by default) of the - deadline; without one, the list is the bound. A provider's `retry-after` - longer than the wait is waited instead. Each attempt is its own - `:model_request` and `:model_response`, and a failed response followed by - another attempt carries `retry_in_ms`. Only the outermost request in a - process retries, so an LM that wraps an LM does not multiply attempts. - Streamed requests are not sent again. `waits` must not be empty. Off unless - given. A timed-out attempt may have been finished and billed by the - provider without a response to record, so a retry can be billed twice; - under a deadline each attempt is capped to the time left. -- **Changed: where a call's charge is read.** OpenRouter bring-your-own-key - billing moved into `Imp.Clients.ReqLLM`, which records the charge it - resolves as `:charge` in its `req_llm` response metadata. `Imp.Core` reads - `LMResponse.cost` from that `:charge` (final when present, `nil` included), - and otherwise from metadata `:cost`; it no longer reads usage `"cost"` or - `"is_byok"`. An LM client other than ReqLLM, or a hand-built response - envelope, must report its charge as `:cost` in its metadata (or - `req_llm.charge`), or its cost is `nil`. -- **Fixed: a forced `submit` with invalid arguments is extracted.** A ReActV2 - forced `submit` whose arguments fail validation now goes to the typed - extractor and ends `:extracted`, where it ended `:incomplete` with no - outputs. If that extraction request fails with an LM error, the call - returns `{:error, %Imp.Predict.ReActV2.StepError{}}`, where it returned - `{:ok, prediction}` ending `:incomplete`. A forced `submit` the tool policy - refuses still ends `:incomplete`. +- **Fixed: a stored answer turn replays its answer under every ending.** + ReActV2 reads a stored turn that carries the inputs and the one output of + the task, and neither `next_thought` nor `tool_calls`, as a step that + answered in text. Since 0.14.0 it did so only when the program ended in + plain text (`finish: :auto`); under `finish: :submit` and `:tools`, with + either `history_replay`, the request held the turn's inputs as a user + message and no assistant message after them. A host that writes such a + turn, for example a recap standing in for the earlier conversation, had + its content left out of every request. The turn now replays as one + assistant message holding the answer, with no "not sent" line after it, + since it carries the task's output. Turns the loop writes itself are + unchanged, and the history the caller holds is unchanged. Under + `finish: :auto`, a one-output signature whose output is constrained or not + typed string gets the same replay when the stored value is a string. -Rollback means restoring `0.15.0` in the lock and removing `request_retry:`. -See the [changelog](CHANGELOG.md) for every change. +Rollback means restoring `0.16.0` in the lock; such turns then replay their +inputs alone again under `finish: :submit` and `:tools`. See the +[changelog](CHANGELOG.md) for every change. ## Known limits diff --git a/docs/coming-from-dspy.md b/docs/coming-from-dspy.md index ad4307e9..1de95752 100644 --- a/docs/coming-from-dspy.md +++ b/docs/coming-from-dspy.md @@ -87,7 +87,7 @@ error row too. Each model request has a receive timeout, and has no time limit of its own (an MCP tool call does, 30 seconds by default), so to bound an agent, start it with `Imp.start_run/3` and cancel it when you choose. The -[deployment example](https://github.com/deepfates/imp/blob/v0.16.0/examples/deployment/README.md) +[deployment example](https://github.com/deepfates/imp/blob/v0.16.1/examples/deployment/README.md) is a complete OTP application. **Optimizers see what you name.** DSPy finds predictors by walking a module's diff --git a/docs/diving-deeper/modules-and-composition.md b/docs/diving-deeper/modules-and-composition.md index 4521f171..d1b8c95b 100644 --- a/docs/diving-deeper/modules-and-composition.md +++ b/docs/diving-deeper/modules-and-composition.md @@ -149,7 +149,7 @@ A custom module is your code, so it is not saved as a whole. What an optimizer chose for it is data: `Imp.ProgramParameters.values/1` reads it, `Imp.ProgramParameters.apply_values/2` puts it on a freshly built program, and `Imp.Optimizer.Artifact` writes it to a checksummed file. The -[deployment example](https://github.com/deepfates/imp/blob/v0.16.0/examples/deployment/README.md) +[deployment example](https://github.com/deepfates/imp/blob/v0.16.1/examples/deployment/README.md) does this in a supervised application. ### Built-in variants diff --git a/docs/diving-deeper/saving-and-artifacts.md b/docs/diving-deeper/saving-and-artifacts.md index ebd689d3..1d9c2968 100644 --- a/docs/diving-deeper/saving-and-artifacts.md +++ b/docs/diving-deeper/saving-and-artifacts.md @@ -21,7 +21,7 @@ Save the whole program when it is made of Imp's modules: `Imp.predict/2`, when the program is your own struct implementing `Imp.Module`, or when you want the program's code to live in your release and only its tuned text to cross the persistence boundary. The -[deployment example](https://github.com/deepfates/imp/tree/v0.16.0/examples/deployment) +[deployment example](https://github.com/deepfates/imp/tree/v0.16.1/examples/deployment) does the second. ### 2. JSON with a checksum diff --git a/docs/getting-started/running-in-your-application.md b/docs/getting-started/running-in-your-application.md index e2807cdc..ad28fb08 100644 --- a/docs/getting-started/running-in-your-application.md +++ b/docs/getting-started/running-in-your-application.md @@ -121,7 +121,7 @@ Changing models is then a configuration change, and rotating a key is a restart. The -[deployment example](https://github.com/deepfates/imp/blob/v0.16.0/examples/deployment/README.md) +[deployment example](https://github.com/deepfates/imp/blob/v0.16.1/examples/deployment/README.md) is the complete version of this page: a two-stage program like `TicketTriage`, learned parameters loaded and reloaded as an `Imp.Optimizer.Artifact`, crashed and timed-out calls contained, and the whole thing restarted in a fresh OS diff --git a/docs/getting-started/where-to-go-next.md b/docs/getting-started/where-to-go-next.md index 62fa74aa..c00b466f 100644 --- a/docs/getting-started/where-to-go-next.md +++ b/docs/getting-started/where-to-go-next.md @@ -51,7 +51,7 @@ The module documentation is the reference for every function. [Running Imp in production](../production.md) covers what the last page began: supervision, provider failures, runs you can observe and cancel, and telemetry. The -[deployment example](https://github.com/deepfates/imp/blob/v0.16.0/examples/deployment/README.md) +[deployment example](https://github.com/deepfates/imp/blob/v0.16.1/examples/deployment/README.md) is a complete application to copy from. ## Try it in a notebook diff --git a/docs/production.md b/docs/production.md index 441d9f41..8b5c1356 100644 --- a/docs/production.md +++ b/docs/production.md @@ -5,7 +5,7 @@ where its credentials come from, how long a call may take and what it may cost. This page covers those decisions in the order you meet them: starting Imp, credentials, concurrency, timeouts, cost and caching, persistence and telemetry. The -[deployment example](https://github.com/deepfates/imp/tree/v0.16.0/examples/deployment) +[deployment example](https://github.com/deepfates/imp/tree/v0.16.1/examples/deployment) is a complete OTP application that puts them together. ## Imp in your supervision tree @@ -263,7 +263,7 @@ standard error. ## The reference application -[`examples/deployment`](https://github.com/deepfates/imp/tree/v0.16.0/examples/deployment) +[`examples/deployment`](https://github.com/deepfates/imp/tree/v0.16.1/examples/deployment) is a small OTP application that loads a checksummed artifact at startup, reads its key from the environment, serves calls from bounded supervised tasks, answers overload and timeouts with errors instead of blocking, and reloads diff --git a/livebooks/05_operating_imp.livemd b/livebooks/05_operating_imp.livemd index 58f189a5..6fd60705 100644 --- a/livebooks/05_operating_imp.livemd +++ b/livebooks/05_operating_imp.livemd @@ -103,7 +103,7 @@ Enum.each(busy, &Task.await/1) refused ``` -The [deployment example](https://github.com/deepfates/imp/tree/v0.16.0/examples/deployment) +The [deployment example](https://github.com/deepfates/imp/tree/v0.16.1/examples/deployment) wraps the same pattern in a `ProgramServer` that also reloads parameters while serving. diff --git a/mix.exs b/mix.exs index a0f291cc..19aa7c82 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Imp.MixProject do use Mix.Project - @version "0.16.0" + @version "0.16.1" def project do [ diff --git a/priv/public_api.json b/priv/public_api.json index 65ccb546..80c3d221 100644 --- a/priv/public_api.json +++ b/priv/public_api.json @@ -10359,7 +10359,7 @@ "types": [] } ], - "package_version": "0.16.0", + "package_version": "0.16.1", "schema_version": 3, "scope": "Curated Imp API compiled from package-shipped sources. Exports come from non-hidden documentation entries; callbacks, types, and struct fields are category- and policy-gated." } diff --git a/test/package_contract_test.exs b/test/package_contract_test.exs index d5833ffe..316db6f2 100644 --- a/test/package_contract_test.exs +++ b/test/package_contract_test.exs @@ -114,7 +114,7 @@ defmodule PackageContractTest do version = Mix.Project.config()[:version] dependency = hex_dependency(version) - assert version == "0.16.0" + assert version == "0.16.1" assert File.read!("RELEASE_NOTES.md") =~ "# Imp v#{version}" assert File.read!("CHANGELOG.md") =~ "## #{version}" assert File.read!("examples/deployment/mix.exs") =~ dependency