ce-dspy-ruby
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDSPy.rb
DSPy.rb
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.
像构建普通软件一样构建LLM应用。类型安全、模块化、可测试。
DSPy.rb将软件工程最佳实践引入LLM开发。无需反复调整提示词,只需用Ruby类型定义需求,剩下的交给DSPy处理。
Overview
概述
DSPy.rb is a Ruby framework for building language model applications with programmatic prompts. It provides:
- Type-safe signatures — Define inputs/outputs with Sorbet types
- Modular components — Compose and reuse LLM logic
- Automatic optimization — Use data to improve prompts, not guesswork
- Production-ready — Built-in observability, testing, and error handling
DSPy.rb是一款用于构建大语言模型应用的Ruby框架,支持程序化提示。它提供以下功能:
- 类型安全签名 —— 使用Sorbet类型定义输入/输出
- 模块化组件 —— 组合并复用LLM逻辑
- 自动优化 —— 借助数据改进提示词,而非凭经验猜测
- 生产就绪 —— 内置可观测性、测试和错误处理机制
Core Concepts
核心概念
1. Signatures
1. 签名
Define interfaces between your app and LLMs using Ruby types:
ruby
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 # Type-safe enum with defined values
const :confidence, Float
end
end使用Ruby类型定义应用与LLM之间的接口:
ruby
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 # Type-safe enum with defined values
const :confidence, Float
end
end2. Modules
2. 模块
Build complex workflows from simple building blocks:
- Predict — Basic LLM calls with signatures
- ChainOfThought — Step-by-step reasoning
- ReAct — Tool-using agents
- CodeAct — Dynamic code generation agents (install the gem)
dspy-code_act
通过简单的构建块搭建复杂工作流:
- Predict —— 带签名的基础LLM调用
- ChainOfThought —— 分步推理
- ReAct —— 可使用工具的智能体
- CodeAct —— 动态代码生成智能体(需安装gem)
dspy-code_act
3. Tools & Toolsets
3. 工具与工具集
Create type-safe tools for agents with comprehensive Sorbet support:
ruby
undefined创建具备全面Sorbet支持的类型安全工具供智能体使用:
ruby
undefinedEnum-based tool with automatic type conversion
Enum-based tool with automatic type conversion
class CalculatorTool < DSPy::Tools::Base
tool_name 'calculator'
tool_description 'Performs arithmetic operations with type-safe enum inputs'
class Operation < T::Enum
enums do
Add = new('add')
Subtract = new('subtract')
Multiply = new('multiply')
Divide = new('divide')
end
end
sig { params(operation: Operation, num1: Float, num2: Float).returns(T.any(Float, String)) }
def call(operation:, num1:, num2:)
case operation
when Operation::Add then num1 + num2
when Operation::Subtract then num1 - num2
when Operation::Multiply then num1 * num2
when Operation::Divide
return "Error: Division by zero" if num2 == 0
num1 / num2
end
end
end
class CalculatorTool < DSPy::Tools::Base
tool_name 'calculator'
tool_description 'Performs arithmetic operations with type-safe enum inputs'
class Operation < T::Enum
enums do
Add = new('add')
Subtract = new('subtract')
Multiply = new('multiply')
Divide = new('divide')
end
end
sig { params(operation: Operation, num1: Float, num2: Float).returns(T.any(Float, String)) }
def call(operation:, num1:, num2:)
case operation
when Operation::Add then num1 + num2
when Operation::Subtract then num1 - num2
when Operation::Multiply then num1 * num2
when Operation::Divide
return "Error: Division by zero" if num2 == 0
num1 / num2
end
end
end
Multi-tool toolset with rich types
Multi-tool toolset with rich types
class DataToolset < DSPy::Tools::Toolset
toolset_name "data_processing"
class Format < T::Enum
enums do
JSON = new('json')
CSV = new('csv')
XML = new('xml')
end
end
tool :convert, description: "Convert data between formats"
tool :validate, description: "Validate data structure"
sig { params(data: String, from: Format, to: Format).returns(String) }
def convert(data:, from:, to:)
"Converted from #{from.serialize} to #{to.serialize}"
end
sig { params(data: String, format: Format).returns(T::Hash[String, T.any(String, Integer, T::Boolean)]) }
def validate(data:, format:)
{ valid: true, format: format.serialize, row_count: 42, message: "Data validation passed" }
end
end
undefinedclass DataToolset < DSPy::Tools::Toolset
toolset_name "data_processing"
class Format < T::Enum
enums do
JSON = new('json')
CSV = new('csv')
XML = new('xml')
end
end
tool :convert, description: "Convert data between formats"
tool :validate, description: "Validate data structure"
sig { params(data: String, from: Format, to: Format).returns(String) }
def convert(data:, from:, to:)
"Converted from #{from.serialize} to #{to.serialize}"
end
sig { params(data: String, format: Format).returns(T::Hash[String, T.any(String, Integer, T::Boolean)]) }
def validate(data:, format:)
{ valid: true, format: format.serialize, row_count: 42, message: "Data validation passed" }
end
end
undefined4. Type System & Discriminators
4. 类型系统与判别器
DSPy.rb uses sophisticated type discrimination for complex data structures:
- Automatic field injection — DSPy adds discriminator fields to structs for type safety
_type - Union type support — types automatically disambiguated by
T.any()_type - Reserved field name — Avoid defining your own fields in structs
_type - Recursive filtering — fields filtered during deserialization at all nesting levels
_type
DSPy.rb为复杂数据结构提供了完善的类型判别机制:
- 自动注入字段 —— DSPy会为结构体添加判别器字段以保证类型安全
_type - 联合类型支持 —— 类型会通过
T.any()自动区分_type - 保留字段名 —— 避免在结构体中自定义字段
_type - 递归过滤 —— 反序列化时会在所有嵌套层级过滤字段
_type
5. Optimization
5. 优化
Improve accuracy with real data:
- MIPROv2 — Advanced multi-prompt optimization with bootstrap sampling and Bayesian optimization
- GEPA — Genetic-Pareto Reflective Prompt Evolution with feedback maps, experiment tracking, and telemetry
- Evaluation — Comprehensive framework with built-in and custom metrics, error handling, and batch processing
借助真实数据提升准确率:
- MIPROv2 —— 高级多提示词优化,支持引导采样和贝叶斯优化
- GEPA —— 遗传帕累托反射式提示词进化,具备反馈映射、实验跟踪和遥测功能
- 评估 —— 全面的框架,内置及自定义指标、错误处理和批处理支持
Quick Start
快速开始
ruby
undefinedruby
undefinedInstall
Install
gem 'dspy'
gem 'dspy'
Configure
Configure
DSPy.configure do |c|
c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
end
DSPy.configure do |c|
c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
end
Define a task
Define a task
class SentimentAnalysis < DSPy::Signature
description "Analyze sentiment of text"
input do
const :text, String
end
output do
const :sentiment, String # positive, negative, neutral
const :score, Float # 0.0 to 1.0
end
end
class SentimentAnalysis < DSPy::Signature
description "Analyze sentiment of text"
input do
const :text, String
end
output do
const :sentiment, String # positive, negative, neutral
const :score, Float # 0.0 to 1.0
end
end
Use it
Use it
analyzer = DSPy::Predict.new(SentimentAnalysis)
result = analyzer.call(text: "This product is amazing!")
puts result.sentiment # => "positive"
puts result.score # => 0.92
undefinedanalyzer = DSPy::Predict.new(SentimentAnalysis)
result = analyzer.call(text: "This product is amazing!")
puts result.sentiment # => "positive"
puts result.score # => 0.92
undefinedProvider Adapter Gems
提供商适配Gem
Two strategies for connecting to LLM providers:
连接LLM提供商的两种策略:
Per-provider adapters (direct SDK access)
按提供商适配(直接SDK访问)
ruby
undefinedruby
undefinedGemfile
Gemfile
gem 'dspy'
gem 'dspy-openai' # OpenAI, OpenRouter, Ollama
gem 'dspy-anthropic' # Claude
gem 'dspy-gemini' # Gemini
Each adapter gem pulls in the official SDK (`openai`, `anthropic`, `gemini-ai`).gem 'dspy'
gem 'dspy-openai' # OpenAI, OpenRouter, Ollama
gem 'dspy-anthropic' # Claude
gem 'dspy-gemini' # Gemini
每个适配Gem都会引入官方SDK(`openai`、`anthropic`、`gemini-ai`)。Unified adapter via RubyLLM (recommended for multi-provider)
通过RubyLLM统一适配(多提供商推荐)
ruby
undefinedruby
undefinedGemfile
Gemfile
gem 'dspy'
gem 'dspy-ruby_llm' # Routes to any provider via ruby_llm
gem 'ruby_llm'
RubyLLM handles provider routing based on the model name. Use the `ruby_llm/` prefix:
```ruby
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)
endgem 'dspy'
gem 'dspy-ruby_llm' # 通过ruby_llm路由到任意提供商
gem 'ruby_llm'
RubyLLM会根据模型名称处理提供商路由,使用`ruby_llm/`前缀:
```ruby
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)
endEvents System
事件系统
DSPy.rb ships with a structured event bus for observing runtime behavior.
DSPy.rb内置结构化事件总线,用于监控运行时行为。
Module-Scoped Subscriptions (preferred for agents)
模块范围订阅(智能体推荐使用)
ruby
class MyAgent < DSPy::Module
subscribe 'lm.tokens', :track_tokens, scope: :descendants
def track_tokens(_event, attrs)
@total_tokens += attrs.fetch(:total_tokens, 0)
end
endruby
class MyAgent < DSPy::Module
subscribe 'lm.tokens', :track_tokens, scope: :descendants
def track_tokens(_event, attrs)
@total_tokens += attrs.fetch(:total_tokens, 0)
end
endGlobal Subscriptions (for observability/integrations)
全局订阅(用于可观测性/集成)
ruby
subscription_id = DSPy.events.subscribe('score.create') do |event, attrs|
Langfuse.export_score(attrs)
endruby
subscription_id = DSPy.events.subscribe('score.create') do |event, attrs|
Langfuse.export_score(attrs)
endWildcards supported
支持通配符
DSPy.events.subscribe('llm.*') { |name, attrs| puts "[#{name}] tokens=#{attrs[:total_tokens]}" }
Event names use dot-separated namespaces (`llm.generate`, `react.iteration_complete`). Every event includes module metadata (`module_path`, `module_leaf`, `module_scope.ancestry_token`) for filtering.DSPy.events.subscribe('llm.*') { |name, attrs| puts "[#{name}] tokens=#{attrs[:total_tokens]}" }
事件名称使用点分隔的命名空间(如`llm.generate`、`react.iteration_complete`)。每个事件都包含模块元数据(`module_path`、`module_leaf`、`module_scope.ancestry_token`)用于过滤。Lifecycle Callbacks
生命周期回调
Rails-style lifecycle hooks ship with every :
DSPy::Module- — Runs ahead of
beforefor setup (metrics, context loading)forward - — Wraps
around, callsforward, and lets you pair setup/teardown logicyield - — Fires after
afterreturns for cleanup or persistenceforward
ruby
class InstrumentedModule < DSPy::Module
before :setup_metrics
around :manage_context
after :log_metrics
def forward(question:)
@predictor.call(question: question)
end
private
def setup_metrics
@start_time = Time.now
end
def manage_context
load_context
result = yield
save_context
result
end
def log_metrics
duration = Time.now - @start_time
Rails.logger.info "Prediction completed in #{duration}s"
end
endExecution order: before → around (before yield) → forward → around (after yield) → after. Callbacks are inherited from parent classes and execute in registration order.
每个都支持Rails风格的生命周期钩子:
DSPy::Module- —— 在
before之前运行,用于初始化(如指标、上下文加载)forward - —— 包裹
around,调用forward,可配对初始化/清理逻辑yield - —— 在
after返回后触发,用于清理或持久化forward
ruby
class InstrumentedModule < DSPy::Module
before :setup_metrics
around :manage_context
after :log_metrics
def forward(question:)
@predictor.call(question: question)
end
private
def setup_metrics
@start_time = Time.now
end
def manage_context
load_context
result = yield
save_context
result
end
def log_metrics
duration = Time.now - @start_time
Rails.logger.info "Prediction completed in #{duration}s"
end
end执行顺序:before → around(yield之前)→ forward → around(yield之后)→ after。回调会从父类继承,并按注册顺序执行。
Fiber-Local LM Context
纤程本地LM上下文
Override the language model temporarily using fiber-local storage:
ruby
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使用纤程本地存储临时覆盖语言模型:
ruby
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") # 此代码块内使用fast_model
endBack to global LM outside the block
代码块外恢复为全局LM
**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:
```ruby
agent = DSPy::ReAct.new(MySignature, tools: tools)
agent.configure { |c| c.lm = default_model }
agent.configure_predictor('thought_generator') { |c| c.lm = powerful_model }
**LM解析优先级**:实例级LM → 纤程本地LM(`DSPy.with_lm`)→ 全局LM(`DSPy.configure`)。
使用`configure_predictor`对智能体内部进行细粒度控制:
```ruby
agent = DSPy::ReAct.new(MySignature, tools: tools)
agent.configure { |c| c.lm = default_model }
agent.configure_predictor('thought_generator') { |c| c.lm = powerful_model }Evaluation Framework
评估框架
Systematically test LLM application performance with :
DSPy::Evalsruby
metric = DSPy::Metrics.exact_match(field: :answer, case_sensitive: false)
evaluator = DSPy::Evals.new(predictor, metric: metric)
result = evaluator.evaluate(test_examples, display_table: true)
puts "Pass Rate: #{(result.pass_rate * 100).round(1)}%"Built-in metrics: , , , . Custom metrics return / or a with and fields.
exact_matchcontainsnumeric_differencecomposite_andtruefalseDSPy::Predictionscore:feedback:Use for typed test data and to push results to Langfuse.
DSPy::Exampleexport_scores: true使用系统地测试LLM应用性能:
DSPy::Evalsruby
metric = DSPy::Metrics.exact_match(field: :answer, case_sensitive: false)
evaluator = DSPy::Evals.new(predictor, metric: metric)
result = evaluator.evaluate(test_examples, display_table: true)
puts "Pass Rate: #{(result.pass_rate * 100).round(1)}%"内置指标:、、、。自定义指标返回/或带和字段的。
exact_matchcontainsnumeric_differencecomposite_andtruefalsescore:feedback:DSPy::Prediction使用定义类型化测试数据,设置可将结果推送到Langfuse。
DSPy::Exampleexport_scores: trueGEPA Optimization
GEPA优化
GEPA (Genetic-Pareto Reflective Prompt Evolution) uses reflection-driven instruction rewrites:
ruby
gem 'dspy-gepa'
teleprompter = DSPy::Teleprompt::GEPA.new(
metric: metric,
reflection_lm: DSPy::ReflectionLM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']),
feedback_map: feedback_map,
config: { max_metric_calls: 600, minibatch_size: 6 }
)
result = teleprompter.compile(program, trainset: train, valset: val)
optimized_program = result.optimized_programThe metric must return so the reflection model can reason about failures. Use to target individual predictors in composite modules.
DSPy::Prediction.new(score:, feedback:)feedback_mapGEPA(遗传帕累托反射式提示词进化)使用反射驱动的指令重写:
ruby
gem 'dspy-gepa'
teleprompter = DSPy::Teleprompt::GEPA.new(
metric: metric,
reflection_lm: DSPy::ReflectionLM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']),
feedback_map: feedback_map,
config: { max_metric_calls: 600, minibatch_size: 6 }
)
result = teleprompter.compile(program, trainset: train, valset: val)
optimized_program = result.optimized_program指标必须返回,以便反射模型分析失败原因。使用可针对复合模块中的特定预测器进行优化。
DSPy::Prediction.new(score:, feedback:)feedback_mapTyped Context Pattern
类型化上下文模式
Replace opaque string context blobs with inputs. Each field gets its own annotation in the JSON schema the LLM sees:
T::Structdescription:ruby
class NavigationContext < T::Struct
const :workflow_hint, T.nilable(String),
description: "Current workflow phase guidance for the agent"
const :action_log, T::Array[String], default: [],
description: "Compact one-line-per-action history of research steps taken"
const :iterations_remaining, Integer,
description: "Budget remaining. Each tool call costs 1 iteration."
end
class ToolSelectionSignature < DSPy::Signature
input do
const :query, String
const :context, NavigationContext # Structured, not an opaque string
end
output do
const :tool_name, String
const :tool_args, String, description: "JSON-encoded arguments"
end
endBenefits: type safety at compile time, per-field descriptions in the LLM schema, easy to test as value objects, extensible by adding declarations.
const用输入替代不透明的字符串上下文。每个字段在LLM看到的JSON Schema中都会有自己的注释:
T::Structdescription:ruby
class NavigationContext < T::Struct
const :workflow_hint, T.nilable(String),
description: "Current workflow phase guidance for the agent"
const :action_log, T::Array[String], default: [],
description: "Compact one-line-per-action history of research steps taken"
const :iterations_remaining, Integer,
description: "Budget remaining. Each tool call costs 1 iteration."
end
class ToolSelectionSignature < DSPy::Signature
input do
const :query, String
const :context, NavigationContext # 结构化,而非不透明字符串
end
output do
const :tool_name, String
const :tool_args, String, description: "JSON-encoded arguments"
end
end优势:编译时类型安全、LLM Schema中的字段级描述、可作为值对象轻松测试、通过添加声明实现扩展。
constSchema Formats (BAML / TOON)
Schema格式(BAML / TOON)
Control how DSPy describes signature structure to the LLM:
- JSON Schema (default) — Standard format, works with
structured_outputs: true - BAML () — 84% token reduction for Enhanced Prompting mode. Requires
schema_format: :bamlgem.sorbet-baml - TOON () — Table-oriented format for both schemas and data. Enhanced Prompting mode only.
schema_format: :toon, data_format: :toon
BAML and TOON apply only when . With , the provider receives JSON Schema directly.
structured_outputs: falsestructured_outputs: true控制DSPy向LLM描述签名结构的方式:
- JSON Schema(默认)—— 标准格式,支持
structured_outputs: true - BAML()—— 在增强提示模式下可减少84%的Token消耗,需安装
schema_format: :bamlgemsorbet-baml - TOON()—— 面向表格的格式,同时支持Schema和数据,仅适用于增强提示模式
schema_format: :toon, data_format: :toon
BAML和TOON仅在时生效。当时,提供商会直接接收JSON Schema。
structured_outputs: falsestructured_outputs: trueStorage System
存储系统
Persist and reload optimized programs with :
DSPy::Storage::ProgramStorageruby
storage = DSPy::Storage::ProgramStorage.new(storage_path: "./dspy_storage")
storage.save_program(result.optimized_program, result, metadata: { optimizer: 'MIPROv2' })Supports checkpoint management, optimization history tracking, and import/export between environments.
使用持久化和重新加载优化后的程序:
DSPy::Storage::ProgramStorageruby
storage = DSPy::Storage::ProgramStorage.new(storage_path: "./dspy_storage")
storage.save_program(result.optimized_program, result, metadata: { optimizer: 'MIPROv2' })支持检查点管理、优化历史跟踪,以及环境间的导入/导出。
Rails Integration
Rails集成
Directory Structure
目录结构
Organize DSPy components using Rails conventions:
app/
entities/ # T::Struct types shared across signatures
signatures/ # DSPy::Signature definitions
tools/ # DSPy::Tools::Base implementations
concerns/ # Shared tool behaviors (error handling, etc.)
modules/ # DSPy::Module orchestrators
services/ # Plain Ruby services that compose DSPy modules
config/
initializers/
dspy.rb # DSPy + provider configuration
feature_flags.rb # Model selection per role
spec/
signatures/ # Schema validation tests
tools/ # Tool unit tests
modules/ # Integration tests with VCR
vcr_cassettes/ # Recorded HTTP interactions使用Rails约定组织DSPy组件:
app/
entities/ # 签名间共享的T::Struct类型
signatures/ # DSPy::Signature定义
tools/ # DSPy::Tools::Base实现
concerns/ # 共享工具行为(如错误处理)
modules/ # DSPy::Module编排器
services/ # 组合DSPy模块的普通Ruby服务
config/
initializers/
dspy.rb # DSPy + 提供商配置
feature_flags.rb # 按角色选择模型
spec/
signatures/ # Schema验证测试
tools/ # 工具单元测试
modules/ # 带VCR的集成测试
vcr_cassettes/ # 录制的HTTP交互Initializer
初始化器
ruby
undefinedruby
undefinedconfig/initializers/dspy.rb
config/initializers/dspy.rb
Rails.application.config.after_initialize do
next if Rails.env.test? && ENV["DSPY_ENABLE_IN_TEST"].blank?
RubyLLM.configure do |config|
config.gemini_api_key = ENV["GEMINI_API_KEY"] if ENV["GEMINI_API_KEY"].present?
config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"] if ENV["ANTHROPIC_API_KEY"].present?
config.openai_api_key = ENV["OPENAI_API_KEY"] if ENV["OPENAI_API_KEY"].present?
end
model = ENV.fetch("DSPY_MODEL", "ruby_llm/gemini-2.5-flash")
DSPy.configure do |config|
config.lm = DSPy::LM.new(model, structured_outputs: true)
config.logger = Rails.logger
end
Langfuse observability (optional)
if ENV["LANGFUSE_PUBLIC_KEY"].present? && ENV["LANGFUSE_SECRET_KEY"].present?
DSPy::Observability.configure!
end
end
undefinedRails.application.config.after_initialize do
next if Rails.env.test? && ENV["DSPY_ENABLE_IN_TEST"].blank?
RubyLLM.configure do |config|
config.gemini_api_key = ENV["GEMINI_API_KEY"] if ENV["GEMINI_API_KEY"].present?
config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"] if ENV["ANTHROPIC_API_KEY"].present?
config.openai_api_key = ENV["OPENAI_API_KEY"] if ENV["OPENAI_API_KEY"].present?
end
model = ENV.fetch("DSPY_MODEL", "ruby_llm/gemini-2.5-flash")
DSPy.configure do |config|
config.lm = DSPy::LM.new(model, structured_outputs: true)
config.logger = Rails.logger
end
Langfuse可观测性(可选)
if ENV["LANGFUSE_PUBLIC_KEY"].present? && ENV["LANGFUSE_SECRET_KEY"].present?
DSPy::Observability.configure!
end
end
undefinedFeature-Flagged Model Selection
功能标记模型选择
Use different models for different roles (fast/cheap for classification, powerful for synthesis):
ruby
undefined为不同角色使用不同模型(快速/廉价模型用于分类,高性能模型用于合成):
ruby
undefinedconfig/initializers/feature_flags.rb
config/initializers/feature_flags.rb
module FeatureFlags
SELECTOR_MODEL = ENV.fetch("DSPY_SELECTOR_MODEL", "ruby_llm/gemini-2.5-flash-lite")
SYNTHESIZER_MODEL = ENV.fetch("DSPY_SYNTHESIZER_MODEL", "ruby_llm/gemini-2.5-flash")
end
Then override per-tool or per-predictor:
```ruby
class ClassifyTool < DSPy::Tools::Base
def call(query:)
predictor = DSPy::Predict.new(ClassifyQuery)
predictor.configure { |c| c.lm = DSPy::LM.new(FeatureFlags::SELECTOR_MODEL, structured_outputs: true) }
predictor.call(query: query)
end
endmodule FeatureFlags
SELECTOR_MODEL = ENV.fetch("DSPY_SELECTOR_MODEL", "ruby_llm/gemini-2.5-flash-lite")
SYNTHESIZER_MODEL = ENV.fetch("DSPY_SYNTHESIZER_MODEL", "ruby_llm/gemini-2.5-flash")
end
然后按工具或预测器覆盖:
```ruby
class ClassifyTool < DSPy::Tools::Base
def call(query:)
predictor = DSPy::Predict.new(ClassifyQuery)
predictor.configure { |c| c.lm = DSPy::LM.new(FeatureFlags::SELECTOR_MODEL, structured_outputs: true) }
predictor.call(query: query)
end
endSchema-Driven Signatures
Schema驱动的签名
Prefer typed schemas over string descriptions. Let the type system communicate structure to the LLM rather than prose in the signature description.
优先使用类型化Schema而非字符串描述。让类型系统向LLM传达结构,而非签名描述中的散文式内容。
Entities as Shared Types
作为共享类型的实体
Define reusable and types in and reference them across signatures:
T::StructT::Enumapp/entities/ruby
undefined在中定义可复用的和类型,并在多个签名中引用:
app/entities/T::StructT::Enumruby
undefinedapp/entities/search_strategy.rb
app/entities/search_strategy.rb
class SearchStrategy < T::Enum
enums do
SingleSearch = new("single_search")
DateDecomposition = new("date_decomposition")
end
end
class SearchStrategy < T::Enum
enums do
SingleSearch = new("single_search")
DateDecomposition = new("date_decomposition")
end
end
app/entities/scored_item.rb
app/entities/scored_item.rb
class ScoredItem < T::Struct
const :id, String
const :score, Float, description: "Relevance score 0.0-1.0"
const :verdict, String, description: "relevant, maybe, or irrelevant"
const :reason, String, default: ""
end
undefinedclass ScoredItem < T::Struct
const :id, String
const :score, Float, description: "Relevance score 0.0-1.0"
const :verdict, String, description: "relevant, maybe, or irrelevant"
const :reason, String, default: ""
end
undefinedSchema vs Description: When to Use Each
Schema vs 描述:何时使用哪种
Use schemas (T::Struct/T::Enum) for:
- Multi-field outputs with specific types
- Enums with defined values the LLM must pick from
- Nested structures, arrays of typed objects
- Outputs consumed by code (not displayed to users)
Use string descriptions for:
- Simple single-field outputs where the type is
String - Natural language generation (summaries, answers)
- Fields where constraint guidance helps (e.g., )
description: "YYYY-MM-DD format"
Rule of thumb: If you'd write a statement on the output, it should be a . If you'd call on it, it should be .
caseT::Enum.eachT::Array[SomeStruct]使用Schema(T::Struct/T::Enum)的场景:
- 具有特定类型的多字段输出
- LLM必须从定义值中选择的枚举
- 嵌套结构、类型化对象数组
- 供代码消费的输出(而非展示给用户)
使用字符串描述的场景:
- 类型为的简单单字段输出
String - 自然语言生成(摘要、回答)
- 约束指导有帮助的字段(如)
description: "YYYY-MM-DD格式"
经验法则:如果要对输出写语句,那么它应该是;如果要对它调用,那么它应该是。
caseT::Enum.eachT::Array[SomeStruct]Tool Patterns
工具模式
Tools That Wrap Predictions
包裹预测的工具
A common pattern: tools encapsulate a DSPy prediction, adding error handling, model selection, and serialization:
ruby
class RerankTool < DSPy::Tools::Base
tool_name "rerank"
tool_description "Score and rank search results by relevance"
MAX_ITEMS = 200
MIN_ITEMS_FOR_LLM = 5
sig { params(query: String, items: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Hash[Symbol, T.untyped]) }
def call(query:, items: [])
return { scored_items: items, reranked: false } if items.size < MIN_ITEMS_FOR_LLM
capped_items = items.first(MAX_ITEMS)
predictor = DSPy::Predict.new(RerankSignature)
predictor.configure { |c| c.lm = DSPy::LM.new(FeatureFlags::SYNTHESIZER_MODEL, structured_outputs: true) }
result = predictor.call(query: query, items: capped_items)
{ scored_items: result.scored_items, reranked: true }
rescue => e
Rails.logger.warn "[RerankTool] LLM rerank failed: #{e.message}"
{ error: "Rerank failed: #{e.message}", scored_items: items, reranked: false }
end
endKey patterns:
- Short-circuit LLM calls when unnecessary (small data, trivial cases)
- Cap input size to prevent token overflow
- Per-tool model selection via
configure - Graceful error handling with fallback data
常见模式:工具封装DSPy预测,添加错误处理、模型选择和序列化:
ruby
class RerankTool < DSPy::Tools::Base
tool_name "rerank"
tool_description "Score and rank search results by relevance"
MAX_ITEMS = 200
MIN_ITEMS_FOR_LLM = 5
sig { params(query: String, items: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Hash[Symbol, T.untyped]) }
def call(query:, items: [])
return { scored_items: items, reranked: false } if items.size < MIN_ITEMS_FOR_LLM
capped_items = items.first(MAX_ITEMS)
predictor = DSPy::Predict.new(RerankSignature)
predictor.configure { |c| c.lm = DSPy::LM.new(FeatureFlags::SYNTHESIZER_MODEL, structured_outputs: true) }
result = predictor.call(query: query, items: capped_items)
{ scored_items: result.scored_items, reranked: true }
rescue => e
Rails.logger.warn "[RerankTool] LLM rerank failed: #{e.message}"
{ error: "Rerank failed: #{e.message}", scored_items: items, reranked: false }
end
end关键模式:
- 在不必要时跳过LLM调用(如数据量小、 trivial场景)
- 限制输入大小以防止Token溢出
- 通过按工具选择模型
configure - 优雅的错误处理,提供回退数据
Error Handling Concern
错误处理关注点
ruby
module ErrorHandling
extend ActiveSupport::Concern
private
def safe_predict(signature_class, **inputs)
predictor = DSPy::Predict.new(signature_class)
yield predictor if block_given?
predictor.call(**inputs)
rescue Faraday::Error, Net::HTTPError => e
Rails.logger.error "[#{self.class.name}] API error: #{e.message}"
nil
rescue JSON::ParserError => e
Rails.logger.error "[#{self.class.name}] Invalid LLM output: #{e.message}"
nil
end
endruby
module ErrorHandling
extend ActiveSupport::Concern
private
def safe_predict(signature_class, **inputs)
predictor = DSPy::Predict.new(signature_class)
yield predictor if block_given?
predictor.call(**inputs)
rescue Faraday::Error, Net::HTTPError => e
Rails.logger.error "[#{self.class.name}] API error: #{e.message}"
nil
rescue JSON::ParserError => e
Rails.logger.error "[#{self.class.name}] Invalid LLM output: #{e.message}"
nil
end
endObservability
可观测性
Tracing with DSPy::Context
使用DSPy::Context追踪
Wrap operations in spans for Langfuse/OpenTelemetry visibility:
ruby
result = DSPy::Context.with_span(
operation: "tool_selector.select",
"dspy.module" => "ToolSelector",
"tool_selector.tools" => tool_names.join(",")
) do
@predictor.call(query: query, context: context, available_tools: schemas)
end将操作包裹在Span中,以便Langfuse/OpenTelemetry可见:
ruby
result = DSPy::Context.with_span(
operation: "tool_selector.select",
"dspy.module" => "ToolSelector",
"tool_selector.tools" => tool_names.join(",")
) do
@predictor.call(query: query, context: context, available_tools: schemas)
endSetup for Langfuse
Langfuse设置
ruby
undefinedruby
undefinedGemfile
Gemfile
gem 'dspy-o11y'
gem 'dspy-o11y-langfuse'
gem 'dspy-o11y'
gem 'dspy-o11y-langfuse'
.env
.env
LANGFUSE_PUBLIC_KEY=pk-...
LANGFUSE_SECRET_KEY=sk-...
DSPY_TELEMETRY_BATCH_SIZE=5
Every `DSPy::Predict`, `DSPy::ReAct`, and tool call is automatically traced when observability is configured.LANGFUSE_PUBLIC_KEY=pk-...
LANGFUSE_SECRET_KEY=sk-...
DSPY_TELEMETRY_BATCH_SIZE=5
配置可观测性后,所有`DSPy::Predict`、`DSPy::ReAct`和工具调用都会被自动追踪。Score Reporting
分数上报
Report evaluation scores to Langfuse:
ruby
DSPy.score(name: "relevance", value: 0.85, trace_id: current_trace_id)将评估分数上报到Langfuse:
ruby
DSPy.score(name: "relevance", value: 0.85, trace_id: current_trace_id)Testing
测试
VCR Setup for Rails
Rails的VCR设置
ruby
VCR.configure do |config|
config.cassette_library_dir = "spec/vcr_cassettes"
config.hook_into :webmock
config.configure_rspec_metadata!
config.filter_sensitive_data('<GEMINI_API_KEY>') { ENV['GEMINI_API_KEY'] }
config.filter_sensitive_data('<OPENAI_API_KEY>') { ENV['OPENAI_API_KEY'] }
endruby
VCR.configure do |config|
config.cassette_library_dir = "spec/vcr_cassettes"
config.hook_into :webmock
config.configure_rspec_metadata!
config.filter_sensitive_data('<GEMINI_API_KEY>') { ENV['GEMINI_API_KEY'] }
config.filter_sensitive_data('<OPENAI_API_KEY>') { ENV['OPENAI_API_KEY'] }
endSignature Schema Tests
签名Schema测试
Test that signatures produce valid schemas without calling any LLM:
ruby
RSpec.describe ClassifyResearchQuery do
it "has required input fields" do
schema = described_class.input_json_schema
expect(schema[:required]).to include("query")
end
it "has typed output fields" do
schema = described_class.output_json_schema
expect(schema[:properties]).to have_key(:search_strategy)
end
end无需调用LLM,测试签名是否生成有效Schema:
ruby
RSpec.describe ClassifyResearchQuery do
it "has required input fields" do
schema = described_class.input_json_schema
expect(schema[:required]).to include("query")
end
it "has typed output fields" do
schema = described_class.output_json_schema
expect(schema[:properties]).to have_key(:search_strategy)
end
endTool Tests with Mocked Predictions
带模拟预测的工具测试
ruby
RSpec.describe RerankTool do
let(:tool) { described_class.new }
it "skips LLM for small result sets" do
expect(DSPy::Predict).not_to receive(:new)
result = tool.call(query: "test", items: [{ id: "1" }])
expect(result[:reranked]).to be false
end
it "calls LLM for large result sets", :vcr do
items = 10.times.map { |i| { id: i.to_s, title: "Item #{i}" } }
result = tool.call(query: "relevant items", items: items)
expect(result[:reranked]).to be true
end
endruby
RSpec.describe RerankTool do
let(:tool) { described_class.new }
it "skips LLM for small result sets" do
expect(DSPy::Predict).not_to receive(:new)
result = tool.call(query: "test", items: [{ id: "1" }])
expect(result[:reranked]).to be false
end
it "calls LLM for large result sets", :vcr do
items = 10.times.map { |i| { id: i.to_s, title: "Item #{i}" } }
result = tool.call(query: "relevant items", items: items)
expect(result[:reranked]).to be true
end
endResources
资源
- — Signatures, modules, predictors, type system deep-dive
references/core-concepts.md - — Tools::Base, Tools::Toolset DSL, type safety, testing
references/toolsets.md - — Provider adapters, RubyLLM, fiber-local LM context, compatibility matrix
references/providers.md - — MIPROv2, GEPA, evaluation framework, storage system
references/optimization.md - — Event system, dspy-o11y gems, Langfuse, score reporting
references/observability.md - — Signature scaffold with T::Enum, Date/Time, defaults, union types
assets/signature-template.rb - — Module scaffold with .call(), lifecycle callbacks, fiber-local LM
assets/module-template.rb - — Rails initializer with RubyLLM, observability, feature flags
assets/config-template.rb
- —— 签名、模块、预测器、类型系统深度解析
references/core-concepts.md - —— Tools::Base、Tools::Toolset DSL、类型安全、测试
references/toolsets.md - —— 提供商适配、RubyLLM、纤程本地LM上下文、兼容性矩阵
references/providers.md - —— MIPROv2、GEPA、评估框架、存储系统
references/optimization.md - —— 事件系统、dspy-o11y gems、Langfuse、分数上报
references/observability.md - —— 带T::Enum、日期/时间、默认值、联合类型的签名模板
assets/signature-template.rb - —— 带.call()、生命周期回调、纤程本地LM的模块模板
assets/module-template.rb - —— 带RubyLLM、可观测性、功能标记的Rails初始化器模板
assets/config-template.rb
Key URLs
关键链接
- Homepage: https://oss.vicente.services/dspy.rb/
- GitHub: https://github.com/vicentereig/dspy.rb
- Documentation: https://oss.vicente.services/dspy.rb/getting-started/
Guidelines for Claude
Claude使用指南
When helping users with DSPy.rb:
- Schema over prose — Define output structure with and
T::Structtypes, not string descriptionsT::Enum - Entities in — Extract shared types so signatures stay thin
app/entities/ - Per-tool model selection — Use to pick the right model per task
predictor.configure { |c| c.lm = ... } - Short-circuit LLM calls — Skip the LLM for trivial cases (small data, cached results)
- Cap input sizes — Prevent token overflow by limiting array sizes before sending to LLM
- Test schemas without LLM — Validate and
input_json_schemain unit testsoutput_json_schema - VCR for integration tests — Record real HTTP interactions, never mock LLM responses by hand
- Trace with spans — Wrap tool calls in for observability
DSPy::Context.with_span - Graceful degradation — Always rescue LLM errors and return fallback data
当帮助用户处理DSPy.rb相关问题时:
- 优先使用Schema而非散文 —— 用和
T::Struct类型定义输出结构,而非字符串描述T::Enum - 实体放在—— 提取共享类型,让签名保持简洁
app/entities/ - 按工具选择模型 —— 使用为每个任务选择合适的模型
predictor.configure { |c| c.lm = ... } - 短路LLM调用 —— 对于trivial场景(如小数据、缓存结果)跳过LLM
- 限制输入大小 —— 在发送给LLM前限制数组大小,防止Token溢出
- 无需LLM即可测试Schema —— 在单元测试中验证和
input_json_schemaoutput_json_schema - 集成测试使用VCR —— 录制真实HTTP交互,切勿手动模拟LLM响应
- 用Span追踪 —— 将工具调用包裹在中以实现可观测性
DSPy::Context.with_span - 优雅降级 —— 始终捕获LLM错误并返回回退数据
Signature Best Practices
签名最佳实践
Keep description concise — The signature should state the goal, not the field details:
descriptionruby
undefined保持描述简洁 —— 签名的应说明目标,而非字段细节:
descriptionruby
undefinedGood — concise goal
良好示例——简洁的目标
class ParseOutline < DSPy::Signature
description 'Extract block-level structure from HTML as a flat list of skeleton sections.'
input do
const :html, String, description: 'Raw HTML to parse'
end
output do
const :sections, T::Array[Section], description: 'Block elements: headings, paragraphs, code blocks, lists'
end
end
**Use defaults over nilable arrays** — For OpenAI structured outputs compatibility:
```rubyclass ParseOutline < DSPy::Signature
description 'Extract block-level structure from HTML as a flat list of skeleton sections.'
input do
const :html, String, description: 'Raw HTML to parse'
end
output do
const :sections, T::Array[Section], description: 'Block elements: headings, paragraphs, code blocks, lists'
end
end
**使用默认值而非可空数组** —— 为了兼容OpenAI结构化输出:
```rubyGood — works with OpenAI structured outputs
良好示例——兼容OpenAI结构化输出
class ASTNode < T::Struct
const :children, T::Array[ASTNode], default: []
end
undefinedclass ASTNode < T::Struct
const :children, T::Array[ASTNode], default: []
end
undefinedRecursive Types with $defs
$defs带$defs
的递归类型
$defsDSPy.rb supports recursive types in structured outputs using JSON Schema :
$defsruby
class TreeNode < T::Struct
const :value, String
const :children, T::Array[TreeNode], default: [] # Self-reference
endThe schema generator automatically creates references for recursive types, compatible with OpenAI and Gemini structured outputs.
#/$defs/TreeNodeDSPy.rb使用JSON Schema的支持结构化输出中的递归类型:
$defsruby
class TreeNode < T::Struct
const :value, String
const :children, T::Array[TreeNode], default: [] # 自引用
endSchema生成器会自动为递归类型创建引用,兼容OpenAI和Gemini的结构化输出。
#/$defs/TreeNodeField Descriptions for T::Struct
T::Struct的字段描述
DSPy.rb extends T::Struct to support field-level kwargs that flow to JSON Schema:
description:ruby
class ASTNode < T::Struct
const :node_type, NodeType, description: 'The type of node (heading, paragraph, etc.)'
const :text, String, default: "", description: 'Text content of the node'
const :level, Integer, default: 0 # No description — field is self-explanatory
const :children, T::Array[ASTNode], default: []
endWhen to use field descriptions: complex field semantics, enum-like strings, constrained values, nested structs with ambiguous names. When to skip: self-explanatory fields like , , , or boolean flags.
nameidurlDSPy.rb扩展了T::Struct,支持字段级关键字参数,这些参数会同步到JSON Schema:
description:ruby
class ASTNode < T::Struct
const :node_type, NodeType, description: 'The type of node (heading, paragraph, etc.)'
const :text, String, default: "", description: 'Text content of the node'
const :level, Integer, default: 0 # 无需描述——字段含义自明
const :children, T::Array[ASTNode], default: []
end何时使用字段描述:复杂字段语义、类枚举字符串、约束值、名称模糊的嵌套结构体。何时跳过:含义自明的字段,如、、或布尔标志。
nameidurlVersion
版本
Current: 0.34.3
当前版本:0.34.3