mcpField
D
dspy-ruby

dspy-ruby

github.com/everyinc開発25.8k494 回インストール今日更新
リポジトリ

dspy-ruby について

DSPy.rbは、プログラムでプロンプトを扱う、モジュール型で型安全なLLMアプリケーション向けRubyフレームワークです。エージェント部品、評価・最適化ツール、イベント監視、最適化プログラムの保存機能を備えています。

主な機能

  1. 01型付きシグネチャと再利用可能なモジュールの定義
  2. 02ツールを使うエージェントとワークフローの構築
  3. 03組み込みまたは独自メトリクスによる出力評価
  4. 04MIPROv2とGEPAによるプロンプト最適化
  5. 05構造化イベントによる実行状況の監視
  6. 06最適化プログラムと履歴の保存

活用シーン

  1. 01Rubyでの型付きLLMアプリケーション構築
  2. 02ツール使用エージェントのワークフロー構成
  3. 03予測品質の測定と改善
  4. 04モデルやモジュールの実行イベント追跡

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/everyinc/compound-engineering-plugin --skill dspy-ruby

インストール後、Claude Code で/dspy-rubyを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
config-template.rb
module-template.rb
signature-template.rb
references/
core-concepts.md
observability.md
optimization.md
providers.md
toolsets.md

詳細

DSPy.rb

ソフトウェアを作るように、LLM アプリを構築できます。型安全で、モジュール化され、テスト可能です。

DSPy.rb は、LLM 開発にソフトウェアエンジニアリングのベストプラクティスを取り入れます。プロンプトを調整する代わりに、Ruby の型を使って必要なものを定義すれば、あとは DSPy が処理します。

概要

DSPy.rb は、プログラムでプロンプトを扱う言語モデルアプリケーションを構築するための Ruby フレームワークです。次の機能を提供します。

  • 型安全なシグネチャ — Sorbet の型を使って入力と出力を定義します
  • モジュール化されたコンポーネント — LLM のロジックを組み合わせて再利用できます
  • 自動最適化 — 当てずっぽうではなく、データを使ってプロンプトを改善します
  • 本番環境対応 — オブザーバビリティ、テスト、エラー処理を組み込みで備えています

主な概念

1. シグネチャ

Ruby の型を使って、アプリと LLM の間のインターフェースを定義します。

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
2. モジュール

シンプルな構成要素から複雑なワークフローを構築します。

  • Predict — シグネチャを使った基本的な LLM 呼び出し
  • ChainOfThought — 段階的な推論
  • ReAct — ツールを使用するエージェント
  • CodeAct — 動的にコードを生成するエージェント(dspy-code_act gem をインストールしてください)
3. ツールとツールセット

包括的な Sorbet サポートを備えた、エージェント向けの型安全なツールを作成します。

# 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

# 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
4. 型システムと識別子

DSPy.rb は、複雑なデータ構造に対して高度な型識別を使用します。

  • _type フィールドの自動挿入 — DSPy は型安全性のため、構造体に識別子フィールドを追加します
  • ユニオン型のサポート — T.any() 型は _type によって自動的に曖昧さが解消されます
  • 予約済みフィールド名 — 構造体に独自の _type フィールドを定義しないでください
  • 再帰的なフィルタリング — デシリアライズ時に、すべてのネスト階層で _type フィールドがフィルタリングされます
5. 最適化

実際のデータを使って精度を向上させます。

  • MIPROv2 — ブートストラップサンプリングとベイズ最適化を用いた、高度なマルチプロンプト最適化
  • GEPA — フィードバックマップ、実験追跡、テレメトリーを備えた、遺伝的パレート型のリフレクティブプロンプト進化
  • 評価 — 組み込みおよびカスタムのメトリクス、エラー処理、バッチ処理を備えた包括的なフレームワーク

クイックスタート

# 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  # positive, negative, neutral
    const :score, Float       # 0.0 to 1.0
  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

プロバイダーアダプター gem

LLM プロバイダーに接続するための、2 つの方法があります。

プロバイダーごとのアダプター(SDK に直接アクセス)
# Gemfile
gem 'dspy'
gem 'dspy-openai'    # OpenAI, OpenRouter, Ollama
gem 'dspy-anthropic' # Claude
gem 'dspy-gemini'    # Gemini

各アダプター gem は、公式 SDK(openai、anthropic、gemini-ai)を取り込みます。

RubyLLM による統合アダプター(複数のプロバイダーを使う場合に推奨)
# Gemfile
gem 'dspy'
gem 'dspy-ruby_llm'  # Routes to any provider via ruby_llm
gem 'ruby_llm'

RubyLLM はモデル名に基づいてプロバイダーへのルーティングを処理します。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

イベントシステム

DSPy.rb には、実行時の動作を監視するための構造化されたイベントバスが組み込まれています。

モジュール単位のサブスクリプション(エージェント向けに推奨)
class MyAgent < DSPy::Module
  subscribe 'lm.tokens', :track_tokens, scope: :descendants

  def track_tokens(_event, attrs)
    @total_tokens += attrs.fetch(:total_tokens, 0)
  end
end
グローバルサブスクリプション(オブザーバビリティ/統合向け)
subscription_id = DSPy.events.subscribe('score.create') do |event, attrs|
  Langfuse.export_score(attrs)
end

# Wildcards supported
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)が含まれます。

ライフサイクルコールバック

Rails スタイルのライフサイクルフックは、すべての DSPy::Module に組み込まれています。

  • before — セットアップ(メトリクス、コンテキストの読み込み)のため、forward の前に実行されます
  • around — forward を囲み、yield を呼び出して、セットアップとティアダウンのロジックを組み合わせられます
  • after — forward の戻り後に実行され、クリーンアップや永続化に使用します
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。コールバックは親クラスから継承され、登録順に実行されます。

ファイバーローカル LM コンテキスト

ファイバーローカルストレージを使って、言語モデルを一時的に上書きできます。

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
# Back to global LM outside the block

LM の解決階層:インスタンスレベルの LM → ファイバーローカル LM(DSPy.with_lm)→ グローバル LM(DSPy.configure)。

configure_predictor を使うと、エージェントの内部をきめ細かく制御できます。

agent = DSPy::ReAct.new(MySignature, tools: tools)
agent.configure { |c| c.lm = default_model }
agent.configure_predictor('thought_generator') { |c| c.lm = powerful_model }

評価フレームワーク

DSPy::Evalsを使用して、LLM アプリケーションのパフォーマンスを体系的にテストします。

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_match、contains、numeric_difference、composite_and。カスタムメトリクスは、true/false、またはscore:とfeedback:フィールドを持つDSPy::Predictionを返します。

型付きテストデータにはDSPy::Exampleを使用し、結果をLangfuseに送信するにはexport_scores: trueを指定します。

GEPA最適化

GEPA(Genetic-Pareto Reflective Prompt Evolution)は、リフレクションに基づいて指示を書き換えます。

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_mapを使用します。

型付きコンテキストパターン

不透明な文字列のコンテキストの塊をT::Struct入力に置き換えます。各フィールドには、LLMが参照するJSONスキーマ内で独自のdescription:アノテーションが付与されます。

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
end

利点:コンパイル時の型安全性、LLMスキーマ内でフィールドごとに説明を指定可能、値オブジェクトとして簡単にテスト可能、const宣言の追加による拡張性。

スキーマ形式(BAML/TOON)

DSPyがLLMにシグネチャ構造をどのように記述するかを制御します。

  • JSON Schema(デフォルト) — 標準形式で、structured_outputs: trueと併用できます。
  • BAML(schema_format: :baml) — Enhanced Promptingモードでトークンを84%削減します。sorbet-baml gemが必要です。
  • TOON(schema_format: :toon, data_format: :toon) — スキーマとデータの両方に対応するテーブル指向形式です。Enhanced Promptingモード専用です。

BAMLとTOONは、structured_outputs: falseの場合にのみ適用されます。structured_outputs: trueの場合、プロバイダーにはJSON Schemaが直接渡されます。

ストレージシステム

DSPy::Storage::ProgramStorageを使用して、最適化済みのプログラムを保存し、再読み込みします。

storage = DSPy::Storage::ProgramStorage.new(storage_path: "./dspy_storage")
storage.save_program(result.optimized_program, result, metadata: { optimizer: 'MIPROv2' })

チェックポイント管理、最適化履歴の追跡、環境間のインポート/エクスポートに対応しています。

Railsとの統合

ディレクトリ構成

Railsの規約に従ってDSPyコンポーネントを整理します。

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
イニシャライザー
# 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
フィーチャーフラグによるモデル選択

用途に応じて異なるモデルを使用します(分類には高速で低コストのモデル、合成には高性能なモデル)。

# 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

次に、ツールまたは予測器ごとに上書きします。

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
end

スキーマ駆動のシグネチャ

**文字列による説明より、型付きスキーマを優先してください。**シグネチャの説明に文章を書くのではなく、型システムを使ってLLMに構造を伝えます。

共有型としてのエンティティ

再利用可能なT::Struct型とT::Enum型をapp/entities/に定義し、シグネチャ間で参照します。

# app/entities/search_strategy.rb
class SearchStrategy < T::Enum
  enums do
    SingleSearch = new("single_search")
    DateDecomposition = new("date_decomposition")
  end
end

# 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
スキーマと説明:使い分け

スキーマ(T::Struct/T::Enum)を使用するケース:

  • 特定の型を持つ複数フィールドの出力
  • LLMが定義済みの値から選択する必要がある列挙型
  • ネストされた構造や、型付きオブジェクトの配列
  • コードで使用する出力(ユーザーに表示しないもの)

文字列による説明を使用するケース:

  • 型がStringの単一フィールドによるシンプルな出力
  • 自然言語の生成(要約、回答)
  • 制約の指針が有用なフィールド(例:description: "YYYY-MM-DD format")

**目安:**出力に対してcase文を書くなら、T::Enumにします。出力に対して.eachを呼び出すなら、T::Array[SomeStruct]にします。

ツールのパターン

予測をラップするツール

よくあるパターンとして、ツールにDSPyの予測をカプセル化し、エラー処理、モデル選択、シリアライズを追加します。

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呼び出しを短絡する
  • トークンの上限超過を防ぐため、入力サイズを制限する
  • configureによるツールごとのモデル選択
  • フォールバックデータによる適切なエラー処理
エラー処理に関する注意点
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
end

オブザーバビリティ

DSPy::Contextによるトレース

Langfuse/OpenTelemetryで可視化できるよう、処理をスパンでラップします。

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
Langfuseのセットアップ
# Gemfile
gem 'dspy-o11y'
gem 'dspy-o11y-langfuse'

# .env
LANGFUSE_PUBLIC_KEY=pk-...
LANGFUSE_SECRET_KEY=sk-...
DSPY_TELEMETRY_BATCH_SIZE=5

オブザーバビリティが設定されている場合、すべてのDSPy::Predict、DSPy::ReAct、ツール呼び出しが自動的にトレースされます。

スコアのレポート

評価スコアをLangfuseにレポートします。

DSPy.score(name: "relevance", value: 0.85, trace_id: current_trace_id)

テスト

Rails用VCRのセットアップ
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'] }
end
シグネチャのスキーマテスト

LLM を呼び出さずに、シグネチャが有効なスキーマを生成することをテストします。

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
予測をモックしたツールのテスト
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
end

リソース

  • references/core-concepts.md — シグネチャ、モジュール、予測器、型システムの詳細
  • references/toolsets.md — Tools::Base、Tools::Toolset DSL、型安全性、テスト
  • references/providers.md — プロバイダーアダプター、RubyLLM、ファイバーローカルな LM コンテキスト、互換性マトリックス
  • references/optimization.md — MIPROv2、GEPA、評価フレームワーク、ストレージシステム
  • references/observability.md — イベントシステム、dspy-o11y gem、Langfuse、スコアレポート
  • assets/signature-template.rb — T::Enum、Date/Time、デフォルト値、ユニオン型を含むシグネチャのひな形
  • assets/module-template.rb — .call()、ライフサイクルコールバック、ファイバーローカルな LM を含むモジュールのひな形
  • assets/config-template.rb — RubyLLM、可観測性、機能フラグを含む Rails イニシャライザー

主な URL

Claude 向けガイドライン

DSPy.rb についてユーザーをサポートする際は、次の点に従ってください。

  1. 文章よりスキーマを優先 — 文字列による説明ではなく、T::Struct と T::Enum 型で出力構造を定義します
  2. app/entities/ にエンティティを配置 — 共通の型を切り出して、シグネチャを簡潔に保ちます
  3. ツールごとにモデルを選択 — predictor.configure { |c| c.lm = ... } を使って、タスクごとに適切なモデルを選びます
  4. 不要な LLM 呼び出しを省略 — 単純なケース(少量のデータ、キャッシュ済みの結果)では LLM を呼び出しません
  5. 入力サイズを制限 — LLM に送信する前に配列サイズを制限し、トークンの上限超過を防ぎます
  6. LLM を使わずにスキーマをテスト — 単体テストで input_json_schema と output_json_schema を検証します
  7. 結合テストには VCR を使用 — 実際の HTTP 通信を記録し、LLM の応答を手動でモックすることは避けます
  8. スパンでトレース — 可観測性を確保するため、ツール呼び出しを DSPy::Context.with_span でラップします
  9. 適切にフォールバック — 常に LLM エラーを rescue し、代替データを返します
シグネチャのベストプラクティス

説明は簡潔にする — シグネチャの description にはフィールドの詳細ではなく、目的を記述します。

# Good — 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

nil を許容する配列よりデフォルト値を使う — OpenAI の構造化出力との互換性を確保するためです。

# Good — works with OpenAI structured outputs
class ASTNode < T::Struct
  const :children, T::Array[ASTNode], default: []
end
$defs を使った再帰型

DSPy.rb は JSON Schema の $defs を使った、構造化出力の再帰型をサポートしています。

class TreeNode < T::Struct
  const :value, String
  const :children, T::Array[TreeNode], default: []  # Self-reference
end

スキーマジェネレーターは、再帰型向けの #/$defs/TreeNode 参照を自動で生成します。これらは OpenAI および Gemini の構造化出力と互換性があります。

T::Struct のフィールド説明

DSPy.rb は T::Struct を拡張し、フィールド単位の description: キーワード引数を JSON Schema に反映できるようにしています。

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: []
end

フィールド説明を使う場合:複雑なフィールドの意味、列挙型に似た文字列、制約付きの値、名前が曖昧なネストされた構造体。省略する場合:name、id、url などの明白なフィールドや、ブール値のフラグ。

バージョン

現在:0.34.3

コメント