ExAgent (ExAgent v0.2.0)

Copy Markdown View Source

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

chat(agent, input, opts \\ [])

@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_type and 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"}])

chat_async(agent, input, opts \\ [])

@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.

chat_stream(agent, input, opts \\ [])

@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)

get_context(agent)

@spec get_context(GenServer.server()) :: ExAgent.Context.t()

Returns the agent's current conversation context.

handoff(target, context)

@spec handoff(pid() | atom(), ExAgent.Context.t()) :: :ok

Transfers conversation context to a target agent.

reset(agent)

@spec reset(GenServer.server()) :: :ok

Reset conversation context.

route(input, opts)

@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.

start_agent(opts)

@spec start_agent(ExAgent.Agent.agent_opts()) :: {:ok, pid()} | {:error, term()}

Starts a new agent under the dynamic supervisor.

Options

  • :provider (required) - struct implementing ExAgent.Provider
  • :id - unique agent identifier
  • :tools - list of ExAgent.Tool structs
  • :skills - list of ExAgent.Skill structs
  • :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)

stop_agent(pid)

@spec stop_agent(pid()) :: :ok | {:error, :not_found}

Stops an agent process.

upload_data(provider, data, mime_type, opts \\ [])

@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")

upload_file(provider, file_path, mime_type, opts \\ [])

@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 of file_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}])