最新资讯
dev.to 在信任 AI 生成的测试用例前,如何逐条复核 unite.ai Franklin Templeton 构建 AI 研究工作流以揭示风险 techxplore.com AI 助力寻找更优的钛合金 3D 打印工艺参数 engadget.com Perplexity 推出“teammates”日常工作任务 AI agent computerworld.com 别提数据中心:OpenAI 改称“compute campuses” theguardian.com 俄美试图阻止就自主武器制定具有约束力的规则 bloomberg.com Nvidia 加强东南亚芯片追踪以打击走私 thestack.technology 模型路由需求升温,Agent Router 推出开源替代方案 www.youtube.com Google DeepMind 的 Philipp Schmid:AI 同事需要具备的七个要素 unite.ai Cashfree 为 AI Agent 的托管结账加入 WebMCP 工具 dev.to 我们在 DolphinBench 上测试记忆检索:纯关键词搜索暴露了自身回归 dev.to 别再把 Agent 写成 while 循环,把它建模为状态机
多元动态
登录 注册
智能体 github.com · @deepfates

Imp v0.5.0 发布:BEAM 上的类型化 LLM 程序框架上架 Hex

原文 我的收藏

Imp 是运行在 BEAM 上的类型化、可优化语言模型程序框架,v0.5.0 正式发布到 Hex。主要变化包括:GEPA 优化器可作用于 Imp.react agent,通过读取完整运行轨迹与工具定义重写 agent 指令;Imp.Deadline 会传播到 Imp.parallel/3、评估行、优化器 worker 和 run;依赖 Hex 上未打补丁的 ExMCP 1.5,原先 fork 所需能力已并入 Imp;ReActV2 仅在签名需要时提供 submit,并新增 finish_on;:model_request 记录完整请求,工具定义以 :tools_sent 每次运行发送一次。发布说明还提到 mix deps.get 与 mix hex.audit 会报告两个 cowlib 安全公告(CVE-2026-43966、CVE-2026-43969),其中第一个可由 Cowboy 2.16.0+ 缓解,第二个尚无修复版本。

AI推荐理由 官方 release notes,信息密度高且具体:Imp 上架 Hex、GEPA 可优化 Imp.react agent、Deadline 传播、ReActV2 的 submit/finish_on 行为变化、依赖 ExMCP 1.5 及 cowlib 两个 CVE 说明。局限是面向 Elixir/BEAM 生态的小众框架,未提供性能数据或采用情况,安全公告也仅描述依赖层问题而非 Imp 自身漏洞。

62

正文

Official release notes (https://github.com/deepfates/imp/releases/tag/v0.5.0) # Imp v0.5.0 Imp is a framework for typed, optimizable language-model programs on the BEAM. Declare a task as named inputs and outputs, call it like any other Elixir program, measure it on examples, compile it with an optimizer, and run the selected program under OTP. This release puts Imp on Hex. It is `0.5.0` rather than a patch because the install line changes, an OTP release that uses the protocol adapters lists one more application, and ReActV2 and `Imp.MCP.OAuth` change shapes a program may depend on. ## Install ```elixir {:imp, "~> 0.5"} ``` Every dependency comes from Hex. Use a path dependency only while developing against a local checkout. ExMCP and erlexec are declared `runtime: false`, so an OTP release that uses `Imp.ACP` or `Imp.MCP` must list `applications: [ex_mcp: :load, erlexec: :load]` 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. `mix deps.get` and `mix hex.audit` report two cowlib advisories (CVE-2026-43966, CVE-2026-43969). cowlib arrives only through ExMCP's Cowboy server, and Imp's HTTP goes through Req, Finch and Mint. The first is fixed one layer up: Cowboy 2.16.0 and later refuse a response header containing CR or LF, and a fresh `mix deps.get` resolves Cowboy 2.19.0. The second is in the encoder for an outgoing `Cookie` request header, which nothing in Imp's dependency tree calls, and no cowlib release fixes it yet. ## Headline changes - GEPA works on agents. Optimizing an `Imp.react` agent, the reflection model reads the whole run (tool calls, tool results, the final answer) and the agent's tools, and GEPA rewrites the agent's instruction. By default `Imp.Optimizer.GEPA` behaves as DSPy's GEPA does; Imp's own search is `execution_profile: :beam_native`. - An `Imp.Deadline` reaches the work Imp starts for you: `Imp.parallel/3`, evaluation rows, optimizer workers and runs inherit the caller's deadline, and `Imp.start_run/3` takes `deadline:`. - Imp depends on ExMCP 1.5 from Hex, unpatched. What Imp needed from the `deepfates/ex_mcp` fork now lives in Imp: stdio MCP servers that end with their connection, children included; a clean `PATH` for them inside a release; trust for authorized remote servers; the connection options public servers need; and the browser OAuth flow. - `ReActV2` offers `submit` only to a signature that needs one. A task with exactly one text output ends its turn on a step that answers in text, and an interrupted turn makes one last request whose text is the answer instead of failing. - `ReActV2` gains `finish_on` for tools whose call is the answer. - `:model_request` events record the whole request, and tool definitions are emitted once per run as `:tools_sent`. - An MCP tool call that got no answer says whether it was refused, had its credential refused, was never sent, or may have run (`Imp.MCP.CallFailure`, `Imp.Tool.outcome/1`), an error result that declares its outcome is read as declared, and a failed tool call reaches the model as plain text. - A ReAct prediction's fields are its outputs; how the turn ended is metadata, in one vocabulary, with `Imp.Prediction.complete?/1`. - `Imp.Run` and `Imp.ACP` refuse options they do not know, and `Imp.Run.Event.kinds/0` lists every event kind. - A host names its own run pool and limit (`Imp.Run.start/3`'s `:admission`), and a failing run event sink is reported to the run's owner. - `Imp.MCP.connect/2` takes `pool_size:`, so several calls to one HTTP server run at once, and an HTTP call can take as long as its `:timeout` allows. - A run no longer outlives its control process, and a cancellation that never returns no longer holds a run. ## Breaking changes from v0.4.0 - Replace `{:imp, github: "deepfates/imp", tag: "v0.4.0"}` with `{:imp, "~> 0.5"}`. `EX_MCP_PATH` is no longer read. - A release that uses `Imp.MCP` or `Imp.ACP` adds `erlexec: :load` beside `ex_mcp: :load`. - Trust for an authorized remote MCP server is VM-wide. While a connection to it is open, its exact origin (`scheme://host:port`) is in ExMCP's `trusted_origins`, so any ExMCP client in the same VM may send credential headers to that origin without consent. In 0.4.0 the trust belonged to the one connection. No other origin is trusted, the origin is removed when the last connection to it closes, and origins the host configured are left alone. A host that runs other ExMCP clients it does not trust with those origins should know this. - `Imp.Optimizer.GEPA` defaults to DSPy's GEPA, `execution_profile: :gepa_v0_1_4_merge`: merge on, no evaluation cache, perfect minibatches skipped, the pinned RNG, and `:generations` turned into a metric budget when `:max_metric_calls` is not given. Options the DSPy profiles fix (ComBee, `:feedback_fn`, `:module_selector`, `:candidate_selection_strategy`, `:proposal_concurrency`, `:reflection_strategy`, the frontier, sampling, selection, evaluation and acceptance policies, `:max_reflection_calls`, and `reflection_record_mode: :beam_native`) raise unless `execution_profile: :beam_native` is given, which is the 0.4.0 behaviour. Resuming a checkpoint written by a 0.4.0-default run raises under the new default; resume it with `execution_profile: :beam_native`. - `Imp.MCP.OAuth.begin/3` no longer takes `:flow`; a pre-registered client is `client_registration: {:pre_registered, client_id, client_secret}` with `client_issuer:` naming the authorization server it belongs to. A server with no OAuth metadata at all is refused instead of given guessed endpoints. - An `Imp.Tool` named with a string keeps the string, and tools imported from an MCP server are named by the server's string. Code that compared an imported tool's `name` to an atom compares it to the string. - An MCP tool call that got no answer returns `{:error, %Imp.MCP.CallFailure{}}` instead of `{:mcp_tool_call_failed, server, reason}` or `{:mcp_connection_unavailable, server, reason}`. A call that reaches its `:timeout` is `%Imp.MCP.CallFailure{outcome: :unknown, reason: :timeout}`, answered at the timeout while the request runs on; a call to an HTTP server whose connections all stay busy until the timeout is `:not_sent` with `reason: :no_idle_connection`. - `"type" => "sse"` is MCP's deprecated HTTP+SSE transport, and its `"url"` is the event stream's. In 0.4.0 it was Streamable HTTP with a standing GET stream; a Streamable HTTP server is now `"type" => "http"`. An `sse` descriptor with `"headers"` or `"auth"`, or with a query string in its URL, is refused before anything is dialed (`:mcp_sse_credentials_refused`, `:mcp_sse_url_refused`): the whole import under the default `on_failure: :refuse`, only that server under `on_failure: :drop`. - When a run's control process ends while the run is still going, the task is killed after its registered cancellations are called; its monitor reports `:killed`. - A run's owner can receive `{:imp_run_event_sink_failed, run_id, details}` and `{:imp_run_event_undelivered, run_id, event}`; an owner with a strict `handle_info/2` needs clauses for them. - `Imp.Run.start/3`, `Imp.ACP.start_link/1`, `Imp.ACP.run/1` and `Imp.ACP.Local.start_link/1` raise `ArgumentError` for an option they do not know. A transport's own options for `Imp.ACP` go in `:transport_options`, and `:capabilities` is spelled `:agent_capabilities`. - `Imp.predict/2`, `Imp.chain_of_thought/2` and `Imp.configure/1` raise `ArgumentError` for an option or setting they do not know. Request options such as `:temperature` go under `config:`; a setting of your own goes through `Imp.context/2`. - `:max_errors` and `:retriever` are no longer settings, and `Imp.configure/1` and `Imp.context/2` refuse them. Pass `:max_errors` to BootstrapFewShot, RandomSearch or COPRO (10 when not given) and a retriever to the program. - ReActV2 emits no `:final` event; `:run_finished` carries the prediction. `Imp.Trajectory.to_atif/2`'s `extra.outcome` is `extra.terminal_event`, and a tool result's `extra.outcome` is the recorded `Imp.Tool.outcome/1` instead of `"returned"` or `"error"`. - A ReActV2 or ReAct prediction's fields are its outputs only: `history`, `termination_reason`, `termination_cause`, `termination_error`, `finished_by_tool`, `unexecuted_tool_calls` and `context_projection` are in `prediction.metadata`. `termination_reason` says how the turn ended, and a turn without an answer is `:incomplete` with `termination_cause` saying why; typed extraction is `:extracted` (no `completion_mode`), and `Imp.Predict.ReAct` spells `:parse_failure` as `:parse_error` and `:direct` as `:answered`. Use `Imp.Prediction.complete?/1` to ask whether a turn answered. - For a signature with one `:string` output, `ReActV2` offers no `submit` tool, and a step answered in text with no tool call ends the turn. - Errors have one shape per tag, with the reason as a term. A failed `Imp.Clients.ReqLLM` request is `%Imp.LMError{}` (with `status`, `retryable` and `context_window_exceeded`; `Imp.ContextWindowExceededError` is gone), and a completion that cannot be parsed is `%Imp.AdapterParseError{kind: ...}`, which `Imp.Predict` returns directly instead of `%{reason: {:error, _}, trace: _}`. A raise inside a client, program, tool, tool policy, retriever, optimizer or ACP callback keeps the exception struct where 0.4.0 kept its message. `{:tool_denied, tool}` is `{:tool_denied, tool, :tool_policy}`, and a run's `:authorize` refusal is `{:tool_denied, tool, reason}`; `Refine` and `Assertions` return `{:error, reason}`; `Imp.optimize!` raises `Imp.Error` for a failed optimization. The CHANGELOG lists every tag that changed. - `Imp.Example` and `Imp.Prediction` keep string keys as strings. Code that read a field of data loaded from JSON with `map.field` or `map[:field]` reads it with `Imp.Example.get/2` or by its string key. - `Imp.MCP.Client`, `Imp.MCP.HTTPClient`, `Imp.MCP.StreamableHTTPClient`, `Imp.MCP.StdioClient`, `Imp.MCP.Catalog`, `Imp.MCP.import_tools`, `Imp.ACP.MCP` and `Imp.Core.ToolCall`/`ToolResult` are gone. `Imp.MCP.connect/2` imports tools; `Imp.ACP.ToolKind.derive_all/1` gives an import's ACP tool kinds. - A saved program holds no HTTP header, credential or not. An LM with custom headers (a routing header such as `x-tenant` included) sends requests without them after loading until it is rebound with `Imp.with_lm/2` or a scoped `Imp.context/2`. - `Imp.save!` refuses an LM whose `base_url` has a query, fragment or user info. - Prompts name types in plain words instead of Python annotations (`one of: atlas, harbor` where 0.4.0 wrote `Literal['atlas', 'harbor']`), values take their JSON spelling (`null`, `true`, `false`), and the structured-output schema is named `outputs`. A non-string answer for a string field is kept as its JSON text (`"true"`, not `"True"`), and a `null` answer is no value rather than the string `"None"`. Fields, order and parsing are unchanged, but a saved optimized program now sends different prompt text. - An `Imp.Telemetry` span's `[:exception]` event carries `:kind`, `:reason` and `:stacktrace`, as `:telemetry.span/3` does, instead of `:error` as text. - An optimizer's `compile/N` is no longer documented where `Imp.optimize` or `Imp.train` runs the optimizer; call those. - `Imp.load!/1` reading a file is `Imp.read!/1`; `Imp.load/1` returns `{:ok, program}` and `Imp.load!/1` takes the dumped map. - `Imp.react/3` builds ReActV2 and `Imp.react_v2` is gone. `Imp.Predict.ReAct`'s `mode: :dspy_3_2_1` is `mode: :dspy`. - `Imp.Predict.Predict` is `Imp.Predict`. - `Imp.Retrievers.KNN` is deleted; `Imp.Retrieve.Memory` retrieves by token overlap. - An LM is a struct or module whose `generate/3` takes it first. The `%{module:, opts:}` map and a bare function are refused; so is a module that defines only `generate/2`. A retriever module's `retrieve/3` takes itself first. - `Imp.MCP.CallFailure` has `server_name`, `tool_name` and `index`; an `unavailable` entry has `server_name`; `:authorize` returns `:allow` or `{:deny, reason}` and its context names the `descriptor`; `:credentials` is `:credential_store`. - A `:tool_policy` function returns `:allow` or `{:deny, reason}`, and a refused call is `{:tool_denied, name, reason}`. - `max_concurrency` is `num_threads` on evaluation, parallel, search, batch and optimizer options. `Refine`'s `max_attempts` is `n`; `RLM` takes `max_iterations` only. `Imp.Optimizer.RandomSearch` and `BootstrapRS` are `Imp.Optimizer.BootstrapFewShotWithRandomSearch`. ## Upgrade path 1. Change the dependency line, run `mix deps.get`, and commit `mix.lock`. 2. Add `erlexec: :load` to any release that lists `ex_mcp: :load`. 3. Replace `OAuth.begin/3`'s `:flow` with `:client_registration` if you used it. 4. Match MCP call failures on `%Imp.MCP.CallFailure{outcome: ...}` (a 401 is `:auth_refused`), compare imported tool names as strings, and give run owners clauses for `:imp_run_event_sink_failed` and `:imp_run_event_undelivered`. 5. Read a ReAct or ReActV2 prediction's `history` and `termination_*` from `prediction.metadata`, and match `termination_reason` against the new values. 6. Change `"type" => "sse"` descriptors for Streamable HTTP servers to `"http"`. A server that needs credentials is reached over Streamable HTTP; an `sse` descriptor takes none. 7. Match LM failures on `%Imp.LMError{}` (or ask `Imp.Errors.retryable?/1` and `Imp.Errors.context_window_exceeded?/1`), parse failures on `%Imp.AdapterParseError{kind: ...}`, and exception reasons on the struct rather than its text. 8. Rename `Imp.react_v2` to `Imp.react`, `Imp.Predict.Predict` to `Imp.Predict`, and `Imp.load!(path)` to `Imp.read!(path)`; give custom LMs and retriever modules the `generate/3` and `retrieve/3` that take the client first, and wrap an LM function in a struct that implements `Imp.LM`. 9. Rename `max_concurrency:` to `num_threads:` where you configure evaluation, optimizers or parallel calls; answer `:authorize` and `:tool_policy` with `:allow` or `{:deny, reason}`; match a refused tool call as `{:tool_denied, name, reason}`; read `server_name` and `tool_name` from MCP failures and absences. Call `Imp.Signature.load!/1`, `Imp.History.load!/1`, `Imp.Optimizer.Report.load!/1` and `Imp.Clients.TrainingJob.load!/2` where you called `load`, and `Imp.Clients.TrainingJob.read!/2` where you read a checkpoint file, and the datasets' `read!` where you called their `load(path)`. 10. Rebind the LM of any loaded program that relies on custom headers, and move a `base_url` query, fragment or user info into configuration the host supplies at load time. 11. Update telemetry handlers for `[:exception]` to read `:kind`, `:reason` and `:stacktrace`. 12. Re-evaluate saved optimized programs on held-out data, since their prompt text changed, and run your application smoke test against the new release; a one-text-output ReActV2 program now ends turns differently. The [CHANGELOG](CHANGELOG.md) records every user-visible change in this release. Generated module documentation is the complete API reference. Start with `Imp`, `Imp.Signature`, `Imp.Module`, `Imp.Evaluate`, `Imp.Optimizer`, `Imp.ACP`, `Imp.MCP`, and `Imp.Telemetry`. Repository README (current repository documentation, not release changes) # Imp Declarative, self-improving language-model programs for Elixir. Imp is a full port of [DSPy](https://dspy.ai) to the BEAM. You describe what each language-model step takes and returns, choose how it thinks, and let an optimizer improve it against examples of what good looks like. You get signatures, modules, optimizers, agent loops and retrieval, running with the reliability and concurrency of OTP. DSPy makes each call to a model a declared, typed function that you can measure and improve. On the BEAM, an agent is a process: it keeps its own state, receives messages, and runs under a supervisor alongside the rest of your application. With both, you can build anything from one typed call to many long-running agents, and improve each part by measuring it. ## Declare a task ```elixir lm = Imp.req_llm("openai:gpt-5.4-mini", api_key: System.fetch_env!("OPENAI_API_KEY")) triage = "issue -> kind: enum[bug,feature,question], summary" |> Imp.signature("Triage a GitHub issue.") |> Imp.predict(lm: lm) {:ok, prediction} = Imp.call(triage, %{issue: "App crashes on startup since 0.4 with ** (KeyError) key :lm not found"}) {Imp.get(prediction, :kind), Imp.get(prediction, :summary)} #=> {"bug", "App crashes on startup since version 0.4 with a KeyError for `:lm` not found."} ``` You never write a prompt or a parser. Imp builds the prompt from the signature, checks the reply against it, and gives you typed fields: `kind` is always one of the three values, or the call returns an error. To make the same task reason first, use `Imp.chain_of_thought/2`; to give it tools, use `Imp.react/3`. The signature stays the same. ## Measure it and improve it Give Imp labeled examples and a metric, and it scores the program and optimizes it. You need three lists of issues you have already labeled: `trainset`, which the optimizer learns from; `valset`, which it uses to choose between the programs it tries; and `testset`, which you score on before and after. `strong_lm` is a more capable model that GEPA uses to read failures and write new instructions. ```elixir # Each set is a list of labeled issues like this one: example = Imp.example(%{issue: "Please add a dark mode to the dashboard", kind: "feature"}) |> Imp.with_inputs([:issue]) metric = Imp.exact_match(:kind) Imp.evaluate(triage, testset, metric).score optimizer = Imp.Optimizer.GEPA.new(metric, reflection_lm: strong_lm, max_metric_calls: 300) improved = Imp.optimize!(triage, optimizer, trainset, valset) Imp.evaluate(improved, testset, metric).score ``` GEPA runs the program, reads where it failed, and rewrites its instructions. Other optimizers choose worked examples (LabeledFewShot, BootstrapFewShot), search over combinations of instructions and examples (MIPROv2), learn rules and examples from the program's own better and worse attempts (SIMBA), or train the model's weights (fine-tuning, GRPO). The result is a new program whose instructions and examples you can read, save as JSON, and review as a diff. ## Build agents A tool is an Elixir function. `Imp.react/3` builds an agent that calls tools until it can answer. This one reads web pages with Req. Imp depends on Req; if your own code calls it, as this tool does, add `{:req, "~> 0.6"}` to your dependencies: ```elixir fetch = Imp.tool(:fetch, "Read a web page as text.", fn %{"url" => url} -> Req.get!(url).body end, schema: %{"type" => "object", "properties" => %{"url" => %{"type" => "string"}}, "required" => ["url"]} ) researcher = Imp.react("question -> answer", [fetch], lm: lm) question = "What version does https://raw.githubusercontent.com/elixir-lang/elixir/v1.18.0/VERSION say? " "Reply with just the version." {:ok, prediction} = Imp.call(researcher, %{question: question}) Imp.get(prediction, :answer) #=> "1.18.0" ``` ## Run agents as processes `Imp.call/2` runs a program in your process. `Imp.start_run/3` runs it as its own supervised process instead, so you can watch it, stop it, and decide which tool calls it may make: ~~~elixir {:ok, run} = Imp.start_run(researcher, %{question: question}, authorize: fn call -> url = call.arguments["url"] || "" if String.starts_with?(url, "https://raw.githubusercontent.com/"), do: :allow, else: {:deny, :untrusted_host} end ) {:ok, prediction} = Task.await(run.task, :infinity) for event [:run_started, :tools_sent, :model_request, :model_response, :tool_call, # :tool_result, :model_request, :model_response, :run_finished] ~~~ Imp also includes: - **MCP:** import the tools of any MCP server you approve, and they work like your own. - **ACP:** serve any Imp program as an agent to Zed and other ACP clients. - **OTP:** a run is a process you can watch, stop and limit, and a run ends when the process that started it does. Model requests are cut to a deadline you set. A tool call that may already have taken effect is reported as unknown, never silently retried. - **More shapes:** RLM for inputs far larger than a context window, CodeAct and program of thought, which compute with small sandboxed expressions, and your own modules composed from these. The optimizers work on agents too. GEPA reflects on whole agent runs and rewrites the instructions that steer them. Optimize Anything rewrites any text or JSON you can score, such as an agent's tool descriptions. ## Install ```elixir {:imp, "~> 0.5"} ``` Imp needs Elixir 1.19 or later and a C and C++ compiler, for the native code in two dependencies (jaxon and erlexec). The first compile needs network access, because erlexec's build fetches rebar3 plugins. It reaches models through [ReqLLM](https://hex.pm/packages/req_llm), so any provider ReqLLM supports works. Imp 0.5 is experimental and is its first release on Hex. 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.5.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. Imp is MIT licensed.

来源报道 · 1 篇 · 以下链接均前往原始媒体

← 返回精选动态 原文

更多内容