# `LangChain.MessageExpansion`
[🔗](https://github.com/brainlid/langchain/blob/v0.14.1/lib/message_expansion.ex#L1)

A tool result's request to expand into conversation messages before the model
is next called.

A tool result normally reaches the model as one thing: `:tool` content. That
is the right container for an answer and the wrong one for a body of material
the model is meant to treat as established. An expansion lets a tool say "put
these messages into the conversation, and trim what my own result keeps once
you have."

One result becomes several messages, and shrinks as they land. That is the
expansion: the conversation grows by what the result gives up.

An expansion is applied by
`LangChain.Chains.LLMChain.Mode.Steps.expand_tool_results/2`, which runs
immediately before the next LLM call. The messages are in the conversation by
the time the model is asked to continue, in the same run.

## Fields

- `:messages` - the messages to insert, in order, exactly as the model will
  see them. Any number of them, at `:user` or `:assistant` roles.
- `:result_content` - what the tool's own result keeps once the messages have
  been inserted. `nil` keeps the result's existing content.

## The two representations

An expanding tool describes its output twice, because the expansion may not
happen. Only a mode that composes the step applies one; under any other mode
the field is inert.

| | What the model reads |
|---|---|
| Expansion not applied | the tool result's content, as the tool wrote it |
| Expansion applied | `:messages`, and the result trimmed to `:result_content` |

`expand/3` takes both in one call so the two cannot drift, and so that the
degraded path is something a tool author passes rather than something they
remember:

    LangChain.MessageExpansion.expand(
      raw_text,
      [
        Message.new_assistant!(records),
        Message.new_user!("Use the records above to answer my question.")
      ],
      result_content: "Loaded 14 records."
    )

The message list is entirely the tool author's: one message, or six, in
whatever order and at whichever of the two roles the prompt needs.

## Shaping the ends of the list

Both ends of the message list meet something, and each has a consequence
worth knowing. These are prompt-shaping recommendations, not rules; nothing
is added or reordered on the author's behalf.

**Starting with a user message.** The messages follow the tool result
directly, and a `:tool` message reaches Anthropic as a user-role message.
Consecutive user-role messages are combined, so a list that starts with a
`:user` message is merged into the tool result's turn, which then holds the
tool result followed by the text. The request is valid, since Anthropic
requires tool results first with any text after them, but the text no longer
stands as a turn of its own. Starting with an `:assistant` message keeps them
apart.

**Ending on an assistant message.** Anthropic reads a trailing assistant
message as a prefill to continue rather than a turn to answer, so an expansion
whose last message is an `:assistant` one becomes the opening of the model's
own next sentence rather than something it responds to. Ending the list with a
short `:user` message that re-anchors the request avoids that.

Together these make `[assistant, user]` the shape for established material:
the material as something the model said, then a turn asking it to act. It is
the same shape an application hand-builds when seeding a conversation before
an agent starts.

## Limitation: unresolved server tools

Do not expand from a tool that runs in the same turn as an Anthropic server
tool (web search, web fetch, code execution) whose result has not arrived yet,
or alongside a programmatic tool call that is still pending.

In that turn Anthropic requires the message answering the tool calls to hold
only `tool_result` blocks, and resumes the server tool on the request that
carries them. A list starting with a `:user` message merges text into that
message, which ends the turn early and, for a server tool the model called
directly, fails the request with a 400 naming the unresolved tool. A list
starting with an `:assistant` message keeps that message clean but puts turns
between the tool results and the server tool's resumption, which Anthropic
does not document as supported.

The step does not detect this case.

## Roles

Only `:user` and `:assistant` can be expanded into.

`:system` is refused because a conversation carries at most one system
message: `LangChain.Utils.split_system_message/2` raises on a second one, far
from the tool that caused it. `:tool` is refused because a tool message is
only meaningful against a tool call that asked for it.

# `content`
[🔗](https://github.com/brainlid/langchain/blob/v0.14.1/lib/message_expansion.ex#L112)

```elixir
@type content() ::
  String.t()
  | LangChain.Message.ContentPart.t()
  | [LangChain.Message.ContentPart.t()]
```

# `t`
[🔗](https://github.com/brainlid/langchain/blob/v0.14.1/lib/message_expansion.ex#L114)

```elixir
@type t() :: %LangChain.MessageExpansion{
  messages: [LangChain.Message.t()],
  result_content: nil | content()
}
```

# `expand`
[🔗](https://github.com/brainlid/langchain/blob/v0.14.1/lib/message_expansion.ex#L172)

```elixir
@spec expand(content(), [LangChain.Message.t()], keyword()) ::
  {:ok, LangChain.Message.ToolResult.t()} | no_return()
```

Build a tool result that expands into `messages`, and return the `{:ok, result}`
tuple a tool body returns.

`fallback_content` is the tool's whole answer in the shape a tool result can
hold. It is what the result says until the expansion is applied, and what the
model reads under a mode that never applies it. It is not a summary: a mode
without the step has to leave the model with the content, in worse wording,
rather than with a description of content it does not have.

    def load_reference(%{"name" => name}, _context) do
      {records, summary} = load(name)

      LangChain.MessageExpansion.expand(
        summary <> "\n\n" <> records,
        [
          Message.new_assistant!(records),
          Message.new_user!("Use the records above to answer my question about #{name}.")
        ],
        result_content: summary
      )
    end

## Options

- `:result_content` - what the result keeps once the messages are inserted.
  Defaults to a short note saying the content is in the conversation. Pass
  `nil` to leave `fallback_content` in place, which keeps the payload in two
  places and is rarely what an expansion is for.

## The call identity is filled in for you

The result comes back with `tool_call_id`, `name` and `display_text` unset.
`LangChain.Chains.LLMChain.execute_tool_call/3` assigns `tool_call_id` from
the call it dispatched, and falls back to the `LangChain.Function`'s own
`name` and `display_text`, which is what it does for any `ToolResult` a tool
hands back.

A tool that wants different UI text for a particular call sets `display_text`
on the returned result; the fallback only applies while it is nil. A tool that
needs the id for its own bookkeeping reads `context.tool_call_id`, which
`execute_tool_call/3` puts there before dispatch. Setting `tool_call_id` on
the result has no effect, because the call is the authority on it.

Code building a result outside a tool body has to assign all three itself.

# `expandable?`
[🔗](https://github.com/brainlid/langchain/blob/v0.14.1/lib/message_expansion.ex#L192)

```elixir
@spec expandable?(LangChain.Message.ToolResult.t()) :: boolean()
```

Whether a tool result carries an expansion that is ready to be applied.

An interrupted result is excluded: its tool has not finished, and the turn is
about to stop rather than continue.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
