Packages
nous
0.8.0
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.17
0.12.16
0.12.15
0.12.14
0.12.13
0.12.12
0.12.11
0.12.9
0.12.7
0.12.6
0.12.5
0.12.3
0.12.2
0.12.0
0.11.3
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.5.0
AI agent framework for Elixir with multi-provider LLM support
Current section
Files
Jump to
Current section
Files
README.md

# Nous AI
> *"Nous (νοῦς) — the ancient Greek concept of mind, reason, and intellect; the faculty of understanding that grasps truth directly."*
AI agent framework for Elixir with multi-provider LLM support.
[](https://elixir-lang.org)
[](https://github.com/nyo16/nous/blob/master/LICENSE)
[](#features)
## Installation
Add to your `mix.exs`:
```elixir
def deps do
[
{:nous, "~> 0.8.0"}
]
end
```
Then run:
```bash
mix deps.get
```
## Quick Start
```elixir
# Create an agent
agent = Nous.new("lmstudio:qwen3",
instructions: "Be helpful and concise."
)
# Run it
{:ok, result} = Nous.run(agent, "What is Elixir?")
IO.puts(result.output)
IO.puts("Tokens: #{result.usage.total_tokens}")
```
## Supported Providers
| Provider | Model String | Streaming |
|----------|-------------|-----------|
| LM Studio | `lmstudio:qwen3` | ✅ |
| OpenAI | `openai:gpt-4` | ✅ |
| Anthropic | `anthropic:claude-sonnet-4-5-20250929` | ✅ |
| Groq | `groq:llama-3.1-70b-versatile` | ✅ |
| Ollama | `ollama:llama2` | ✅ |
| OpenRouter | `openrouter:anthropic/claude-3.5-sonnet` | ✅ |
| Together AI | `together:meta-llama/Llama-3-70b-chat-hf` | ✅ |
| Custom | `openai_compatible:model` + `:base_url` | ✅ |
All providers use pure Elixir HTTP clients (Req + Finch).
```elixir
# Switch providers with one line change
agent = Nous.new("lmstudio:qwen3") # Local (free)
agent = Nous.new("openai:gpt-4") # OpenAI
agent = Nous.new("anthropic:claude-sonnet-4-5-20250929") # Anthropic
```
## Features
### Tool Calling
Define Elixir functions as tools. The AI calls them automatically when needed.
```elixir
get_weather = fn _ctx, %{"city" => city} ->
%{city: city, temperature: 72, conditions: "sunny"}
end
agent = Nous.new("openai:gpt-4",
instructions: "You can check the weather.",
tools: [get_weather]
)
{:ok, result} = Nous.run(agent, "What's the weather in Tokyo?")
```
### Tools with Context
Pass dependencies (user, database, API keys) via context:
```elixir
get_balance = fn ctx, _args ->
user = ctx.deps[:user]
%{balance: user.balance}
end
agent = Nous.new("openai:gpt-4", tools: [get_balance])
{:ok, result} = Nous.run(agent, "What's my balance?",
deps: %{user: %{id: 123, balance: 1000}}
)
```
### Context Continuation
Continue conversations with full context preservation:
```elixir
{:ok, result1} = Nous.run(agent, "My name is Alice")
{:ok, result2} = Nous.run(agent, "What's my name?", context: result1.context)
# => "Your name is Alice"
```
### Streaming
```elixir
{:ok, stream} = Nous.run_stream(agent, "Write a haiku")
stream
|> Enum.each(fn
{:text_delta, text} -> IO.write(text)
{:finish, _} -> IO.puts("")
_ -> :ok
end)
```
### Callbacks
Monitor execution with callbacks or process messages:
```elixir
# Map-based callbacks
{:ok, result} = Nous.run(agent, "Hello",
callbacks: %{
on_llm_new_delta: fn _event, delta -> IO.write(delta) end,
on_tool_call: fn _event, call -> IO.puts("Tool: #{call.name}") end
}
)
# Process messages (for LiveView)
{:ok, result} = Nous.run(agent, "Hello", notify_pid: self())
# Receives: {:agent_delta, text}, {:tool_call, call}, {:agent_complete, result}
```
### Module-Based Tools
Define tools as modules for better organization and testability:
```elixir
defmodule MyTools.Search do
@behaviour Nous.Tool.Behaviour
@impl true
def metadata do
%{
name: "search",
description: "Search the web",
parameters: %{
"type" => "object",
"properties" => %{
"query" => %{"type" => "string"}
},
"required" => ["query"]
}
}
end
@impl true
def execute(ctx, %{"query" => query}) do
http = ctx.deps[:http_client] || MyApp.HTTP
{:ok, http.search(query)}
end
end
agent = Nous.new("openai:gpt-4",
tools: [Nous.Tool.from_module(MyTools.Search)]
)
```
### Tool Context Updates
Tools can modify context state for subsequent calls:
```elixir
alias Nous.Tool.ContextUpdate
add_item = fn ctx, %{"item" => item} ->
items = ctx.deps[:cart] || []
{:ok, %{added: item}, ContextUpdate.set(ContextUpdate.new(), :cart, items ++ [item])}
end
```
### Prompt Templates
Build prompts with EEx variable substitution:
```elixir
alias Nous.PromptTemplate
template = PromptTemplate.from_template(
"You are a <%= @role %> who speaks <%= @language %>.",
role: :system
)
message = PromptTemplate.to_message(template, %{role: "teacher", language: "Spanish"})
{:ok, result} = Nous.run(agent, messages: [message, Message.user("Hello")])
```
### ReActAgent
For complex multi-step reasoning with planning:
```elixir
agent = Nous.ReActAgent.new("openai:gpt-4",
tools: [&search/2, &calculate/2]
)
{:ok, result} = Nous.run(agent,
"Research the population of Tokyo and calculate its density"
)
```
### LiveView Integration
```elixir
defmodule MyAppWeb.ChatLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
agent = Nous.new("lmstudio:qwen3", instructions: "Be helpful.")
{:ok, assign(socket, agent: agent, messages: [], streaming: false)}
end
def handle_event("send", %{"message" => msg}, socket) do
Task.start(fn ->
Nous.run(socket.assigns.agent, msg, notify_pid: socket.root_pid)
end)
{:noreply, assign(socket, streaming: true)}
end
def handle_info({:agent_delta, text}, socket) do
{:noreply, update(socket, :current, &(&1 <> text))}
end
def handle_info({:agent_complete, result}, socket) do
messages = socket.assigns.messages ++ [%{role: :assistant, content: result.output}]
{:noreply, assign(socket, messages: messages, streaming: false)}
end
end
```
See [examples/advanced/liveview_integration.exs](examples/advanced/liveview_integration.exs) for complete patterns.
## Examples
**[Full Examples Collection](examples/README.md)** - 21 focused examples from basics to production.
### Core Examples (01-10)
| Example | Description |
|---------|-------------|
| [01_hello_world.exs](examples/01_hello_world.exs) | Minimal example |
| [02_with_tools.exs](examples/02_with_tools.exs) | Tool calling |
| [03_streaming.exs](examples/03_streaming.exs) | Streaming responses |
| [04_conversation.exs](examples/04_conversation.exs) | Multi-turn with context |
| [05_callbacks.exs](examples/05_callbacks.exs) | Callbacks + LiveView |
| [06_prompt_templates.exs](examples/06_prompt_templates.exs) | EEx templates |
| [07_module_tools.exs](examples/07_module_tools.exs) | Module-based tools |
| [08_tool_testing.exs](examples/08_tool_testing.exs) | Test helpers |
| [09_agent_server.exs](examples/09_agent_server.exs) | GenServer agent |
| [10_react_agent.exs](examples/10_react_agent.exs) | ReAct pattern |
### Provider Examples
- [providers/anthropic.exs](examples/providers/anthropic.exs) - Claude, extended thinking
- [providers/openai.exs](examples/providers/openai.exs) - GPT models
- [providers/lmstudio.exs](examples/providers/lmstudio.exs) - Local AI
- [providers/switching_providers.exs](examples/providers/switching_providers.exs) - Provider comparison
### Advanced Examples
- [advanced/context_updates.exs](examples/advanced/context_updates.exs) - Tool state management
- [advanced/error_handling.exs](examples/advanced/error_handling.exs) - Retries, fallbacks
- [advanced/telemetry.exs](examples/advanced/telemetry.exs) - Metrics, cost tracking
- [advanced/cancellation.exs](examples/advanced/cancellation.exs) - Task cancellation
- [advanced/liveview_integration.exs](examples/advanced/liveview_integration.exs) - LiveView patterns
## Telemetry
Attach handlers for monitoring:
```elixir
Nous.Telemetry.attach_default_handler()
```
**Events:**
- `[:nous, :agent, :run, :start/stop/exception]`
- `[:nous, :agent, :iteration, :start/stop]`
- `[:nous, :provider, :request, :start/stop/exception]`
- `[:nous, :tool, :execute, :start/stop/exception]`
- `[:nous, :tool, :timeout]`
- `[:nous, :context, :update]`
## Architecture
```
Nous.new/2 → Agent struct
↓
Nous.run/3 → AgentRunner
↓
├─→ Context (messages, deps, callbacks)
├─→ Behaviour (BasicAgent | ReActAgent | custom)
├─→ ModelDispatcher → Provider → HTTP
├─→ ToolExecutor (timeout, validation)
└─→ Callbacks (map | notify_pid)
```
## Contributing
Contributions welcome! See [CHANGELOG.md](CHANGELOG.md) for recent changes.
## License
Apache 2.0 - see [LICENSE](https://github.com/nyo16/nous/blob/master/LICENSE)
## Credits
- Inspired by [Pydantic AI](https://ai.pydantic.dev/)
- HTTP: [Req](https://github.com/wojtekmach/req) + [Finch](https://github.com/sneako/finch)