OpEx
An agentic LLM toolkit for Elixir.
Overview
- OpenAI-compatible API Client: HTTP client with automatic retry logic and error handling
- MCP Support: Full support for Model Context Protocol servers via stdio and HTTP transports
- Flexible Chat Loop: Tool calling with customizable hooks for integration
- Session Management: Automatic health monitoring and reconnection for MCP servers
Architecture
Core Modules
OpEx.Client- HTTP client for OpenAI-compatible API with exponential backoff retry logicOpEx.Chat- Chat conversation loop with tool calling and hook supportOpEx.MCP.StdioClient- MCP client for stdio transport (local process spawning)OpEx.MCP.HttpClient- MCP client for HTTP transport (remote MCP servers)OpEx.MCP.SessionManager- Manages multiple MCP server sessions with health checksOpEx.MCP.Tools- Utilities for converting between MCP and OpenAI tool formats
Hooks System
OpEx uses a hooks-based architecture to avoid hard-coded dependencies. You can customize behavior via:
custom_tool_executor- Execute application-specific toolson_assistant_message- Handle assistant messages (e.g., save to database)on_tool_result- Handle tool results (e.g., logging, metrics)
See OpEx.Hooks for detailed documentation.
Installation
Add to your mix.exs:
def deps do [ {:opex, "~> 0.1.0"} ] end
Quick Start
# 1. Create OpenRouter client client = OpEx.Client.new(System.get_env("OPENROUTER_KEY")) # 2. Start MCP session manager {:ok, _pid} = OpEx.MCP.SessionManager.start_link(name: MyApp.MCPManager) # 3. Add MCP servers {:ok, server_id} = OpEx.MCP.SessionManager.add_server(MyApp.MCPManager, %{ "command" => "npx", "args" => ["-y", "@modelcontextprotocol/server-filesystem"], "env" => [] }) # 4. Create chat session with hooks session = OpEx.Chat.new(client, mcp_clients: [{:ok, server_id}], custom_tool_executor: &MyApp.execute_custom_tool/3, on_assistant_message: &MyApp.save_message/2 ) # 5. Have a conversation {:ok, response} = OpEx.Chat.chat(session, model: "anthropic/claude-3.5-sonnet", messages: [%{"role" => "user", "content" => "List files in /tmp"}], system_prompt: "You are a helpful assistant", context: %{conversation_id: 123} )
MCP Server Examples
Stdio Transport (Local)
# Filesystem server %{ "command" => "npx", "args" => ["-y", "@modelcontextprotocol/server-filesystem"], "env" => [] } # Brave Search server %{ "command" => "npx", "args" => ["-y", "@modelcontextprotocol/server-brave-search"], "env" => [{"BRAVE_API_KEY", api_key}] }
HTTP Transport (Remote)
%{ "url" => "https://api.example.com/mcp", "auth_token" => "your-token", "execution_id" => "exec-123" # Optional }
Custom Tools
Define custom tools and provide an executor function:
custom_tools = [ %{ "type" => "function", "function" => %{ "name" => "search_database", "description" => "Search the internal database", "parameters" => %{ "type" => "object", "properties" => %{ "query" => %{"type" => "string", "description" => "Search query"} }, "required" => ["query"] } } } ] def execute_custom_tool("search_database", args, _context) do results = MyApp.Database.search(args["query"]) {:ok, %{"results" => results}} end def execute_custom_tool(_, _, _), do: {:error, :tool_not_found} session = OpEx.Chat.new(client, custom_tools: custom_tools, custom_tool_executor: &execute_custom_tool/3 )
Configuration
OpenRouter Client Options
client = OpEx.Client.new(api_key, base_url: "https://openrouter.ai/api/v1", # Default user_agent: "my-app/1.0.0", app_title: "My Application" # For X-Title header )
Chat Options
OpEx.Chat.chat(session, model: "anthropic/claude-haiku-4.5", # Required messages: messages, # Required system_prompt: "You are helpful", # Optional execute_tools: true, # Auto-execute tools (default: true) context: %{} # Passed to all hooks (default: %{}) )
Error Handling
OpEx automatically retries transient errors:
- HTTP 429: Rate limits (retry with 5s+ backoff)
- HTTP 500-504, 508: Server errors (retry with 2s+ backoff)
- Transport errors: Closed connections, timeouts (retry with 1s+ backoff)
Maximum 3 retries with exponential backoff.
Future Enhancements
Potential additions for OpEx:
- Streaming support (SSE from OpenRouter)
- Telemetry events
- Supervision tree helpers
License
MIT