Public API for the ExAgent multi-agent LLM library.
ExAgent abstracts calls to various LLMs (OpenAI, Gemini, DeepSeek)
via the ExAgent.Provider behaviour and orchestrates them using
OTP primitives and four multi-agent design patterns.
Quick Start
# Create a provider
provider = ExAgent.Providers.OpenAI.new(api_key: System.get_env("OPENAI_API_KEY"))
# Start an agent
{:ok, agent} = ExAgent.start_agent(provider: provider)
# Chat
{:ok, response} = ExAgent.chat(agent, "Hello!")Multi-Agent Patterns
- Subagents - Centralized orchestration with isolated subagent calls
- Skills - Progressive disclosure of specialized personas
- Handoffs - State-driven transitions between agents
- Router - Parallel dispatch and synthesis across agents
Summary
Functions
Sends a user message to an agent and returns the response.
Sends a user message asynchronously, returning a Task.
Streams an agent's response as a lazy Stream of text chunks.
Returns the agent's current conversation context.
Transfers conversation context to a target agent.
Reset conversation context.
Routes input through matching agents and synthesizes results.
Starts a new agent under the dynamic supervisor.
Stops an agent process.
Uploads raw binary data to the provider and returns a reference.
Uploads a file from disk to the provider and returns a reference.
Functions
@spec chat(GenServer.server(), String.t(), keyword()) :: {:ok, ExAgent.Message.t()} | {:handoff, pid() | atom(), ExAgent.Context.t()} | {:error, term()}
Sends a user message to an agent and returns the response.
Options
:files- list of file attachments, each a map with:mime_typeand either:data(binary) or:path(file path). Files become part of the conversation context and are sent to the LLM alongside the text.:built_in_tools- override agent-level built-in tools for this message. Examples:[:google_search],[:web_search],[:thinking].
Examples
{:ok, response} = ExAgent.chat(agent, "What is Elixir?")
response.content
#=> "Elixir is a functional programming language..."
# With file attachments
{:ok, response} = ExAgent.chat(agent, "Describe this image",
files: [%{path: "photo.jpg", mime_type: "image/jpeg"}])
@spec chat_async(GenServer.server(), String.t(), keyword()) :: Task.t()
Sends a user message asynchronously, returning a Task.
Accepts the same options as chat/3.
@spec chat_stream(GenServer.server(), String.t(), keyword()) :: Enumerable.t()
Streams an agent's response as a lazy Stream of text chunks.
Only the final assistant turn is streamed; tool-call turns (if any) are resolved first. The conversation context is committed when the stream is fully consumed, so the returned stream must be consumed.
Examples
agent
|> ExAgent.chat_stream("Explain OTP supervision step by step")
|> Enum.each(&IO.write/1)
@spec get_context(GenServer.server()) :: ExAgent.Context.t()
Returns the agent's current conversation context.
@spec handoff(pid() | atom(), ExAgent.Context.t()) :: :ok
Transfers conversation context to a target agent.
@spec reset(GenServer.server()) :: :ok
Reset conversation context.
@spec route(String.t(), ExAgent.Patterns.Router.router_opts()) :: {:ok, String.t()} | {:error, term()}
Routes input through matching agents and synthesizes results.
See ExAgent.Patterns.Router.route/2 for options.
@spec start_agent(ExAgent.Agent.agent_opts()) :: {:ok, pid()} | {:error, term()}
Starts a new agent under the dynamic supervisor.
Options
:provider(required) - struct implementingExAgent.Provider:id- unique agent identifier:tools- list ofExAgent.Toolstructs:skills- list ofExAgent.Skillstructs:built_in_tools- provider-specific built-in tools (e.g.,[:google_search],[:web_search],[:thinking]):name- GenServer name for registration
Examples
provider = ExAgent.Providers.OpenAI.new(api_key: "sk-...")
{:ok, pid} = ExAgent.start_agent(provider: provider)
@spec stop_agent(pid()) :: :ok | {:error, :not_found}
Stops an agent process.
@spec upload_data(struct(), binary(), String.t(), keyword()) :: {:ok, ExAgent.FileRef.t()} | {:error, term()}
Uploads raw binary data to the provider and returns a reference.
Use this when you already have file contents in memory.
Options
:filename- filename for the upload (default:"upload"):purpose- OpenAI-specific file purpose (default:"user_data")
Examples
image_bytes = File.read!("screenshot.png")
{:ok, ref} = ExAgent.upload_data(provider, image_bytes, "image/png", filename: "screenshot.png")
@spec upload_file(struct(), String.t(), String.t(), keyword()) :: {:ok, ExAgent.FileRef.t()} | {:error, term()}
Uploads a file from disk to the provider and returns a reference.
The returned FileRef can be passed in chat messages via
files: [%{file_ref: ref}] to avoid sending base64-encoded data inline.
Options
:filename- override filename (defaults to basename offile_path):purpose- OpenAI-specific file purpose (default:"user_data")
Examples
provider = ExAgent.Providers.OpenAI.new(api_key: "sk-...")
{:ok, ref} = ExAgent.upload_file(provider, "report.pdf", "application/pdf")
{:ok, response} = ExAgent.chat(agent, "Summarize", files: [%{file_ref: ref}])