Represents a the result of running a requested tool. The LLM's requests a tool
use through a ToolCall. A ToolResult returns the answer or result from the
application back to the AI.
Content
The content can be a string or a list of ContentParts for multi-modal
responses (text, images, etc.) that gets returned to the LLM as the result.
Processed Content
The processed_content field is optional. When you want to keep the results
of the Elixir function call as a native Elixir data structure,
processed_content can hold it.
Advanced use: You can use the options field for LLM-specific features like
cache control.
Errors and rescued exceptions
is_error marks any failed result: a tool that returned {:error, reason}, a
tool call naming a tool that doesn't exist, a rejected human review, or a tool
that raised.
is_exception narrows that to results where LangChain rescued an exception, and
exception holds the {exception, stacktrace} pair it rescued. The exception is
never sent to the LLM — only the formatted message in content is — and it is
never persisted: exception is a virtual field, so a result restored from
storage carries is_exception: true with exception: nil. Code that needs the
exception itself should read it during the run, most conveniently through the
:on_tool_execution_exception callback.
Because content carries LangChain's formatted exception message and source
location, tools should not put secrets in exception messages.
Refusals
is_refusal narrows is_error to a call that was turned down before it changed
anything, so resending a corrected call is safe. A refusal is always also an
error, so every check of is_error still sees it as one. LangChain sets it for
arguments the required-key check or a :parse_args parser turned down, for a
call naming a tool that doesn't exist, and for a rejected human review reported
as an error. A tool sets it on a ToolResult it returns, and should do so only
when it has written nothing.
A rescued exception is never a refusal: code that raised may have done part of
its work. A refusal is also not a review denial. A call turned away by an
:on_tool_call_review handler reports is_error: false, because it is a
decision about the call, not a fault in it.
Message expansion
message_expansion holds a LangChain.MessageExpansion: a request to insert
messages into the conversation before the next LLM call, and to trim what this
result keeps once they are in. It is applied by
LangChain.Chains.LLMChain.Mode.Steps.expand_tool_results/2, so it does
nothing under a mode that does not compose that step. Like interrupt_data it
is virtual: it is never sent to the LLM and does not survive storage, which is
what keeps a restored conversation from inserting the same material twice.
To do this, the Elixir function's result should be a {:ok, "String response for LLM", native_elixir_data}. See LangChain.Function for details and
examples.
Summary
Functions
Build a new ToolResult and return an :ok/:error tuple with the result.
Build a new ToolResult and return it or raise an error if invalid.
Types
@type t() :: %LangChain.Message.ToolResult{ content: term(), display_text: term(), exception: term(), interrupt_data: term(), is_error: term(), is_exception: term(), is_interrupt: term(), is_refusal: term(), message_expansion: term(), name: term(), options: term(), processed_content: term(), tool_call_id: term(), type: term() }