AI Integration with Jido
MANDATORY PREPARATION
Invoke /jido-core first — it contains ecosystem context, conventions, and core design principles. Follow its guidance before proceeding.
Overview
jido_ai provides AI/LLM integration for Jido agents, built on req_llm.
It offers:
- Model aliases for semantic model references (
:fast,:capable,:thinking) - High-level generation facades (
generate_text,generate_object,stream_text) Jido.AI.Agentmacro for building ReAct-based AI agents- Tool calling: expose
Jido.Actionmodules as LLM-callable tools - Request tracking with async
ask/awaitpattern
Step 1: Add Dependencies
# mix.exs
defp deps do
[
{:jido, "~> 2.0"},
{:jido_ai, "~> 2.0"},
{:req_llm, "~> 1.9"}
]
end
Step 2: Configure LLM Providers
Set API keys and model aliases in your config:
# config/config.exs
config :req_llm,
anthropic: [api_key: System.get_env("ANTHROPIC_API_KEY")],
openai: [api_key: System.get_env("OPENAI_API_KEY")],
google: [api_key: System.get_env("GOOGLE_API_KEY")]
# config/config.exs
config :jido_ai,
model_aliases: %{
fast: "anthropic:claude-sonnet-4-20250514",
capable: "anthropic:claude-sonnet-4-20250514",
thinking: "anthropic:claude-sonnet-4-20250514"
}
Model Aliases
Use semantic aliases instead of hardcoded model strings:
Jido.AI.resolve_model(:fast) # => "anthropic:claude-sonnet-4-20250514"
Jido.AI.resolve_model(:capable) # => "anthropic:claude-sonnet-4-20250514"
Jido.AI.model_aliases() # => all configured aliases
LLM Defaults
Configure role-based defaults for generation helpers:
config :jido_ai,
llm_defaults: %{
text: %{model: :fast, temperature: 0.2, max_tokens: 1024},
object: %{model: :thinking, temperature: 0.0, max_tokens: 1024},
stream: %{model: :fast, temperature: 0.2, max_tokens: 1024}
}
Step 3: Using Generation Facades
Jido.AI provides thin wrappers over ReqLLM.Generation:
# Simple text generation
{:ok, response} = Jido.AI.generate_text("Summarize this in one sentence.")
# Structured object generation
{:ok, json} = Jido.AI.generate_object("Extract fields from this text", schema)
# Streaming text
{:ok, stream} = Jido.AI.stream_text("Stream this response")
# With explicit model and options
{:ok, response} = Jido.AI.generate_text("Hello",
model: :capable,
temperature: 0.5,
max_tokens: 2048
)
Step 4: Build AI Agents
Use Jido.AI.Agent to create agents with the ReAct strategy:
defmodule MyApp.WeatherAgent do
use Jido.AI.Agent,
name: "weather_agent",
description: "Answers weather questions using tools",
tools: [MyApp.Actions.GetWeather, MyApp.Actions.GetForecast],
system_prompt: "You are a weather expert. Use tools to look up weather data.",
model: :fast,
max_iterations: 10,
max_tokens: 4096,
streaming: true
end
AI Agent Options
| Option | Default | Description |
|---|---|---|
name |
required | Agent name |
tools |
required | List of Jido.Action modules as tools |
description |
auto | Agent description |
system_prompt |
none | System prompt for the LLM |
model |
:fast |
Model alias or direct spec |
max_iterations |
10 |
Max ReAct reasoning iterations |
max_tokens |
4096 |
Max tokens per LLM response |
streaming |
true |
Stream LLM responses |
tool_timeout_ms |
15_000 |
Per-attempt tool execution timeout |
tool_max_retries |
1 |
Retries for tool failures |
tool_context |
%{} |
Context map passed to all tools |
Step 5: Interact with AI Agents
Async Pattern (Preferred)
{:ok, pid} = Jido.AgentServer.start(agent: MyApp.WeatherAgent)
# Send query — returns immediately with a request handle
{:ok, request} = MyApp.WeatherAgent.ask(pid, "What's the weather in Tokyo?")
# Await the result
{:ok, answer} = MyApp.WeatherAgent.await(request, timeout: 30_000)
Sync Convenience
{:ok, answer} = MyApp.WeatherAgent.ask_sync(pid, "What's the weather in Tokyo?",
timeout: 30_000
)
Per-Request Tool Context
{:ok, request} = MyApp.WeatherAgent.ask(pid, "Get my preferences",
tool_context: %{actor: current_user, tenant_id: "acme"}
)
Step 6: Tool Calling
Any Jido.Action can serve as an LLM tool. Define actions with descriptive
doc strings in the schema — these become the tool parameter descriptions:
defmodule MyApp.Actions.GetWeather do
use Jido.Action,
name: "get_weather",
description: "Gets current weather for a location",
schema: [
location: [type: :string, required: true, doc: "City or location name"],
units: [type: {:in, [:celsius, :fahrenheit]}, default: :celsius, doc: "Temperature units"]
]
@impl true
def run(params, _context) do
# Call weather API
{:ok, %{temperature: 22, conditions: "sunny", location: params.location}}
end
end
# Convert to tool spec for LLM consumption
MyApp.Actions.GetWeather.to_tool()
Runtime Tool Management
Register and unregister tools dynamically on running agents:
{:ok, agent} = Jido.AI.register_tool(agent_pid, MyApp.Tools.Calculator)
{:ok, agent} = Jido.AI.unregister_tool(agent_pid, "calculator")
{:ok, tools} = Jido.AI.list_tools(agent_pid)
{:ok, true} = Jido.AI.has_tool?(agent_pid, "calculator")
Step 7: Agent-as-Team Patterns
Compose multiple specialized agents for complex workflows:
defmodule MyApp.ResearchAgent do
use Jido.AI.Agent,
name: "research_agent",
description: "Researches topics using multiple sources",
tools: [MyApp.Actions.WebSearch, MyApp.Actions.DatabaseQuery],
system_prompt: "You are a research assistant. Search multiple sources."
end
defmodule MyApp.WriterAgent do
use Jido.AI.Agent,
name: "writer_agent",
description: "Writes content based on research",
tools: [MyApp.Actions.FormatDocument],
system_prompt: "You are a technical writer. Format research into documents."
end
Use parent-child hierarchies or signal routing to coordinate agents. See the
Jido Workbench (jido_workbench) for working multi-agent examples.
Step 8: Error Handling and Rate Limiting
Error Handling
case MyApp.WeatherAgent.ask_sync(pid, query, timeout: 30_000) do
{:ok, answer} ->
# Success
answer
{:error, {:timeout, diagnostics}} ->
# Timeout — check diagnostics map for server_status, queue_length
Logger.warning("Agent timed out: #{inspect(diagnostics)}")
{:error, reason} ->
# Other error
Logger.error("Agent error: #{inspect(reason)}")
end
Completion Detection
Agents signal completion via state, not process death:
{:ok, state} = Jido.AgentServer.state(pid)
case state.agent.state.status do
:completed -> state.agent.state.last_answer
:failed -> {:error, state.agent.state.error}
_ -> :still_running
end
Rate Limiting
Use the request_policy option to control concurrency:
use Jido.AI.Agent,
name: "rate_limited_agent",
request_policy: :reject, # reject concurrent requests (default)
tools: [...]
DO / DON'T
DO
- Use model aliases (
:fast,:capable) instead of hardcoded model strings. - Add descriptive
docto action schemas — these become LLM tool descriptions. - Use async
ask/awaitfor concurrent request handling. - Set appropriate timeouts for tool execution and streaming.
- Keep API keys in environment variables, never in source code.
DON'T
- Hardcode model provider strings in agent definitions — use aliases.
- Skip error handling on LLM calls — network failures are common.
- Use blocking
ask_syncin production hot paths — prefer async. - Expose sensitive data in tool context — filter before passing to LLM.
- Ignore token limits — set
max_tokensappropriate to your use case.