Extracts tool calls from the LLM response.
If the response contains tool calls (non-empty list), populates
ctx.tool_calls for downstream stages.
If the response contains no tool calls and non-blank content, sets
decision to {:done, response} — the LLM has produced a final text
response and no further iteration is needed.
If the response contains no tool calls AND blank content (nil or
whitespace-only), sets decision to {:blank_stop, response} — a
degenerate completion that must not be accepted as final. Some
OpenAI-compatible backends (observed on Ollama gpt-oss) occasionally
return HTTP success with finish_reason: "stop", empty content, and no
tool calls while a tool-driven task is still incomplete. The outer loop
(LlmCore.Agent.Loop) owns the bounded recovery policy for this
decision: it retries the turn and terminates with a typed error when
blanks repeat.
When ctx.terminal_tool is set and a matching call is present, the
pipeline marks the response as done and stores the matching call and raw
arguments on the context. The call is not validated, dispatched, or injected
into the next turn. A terminal-tool completion with blank text content is
still :done — the payload travels in the tool arguments.
Analogous to a context merge stage: takes raw input and normalizes it into the pipeline's working format.
Summary
Functions
Extracts tool calls from ctx.response.tool_calls.
Functions
@spec call( LlmCore.Agent.Context.t(), keyword() ) :: LlmCore.Agent.Context.t()
Extracts tool calls from ctx.response.tool_calls.
Short-circuits when ctx.status is :error.
Parameters
ctx—%Context{}with the LLM responseopts— ALF stage options (unused)
Returns
Updated %Context{} with either:
tool_callspopulated and pipeline continues, ordecision: {:done, response}when no tool calls are present and the content is non-blankdecision: {:blank_stop, response}when no tool calls are present and the content is blankdecision: {:done, response}plus terminal fields when the terminal tool is called