Powered by AppSignal & Oban Pro

Setup

first-llm-agent.livemd

%{ title: “Your first LLM agent”, description: “Add LLM reasoning to a Jido agent with jido_ai, configure a provider, and run your first AI-enhanced command.”, category: :docs, order: 40, tags: [:docs, :getting_started, :tutorial, :ai, :livebook], draft: false, legacy_paths: [“/docs/learn/first-llm-agent”], learning_outcomes: [

"Install jido_ai and req_llm in a self-contained Livebook",
"Configure a provider key for notebook execution",
"Start the default Jido runtime and run your first AI-enhanced command"

], prerequisites: [“/docs/getting-started/first-agent”], livebook: %{

runnable: true,
required_env_vars: ["OPENAI_API_KEY"],
requires_network: true,
setup_instructions: "Set OPENAI_API_KEY or LB_OPENAI_API_KEY before running the request cell."

} }


Setup

This notebook is self-contained: install the dependencies, configure a provider key, start the default Jido runtime, and send one prompt through an AI agent. If you already completed Your first agent, still run this notebook top to bottom because it defines its own module.

Mix.install([
  {{mix_dep:jido}},
  {{mix_dep:jido_ai}},
  {{mix_dep:req_llm}}
])

Logger.configure(level: :warning)

# Livebook imports can execute generated docs as doctests.
# Disable compiler docs until the current Jido Hex release drops the invalid signal_types/0 example.
Code.put_compiler_option(:docs, false)

Configure credentials

In Livebook, store OPENAI_API_KEY as a secret. Livebook exposes that secret as LB_OPENAI_API_KEY inside the notebook, so this cell checks both names.

openai_key = System.get_env("LB_OPENAI_API_KEY") || System.get_env("OPENAI_API_KEY")

configured? =
  if is_binary(openai_key) do
    ReqLLM.put_key(:openai_api_key, openai_key)
    true
  else
    IO.puts("Set OPENAI_API_KEY or LB_OPENAI_API_KEY before running the request cell.")
    false
  end

Define the agent

use Jido.AI.Agent keeps the module declarative: describe the agent, choose a model, and provide a system prompt. This first example does not use tools, so the only new capability is the LLM call.

defmodule MyAgentApp.Greeter do
  use Jido.AI.Agent,
    name: "greeter",
    description: "Generates a friendly greeting",
    tools: [],
    model: :fast,
    system_prompt: """
    You are a friendly greeter.
    Generate a short, warm welcome message.
    One or two sentences maximum.
    """
end

model: :fast uses the default fast model alias from jido_ai. You can swap in a provider-specific string later, but the alias keeps the tutorial portable.

Start the runtime and agent

Jido.start/0 is the notebook-friendly runtime helper. It is safe to call more than once, so rerunning this cell does not force you to restart the notebook. Jido.start_agent/3 then starts your agent inside that runtime.

{:ok, _} = Jido.start()
runtime = Jido.default_instance()
agent_id = "greeter-demo-#{System.unique_integer([:positive])}"

{:ok, pid} = Jido.start_agent(runtime, MyAgentApp.Greeter, id: agent_id)

Ask the agent

Send one prompt with ask_sync/3. The return value is {:ok, text} on success or {:error, reason} if the provider call fails.

greeting =
  if configured? do
    MyAgentApp.Greeter.ask_sync(
      pid,
      "Say hello to someone just getting started with Jido.",
      timeout: 30_000
    )
  else
    {:skip, :no_openai_key}
  end

IO.inspect(greeting, label: "Greeting")
case greeting do
  {:ok, text} ->
    IO.puts(text)

  {:error, reason} ->
    IO.puts("Greeting failed: #{inspect(reason)}")

  {:skip, :no_openai_key} ->
    IO.puts("Skipped request. Set OPENAI_API_KEY or LB_OPENAI_API_KEY to run it.")
end

Inspect the last request

Show the success path first, then inspect the runtime state. Jido.AgentServer.status/1 gives you the current snapshot for the running agent process.

snapshot =
  case Jido.AgentServer.status(pid) do
    {:ok, status} ->
      %{
        done?: status.snapshot.done?,
        result: status.snapshot.result,
        model: status.snapshot.details[:model]
      }

    other ->
      other
  end

IO.inspect(snapshot, label: "Snapshot")

What happened

  1. Jido.start/0 started the default runtime used for scripts and Livebook.
  2. Jido.start_agent/3 started MyAgentApp.Greeter as a supervised agent process.
  3. ask_sync/3 combined the system prompt and your message, then sent the request through req_llm.
  4. Jido.AgentServer.status/1 exposed the resulting snapshot so you could inspect the completed request.

The agent module stays free of provider-specific HTTP code. The runtime handles request execution, while your module stays focused on behavior.

Moving this into a Mix project

Keep the agent module the same, add the dependencies to mix.exs, configure the provider key in config/runtime.exs, and supervise a use Jido module in your application:

defmodule MyAgentApp.Jido do
  use Jido, otp_app: :my_agent_app
end
children = [
  {MyAgentApp.Jido, name: MyAgentApp.Jido}
]

Then start agents against that named runtime:

{:ok, pid} = Jido.start_agent(MyAgentApp.Jido, MyAgentApp.Greeter, id: "greeter-1")
MyAgentApp.Greeter.ask_sync(pid, "Say hello to the production app.", timeout: 30_000)

Next steps