Imp.Predict.ReAct (Imp v0.5.0)

Copy Markdown View Source

ReAct agent with two distinct contracts, selected by :mode.

ReAct lets the LM reason about the current situation and choose an action each turn, appending observations to a growing trajectory until the task is done.

:provider_native (default) — Imp's provider-tool-calling ReAct

The LM chooses from an explicit tool catalog via provider function calls, appends observations to history, and eventually calls the reserved submit tool. Keep the tool policy narrow in production:

lookup = Imp.tool(:lookup, "lookup facts", fn %{"query" => query} -> query end)

program =
  Imp.Predict.ReAct.new("question -> answer", [lookup],
    tool_policy: [:lookup, :submit],
    max_iters: 4
  )

This mode preserves the original Imp contract:

  • unknown model-selected tools return {:error, {:unknown_tool, name}};
  • denied tools return {:error, {:tool_denied, name, reason}} (Imp.ToolPolicy);
  • tool crashes return {:error, {:tool_error, name, reason}}, where reason is the exception raised or {kind, value} for a throw or an exit;
  • tool-policy crashes return {:error, {:tool_policy_error, name, reason}};
  • final outputs that are missing or do not fit the signature return {:error, %Imp.AdapterParseError{kind: :missing_fields | :invalid_fields}}.

:dspy — a port of DSPy 3.2.1 dspy.ReAct

This mode reproduces upstream dspy/predict/react.py, not a provider-tool-calling loop. It builds the same reasoning signature DSPy builds, with types and tool arguments named in Imp's neutral words rather than Python's:

  • inputs: the original inputs plus a trajectory string input;
  • outputs: next_thought (a string), next_tool_name (one of the tool names or finish), and next_tool_args (an object);
  • instructions: DSPy's "You are an Agent..." block, listing each tool textually (name, <desc>, and It takes arguments {...}), the reserved finish tool, and the JSON-format reminder.

Each turn the model emits the three fields as ordinary chat output (no provider tool calls). After every tool call the observation is appended to the trajectory as text ([[ ## thought_N ## ]], [[ ## tool_name_N ## ]], [[ ## tool_args_N ## ]], [[ ## observation_N ## ]]). The finish tool terminates the loop; a separate dspy.ChainOfThought extraction pass then maps (inputs + trajectory) to the original output fields.

DSPy control flow reproduced here: finish terminates, iteration exhaustion falls through to extraction, and a reasoning-signature parse failure (an invalid/missing action, mirroring DSPy's ValueError break) also falls through to extraction. Tool exceptions become recoverable Execution error in <tool>: ... observations so the model can recover on a later turn (the message text is Imp's, not Python's traceback — see the release note). Tool policy is an Imp safety extension with no DSPy analogue: a denied or crashing policy stays fail-fast.

Like upstream ReAct, a call may override the constructor's iteration budget with an invocation-local :max_iters or "max_iters" input. The control value is validated and removed before task inputs are sent to the LM.

The prediction

The prediction's fields are the signature's outputs (and, from an extraction pass, its reasoning). Its metadata carries the redacted tool call :history, :termination_reason, how the turn ended, and, when something interrupted it, :termination_cause:

  • :submit, :finish — the model called the reserved tool.
  • :forced_submit — a :provider_native step called no tool (termination_cause: :empty_tool_calls) and the forced submit that followed answered.
  • :answered — as above, but the forced submit did not answer, so the step's own text is projected onto the outputs.
  • :extracted — a :dspy turn that ended at max_iters, on a step that could not be parsed, or on a step with no tool call, answered by DSPy's extraction pass; termination_cause is :max_iters, :parse_error or :empty_tool_calls.

Summary

Types

t()

@type t() :: %Imp.Predict.ReAct{
  max_iters: term(),
  mode: term(),
  react: term(),
  signature: term(),
  tool_policy: term(),
  tools: term()
}

Functions

new(signature, tools, opts \\ [])