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 ofFunction.FunctionParamstructs that are converted to a JSONSchema format. (Use in place ofparameters_schema)parameters_schema- A JSONSchema structure 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-arity function that runs beforefunctionto parse, coerce, and validate the raw arguments handed back by the LLM. 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 tofalse.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
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
endIn 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 ToolCalls 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_textfor the ToolResult - Provide detailed metadata in the
optionsfield
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 LLMOn 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.
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
})
Summary
Types
Pre-execution argument parser. A 1-arity function that takes the raw
arguments map handed back by the LLM and returns a parse_result/0.
Return shape for a :parse_args callback. See module doc for full details.
Functions
Return the names of every top-level parameter the function accepts.
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.
Given a list of functions, return the display_text for the named function.
If it not found, return the fallback text.
Build a new function.
Build a new function and return it or raise an error if invalid.
Return the names of the function's required top-level parameters.
Types
@type parse_args() :: (arguments() -> parse_result())
Pre-execution argument parser. A 1-arity function that takes the raw
arguments map handed back by the LLM and returns a parse_result/0.
Return shape for a :parse_args callback. See module doc for full details.
Functions
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 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.
Given a list of functions, return the display_text for the named function.
If it not found, return the fallback text.
@spec new(attrs :: map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
Build a new function.
Build a new function and return it or raise an error if invalid.
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"]