# `LangChain.Function`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L1)

Defines a "function" that can be provided to an LLM for the LLM to optionally
execute and pass argument data to.

A function is defined using a schema.

* `name` - The name of the function given to the LLM.
* `description` - A description of the function provided to the LLM. This
  should describe what the function is used for or what it returns. This
  information is used by the LLM to decide which function to call and for what
  purpose.
* ` parameters` - A list of `Function.FunctionParam` structs that are
  converted to a JSONSchema format. (Use in place of `parameters_schema`)
* ` parameters_schema` - A [JSONSchema
  structure](https://json-schema.org/learn/getting-started-step-by-step.html)
  that describes the required data structure format for how arguments are
  passed to the function. (Use if greater control or unsupported features are
  needed.)
* `function` - An Elixir function to execute when an LLM requests to execute
  the function. The function can return a string, a tuple, or a
  `%ToolResult{}` struct for advanced control. Returning a ToolResult allows
  for multi-modal responses (list of ContentParts), cache control, and
  processed_content.
* `parse_args` - An optional 1- or 2-arity function that runs **before**
  `function` to parse, coerce, and validate the raw arguments handed back by
  the LLM. The 2-arity form also receives the tool context.
  See "Parsing arguments before execution" below for the full contract.
* `async` - Boolean value that flags if this can function can be executed
  asynchronously, potentially concurrently with other calls to the same
  function. Defaults to `false`.
* `options` - A Keyword list of options that can be passed to the LLM. For
  example, this can be used for passing caching config to Anthropic.

When passing arguments from an LLM to a function, they go through a single
`map` argument. This allows for multiple keys or named parameters.

## Example

This example defines a function that an LLM can execute for performing basic
math calculations. **NOTE:** This is a partial implementation of the
`LangChain.Tools.Calculator`.

    Function.new(%{
      name: "calculator",
      description: "Perform basic math calculations",
      parameters_schema: %{
        type: "object",
        properties: %{
          expression: %{type: "string", description: "A simple mathematical expression."}
        },
        required: ["expression"]
      },
      function:
        fn(%{"expression" => expr} = _args, _context) ->
          {:ok, "42?"}
        end)
    })

The `function` attribute is an Elixir function that can be executed when the
function is "called" by the LLM.

The `args` argument is the JSON data passed by the LLM after being parsed to a
map.

The `context` argument is passed through as the `context` on a
`LangChain.Chains.LLMChain`. This is whatever context data is needed for the
function to do it's work.

Context examples could be data like user_id, account_id, account struct,
billing level, etc.

## Function Parameters

The `parameters` field is a list of `LangChain.FunctionParam` structs. This is
a convenience for defining the parameters to the function. If it does not work
for more complex use-cases, then use the `parameters_schema` to declare it as
needed.

The `parameters_schema` is an Elixir map that follows a
[JSONSchema](https://json-schema.org/learn/getting-started-step-by-step.html)
structure. It is used to define the required data structure format for
receiving data to the function from the LLM.

NOTE: Only use `parameters` or `parameters_schema`, not both.

## Expanded Parameter Examples

Function with no arguments:

    alias LangChain.Function

    Function.new!(%{name: "get_current_user_info"})

Function that takes a simple required argument:

    alias LangChain.FunctionParam

    Function.new!(%{name: "set_user_name", parameters: [
      FunctionParam.new!(%{name: "user_name", type: :string, required: true})
    ]})

Function that takes an array of strings:

    Function.new!(%{name: "set_tags", parameters: [
      FunctionParam.new!(%{name: "tags", type: :array, item_type: "string"})
    ]})

Function that takes two arguments and one is an object/map:

    Function.new!(%{name: "update_preferences", parameters: [
      FunctionParam.new!(%{name: "unique_code", type: :string, required: true})
      FunctionParam.new!(%{name: "data", type: :object, object_properties: [
        FunctionParam.new!(%{name: "auto_complete_email", type: :boolean}),
        FunctionParam.new!(%{name: "items_per_page", type: :integer}),
      ]})
    ]})

The `LangChain.FunctionParam` is nestable allowing for arrays of object and
objects with nested objects.

## Example that also stores the Elixir result

Sometimes we want to process a `ToolCall` from the LLM and keep the processed
Elixir data for ourselves. This is particularly useful when using an LLM to
perform structured data extraction. Our Elixir function may even process that
data into a newly created Ecto Schema database entry. The result of the
`ToolCall` that goes back to the LLM must be in a string form. That typically
means returning a JSON string of the result data.

To make it easier to process the data, return a string response to the LLM,
but **keep** the original Elixir data as well, our Elixir function can return
a 3-tuple result.

    Function.new!(%{name: "create_invoice",
      parameters: [
        FunctionParam.new!(%{name: "vendor_name", type: :string, required: true})
        FunctionParam.new!(%{name: "total_amount", type: :string, required: true})
      ],
      function: &execute_create_invoice/2
    })

    # ...

    def execute_create_invoice(args, %{account_id: account_id} = _context) do
      case MyApp.Invoices.create_invoice(account_id, args) do
        {:ok, invoice} ->
          {:ok, "SUCCESS", invoice}

        {:error, changeset} ->
          {:error, "ERROR: " <> LangChain.Utils.changeset_error_to_string(changeset)}
      end
    end

In this example, the `LangChain.Function` is tied to the
`MyApp.Invoices.create_invoice/2` function in our application.

The Elixir function returns a 3-tuple result. The `"SUCCESS"` is returned to
the LLM. In our scenario, we don't care to return a JSON version of the
invoice. The important part is we return the actual
`%MyApp.Invoices.Invoice{}` struct in the tuple. This is stored on the
`LangChain.ToolResult`'s `processed_content` field.

This is really helpful when all we want is the final, fully processed Elixir
result. This pairs well with the `LLMChain.run(chain, mode: :until_success)`.
This is when we want the LLM to perform some data extraction and it should be
re-run until it succeeds and we have our final, processed result in the
`ToolResult`.

Note: The LLM may issue one or more `ToolCall`s in a single assistant message.
Each Elixir function's `ToolResult` may contain a `processed_content`.

## Explicit ToolResult Control

For advanced use cases where you need explicit control over the `ToolResult`
structure or want to set LLM-specific options, your Elixir function can return
a fully constructed `%ToolResult{}` struct. The `content` field can be a list
of ContentParts for multi-modal responses.

This approach is particularly useful when you need to:

* Set LLM-specific options (like Anthropic's `cache_control`)
* Set custom error states beyond simple string responses
* Customize the `display_text` for the ToolResult
* Provide detailed metadata in the `options` field

The `options` field can contain any LLM-specific configuration that gets
passed through to the chat model's API conversion layer. If an LLM does not
support it, it will be ignored.

## Parsing arguments before execution

Unless a `:parse_args` parser is supplied, both parameter declarations,
`parameters: [%FunctionParam{}]` and `parameters_schema:`, get a **top-level
required-key presence check** at execute time. Nothing else is enforced:
types, enums, formats, and nested object shapes are all passed through to the
tool as the LLM sent them. Provider "strict mode" closes that remaining gap
somewhat, but is best-effort and varies by provider.

When the check fails, the returned error names the required parameters, the
missing ones, any unrecognized argument names that were sent, and the full
list of accepted parameters. This matters because a model that renames an
argument (sending `file_path` where the tool declared `path`) will otherwise
read a raw exception as a transient fault and retry the same call verbatim,
with each retry teaching itself the wrong calling convention.

### `:parse_args` owns argument validation outright

The optional `:parse_args` callback replaces the built-in check rather than
layering on top of it. **When a parser is supplied, LangChain performs no
argument validation of its own**: the required-key check is skipped, and an
exception raised by the tool body is reported with its original formatting
instead of being reinterpreted as an argument-name problem.

This keeps one voice and one round trip. A parser such as `Zoi` reports
missing keys *and* type violations together in a single message, formatted
the way you wrote it, rather than having LangChain answer the missing-key
case in a different format and hide the rest until the next turn. It also
avoids second-guessing a parser that legitimately coerces keys or injects
defaults, since the arguments reaching the tool body no longer have to match
the declared schema.

The trade-off is that tools using a parser do not get LangChain's
"unrecognized parameter / did you mean" diagnostic. Parsers that want it can
build the same message from `required_param_names/1` and
`accepted_param_names/1`.

The parser runs **before** the user-supplied `function` and receives the raw,
string-keyed arguments map from the LLM. It returns one of three shapes:

    :ok                                # arguments are fine, hand them to `function` as-is
    {:ok, parsed_arguments :: map()}   # use these parsed/coerced arguments instead
    {:error, reason :: String.t()}     # reject the call, return `reason` to the LLM

On rejection, the tool's body is **not** run. The error string flows through
as the tool's response, so the model sees a structured "your args were wrong"
message and can self-correct. Tool-execution callbacks (e.g.
`:on_tool_response_created`) and the `[:langchain, :tool, :call]` telemetry
span still fire, meaning failed parses are observable for telemetry, token
accounting, and trajectory analysis.

This is a "parse, don't validate" hook: tools that need typed/coerced
arguments parse once here and pattern-match the parsed result in `function`,
rather than re-parsing inside the body.

### Parsing with the tool context

A 2-arity parser is called as `parser.(arguments, context)` with the same
context the tool body receives: the chain's `custom_context` with
`:tool_call_id` added. That lets a parser resolve what the arguments refer
to, not just check their shape. It can look up a record for the current
tenant, refuse an id that does not exist, or compute the change a call would
make, and return the resolved data for `function` to use:

    defp parse_args(args, %{scope: scope}) do
      with {:ok, input} <- cast_input(args),
           {:ok, record} <- Records.fetch(scope, input.id) do
        {:ok, %{record: record, changes: input.changes}}
      end
    end

A parser may **read** but must **never write**. It can run more than once
for a single call: a caller can run it ahead of execution through
`parse_arguments/3` (for example, to decide whether a call is worth putting
to a human for approval), and `execute/3` runs it again when the call
executes. Only the execution-time result reaches `function`.

LangChain takes no dependency on any specific schema library. Adapters for
`Zoi`, `NimbleOptions`, `Ecto.Changeset`, `JSV`, or hand-rolled checks all
conform to the same `:ok | {:ok, map()} | {:error, String.t()}` contract.

    defp parse_args(args) do
      case Zoi.parse(@params, args) do
        {:ok, parsed} -> {:ok, parsed}
        {:error, errors} -> {:error, format_zoi_errors(errors)}
      end
    end

    Function.new!(%{
      name: "...",
      parameters_schema: ReqLLM.Schema.to_json(@params),
      parse_args: &parse_args/1,
      function: &execute/2
    })

# `arguments`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L328)

```elixir
@type arguments() :: %{required(String.t()) =&gt; any()}
```

# `context`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L329)

```elixir
@type context() :: nil | %{required(atom()) =&gt; any()}
```

# `execution_exception`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L335)

```elixir
@type execution_exception() :: {Exception.t(), Exception.stacktrace()}
```

A rescued exception and its stacktrace, as returned alongside the model-facing
message in a `{:error, message, execution_exception()}` result.

# `parse_args`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L349)

```elixir
@type parse_args() ::
  (arguments() -&gt; parse_result()) | (arguments(), context() -&gt; parse_result())
```

Pre-execution argument parser. Takes the raw arguments map handed back by the
LLM, and optionally the tool context, and returns a `t:parse_result/0`.

# `parse_result`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L340)

```elixir
@type parse_result() ::
  :ok | {:ok, parsed_arguments :: map()} | {:error, reason :: String.t()}
```

Return shape for a `:parse_args` callback. See module doc for full details.

# `t`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L327)

```elixir
@type t() :: %LangChain.Function{
  async: term(),
  description: term(),
  display_text: term(),
  function: term(),
  name: term(),
  options: term(),
  parameters: term(),
  parameters_schema: term(),
  parse_args: term(),
  strict: term()
}
```

# `accepted_param_names`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L603)

```elixir
@spec accepted_param_names(t()) :: [String.t()]
```

Return the names of every top-level parameter the function accepts.

Works for both declaration styles. Returns `[]` when the function's
declaration doesn't enumerate its parameters, which happens for a
`parameters_schema:` without a `properties` map and for a function declaring
no parameters at all. Callers should treat `[]` as "unknown", not as "accepts
nothing", since an empty list carries no information about which argument
names are valid.

    iex> alias LangChain.Function
    iex> fun = Function.new!(%{
    ...>   name: "demo",
    ...>   function: fn _args, _context -> {:ok, "ok"} end,
    ...>   parameters_schema: %{
    ...>     type: "object",
    ...>     properties: %{path: %{type: "string"}, limit: %{type: "integer"}},
    ...>     required: ["path"]
    ...>   }
    ...> })
    iex> fun |> Function.accepted_param_names() |> Enum.sort()
    ["limit", "path"]

# `execute`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L423)

```elixir
@spec execute(t(), arguments(), context()) :: any() | no_return()
```

Execute the function passing in arguments and additional optional context.
This is called by a `LangChain.Chains.LLMChain` when a `Function` execution is
requested by the LLM.

## Return shapes

- `{:ok, llm_result}` - success
- `{:ok, llm_result, processed_content}` - success, keeping a native Elixir result
- `{:interrupt, display_message, interrupt_data}` - paused for review
- `{:error, message}` - the tool returned an error, or a check rejected the arguments
- `{:error, message, {exception, stacktrace}}` - the tool, or its `:parse_args`
  parser, raised and the exception was rescued

The last shape mirrors `{:ok, llm_result, processed_content}`: the second element
is the message the model sees, and the third is the native Elixir detail kept for
the application. It only appears when an exception was actually rescued, so
matching on `{:error, message}` still covers every error a tool returns
deliberately.

# `get_display_text`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L534)

```elixir
@spec get_display_text([t()], String.t(), String.t()) :: String.t()
```

Given a list of functions, return the `display_text` for the named function.
If it not found, return the fallback text.

# `new`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L371)

```elixir
@spec new(attrs :: map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
```

Build a new function.

# `new!`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L382)

```elixir
@spec new!(attrs :: map()) :: t() | no_return()
```

Build a new function and return it or raise an error if invalid.

# `parse_arguments`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L449)

```elixir
@spec parse_arguments(t(), arguments() | nil, context()) ::
  {:ok, map()} | {:error, String.t()}
```

Parse the arguments for a call to this function, without executing it.

Runs exactly the argument handling `execute/3` runs before the tool body:
the `:parse_args` parser when one is supplied, otherwise the required-key
check. Pass the same `context` the body would receive.

Returns `{:ok, parsed_arguments}` with the arguments the body would be given:
the parser's converted result, or the raw arguments when the parser returns
`:ok` or there is no parser. Returns `{:error, message}` with the message the
model would be given when the arguments are refused. An exception raised by
the parser is rescued and reported as `{:error, message}`.

A parser may run more than once for one call, so it must not write. See
"Parsing with the tool context" in the module doc.

# `required_param_names`
[🔗](https://github.com/brainlid/langchain/blob/v0.15.3/lib/function.ex#L566)

```elixir
@spec required_param_names(t()) :: [String.t()]
```

Return the names of the function's required top-level parameters.

Works for both declaration styles. For `parameters:` it reads the `required`
flag from each `LangChain.FunctionParam`. For `parameters_schema:` it reads
the schema's `required` list.

Returns `[]` when the function declares no parameters or declares none as
required.

    iex> alias LangChain.{Function, FunctionParam}
    iex> fun = Function.new!(%{
    ...>   name: "demo",
    ...>   function: fn _args, _context -> {:ok, "ok"} end,
    ...>   parameters: [
    ...>     FunctionParam.new!(%{name: "path", type: :string, required: true}),
    ...>     FunctionParam.new!(%{name: "limit", type: :integer})
    ...>   ]
    ...> })
    iex> Function.required_param_names(fun)
    ["path"]

---

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