Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .dialyzer_ignore.exs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
# defensive guard: ReqLLM.Response types provider_meta as map() with a %{}
# default, but the struct does not enforce it (a caller can build one with
# nil), and ReqLLM's own OpenTelemetry attributes guard it with is_map/1.
{"lib/imp/clients/req_llm.ex", :guard_fail, 1917},
{"lib/imp/clients/req_llm.ex", :guard_fail, 1919},
# defensive error clause on an always-ok internal call
{"lib/imp/clients/training.ex", :pattern_match, {1215, 13}},
# defensive error clause on an always-ok internal call
Expand Down
70 changes: 44 additions & 26 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

User-visible changes to Imp are recorded here.

## Unreleased
## 0.7.0 — 2026-09-28

### Changed

Expand All @@ -16,28 +16,53 @@ User-visible changes to Imp are recorded here.
provider key reports OpenRouter's fee plus the upstream charge, or `nil`
when the upstream charge is missing. A call to any catalog-priced provider
that reports no charge (Anthropic, OpenAI, Google, Groq, xAI and others)
now has a `nil` cost where it had the estimate. The estimate is the new `estimated_cost` field on
`Imp.Core.LMResponse` and `:estimated_cost` on the `:model_response` event;
`billing` is the breakdown behind that estimate, as it always was, and its
docs now say so. Migration: a host that sums `cost` treats `nil` as an
unknown charge, not a free one; a host that wants the old number for calls
with no reported charge reads `estimated_cost` for them, knowing it is an
estimate.
now has a `nil` cost where it had the estimate. The estimate is the new
`estimated_cost` field on `Imp.Core.LMResponse` and `:estimated_cost` on
the `:model_response` event, `nil` for a streamed call; `billing` is the
breakdown behind that estimate, as it always was, and its docs now say so.
Migration: a host that sums `cost` treats `nil` as an unknown charge, not a
free one; a host that wants the old number for calls with no reported
charge reads `estimated_cost` for them, knowing it is an estimate.
- Breaking: a stream from `Imp.Clients.ReqLLM` that did not complete ends in
`{:error, %Imp.LMError{}}`, not in a completion of whatever text arrived
first: one that carries a provider error, finishes with reason `:error` or
`:cancelled`, or is incomplete, its body ending with no finish and no
`[DONE]` (reason `{:stream_finished, :incomplete}`). The error event
carries the usage and other metadata that arrived before it. Migration: a
caller of `Imp.stream/3` with `provider_stream: true` handles that error as
any failed call; the request reached the provider, so it may have been
charged.
- Breaking: a call streamed through `Imp.Clients.ReqLLM` records the
configured model id without its provider prefix (`gpt-test`, where it
recorded the whole `openai:gpt-test`) in the `req_llm` metadata of its
`:model_response` event's `:response`, and its `Imp.Usage` entry is keyed
`"openai/gpt-test"`. In 0.6.0 a real streamed call had no `Imp.Usage`
entry at all.
A call to a client built from a string-keyed spec map or a
`{provider, opts}` or `{provider, model, opts}` tuple, streamed or not,
records its provider, where it recorded none, so its `Imp.Usage` key gains
the `"provider/"` prefix. Migration: a host that matched the
provider-prefixed model of a streamed call matches the model id, and one
that reads `Imp.Usage` by key for such
a client uses the prefixed key.
- Breaking: `Imp.Predict.ReActV2`'s `:last_request_note` reaches the model
as a user message with no assistant reply after it, as documented, in the
last request and whenever the returned history is passed back. It was
stored as a history entry with inputs and no outputs. The chat adapter
renders that as a finished exchange, so the note was followed by an
assistant message the model never gave, each field reading "Not supplied
for this conversation history message." That filler is Imp's; DSPy 3.2.1
for this conversation history message." Using that filler for history is
Imp's; DSPy 3.2.1
renders a missing history output as `None`. The note, and the entry for
inputs no step spent that comes before it when the first step failed, now
carry `tool_calls: %Imp.Adapter.Types.ToolCalls{tool_calls: []}` and
`tool_call_results: []`, like every other step the loop records. Migration: a
host that recognises the note in `metadata.history` by its shape (only the
first input's key) matches it by that key with an empty `tool_calls` list,
or by its text. A note recorded by an earlier version keeps the old
shape and still renders with the filler. `History` entries a host writes
shape and still renders with the filler; to render it as this release
does, add `tool_calls: %Imp.Adapter.Types.ToolCalls{tool_calls: []}` and
`tool_call_results: []` to that entry. `History` entries a host writes
keep Imp's rendering.
- In written tool mode (an LM that cannot call tools natively, where earlier
steps replay as text), a stored step that recorded no call and no other
Expand All @@ -56,28 +81,21 @@ User-visible changes to Imp are recorded here.
with exactly one terminal event, `done: true` with the provider's usage
(including its `"cost"`), model and finish reason, taking the finish
reason, and usage no chunk reported, from ReqLLM's metadata handle.
- A stream that did not complete ends in `{:error, %Imp.LMError{}}`, not in
a completion of whatever text arrived first: one that carries a provider
error, finishes with reason `:error` or `:cancelled`, or is incomplete,
its body ending with no finish and no `[DONE]` (reason
`{:stream_finished, :incomplete}`). The error event carries the usage and
other metadata that arrived before it.
- A streamed call that fails after the provider reported usage records that
usage and cost on its failed `:model_response` event and in
`Imp.Usage`, since the provider may have charged for it. The caller still
usage and cost on its failed `:model_response` event, since the provider
may have charged for it. The caller still
receives `{:error, reason}`.
- A streamed call to a client built from an inline spec map, with atom or
string keys, or a `{provider, opts}` tuple no longer fails with
`{:lm_stream_failed, "protocol String.Chars not implemented ..."}`. A
streamed call records the model the provider reported, as a non-streamed
call does, and otherwise the configured model id (`gpt-test`, where it
recorded the whole `openai:gpt-test`), so its `Imp.Usage` key changes the
same way.
string keys, or a `{provider, opts}` or `{provider, model, opts}` tuple no
longer fails with
`{:lm_stream_failed, "protocol String.Chars not implemented ..."}`.
- `:reasoning_effort` accepts `max`, when an LM is built, on a call and in a
saved program. Imp's accepted efforts are read from ReqLLM's own
`reasoning_effort` option, so they are every effort ReqLLM accepts, on every
provider; ReqLLM's provider maps or clamps it to what that provider's API
takes. OpenRouter receives `"max"` in either wire field.
provider. ReqLLM's provider translates it where it has a translation
(Anthropic and Google turn it into a thinking budget); OpenAI, OpenRouter,
Groq and xAI receive the effort as written, and it is the provider's to
accept. OpenRouter receives `"max"` in either wire field.
- A string effort such as `"high"` reaches ReqLLM as its atom. ReqLLM checks
the effort against its atom list before any provider sees it, so in 0.6.0 a
string effort, including every effort loaded from a saved program, failed
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ text or JSON you can score, such as an agent's tool descriptions.
## Install

```elixir
{:imp, "~> 0.6"}
{:imp, "~> 0.7"}
```

Imp needs Elixir 1.19 or later and a C and C++ compiler, for the native code
Expand All @@ -154,15 +154,15 @@ access, because erlexec's build fetches rebar3 plugins. It reaches models throug
[ReqLLM](https://hex.pm/packages/req_llm), so any provider ReqLLM supports
works.

Imp 0.6 is experimental. Its API may still change, and its optimizers need
Imp 0.7 is experimental. Its API may still change, and its optimizers need
large-scale benchmarking. Bug reports and pull requests are welcome.

## Learn

- [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.6.0/livebooks) are Livebook notebooks
- [Tutorials](https://github.com/deepfates/imp/tree/v0.7.0/livebooks) are Livebook notebooks
you can run offline or with a key.
- The [cheatsheet](docs/cheatsheet.cheatmd) has the common calls on one page.

Expand Down
Loading
Loading