Imp: Declaratieve, zelfverbeterende taalmodel-programma's voor Elixir

DSPy maakt van elke aanroep naar een model een gedeclareerde, getypeerde functie die gemeten en verbeterd kan worden. Op de BEAM is een agent een proces: het behoudt zijn eigen status, ontvangt berichten en draait onder een supervisor naast de rest van je applicatie. Door beide te combineren kun je alles bouwen, van één getypeerde aanroep tot meerdere langlopende agents, en elk onderdeel verbeteren door het te meten.

Een taak definiëren

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."}

Je schrijft nooit zelf een prompt of een parser. Imp bouwt de prompt op basis van de signatuur, controleert het antwoord hierop en geeft je getypeerde velden terug: kind is altijd een van de drie waarden, anders geeft de aanroep een foutmelding terug. Om dezelfde taak eerst te laten redeneren, gebruik je Imp.chainofthought/2; om het model tools te geven, gebruik je Imp.react/3. De signatuur blijft in beide gevallen hetzelfde.

Meten en verbeteren

Door Imp gelabelde voorbeelden en een metriek te geven, kan het programma worden gescoord en geoptimaliseerd. Hiervoor zijn drie lijsten met reeds gelabelde issues nodig:

  • trainset: hiervan leert de optimizer.
  • valset: wordt gebruikt om te kiezen tussen de programma's die worden geprobeerd.
  • testset: waarop gescoord wordt vóór en na de optimalisatie.

Een strong_lm (een krachtiger model) wordt gebruikt door GEPA om fouten te analyseren en nieuwe instructies te schrijven.

# Elke set is een lijst van gelabelde issues zoals deze:
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 voert het programma uit, analyseert waar het faalde en herschrijft de instructies. Andere optimizers kiezen gewerkte voorbeelden (LabeledFewShot, BootstrapFewShot), zoeken naar combinaties van instructies en voorbeelden (MIPROv2), leren regels en voorbeelden uit de betere en slechtere pogingen van het programma zelf (SIMBA), of trainen de gewichten van het model (fine-tuning, GRPO). Het resultaat is een nieuw programma waarvan je de instructies en voorbeelden kunt lezen, opslaan als JSON en reviewen als een diff.

Agents bouwen

Een tool is een Elixir-functie. Imp.react/3 bouwt een agent die tools aanroept totdat er een antwoord kan worden gegeven. In dit voorbeeld leest de agent webpagina's met Req. Omdat Imp afhankelijk is van Req, moet je {:req, "~> 0.6"} toevoegen aan je dependencies als je eigen code dit aanroept:

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"

Agents uitvoeren als processen

Imp.call/2 voert een programma uit in je huidige proces. Imp.start_run/3 voert het uit als een eigen gesuperviseerd proces, zodat je het kunt monitoren, stoppen en kunt bepalen welke tool-aanroepen zijn toegestaan:

{: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 <- Imp.Run.events(run), do: event.kind
#=> [:run_started, :tools_sent, :model_request, :model_response, :tool_call,
#    :tool_result, :model_request, :model_response, :run_finished]

Overige functionaliteiten

Imp bevat daarnaast:

  • MCP: Importeer tools van elke goedgekeurde MCP-server; deze werken als je eigen tools.
  • ACP: Serveer elk Imp-programma als een agent voor Zed en andere ACP-clients.
  • OTP: Een 'run' is een proces dat je kunt monitoren, stoppen en beperken. Een run eindigt wanneer het proces dat het startte eindigt. Model-aanvragen worden afgekapt bij een door jou ingestelde deadline. Een tool-aanroep die mogelijk al effect heeft gehad, wordt gerapporteerd als onbekend en nooit stilzwijgend opnieuw geprobeerd.
  • Diverse vormen: RLM voor inputs die veel groter zijn dan een context window, CodeAct en "program of thought" (die berekeningen uitvoeren met kleine sandboxed expressies), en je eigen modules samengesteld uit deze elementen.

De optimizers werken ook op agents. GEPA reflecteert op volledige agent-runs en herschrijft de instructies die ze aansturen. "Optimize Anything" herschrijft elke tekst of JSON die gescoord kan worden, zoals de tool-beschrijvingen van een agent.

Installatie

Voeg het volgende toe aan je dependencies: {:imp, "~> 0.5"}

Imp vereist Elixir 1.19 of later en een C- en C++ compiler voor de native code in twee dependencies (jaxon en erlexec). De eerste compilatie vereist netwerktoegang, omdat de build van erlexec rebar3-plugins ophaalt. Modellen worden benaderd via ReqLLM, waardoor elke provider die door ReqLLM wordt ondersteund, werkt.

Let op: Imp 0.5 is experimenteel en is de eerste release op Hex. De API kan nog veranderen en de optimizers vereisen grootschalige benchmarking. Bugrapporten en pull requests zijn welkom.

Leren en documentatie

  • Getting started: Bouw stap voor stap een programma, van de eerste aanroep tot een gesuperviseerde server, met echte scores.
  • Coming from DSPy: Een overzicht dat de namen van DSPy koppelt aan die van Imp.
  • Tutorials: Beschikbaar als Livebook-notebooks die offline of met een key kunnen worden uitgevoerd.
  • Cheatsheet: Bevat de meest gebruikte aanroepen op één pagina.

Imp is gelicenseerd onder de MIT-licentie.