dspy-ruby について
DSPy.rbは、プログラムでプロンプトを扱う、モジュール型で型安全なLLMアプリケーション向けRubyフレームワークです。エージェント部品、評価・最適化ツール、イベント監視、最適化プログラムの保存機能を備えています。
主な機能
- 01型付きシグネチャと再利用可能なモジュールの定義
- 02ツールを使うエージェントとワークフローの構築
- 03組み込みまたは独自メトリクスによる出力評価
- 04MIPROv2とGEPAによるプロンプト最適化
- 05構造化イベントによる実行状況の監視
- 06最適化プログラムと履歴の保存
活用シーン
- 01Rubyでの型付きLLMアプリケーション構築
- 02ツール使用エージェントのワークフロー構成
- 03予測品質の測定と改善
- 04モデルやモジュールの実行イベント追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ everyinc/ compound-engineering-plugin --skill dspy-rubyインストール後、Claude Code で/dspy-rubyを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/config-template.rbmodule-template.rbsignature-template.rbreferences/core-concepts.mdobservability.mdoptimization.mdproviders.mdtoolsets.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_actgem をインストールしてください)
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-bamlgemが必要です。 - 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
- ホームページ:https://oss.vicente.services/dspy.rb/
- GitHub:https://github.com/vicentereig/dspy.rb
- ドキュメント:https://oss.vicente.services/dspy.rb/getting-started/
Claude 向けガイドライン
DSPy.rb についてユーザーをサポートする際は、次の点に従ってください。
- 文章よりスキーマを優先 — 文字列による説明ではなく、
T::StructとT::Enum型で出力構造を定義します app/entities/にエンティティを配置 — 共通の型を切り出して、シグネチャを簡潔に保ちます- ツールごとにモデルを選択 —
predictor.configure { |c| c.lm = ... }を使って、タスクごとに適切なモデルを選びます - 不要な LLM 呼び出しを省略 — 単純なケース(少量のデータ、キャッシュ済みの結果)では LLM を呼び出しません
- 入力サイズを制限 — LLM に送信する前に配列サイズを制限し、トークンの上限超過を防ぎます
- LLM を使わずにスキーマをテスト — 単体テストで
input_json_schemaとoutput_json_schemaを検証します - 結合テストには VCR を使用 — 実際の HTTP 通信を記録し、LLM の応答を手動でモックすることは避けます
- スパンでトレース — 可観測性を確保するため、ツール呼び出しを
DSPy::Context.with_spanでラップします - 適切にフォールバック — 常に 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