Build type-safe LLM applications with DSPy.rb — Ruby's programmatic prompt framework with signatures, modules, agents, and optimization. Use when implementing predictable AI features, creating LLM signatures and modules, configuring language model providers, building agent systems with tools, optimizing prompts, or testing LLM-powered functionality in Ruby applications.
From aimi-engineeringnpx claudepluginhub aimi-so/aimi-engineering-plugin --plugin aimi-engineeringThis skill uses the workspace's default tool permissions.
assets/config-template.rbassets/module-template.rbassets/signature-template.rbreferences/core-concepts.mdreferences/observability.mdreferences/optimization.mdreferences/patterns.mdreferences/providers.mdreferences/rails-integration.mdreferences/testing.mdreferences/toolsets.mdDesigns and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Build LLM apps like you build software. Type-safe, modular, testable.
DSPy.rb brings software engineering best practices to LLM development. Instead of tweaking prompts, define what you want with Ruby types and let DSPy handle the rest.
Define interfaces between your app and LLMs using Ruby types:
class EmailClassifier < DSPy::Signature
description "Classify customer support emails by category and priority"
class Priority < T::Enum
enums do
Low = new('low')
Medium = new('medium')
High = new('high')
Urgent = new('urgent')
end
end
input do
const :email_content, String
const :sender, String
end
output do
const :category, String
const :priority, Priority
const :confidence, Float
end
end
For detailed signature syntax, supported types, defaults, field descriptions, recursive types, union types, and schema formats, use the Read tool on
references/core-concepts.md
Composable building blocks that wrap predictors. Define a forward method; invoke with .call().
reasoning field automatically. Do not define :reasoning in output.dspy-code_act).class SentimentAnalyzer < DSPy::Module
def initialize
super
@predictor = DSPy::Predict.new(SentimentSignature)
end
def forward(text:)
@predictor.call(text: text)
end
end
analyzer = SentimentAnalyzer.new
result = analyzer.call(text: "I love this product!")
result.sentiment # => "positive"
API rules:
.call(), not .forward().result.field, not result[:field].For module composition, lifecycle callbacks, instruction update contract, predictor comparison, concurrent predictions, and few-shot examples, use the Read tool on
references/core-concepts.md
Create type-safe tools for agents with Sorbet signatures:
class CalculatorTool < DSPy::Tools::Base
tool_name 'calculator'
tool_description 'Performs arithmetic operations'
sig { params(operation: String, num1: Float, num2: Float).returns(T.any(Float, String)) }
def call(operation:, num1:, num2:)
case operation
when 'add' then num1 + num2
when 'subtract' then num1 - num2
when 'multiply' then num1 * num2
when 'divide'
return "Error: Division by zero" if num2 == 0
num1 / num2
end
end
end
Group related tools with DSPy::Tools::Toolset:
class DataToolset < DSPy::Tools::Toolset
toolset_name "data_processing"
tool :convert, description: "Convert data between formats"
tool :validate, description: "Validate data structure"
sig { params(data: String, format: String).returns(String) }
def convert(data:, format:)
# ...
end
sig { params(data: String, format: String).returns(T::Hash[String, T.untyped]) }
def validate(data:, format:)
# ...
end
end
For toolset DSL details, type safety, built-in toolsets (TextProcessing, GitHubCLI), enum parameters, and testing tools, use the Read tool on
references/toolsets.md
_type field injection for union types (T.any()) with structs"HIGH" matches new('high')$defs and $ref pointersT::Array[X], default: [] over T.nilable(T::Array[X])Improve accuracy with real data:
gem 'dspy-miprov2'gem 'dspy-gepa'DSPy::Evals with built-in metrics (exact_match, contains, numeric_difference, composite_and)For optimization configuration, metrics, GEPA feedback maps, evaluation framework, and storage system, use the Read tool on
references/optimization.md
# Install
gem 'dspy'
# Configure
DSPy.configure do |c|
c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
end
# Define a task
class SentimentAnalysis < DSPy::Signature
description "Analyze sentiment of text"
input do
const :text, String
end
output do
const :sentiment, String
const :score, Float
end
end
# Use it
analyzer = DSPy::Predict.new(SentimentAnalysis)
result = analyzer.call(text: "This product is amazing!")
puts result.sentiment # => "positive"
puts result.score # => 0.92
Two strategies for connecting to LLM providers:
gem 'dspy-openai' # OpenAI, OpenRouter, Ollama
gem 'dspy-anthropic' # Claude
gem 'dspy-gemini' # Gemini
gem 'dspy-ruby_llm'
gem 'ruby_llm'
DSPy.configure do |c|
c.lm = DSPy::LM.new('ruby_llm/gemini-2.5-flash', structured_outputs: true)
# c.lm = DSPy::LM.new('ruby_llm/claude-sonnet-4-20250514', structured_outputs: true)
# c.lm = DSPy::LM.new('ruby_llm/gpt-4o-mini', structured_outputs: true)
end
Override the language model temporarily:
fast_model = DSPy::LM.new("openai/gpt-4o-mini", api_key: ENV['OPENAI_API_KEY'])
DSPy.with_lm(fast_model) do
result = classifier.call(text: "test") # Uses fast_model inside this block
end
LM resolution hierarchy: Instance-level LM -> Fiber-local LM (DSPy.with_lm) -> Global LM (DSPy.configure).
Use configure_predictor for fine-grained control over agent internals:
agent = DSPy::ReAct.new(MySignature, tools: tools)
agent.configure { |c| c.lm = default_model }
agent.configure_predictor('thought_generator') { |c| c.lm = powerful_model }
For full provider configuration, Bedrock/VertexAI setup, compatibility matrix, and feature-flagged model selection, use the Read tool on
references/providers.md
When helping users with DSPy.rb:
T::Struct and T::Enum types, not string descriptionsapp/entities/ -- Extract shared types so signatures stay thinpredictor.configure { |c| c.lm = ... } to pick the right model per taskinput_json_schema and output_json_schema in unit testsDSPy::Context.with_span for observabilityFor detailed signature best practices, typed context patterns, error handling concerns, and advanced tool patterns, use the Read tool on
references/patterns.mdFor Rails directory structure, initializers, and feature-flagged model selection, use the Read tool onreferences/rails-integration.mdFor testing strategies and examples, use the Read tool onreferences/testing.md
Current: 0.34.3