Current section

Files

Jump to
langchain lib chains llm_chain modes until_tool_used.ex
Raw

lib/chains/llm_chain/modes/until_tool_used.ex

defmodule LangChain.Chains.LLMChain.Modes.UntilToolUsed do
@moduledoc """
Execution mode that loops until a specific tool is called.
Repeatedly calls the LLM and executes tools until a tool result
matching one of the target tool names is found.
## Options
- `:tool_names` — (required) List of tool name strings to watch for.
- `:max_runs` — Maximum LLM calls before error. Default: 25.
## Usage
LLMChain.run(chain,
mode: LangChain.Chains.LLMChain.Modes.UntilToolUsed,
tool_names: ["submit"],
max_runs: 25
)
# => {:ok, chain, %ToolResult{name: "submit", ...}}
"""
@behaviour LangChain.Chains.LLMChain.Mode
import LangChain.Chains.LLMChain.Mode.Steps
alias LangChain.Chains.LLMChain
alias LangChain.LangChainError
@impl true
def run(%LLMChain{} = chain, opts) do
tool_names = Keyword.fetch!(opts, :tool_names)
# Validate tools exist
missing = Enum.filter(tool_names, fn name -> !Map.has_key?(chain._tool_map, name) end)
if missing != [] do
message =
if length(missing) > 1 do
"Tool names '#{Enum.join(missing, ", ")}' not found in available tools"
else
"Tool name '#{List.first(missing)}' not found in available tools"
end
{:error, chain,
LangChainError.exception(
type: "invalid_tool_name",
message: message
)}
else
chain = ensure_mode_state(chain)
do_run(chain, opts)
end
end
defp do_run(chain, opts) do
# Order matters: `check_max_runs` must come AFTER `check_until_tool`,
# otherwise the very LLM call that successfully invokes the target
# tool can have its result discarded — `call_llm` increments the run
# counter, the check fires and short-circuits the pipeline before
# `execute_tools`/`check_until_tool` get to see the result. With this
# order, a successful run terminates first; `check_max_runs` is a
# no-op on terminal pipeline states.
{:continue, chain}
|> call_llm()
|> execute_tools()
|> check_until_tool(opts)
|> check_max_runs(opts)
|> continue_or_done(&do_run/2, opts)
end
end