# Jido AI
Jido AI is an extension of the Jido framework for building AI Agents and Workflows in Elixir. At present, it provides a single action for interacting with Anthropic's Claude models via the Instructor library.
## Installation
> **Note:** You must install `instructor` from GitHub until a new version is released. Hex does not yet have a release of `instructor` that supports the `Instructor.Adapters.Anthropic` adapter.
```elixir
def deps do
[
{:jido, "~> 1.0.0"},
{:jido_ai, "~> 1.0.0"},
# Must install from github until a new version is released
{:instructor, github: "thmsmlr/instructor_ex"}
]
end
```
## Configuration
You will need to properly configure the `Instructor` library to use the Anthropic adapter:
```elixir
# config/config.exs
config :instructor,
adapter: Instructor.Adapters.Anthropic,
anthropic: [
api_key: System.get_env("ANTHROPIC_API_KEY")
]
```
## Supported Providers
Jido AI supports multiple LLM providers:
- Anthropic (Claude models)
- OpenAI (GPT models)
- OpenRouter (proxy for multiple models)
- Cloudflare (Workers AI models)
- Google (Gemini models)
### Using Google Gemini
To use Google's Gemini models with the OpenAI-compatible API:
```elixir
# Set your Google API key
Jido.AI.Keyring.set_session_value(:google_api_key, "your_gemini_api_key")
# Create a model using the Google provider
{:ok, model} = Jido.AI.Model.from({:google, [model: "gemini-2.0-flash"]})
# Use the model with any OpenAI-compatible action
{:ok, result} = Jido.AI.Actions.OpenaiEx.run(
%{
model: model,
messages: [
%{role: "user", content: "Tell me about Elixir programming language"}
],
temperature: 0.7
},
%{}
)
```
You can also set the Google API key using environment variables:
```
GOOGLE_API_KEY=your_gemini_api_key
```
## Prompt and Message Handling
Jido AI provides a robust system for handling prompts and messages when interacting with LLMs.
### MessageItem
The `Jido.AI.Prompt.MessageItem` module is used to represent messages in conversations with LLMs. It supports:
- Basic text messages with different roles (user, assistant, system, function)
- Rich content including images and files
- Template-based messages using EEx
```elixir
alias Jido.AI.Prompt.MessageItem
# Create a simple user message
user_msg = MessageItem.new(%{role: :user, content: "Hello"})
# Create a system message
system_msg = MessageItem.new(%{role: :system, content: "You are a helpful assistant"})
# Create a message with rich content (image)
rich_msg = MessageItem.new_multipart(:user, [
MessageItem.text_part("Check out this image:"),
MessageItem.image_part("https://example.com/image.jpg")
])
# Create a message with a template
template_msg = MessageItem.new(%{
role: :system,
content: "You are a <%= @assistant_type %>",
engine: :eex
})
```
For more details on MessageItem usage, refer to the documentation.
## Example
Here's how to use Jido AI with Jido.Exec to get structured information about US politicians. See the [examples/politician.ex](examples/politician.ex) for more a full example.
```elixir
# Define a simple workflow
defmodule JidoAi.Examples.Politician do
defmodule Schema do
use Ecto.Schema
use Instructor
@primary_key false
embedded_schema do
field(:first_name, :string)
field(:last_name, :string)
embeds_many :offices_held, Office, primary_key: false do
field(:office, Ecto.Enum,
values: [:president, :vice_president, :governor, :congress, :senate]
)
field(:from_date, :date)
field(:to_date, :date)
end
end
end
use Jido.Action,
name: "politician",
description: "A description of United States Politicians and the offices that they held",
schema: [
query: [type: :string, required: true, doc: "The query to search for"]
]
def run(params, _context) do
# Run the Anthropic ChatCompletion action
JidoAi.Actions.Anthropic.ChatCompletion.run(
%{
model: "claude-3-5-haiku-latest",
messages: [
%{
role: "user",
content: params.query
}
],
response_model: Schema,
temperature: 0.5,
max_tokens: 1000
},
%{}
)
|> case do
{:ok, %{result: politician}} -> {:ok, %{result: politician}}
{:error, reason} -> {:error, reason}
end
end
end
# Run the workflow
iex> {:ok, result} = Jido.Exec.run(JidoAi.Examples.Politician, %{query: "Tell me about Barack Obama's political career"})
iex> result.result
%JidoAi.Examples.Politician.Schema{
first_name: "Barack",
last_name: "Obama",
offices_held: [
%JidoAi.Examples.Politician.Schema.Office{
office: :senate,
from_date: ~D[2005-01-03],
to_date: ~D[2008-11-16]
},
%JidoAi.Examples.Politician.Schema.Office{
office: :president,
from_date: ~D[2009-01-20],
to_date: ~D[2017-01-20]
}
]
}
```
The example demonstrates how JidoAi can:
- Write Actions that can wrap other Actions
- Use Jido.Exec to orchestrate AI operations
- Parse natural language queries about politicians
- Return structured data using Ecto schemas
- Handle complex nested data structures
- Provide type validation through Ecto's type system
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/jido_ai>.
## LLM Keyring
The Jido.AI application includes a Keyring system that manages API keys for various LLM providers. The Keyring is a singleton GenServer that helps to manage LLM keys for convenience, not for security.
### Key Sources
Keys are loaded with the following priority:
1. Environment variables (highest priority)
2. Application environment
3. Default values (lowest priority)
### Session-based Keys
Keys can also be set on a per-session (per-process) basis. This allows different parts of your application to use different API keys without affecting other processes.
### Usage
```elixir
# Get a key (checks session keys first, then environment keys)
api_key = Jido.AI.Keyring.get_key(:anthropic)
# Get only the environment-level key
env_key = Jido.AI.Keyring.get_env_key(:anthropic)
# Set a session-specific key (only affects the current process)
Jido.AI.Keyring.set_session_key(:anthropic, "my_session_key")
# Clear a session key
Jido.AI.Keyring.clear_session_key(:anthropic)
# Clear all session keys for the current process
Jido.AI.Keyring.clear_all_session_keys()
# Check if a key is valid (non-nil and non-empty)
Jido.AI.Keyring.has_valid_key?(api_key)
# Test if a key is valid by making an API request
Jido.AI.Keyring.test_key(:anthropic, api_key)
```
### Configuration
You can configure keys in your `config.exs` file:
```elixir
config :jido_ai, :instructor,
anthropic: [
api_key: "your_anthropic_key"
]
config :jido_ai, :openai,
api_key: "your_openai_key"
```
Or using environment variables:
```
ANTHROPIC_API_KEY=your_anthropic_key
OPENAI_API_KEY=your_openai_key
```
Environment variables take precedence over application configuration.