{"slug": "deepfates-imp-declarative-self-improving-language-model-programs-for-elixir", "title": "Deepfates/imp: declarative self-improving language-model programs for Elixir", "summary": "Deepfates released Imp, a full port of the DSPy language-model programming framework to the Elixir/BEAM ecosystem, letting developers declare typed model calls, agent loops and retrieval that run under OTP supervision. Imp provides signatures, modules, optimizers including GEPA, MIPROv2, SIMBA, LabeledFewShot and BootstrapFewShot, plus Imp.react/3 for tool-calling agents, and can save optimized programs as JSON for diff review. The library builds prompts and validates replies from declared signatures, so developers write no prompts or parsers.", "body_md": "Declarative, self-improving language-model programs for Elixir.\n\nImp is a full port of [DSPy](https://dspy.ai) to the BEAM. You describe what\neach language-model step takes and returns, choose how it thinks, and let an\noptimizer improve it against examples of what good looks like. You get\nsignatures, modules, optimizers, agent loops and retrieval, running with the\nreliability and concurrency of OTP.\n\nDSPy 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.\n\n```\nlm = Imp.req_llm(\"openai:gpt-5.4-mini\", api_key: System.fetch_env!(\"OPENAI_API_KEY\"))\n\ntriage =\n  \"issue -> kind: enum[bug,feature,question], summary\"\n  |> Imp.signature(\"Triage a GitHub issue.\")\n  |> Imp.predict(lm: lm)\n\n{:ok, prediction} =\n  Imp.call(triage, %{issue: \"App crashes on startup since 0.4 with ** (KeyError) key :lm not found\"})\n\n{Imp.get(prediction, :kind), Imp.get(prediction, :summary)}\n#=> {\"bug\", \"App crashes on startup since version 0.4 with a KeyError for `:lm` not found.\"}\n```\n\nYou never write a prompt or a parser. Imp builds the prompt from the\nsignature, checks the reply against it, and gives you typed fields: `kind` is\nalways one of the three values, or the call returns an error. To make the\nsame task reason first, use `Imp.chain_of_thought/2`; to give it tools, use\n`Imp.react/3`. The signature stays the same.\n\nGive Imp labeled examples and a metric, and it scores the program and\noptimizes it. You need three lists of issues you have already labeled:\n`trainset`, which the optimizer learns from; `valset`, which it uses to choose\nbetween the programs it tries; and `testset`, which you score on before and\nafter. `strong_lm` is a more capable model that GEPA uses to read failures and\nwrite new instructions.\n\n```\n# Each set is a list of labeled issues like this one:\nexample =\n  Imp.example(%{issue: \"Please add a dark mode to the dashboard\", kind: \"feature\"})\n  |> Imp.with_inputs([:issue])\n\nmetric = Imp.exact_match(:kind)\n\nImp.evaluate(triage, testset, metric).score\n\noptimizer = Imp.Optimizer.GEPA.new(metric, reflection_lm: strong_lm, max_metric_calls: 300)\nimproved = Imp.optimize!(triage, optimizer, trainset, valset)\n\nImp.evaluate(improved, testset, metric).score\n```\n\nGEPA 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.\n\nA tool is an Elixir function. `Imp.react/3` builds an agent that calls tools\nuntil it can answer. This one reads web pages with Req. Imp depends on Req;\nif your own code calls it, as this tool does, add `{:req, \"~> 0.6\"}` to your\ndependencies:\n\n``` php\nfetch =\n  Imp.tool(:fetch, \"Read a web page as text.\", fn %{\"url\" => url} -> Req.get!(url).body end,\n    schema: %{\"type\" => \"object\", \"properties\" => %{\"url\" => %{\"type\" => \"string\"}}, \"required\" => [\"url\"]}\n  )\n\nresearcher = Imp.react(\"question -> answer\", [fetch], lm: lm)\n\nquestion =\n  \"What version does https://raw.githubusercontent.com/elixir-lang/elixir/v1.18.0/VERSION say? \" <>\n    \"Reply with just the version.\"\n\n{:ok, prediction} = Imp.call(researcher, %{question: question})\nImp.get(prediction, :answer)\n#=> \"1.18.0\"\n```\n\n`Imp.call/2` runs a program in your process. `Imp.start_run/3` runs it as its\nown supervised process instead, so you can watch it, stop it, and decide\nwhich tool calls it may make:\n\n```\n{:ok, run} =\n  Imp.start_run(researcher, %{question: question},\n    authorize: fn call ->\n      url = call.arguments[\"url\"] || \"\"\n\n      if String.starts_with?(url, \"https://raw.githubusercontent.com/\"),\n        do: :allow,\n        else: {:deny, :untrusted_host}\n    end\n  )\n\n{:ok, prediction} = Task.await(run.task, :infinity)\n\nfor event <- Imp.Run.events(run), do: event.kind\n#=> [:run_started, :tools_sent, :model_request, :model_response, :tool_call,\n#    :tool_result, :model_request, :model_response, :run_finished]\n```\n\nImp also includes:\n\n- **MCP:** import the tools of any MCP server you approve, and they work like\nyour own.\n- **ACP:** serve any Imp program as an agent to Zed and other ACP clients.\n- **OTP:** a run is a process you can watch, stop and limit, and a run ends\nwhen the process that started it does. Model requests are cut to a\ndeadline you set. A tool call that may already have taken effect is\nreported as unknown, never silently retried.\n- **More shapes:** RLM for inputs far larger than a context window, CodeAct\nand program of thought, which compute with small sandboxed expressions, and your own modules\ncomposed from these.\n\nThe 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.\n\n```\n{:imp, \"~> 0.5\"}\n```\n\nImp needs Elixir 1.19 or later and a C++ compiler for one dependency\n(erlexec). It reaches models through\n[ReqLLM](https://hex.pm/packages/req_llm), so any provider ReqLLM supports\nworks.\n\nImp 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.\n\n- [Getting started](https://github.com/deepfates/imp/blob/main/docs/getting-started/index.md) builds one program step by\nstep, from the first call to a supervised server, with real scores.\n- [Coming from DSPy](https://github.com/deepfates/imp/blob/main/docs/coming-from-dspy.md) maps DSPy's names to Imp's.\n- [Tutorials](https://github.com/deepfates/imp/tree/v0.5.0/livebooks) are Livebook notebooks\nyou can run offline or with a key.\n- The [cheatsheet](https://github.com/deepfates/imp/blob/main/docs/cheatsheet.cheatmd) has the common calls on one page.\n\nImp is MIT licensed.", "url": "https://wpnews.pro/news/deepfates-imp-declarative-self-improving-language-model-programs-for-elixir", "canonical_source": "https://github.com/deepfates/imp", "published_at": "2026-09-27 19:28:23+00:00", "updated_at": "2026-09-27 19:31:27.703636+00:00", "lang": "en", "topics": ["large-language-models", "ai-agents", "developer-tools", "ai-tools", "ai-research"], "entities": ["Deepfates", "Imp", "DSPy", "Elixir", "BEAM", "OTP", "GEPA", "MIPROv2"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/deepfates-imp-declarative-self-improving-language-model-programs-for-elixir", "markdown": "https://wpnews.pro/news/deepfates-imp-declarative-self-improving-language-model-programs-for-elixir.md", "text": "https://wpnews.pro/news/deepfates-imp-declarative-self-improving-language-model-programs-for-elixir.txt", "jsonld": "https://wpnews.pro/news/deepfates-imp-declarative-self-improving-language-model-programs-for-elixir.jsonld"}}