Behaviour for LLM provider adapters.
Implement this behaviour to add support for additional LLM providers.
Creating a Custom Provider
Any module implementing the chat/2 callback can be used as a provider.
Here's a minimal example:
defmodule MyApp.LLM.Groq do
@behaviour LangEx.LLM
@impl true
def chat(messages, opts \\ []) do
api_key = opts[:api_key] || System.get_env("GROQ_API_KEY")
model = opts[:model] || "llama-3.3-70b"
tools = opts[:tools] || []
formatted_tools = Enum.map(tools, fn
%LangEx.Tool{name: name, description: desc, parameters: params} ->
%{type: "function", function: %{name: name, description: desc, parameters: params}}
raw -> raw
end)
# Make API call, return {:ok, Message.ai(...)} or {:ok, Message.ai(nil, tool_calls: [...])}
# ...
end
endThen register it so model-string resolution and ChatModel.node work:
# In your application startup or config:
LangEx.LLM.Registry.register_provider(:groq, MyApp.LLM.Groq)
LangEx.LLM.Registry.register_prefix("llama-", :groq)Or use it directly without registration:
MyApp.LLM.Groq.chat(messages, model: "llama-3.3-70b", api_key: "...")
# In a graph node:
LangEx.LLM.ChatModel.node(provider: MyApp.LLM.Groq, model: "llama-3.3-70b")Using LangEx.Config for Custom Providers
To use the shared config resolution (env vars, app config, explicit opts):
# In config/runtime.exs:
config :lang_ex, :providers,
groq: %{env_key: "GROQ_API_KEY", default_model: "llama-3.3-70b"}
# Then in your adapter:
api_key = LangEx.Config.api_key!(:groq, opts)
model = LangEx.Config.model(:groq, opts)Tool Calling
All built-in adapters support tool calling via the :tools option:
:tools- list of%LangEx.Tool{}definitions. Each adapter translates to its native format by accessing the struct fields directly.
When the model requests tool calls the adapter returns
{:ok, %Message.AI{tool_calls: [...]}}. Use LangEx.Tool.Node in your
graph to execute tool calls and feed results back to the LLM.
Summary
Callbacks
Sends a list of messages to the LLM and returns an AI response message.
Like chat/2 but also returns token usage information.
Types
@type chat_result() :: {:ok, LangEx.Message.AI.t()} | {:error, term()}
@type chat_with_usage_result() :: {:ok, LangEx.Message.AI.t(), usage()} | {:error, term()}
@type usage() :: %{ :input_tokens => non_neg_integer(), :output_tokens => non_neg_integer(), optional(:cache_creation_input_tokens) => non_neg_integer(), optional(:cache_read_input_tokens) => non_neg_integer(), optional(:thinking) => String.t() }
Callbacks
@callback chat( [message()], keyword() ) :: chat_result()
Sends a list of messages to the LLM and returns an AI response message.
Options are provider-specific but commonly include:
:api_key- API key override:model- model name override:temperature- sampling temperature:max_tokens- maximum response tokens:tools- list of%LangEx.Tool{}definitions
@callback chat_with_usage( [message()], keyword() ) :: chat_with_usage_result()
Like chat/2 but also returns token usage information.
Returns {:ok, %Message.AI{}, usage_map} on success. The usage map
contains at minimum :input_tokens and :output_tokens.
Optional callback — defaults to calling chat/2 with zero usage.