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 +4 -4
- data/README.md +63 -44
- data/lib/dspy/lm/strategy_selector.rb +44 -2
- data/lib/dspy/lm/structured_output_strategy.rb +16 -0
- data/lib/dspy/re_act.rb +86 -43
- data/lib/dspy/strategy.rb +18 -0
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +2 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 306fa376ea024edd2fc75d7bc1af6a42e60770e9566b4dd114414a40c685f2a7
|
4
|
+
data.tar.gz: 74f321fdd09a5761d17481af846dcd85dc3a372f573f79659c4fcfc761e92a18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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** -
|
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** -
|
32
|
+
- **File-based Storage** - Optimization result persistence with versioning
|
32
33
|
- **Multi-Platform Observability** - OpenTelemetry, New Relic, and Langfuse integration
|
33
|
-
- **
|
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
|
-
##
|
44
|
+
## Development Status
|
44
45
|
|
45
|
-
|
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
|
-
|
54
|
+
```ruby
|
55
|
+
gem 'dspy', '~> 0.9'
|
56
|
+
```
|
57
|
+
|
58
|
+
Or add to your Gemfile:
|
52
59
|
|
53
60
|
```ruby
|
54
|
-
gem 'dspy'
|
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
|
-
##
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
- β
|
131
|
-
- β
|
132
|
-
- β
|
133
|
-
- β
|
134
|
-
- β
|
135
|
-
- β
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
-
|
140
|
-
|
141
|
-
|
142
|
-
-
|
143
|
-
-
|
144
|
-
-
|
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 =
|
34
|
+
strategy = select_strategy_from_preference(DSPy.config.structured_outputs.strategy)
|
35
35
|
return strategy if strategy&.available?
|
36
36
|
|
37
|
-
|
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
|
-
#
|
34
|
-
class
|
35
|
-
description "Generate a thought about what to do next to
|
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
|
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
|
-
#
|
64
|
-
class
|
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(
|
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(
|
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
|
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(
|
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(
|
169
|
-
def execute_react_reasoning_loop(
|
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
|
-
|
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(
|
206
|
-
def execute_single_iteration(
|
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
|
-
|
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
|
-
|
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(
|
341
|
-
def process_observation_and_decide_next_step(
|
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
|
-
|
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
|
-
|
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(
|
360
|
-
def generate_forced_final_answer(
|
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
|
-
|
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
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
|
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.
|
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-
|
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
|