Ruby-native agents on DSPy's programming model · v1.0.2

Build typed AI agents in Ruby

Define task contracts with Sorbet. Give models typed tools. Keep state, limits, errors, and side effects in Ruby.

  • Typed agent contracts with Sorbet
  • Evaluation and prompt optimization
  • OpenAI, Anthropic, Gemini, Ollama
class AnswerWeather < DSPy::Signature
  description "Answer weather questions with tools"

  input  { const :question, String }
  output { const :answer,   String }
end

agent = DSPy::ReAct.new(
  AnswerWeather,
  tools: [WeatherTool.new],
  max_iterations: 3
)

agent.call(question: "Weather in Valencia?").answer
# => "72°F and sunny in Valencia"

A signature types the boundary; Ruby owns the tools and the loop.

The shape of an agent

A contract, a tool, a bounded loop

A signature defines the task and result. Ruby implements the tools and owns permissions, errors, side effects, and iteration limits — the same shape every program on this page is built from.

  1. 01

    Define the contract and tool

    The signature types the agent's boundary. The tool exposes one narrow Ruby capability.

    class AnswerWeather < DSPy::Signature
      description "Answer weather questions with tools"
    
      input do
        const :question, String
      end
    
      output do
        const :answer, String
      end
    end
    
    class WeatherTool < DSPy::Tools::Base
      tool_name "weather"
      tool_description "Get weather for a location"
    
      sig { params(location: String).returns(String) }
      def call(location:)
        "72°F and sunny in #{location}"
      end
    end
    
  2. 02

    Run a bounded tool loop

    ReAct lets the model call the weather tool, observe its result, and finish with a typed answer.

    agent = DSPy::ReAct.new(
      AnswerWeather,
      tools: [WeatherTool.new],
      max_iterations: 3
    )
    
    result = agent.call(question: "What is the weather in Valencia?")
    puts result.answer
    
  3. 03

    Inspect what happened

    The answer follows the declared type. The history records tool choices and results.

    result.answer.class
    # => String
    
    result.history.each do |step|
      puts [step[:action], step[:tool_input], step[:observation]].inspect
    end
    

The model chooses whether to call a tool or finish; Ruby executes each tool and enforces the loop limit. Evaluate complete runs with examples and metrics, then use an optimizer to search for better instructions and demonstrations.

The same shape, real programs

Each runs from a checkout of the repository. Here is the piece that matters; the rest is in the example's README.

Agents & tools

A read-only GitHub agent

A ReAct agent handed the GitHub CLI as read-only tools — it inspects repos, issues, and pull requests, and cannot write.

examples/github-assistant
class GitHubAssistant < DSPy::Signature
  description "Operate on a repo with the GitHub CLI"

  input do
    const :task, String
    const :repository, String, default: ""
  end
  output { const :result, String }
end

# Read-only GitHub CLI tools — inspect only, no writes
tools = DSPy::Tools::GitHubCLIToolset.to_tools
agent = DSPy::ReAct.new(
  GitHubAssistant,
  tools: tools,
  max_iterations: 15
)

agent.call(
  task: "List open PRs and flag those ready for review",
  repository: "vicentereig/dspy.rb"
).result

Type-driven control

Union types choose the action

The model returns one of several typed actions in a single union field; Ruby pattern matching dispatches on the T::Struct it chose.

examples/coffee-shop-agent
class CoffeeShopSignature < DSPy::Signature
  description "Analyze a request and pick an action"

  input { const :customer_request, String }
  output do
    # one typed action from a union
    const :action, T.any(
      CoffeeShopActions::MakeDrink,
      CoffeeShopActions::RefundOrder,
      CoffeeShopActions::CallManager
    )
  end
end

# Ruby pattern-matches the action the model chose
case (action = result.action)
when CoffeeShopActions::MakeDrink
  "Making a #{action.size.serialize} #{action.drink_type}"
when CoffeeShopActions::RefundOrder
  "Refunding $#{action.refund_amount}"
when CoffeeShopActions::CallManager
  "Escalating: #{action.issue}"
end

Optimization

Compile a classifier with MIPROv2

Give the optimizer a program, a metric, and labelled examples; it searches instructions and demonstrations and keeps the best.

examples/ade_optimizer_miprov2
class ADETextClassifier < DSPy::Signature
  description "Flag adverse drug events in clinical text"

  input  { const :text, String }
  output { const :label, ADELabel }
end

# Search instructions + demonstrations against a metric
optimizer = DSPy::Teleprompt::MIPROv2.new(metric: metric)
result = optimizer.compile(
  baseline_program,
  trainset: train_examples,
  valset: val_examples
)

optimized_program = result.optimized_program

Built for Ruby developers

Ruby types and control flow for agents and model-backed programs.

Type-safe from the start
Signatures validate inputs and convert provider responses into declared Ruby types. Invalid outputs fail before application code uses them.
Test like normal code
Use RSpec for deterministic behavior and evaluation sets for model behavior. Tests and metrics answer different questions.
Optimize with data
Give an optimizer examples and a metric. It can search instructions and demonstrations, then persist the resulting prompt artifacts.
Compose and reuse
Compose modules with Ruby control flow. Keep fixed steps deterministic; use an agent when the model has a useful choice among tools or actions.
Control the runtime
Ruby owns state, permissions, budgets, errors, and termination. Traces and persisted prompt artifacts make executions inspectable.
Observe behavior
Modules emit events and tracing attributes. Optional integrations export spans; evaluation measures behavior against examples and metrics.

Choose the adapter for the model you deploy

DSPy.rb keeps provider SDKs in separate packages. Model capabilities still depend on the selected provider, endpoint, and SDK version.

OpenAI
Install dspy-openai. Check the selected model and endpoint for structured output, tools, media, and streaming support.
Google Gemini
Install dspy-gemini. Verify model capabilities before relying on structured output, tools, media, or streaming.
Anthropic Claude
Install dspy-anthropic. Verify model capabilities before relying on structured output, tools, media, or streaming.
RubyLLM
Install dspy-ruby_llm to route models through RubyLLM’s registry with the ruby_llm/… prefix. It reaches every provider RubyLLM supports and reuses an existing RubyLLM configuration.
Local & compatible
Use Ollama or an OpenAI-compatible endpoint. Confirm the server and model support each capability your agent needs.

One agent, different models: keep the signature, tools, and Ruby control flow stable when changing adapters. Re-evaluate the agent — model behavior and capabilities can change.

Learn more about provider setup

Build your first typed program

Define the contract, evaluate the output, and run an optimizer when examples, a metric, and a budget are ready.