dspy 0.8.1 β†’ 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4d5c2fd5e569cd01d945e1682cc33e6106d8291416d062519b016df4d0bb03f
4
- data.tar.gz: 7bca442bd18af297941bb288c73c36d87a5f6017c104209016bf48f13db1b916
3
+ metadata.gz: 306fa376ea024edd2fc75d7bc1af6a42e60770e9566b4dd114414a40c685f2a7
4
+ data.tar.gz: 74f321fdd09a5761d17481af846dcd85dc3a372f573f79659c4fcfc761e92a18
5
5
  SHA512:
6
- metadata.gz: a71c1fd214c02651eed8a8a868b0d07a5dd02ee48c68ef67c4d7b3aa5e6f25461efbc85212f83b264d48d2d6ead4297f453d515365488d52142c09fd0d5c5315
7
- data.tar.gz: '087c066a4b0a83c701157187c58a678e55f9381162effe9d422a82a69e6914bcb8258f5c40872cee43f506d0d5f556e0863e75758e00ccf1e6909f0a299717f6'
6
+ metadata.gz: 5efb12df9c365114696857067bb4f869ce2329c0977666331846a5f070aca050312992c176fddb60ab701b5d31b017d23182d7a4e813e80cba152d155fc2091c
7
+ data.tar.gz: 2fd7f831b6fab1624d2de7702d54d6377a4a96fff4cb0230eb810285828b764033288bc3180e2aa2cf92b1a1026fa6552cdbf851174216a3fc1a651419accc59
data/README.md CHANGED
@@ -25,12 +25,13 @@ The result? LLM applications that actually scale and don't break when you sneeze
25
25
  - **Basic Optimization** - Simple prompt optimization techniques
26
26
 
27
27
  **Production Features:**
28
- - **Reliable JSON Extraction** - Automatic strategy selection for OpenAI structured outputs, Anthropic patterns, and fallback modes
28
+ - **Reliable JSON Extraction** - Native OpenAI structured outputs, Anthropic extraction patterns, and automatic strategy selection with fallback
29
+ - **Type-Safe Configuration** - Strategy enums with automatic provider optimization (Strict/Compatible modes)
29
30
  - **Smart Retry Logic** - Progressive fallback with exponential backoff for handling transient failures
30
31
  - **Performance Caching** - Schema and capability caching for faster repeated operations
31
- - **File-based Storage** - Basic optimization result persistence
32
+ - **File-based Storage** - Optimization result persistence with versioning
32
33
  - **Multi-Platform Observability** - OpenTelemetry, New Relic, and Langfuse integration
33
- - **Basic Instrumentation** - Event tracking and logging
34
+ - **Comprehensive Instrumentation** - Event tracking, performance monitoring, and detailed logging
34
35
 
35
36
  **Developer Experience:**
36
37
  - LLM provider support using official Ruby clients:
@@ -40,18 +41,30 @@ The result? LLM applications that actually scale and don't break when you sneeze
40
41
  - Type-safe tool definitions for ReAct agents
41
42
  - Comprehensive instrumentation and observability
42
43
 
43
- ## Fair Warning
44
+ ## Development Status
44
45
 
45
- This is fresh off the oven and evolving fast. I'm actively building this as a Ruby port of the [DSPy library](https://dspy.ai/). If you hit bugs or want to contribute, just email me directly!
46
+ DSPy.rb is actively developed and approaching stability at **v0.9.0**. The core framework is production-ready with comprehensive documentation, but I'm battle-testing features through the 0.x series before committing to a stable v1.0 API.
47
+
48
+ Real-world usage feedback is invaluable - if you encounter issues or have suggestions, please open a GitHub issue!
46
49
 
47
50
  ## Quick Start
48
51
 
49
52
  ### Installation
50
53
 
51
- Skip the gem for now - install straight from this repo while I prep the first release:
54
+ ```ruby
55
+ gem 'dspy', '~> 0.9'
56
+ ```
57
+
58
+ Or add to your Gemfile:
52
59
 
53
60
  ```ruby
54
- gem 'dspy', github: 'vicentereig/dspy.rb'
61
+ gem 'dspy'
62
+ ```
63
+
64
+ Then run:
65
+
66
+ ```bash
67
+ bundle install
55
68
  ```
56
69
 
57
70
  ### Your First DSPy Program
@@ -96,52 +109,58 @@ puts result.confidence # => 0.85
96
109
 
97
110
  ## Documentation
98
111
 
112
+ πŸ“– **[Complete Documentation Website](https://vicentereig.github.io/dspy.rb/)**
113
+
99
114
  ### Getting Started
100
- - **[Installation & Setup](docs/getting-started/installation.md)** - Detailed installation and configuration
101
- - **[Quick Start Guide](docs/getting-started/quick-start.md)** - Your first DSPy programs
102
- - **[Core Concepts](docs/getting-started/core-concepts.md)** - Understanding signatures, predictors, and modules
115
+ - **[Installation & Setup](docs/src/getting-started/installation.md)** - Detailed installation and configuration
116
+ - **[Quick Start Guide](docs/src/getting-started/quick-start.md)** - Your first DSPy programs
117
+ - **[Core Concepts](docs/src/getting-started/core-concepts.md)** - Understanding signatures, predictors, and modules
103
118
 
104
119
  ### Core Features
105
- - **[Signatures & Types](docs/core-concepts/signatures.md)** - Define typed interfaces for LLM operations
106
- - **[Predictors](docs/core-concepts/predictors.md)** - Predict, ChainOfThought, ReAct, and more
107
- - **[Modules & Pipelines](docs/core-concepts/modules.md)** - Compose complex multi-stage workflows
108
- - **[Examples & Validation](docs/core-concepts/examples.md)** - Type-safe training data
120
+ - **[Signatures & Types](docs/src/core-concepts/signatures.md)** - Define typed interfaces for LLM operations
121
+ - **[Predictors](docs/src/core-concepts/predictors.md)** - Predict, ChainOfThought, ReAct, and more
122
+ - **[Modules & Pipelines](docs/src/core-concepts/modules.md)** - Compose complex multi-stage workflows
123
+ - **[Examples & Validation](docs/src/core-concepts/examples.md)** - Type-safe training data
109
124
 
110
125
  ### Optimization
111
- - **[Evaluation Framework](docs/optimization/evaluation.md)** - Basic testing with simple metrics
112
- - **[Prompt Optimization](docs/optimization/prompt-optimization.md)** - Manipulate prompts as objects
113
- - **[MIPROv2 Optimizer](docs/optimization/miprov2.md)** - Basic automatic optimization
114
- - **[Simple Optimizer](docs/optimization/simple-optimizer.md)** - Random search experimentation
126
+ - **[Evaluation Framework](docs/src/optimization/evaluation.md)** - Basic testing with simple metrics
127
+ - **[Prompt Optimization](docs/src/optimization/prompt-optimization.md)** - Manipulate prompts as objects
128
+ - **[MIPROv2 Optimizer](docs/src/optimization/miprov2.md)** - Basic automatic optimization
115
129
 
116
130
  ### Production Features
117
- - **[Storage System](docs/production/storage.md)** - Basic file-based persistence
118
- - **[Observability](docs/production/observability.md)** - Multi-platform monitoring and metrics
131
+ - **[Storage System](docs/src/production/storage.md)** - Basic file-based persistence
132
+ - **[Observability](docs/src/production/observability.md)** - Multi-platform monitoring and metrics
119
133
 
120
134
  ### Advanced Usage
121
- - **[Complex Types](docs/advanced/complex-types.md)** - Sorbet type integration with automatic coercion for structs, enums, and arrays
122
- - **[Manual Pipelines](docs/advanced/pipelines.md)** - Manual module composition patterns
123
- - **[RAG Patterns](docs/advanced/rag.md)** - Manual RAG implementation with external services
124
- - **[Custom Metrics](docs/advanced/custom-metrics.md)** - Proc-based evaluation logic
125
-
126
- ## What's Next
127
-
128
- These are my goals to release v1.0.
129
-
130
- - βœ… Prompt objects foundation - *Done*
131
- - βœ… Evaluation framework - *Done*
132
- - βœ… Teleprompter base classes - *Done*
133
- - βœ… MIPROv2 optimization algorithm - *Done*
134
- - βœ… Storage & persistence system - *Done*
135
- - βœ… Registry & version management - *Done*
136
- - βœ… OpenTelemetry integration - *Done*
137
- - βœ… New Relic integration - *Done*
138
- - βœ… Langfuse integration - *Done*
139
- - 🚧 Ollama support
140
- - Context Engineering (see recent research: [How Contexts Fail](https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html), [How to Fix Your Context](https://www.dbreunig.com/2025/06/26/how-to-fix-your-context.html), [Context Engineering](https://simonwillison.net/2025/Jun/27/context-engineering/))
141
- - Agentic Memory support
142
- - MCP Support
143
- - Documentation website
144
- - Performance benchmarks
135
+ - **[Complex Types](docs/src/advanced/complex-types.md)** - Sorbet type integration with automatic coercion for structs, enums, and arrays
136
+ - **[Manual Pipelines](docs/src/advanced/pipelines.md)** - Manual module composition patterns
137
+ - **[RAG Patterns](docs/src/advanced/rag.md)** - Manual RAG implementation with external services
138
+ - **[Custom Metrics](docs/src/advanced/custom-metrics.md)** - Proc-based evaluation logic
139
+
140
+ ## Recent Achievements
141
+
142
+ DSPy.rb has rapidly evolved from experimental to production-ready:
143
+
144
+ - βœ… **JSON Parsing Reliability** (v0.8.0) - Native OpenAI structured outputs, strategy selection, retry logic
145
+ - βœ… **Type-Safe Strategy Configuration** (v0.9.0) - Provider-optimized automatic strategy selection
146
+ - βœ… **Documentation Website** (v0.6.4) - Comprehensive docs at [vicentereig.github.io/dspy.rb](https://vicentereig.github.io/dspy.rb)
147
+ - βœ… **Production Observability** - OpenTelemetry, New Relic, and Langfuse integration
148
+ - βœ… **Optimization Framework** - MIPROv2 algorithm with storage & persistence
149
+ - βœ… **Core Module System** - Predict, ChainOfThought, ReAct, CodeAct with type safety
150
+
151
+ ## Roadmap - Battle-Testing Toward v1.0
152
+
153
+ DSPy.rb is currently at **v0.9.0** and approaching stability. I'm focusing on real-world usage and refinement through the 0.10, 0.11, 0.12+ series before committing to a stable v1.0 API.
154
+
155
+ **Current Focus Areas:**
156
+ - 🚧 **Ollama Support** - Local model integration
157
+ - 🚧 **Context Engineering** - Advanced prompt optimization techniques
158
+ - 🚧 **MCP Support** - Model Context Protocol integration
159
+ - 🚧 **Agentic Memory** - Persistent agent state management
160
+ - 🚧 **Performance Optimization** - Based on production usage patterns
161
+
162
+ **v1.0 Philosophy:**
163
+ v1.0 will be released after extensive production battle-testing, not after checking off features. This ensures a stable, reliable API backed by real-world validation.
145
164
 
146
165
  ## License
147
166
 
@@ -31,10 +31,16 @@ module DSPy
31
31
  def select
32
32
  # Allow manual override via configuration
33
33
  if DSPy.config.structured_outputs.strategy
34
- strategy = find_strategy_by_name(DSPy.config.structured_outputs.strategy)
34
+ strategy = select_strategy_from_preference(DSPy.config.structured_outputs.strategy)
35
35
  return strategy if strategy&.available?
36
36
 
37
- DSPy.logger.warn("Requested strategy '#{DSPy.config.structured_outputs.strategy}' is not available")
37
+ # If strict strategy not available, fall back to compatible for Strict preference
38
+ if is_strict_preference?(DSPy.config.structured_outputs.strategy)
39
+ compatible_strategy = find_strategy_by_name("enhanced_prompting")
40
+ return compatible_strategy if compatible_strategy&.available?
41
+ end
42
+
43
+ DSPy.logger.warn("No available strategy found for preference '#{DSPy.config.structured_outputs.strategy}'")
38
44
  end
39
45
 
40
46
  # Select the highest priority available strategy
@@ -65,6 +71,42 @@ module DSPy
65
71
 
66
72
  private
67
73
 
74
+ # Select internal strategy based on user preference
75
+ sig { params(preference: DSPy::Strategy).returns(T.nilable(Strategies::BaseStrategy)) }
76
+ def select_strategy_from_preference(preference)
77
+ case preference
78
+ when DSPy::Strategy::Strict
79
+ # Try provider-optimized strategies first
80
+ select_provider_optimized_strategy
81
+ when DSPy::Strategy::Compatible
82
+ # Use enhanced prompting
83
+ find_strategy_by_name("enhanced_prompting")
84
+ else
85
+ nil
86
+ end
87
+ end
88
+
89
+ # Check if preference is for strict (provider-optimized) strategies
90
+ sig { params(preference: DSPy::Strategy).returns(T::Boolean) }
91
+ def is_strict_preference?(preference)
92
+ preference == DSPy::Strategy::Strict
93
+ end
94
+
95
+ # Select the best provider-optimized strategy for the current adapter
96
+ sig { returns(T.nilable(Strategies::BaseStrategy)) }
97
+ def select_provider_optimized_strategy
98
+ # Try OpenAI structured output first
99
+ openai_strategy = find_strategy_by_name("openai_structured_output")
100
+ return openai_strategy if openai_strategy&.available?
101
+
102
+ # Try Anthropic extraction
103
+ anthropic_strategy = find_strategy_by_name("anthropic_extraction")
104
+ return anthropic_strategy if anthropic_strategy&.available?
105
+
106
+ # No provider-specific strategy available
107
+ nil
108
+ end
109
+
68
110
  sig { returns(T::Array[Strategies::BaseStrategy]) }
69
111
  def build_strategies
70
112
  STRATEGIES.map { |klass| klass.new(@adapter, @signature_class) }
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sorbet-runtime"
4
+
5
+ module DSPy
6
+ class LM
7
+ # Enum for structured output strategies
8
+ class StructuredOutputStrategy < T::Enum
9
+ enums do
10
+ OpenAIStructuredOutput = new("openai_structured_output")
11
+ AnthropicExtraction = new("anthropic_extraction")
12
+ EnhancedPrompting = new("enhanced_prompting")
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/dspy/re_act.rb CHANGED
@@ -30,18 +30,9 @@ module DSPy
30
30
  }.compact
31
31
  end
32
32
  end
33
- # Defines the signature for ReAct reasoning using Sorbet signatures
34
- class Thought < DSPy::Signature
35
- description "Generate a thought about what to do next to answer the question."
36
-
37
- input do
38
- const :question, String,
39
- description: "The question to answer"
40
- const :history, T::Array[HistoryEntry],
41
- description: "Previous thoughts and actions, including observations from tools. The agent MUST use information from the history to inform its actions and final answer. Each entry is a hash representing a step in the reasoning process."
42
- const :available_tools, T::Array[T::Hash[String, T.untyped]],
43
- description: "Array of available tools with their JSON schemas. The agent MUST choose an action from the tool names in this list or use \"finish\". For each tool, use the name exactly as specified and provide action_input as a JSON object matching the tool's schema."
44
- end
33
+ # Base class for ReAct thought generation - will be customized per input type
34
+ class ThoughtBase < DSPy::Signature
35
+ description "Generate a thought about what to do next to process the given inputs."
45
36
 
46
37
  output do
47
38
  const :thought, String,
@@ -49,7 +40,7 @@ module DSPy
49
40
  const :action, String,
50
41
  description: "The action to take. MUST be one of the tool names listed in `available_tools` input, or the literal string \"finish\" to provide the final answer."
51
42
  const :action_input, T.any(String, T::Hash[T.untyped, T.untyped]),
52
- description: "Input for the chosen action. If action is a tool name, this MUST be a JSON object matching the tool's schema. If action is \"finish\", this field MUST contain the final answer to the original question. This answer MUST be directly taken from the relevant Observation in the history if available. For example, if an observation showed \"Observation: 100.0\", and you are finishing, this field MUST be \"100.0\". Do not leave empty if finishing with an observed answer."
43
+ description: "Input for the chosen action. If action is a tool name, this MUST be a JSON object matching the tool's schema. If action is \"finish\", this field MUST contain the final result based on processing the input data. This result MUST be directly taken from the relevant Observation in the history if available."
53
44
  end
54
45
  end
55
46
 
@@ -60,19 +51,10 @@ module DSPy
60
51
  end
61
52
  end
62
53
 
63
- # Defines the signature for processing observations and deciding next steps
64
- class ReActObservation < DSPy::Signature
54
+ # Base class for observation processing - will be customized per input type
55
+ class ReActObservationBase < DSPy::Signature
65
56
  description "Process the observation from a tool and decide what to do next."
66
57
 
67
- input do
68
- const :question, String,
69
- description: "The original question"
70
- const :history, T::Array[HistoryEntry],
71
- description: "Previous thoughts, actions, and observations. Each entry is a hash representing a step in the reasoning process."
72
- const :observation, String,
73
- description: "The result from the last action"
74
- end
75
-
76
58
  output do
77
59
  const :interpretation, String,
78
60
  description: "Interpretation of the observation"
@@ -108,11 +90,15 @@ module DSPy
108
90
  tools.each { |tool| @tools[tool.name.downcase] = tool }
109
91
  @max_iterations = max_iterations
110
92
 
93
+ # Create dynamic signature classes that include the original input fields
94
+ thought_signature = create_thought_signature(signature_class)
95
+ observation_signature = create_observation_signature(signature_class)
96
+
111
97
  # Create thought generator using Predict to preserve field descriptions
112
- @thought_generator = T.let(DSPy::Predict.new(Thought), DSPy::Predict)
98
+ @thought_generator = T.let(DSPy::Predict.new(thought_signature), DSPy::Predict)
113
99
 
114
100
  # Create observation processor using Predict to preserve field descriptions
115
- @observation_processor = T.let(DSPy::Predict.new(ReActObservation), DSPy::Predict)
101
+ @observation_processor = T.let(DSPy::Predict.new(observation_signature), DSPy::Predict)
116
102
 
117
103
  # Create enhanced output struct with ReAct fields
118
104
  @enhanced_output_struct = create_enhanced_output_struct(signature_class)
@@ -148,12 +134,11 @@ module DSPy
148
134
  max_iterations: @max_iterations,
149
135
  available_tools: available_tools
150
136
  }) do
151
- # Validate input and extract question
137
+ # Validate input
152
138
  input_struct = @original_signature_class.input_struct_class.new(**kwargs)
153
- question = T.cast(input_struct.serialize.values.first, String)
154
139
 
155
140
  # Execute ReAct reasoning loop
156
- reasoning_result = execute_react_reasoning_loop(question)
141
+ reasoning_result = execute_react_reasoning_loop(input_struct)
157
142
 
158
143
  # Create enhanced output with all ReAct data
159
144
  create_enhanced_result(kwargs, reasoning_result)
@@ -164,9 +149,67 @@ module DSPy
164
149
 
165
150
  private
166
151
 
152
+ # Creates a dynamic Thought signature that includes the original input fields
153
+ sig { params(signature_class: T.class_of(DSPy::Signature)).returns(T.class_of(DSPy::Signature)) }
154
+ def create_thought_signature(signature_class)
155
+ # Create new class that inherits from DSPy::Signature
156
+ Class.new(DSPy::Signature) do
157
+ # Set description
158
+ description "Generate a thought about what to do next to process the given inputs."
159
+
160
+ # Define input fields
161
+ input do
162
+ const :input_context, String,
163
+ desc: "Serialized representation of all input fields"
164
+ const :history, T::Array[HistoryEntry],
165
+ desc: "Previous thoughts and actions, including observations from tools."
166
+ const :available_tools, T::Array[T::Hash[String, T.untyped]],
167
+ desc: "Array of available tools with their JSON schemas."
168
+ end
169
+
170
+ # Define output fields (same as ThoughtBase)
171
+ output do
172
+ const :thought, String,
173
+ desc: "Reasoning about what to do next, considering the history and observations."
174
+ const :action, String,
175
+ desc: "The action to take. MUST be one of the tool names listed in `available_tools` input, or the literal string \"finish\" to provide the final answer."
176
+ const :action_input, T.any(String, T::Hash[T.untyped, T.untyped]),
177
+ desc: "Input for the chosen action. If action is a tool name, this MUST be a JSON object matching the tool's schema. If action is \"finish\", this field MUST contain the final result based on processing the input data."
178
+ end
179
+ end
180
+ end
181
+
182
+ # Creates a dynamic observation signature that includes the original input fields
183
+ sig { params(signature_class: T.class_of(DSPy::Signature)).returns(T.class_of(DSPy::Signature)) }
184
+ def create_observation_signature(signature_class)
185
+ # Create new class that inherits from DSPy::Signature
186
+ Class.new(DSPy::Signature) do
187
+ # Set description
188
+ description "Process the observation from a tool and decide what to do next."
189
+
190
+ # Define input fields
191
+ input do
192
+ const :input_context, String,
193
+ desc: "Serialized representation of all input fields"
194
+ const :history, T::Array[HistoryEntry],
195
+ desc: "Previous thoughts, actions, and observations."
196
+ const :observation, String,
197
+ desc: "The result from the last action"
198
+ end
199
+
200
+ # Define output fields (same as ReActObservationBase)
201
+ output do
202
+ const :interpretation, String,
203
+ desc: "Interpretation of the observation"
204
+ const :next_step, NextStep,
205
+ desc: "What to do next: '#{NextStep::Continue}' or '#{NextStep::Finish}'"
206
+ end
207
+ end
208
+ end
209
+
167
210
  # Executes the main ReAct reasoning loop
168
- sig { params(question: String).returns(T::Hash[Symbol, T.untyped]) }
169
- def execute_react_reasoning_loop(question)
211
+ sig { params(input_struct: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
212
+ def execute_react_reasoning_loop(input_struct)
170
213
  history = T.let([], T::Array[HistoryEntry])
171
214
  available_tools_desc = @tools.map { |name, tool| JSON.parse(tool.schema) }
172
215
  final_answer = T.let(nil, T.nilable(String))
@@ -178,7 +221,7 @@ module DSPy
178
221
  iterations_count += 1
179
222
 
180
223
  iteration_result = execute_single_iteration(
181
- question, history, available_tools_desc, iterations_count, tools_used, last_observation
224
+ input_struct, history, available_tools_desc, iterations_count, tools_used, last_observation
182
225
  )
183
226
 
184
227
  if iteration_result[:should_finish]
@@ -202,8 +245,8 @@ module DSPy
202
245
  end
203
246
 
204
247
  # Executes a single iteration of the ReAct loop
205
- sig { params(question: String, history: T::Array[HistoryEntry], available_tools_desc: T::Array[T::Hash[String, T.untyped]], iteration: Integer, tools_used: T::Array[String], last_observation: T.nilable(String)).returns(T::Hash[Symbol, T.untyped]) }
206
- def execute_single_iteration(question, history, available_tools_desc, iteration, tools_used, last_observation)
248
+ sig { params(input_struct: T.untyped, history: T::Array[HistoryEntry], available_tools_desc: T::Array[T::Hash[String, T.untyped]], iteration: Integer, tools_used: T::Array[String], last_observation: T.nilable(String)).returns(T::Hash[Symbol, T.untyped]) }
249
+ def execute_single_iteration(input_struct, history, available_tools_desc, iteration, tools_used, last_observation)
207
250
  # Instrument each iteration
208
251
  Instrumentation.instrument('dspy.react.iteration', {
209
252
  iteration: iteration,
@@ -213,7 +256,7 @@ module DSPy
213
256
  }) do
214
257
  # Generate thought and action
215
258
  thought_obj = @thought_generator.forward(
216
- question: question,
259
+ input_context: input_struct.serialize.to_json,
217
260
  history: history,
218
261
  available_tools: available_tools_desc
219
262
  )
@@ -243,7 +286,7 @@ module DSPy
243
286
 
244
287
  # Process observation and decide next step
245
288
  observation_decision = process_observation_and_decide_next_step(
246
- question, history, observation, available_tools_desc, iteration
289
+ input_struct, history, observation, available_tools_desc, iteration
247
290
  )
248
291
 
249
292
  if observation_decision[:should_finish]
@@ -337,12 +380,12 @@ module DSPy
337
380
  )
338
381
  end
339
382
 
340
- sig { params(question: String, history: T::Array[HistoryEntry], observation: String, available_tools_desc: T::Array[T::Hash[String, T.untyped]], iteration: Integer).returns(T::Hash[Symbol, T.untyped]) }
341
- def process_observation_and_decide_next_step(question, history, observation, available_tools_desc, iteration)
383
+ sig { params(input_struct: T.untyped, history: T::Array[HistoryEntry], observation: String, available_tools_desc: T::Array[T::Hash[String, T.untyped]], iteration: Integer).returns(T::Hash[Symbol, T.untyped]) }
384
+ def process_observation_and_decide_next_step(input_struct, history, observation, available_tools_desc, iteration)
342
385
  return { should_finish: false } if observation.include?("Unknown action")
343
386
 
344
387
  observation_result = @observation_processor.forward(
345
- question: question,
388
+ input_context: input_struct.serialize.to_json,
346
389
  history: history,
347
390
  observation: observation
348
391
  )
@@ -350,16 +393,16 @@ module DSPy
350
393
  return { should_finish: false } unless observation_result.next_step == NextStep::Finish
351
394
 
352
395
  final_answer = generate_forced_final_answer(
353
- question, history, available_tools_desc, observation_result, iteration
396
+ input_struct, history, available_tools_desc, observation_result, iteration
354
397
  )
355
398
 
356
399
  { should_finish: true, final_answer: final_answer }
357
400
  end
358
401
 
359
- sig { params(question: String, history: T::Array[HistoryEntry], available_tools_desc: T::Array[T::Hash[String, T.untyped]], observation_result: T.untyped, iteration: Integer).returns(String) }
360
- def generate_forced_final_answer(question, history, available_tools_desc, observation_result, iteration)
402
+ sig { params(input_struct: T.untyped, history: T::Array[HistoryEntry], available_tools_desc: T::Array[T::Hash[String, T.untyped]], observation_result: T.untyped, iteration: Integer).returns(String) }
403
+ def generate_forced_final_answer(input_struct, history, available_tools_desc, observation_result, iteration)
361
404
  final_thought = @thought_generator.forward(
362
- question: question,
405
+ input_context: input_struct.serialize.to_json,
363
406
  history: history,
364
407
  available_tools: available_tools_desc
365
408
  )
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sorbet-runtime"
4
+
5
+ module DSPy
6
+ # User-facing enum for structured output strategy preferences
7
+ class Strategy < T::Enum
8
+ enums do
9
+ # Use provider-optimized strategies when available (OpenAI structured outputs, Anthropic extraction)
10
+ # Falls back to Compatible if provider-specific strategy isn't available
11
+ Strict = new("strict")
12
+
13
+ # Use enhanced prompting that works with any provider
14
+ # More compatible but potentially less reliable than provider-specific strategies
15
+ Compatible = new("compatible")
16
+ end
17
+ end
18
+ end
data/lib/dspy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSPy
4
- VERSION = "0.8.1"
4
+ VERSION = "0.9.1"
5
5
  end
data/lib/dspy.rb CHANGED
@@ -25,7 +25,7 @@ module DSPy
25
25
  setting :structured_outputs do
26
26
  setting :openai, default: false
27
27
  setting :anthropic, default: false # Reserved for future use
28
- setting :strategy, default: nil # Can be 'openai_structured_output', 'anthropic_extraction', 'enhanced_prompting', or nil for auto
28
+ setting :strategy, default: nil # Can be DSPy::Strategy::Strict, DSPy::Strategy::Compatible, or nil for auto
29
29
  setting :retry_enabled, default: true
30
30
  setting :max_retries, default: 3
31
31
  setting :fallback_enabled, default: true
@@ -122,6 +122,7 @@ require_relative 'dspy/few_shot_example'
122
122
  require_relative 'dspy/prompt'
123
123
  require_relative 'dspy/example'
124
124
  require_relative 'dspy/lm'
125
+ require_relative 'dspy/strategy'
125
126
  require_relative 'dspy/predict'
126
127
  require_relative 'dspy/chain_of_thought'
127
128
  require_relative 'dspy/re_act'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig RincΓ³n de Arellano
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-11 00:00:00.000000000 Z
10
+ date: 2025-07-16 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dry-configurable
@@ -182,6 +182,7 @@ files:
182
182
  - lib/dspy/lm/strategies/enhanced_prompting_strategy.rb
183
183
  - lib/dspy/lm/strategies/openai_structured_output_strategy.rb
184
184
  - lib/dspy/lm/strategy_selector.rb
185
+ - lib/dspy/lm/structured_output_strategy.rb
185
186
  - lib/dspy/memory.rb
186
187
  - lib/dspy/memory/embedding_engine.rb
187
188
  - lib/dspy/memory/in_memory_store.rb
@@ -204,6 +205,7 @@ files:
204
205
  - lib/dspy/signature.rb
205
206
  - lib/dspy/storage/program_storage.rb
206
207
  - lib/dspy/storage/storage_manager.rb
208
+ - lib/dspy/strategy.rb
207
209
  - lib/dspy/subscribers/langfuse_subscriber.rb
208
210
  - lib/dspy/subscribers/logger_subscriber.rb
209
211
  - lib/dspy/subscribers/newrelic_subscriber.rb