dspy 0.29.1 → 0.30.0

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +45 -0
  3. data/README.md +120 -100
  4. data/lib/dspy/callbacks.rb +74 -19
  5. data/lib/dspy/context.rb +49 -4
  6. data/lib/dspy/errors.rb +19 -1
  7. data/lib/dspy/{datasets.rb → evals/version.rb} +2 -3
  8. data/lib/dspy/{evaluate.rb → evals.rb} +373 -110
  9. data/lib/dspy/mixins/instruction_updatable.rb +22 -0
  10. data/lib/dspy/observability.rb +40 -182
  11. data/lib/dspy/predict.rb +10 -2
  12. data/lib/dspy/propose/dataset_summary_generator.rb +28 -18
  13. data/lib/dspy/re_act.rb +21 -0
  14. data/lib/dspy/schema/sorbet_json_schema.rb +302 -0
  15. data/lib/dspy/schema/version.rb +7 -0
  16. data/lib/dspy/schema.rb +4 -0
  17. data/lib/dspy/structured_outputs_prompt.rb +48 -0
  18. data/lib/dspy/support/warning_filters.rb +27 -0
  19. data/lib/dspy/teleprompt/gepa.rb +9 -588
  20. data/lib/dspy/teleprompt/instruction_updates.rb +94 -0
  21. data/lib/dspy/teleprompt/teleprompter.rb +6 -6
  22. data/lib/dspy/teleprompt/utils.rb +5 -65
  23. data/lib/dspy/type_system/sorbet_json_schema.rb +2 -299
  24. data/lib/dspy/version.rb +1 -1
  25. data/lib/dspy.rb +33 -7
  26. metadata +18 -61
  27. data/lib/dspy/code_act.rb +0 -477
  28. data/lib/dspy/datasets/ade.rb +0 -90
  29. data/lib/dspy/observability/async_span_processor.rb +0 -250
  30. data/lib/dspy/observability/observation_type.rb +0 -65
  31. data/lib/dspy/optimizers/gaussian_process.rb +0 -141
  32. data/lib/dspy/teleprompt/mipro_v2.rb +0 -1672
  33. data/lib/gepa/api.rb +0 -61
  34. data/lib/gepa/core/engine.rb +0 -226
  35. data/lib/gepa/core/evaluation_batch.rb +0 -26
  36. data/lib/gepa/core/result.rb +0 -92
  37. data/lib/gepa/core/state.rb +0 -231
  38. data/lib/gepa/logging/experiment_tracker.rb +0 -54
  39. data/lib/gepa/logging/logger.rb +0 -57
  40. data/lib/gepa/logging.rb +0 -9
  41. data/lib/gepa/proposer/base.rb +0 -27
  42. data/lib/gepa/proposer/merge_proposer.rb +0 -424
  43. data/lib/gepa/proposer/reflective_mutation/base.rb +0 -48
  44. data/lib/gepa/proposer/reflective_mutation/reflective_mutation.rb +0 -188
  45. data/lib/gepa/strategies/batch_sampler.rb +0 -91
  46. data/lib/gepa/strategies/candidate_selector.rb +0 -97
  47. data/lib/gepa/strategies/component_selector.rb +0 -57
  48. data/lib/gepa/strategies/instruction_proposal.rb +0 -120
  49. data/lib/gepa/telemetry.rb +0 -122
  50. data/lib/gepa/utils/pareto.rb +0 -119
  51. data/lib/gepa.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42a6bfa4a1e0fe7b7dd38e3a2fe017959365f566248319e1deec6cadcd06efc3
4
- data.tar.gz: ffceb724c96e7803ec7531bf52c12f502266e21d4ab9bcf534a8005e59835883
3
+ metadata.gz: 5e59fde98eb918cca54822bc6af704100c51b71081d8ac77e17e8df3cddcb016
4
+ data.tar.gz: f8ff81ef1873fc1bffac8a209ddb4172586295e6ef1f46d6bb598045148fc3eb
5
5
  SHA512:
6
- metadata.gz: 2d81f0ab86954523f8b3511f3105f8005b9709f0a46fb08f0403dcac841c70efcfbff90dcdedccaf322554280f62bdbab6a36be34d0e787f01734d8ee2219bf6
7
- data.tar.gz: 31d048ad3d7493be0c52a24729d033ce58a891039c180945aa80d54afb7a529b6e420ccfc49fdab32ef00803483ecbb63dd2c2bd9d15f569c44debfdfd6dea0c
6
+ metadata.gz: dc880712e317a8e88c188527b5e1ff906da8c03fe1cbc4251e265bd87c141e52a16b4498f2f9316c33543f3428fb3067fd80a0728ce33de81575f111b93d4553
7
+ data.tar.gz: 7d1899b7e8a61b5d2c80fb8f237085171e9b64407f99135b22c341076e11867ee0c323d15c67c5428337606c869702ec9e6ada13edf27c38a6a40d8c83ef4f3b
data/LICENSE ADDED
@@ -0,0 +1,45 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vicente Services SL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ This project is a Ruby port of the original Python [DSPy library](https://github.com/stanfordnlp/dspy), which is licensed under the MIT License:
24
+
25
+ MIT License
26
+
27
+ Copyright (c) 2023 Stanford Future Data Systems
28
+
29
+ Permission is hereby granted, free of charge, to any person obtaining a copy
30
+ of this software and associated documentation files (the "Software"), to deal
31
+ in the Software without restriction, including without limitation the rights
32
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
33
+ copies of the Software, and to permit persons to whom the Software is
34
+ furnished to do so, subject to the following conditions:
35
+
36
+ The above copyright notice and this permission notice shall be included in all
37
+ copies or substantial portions of the Software.
38
+
39
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45
+ SOFTWARE.
data/README.md CHANGED
@@ -5,26 +5,79 @@
5
5
  [![Build Status](https://img.shields.io/github/actions/workflow/status/vicentereig/dspy.rb/ruby.yml?branch=main&label=build)](https://github.com/vicentereig/dspy.rb/actions/workflows/ruby.yml)
6
6
  [![Documentation](https://img.shields.io/badge/docs-vicentereig.github.io%2Fdspy.rb-blue)](https://vicentereig.github.io/dspy.rb/)
7
7
 
8
+ > [!NOTE]
9
+ > The core Prompt Engineering Framework is production-ready with
10
+ > comprehensive documentation. I am focusing now on educational content on systematic Prompt Optimization and Context Engineering.
11
+ > Your feedback is invaluable. if you encounter issues, please open an [issue](https://github.com/vicentereig/dspy.rb/issues). If you have suggestions, open a [new thread](https://github.com/vicentereig/dspy.rb/discussions).
12
+ >
13
+ > If you want to contribute, feel free to reach out to me to coordinate efforts: hey at vicente.services
14
+ >
15
+ > And, yes, this is 100% a legit project. :)
16
+
17
+
8
18
  **Build reliable LLM applications in idiomatic Ruby using composable, type-safe modules.**
9
19
 
10
- The Ruby framework for programming with large language models. DSPy.rb brings structured LLM programming to Ruby developers. Instead of wrestling with prompt strings and parsing responses, you define typed signatures using idiomatic Ruby to compose and decompose AI Worklows and AI Agents.
20
+ The Ruby framework for programming with large language models. DSPy.rb brings structured LLM programming to Ruby developers, programmatic Prompt Engineering and Context Engineering.
21
+ Instead of wrestling with prompt strings and parsing responses, you define typed signatures using idiomatic Ruby to compose and decompose AI Worklows and AI Agents.
11
22
 
12
23
  **Prompts are the just Functions.** Traditional prompting is like writing code with string concatenation: it works until it doesn't. DSPy.rb brings you
13
24
  the programming approach pioneered by [dspy.ai](https://dspy.ai/): instead of crafting fragile prompts, you define modular
14
25
  signatures and let the framework handle the messy details.
15
26
 
16
27
  DSPy.rb is an idiomatic Ruby surgical port of Stanford's [DSPy framework](https://github.com/stanfordnlp/dspy). While implementing
17
- the core concepts of signatures, predictors, and optimization from the original Python library, DSPy.rb embraces Ruby
18
- conventions and adds Ruby-specific innovations like CodeAct agents and enhanced production instrumentation.
28
+ the core concepts of signatures, predictors, and the main optimization algorithms from the original Python library, DSPy.rb embraces Ruby
29
+ conventions and adds Ruby-specific innovations like Sorbet-base Typed system, ReAct loops, and production-ready integrations like non-blocking Open Telemetry Instrumentation.
19
30
 
20
- The result? LLM applications that actually scale and don't break when you sneeze.
31
+ **What you get?** Ruby LLM applications that actually scale and don't break when you sneeze.
32
+
33
+ Check the [examples](examples/) and take them for a spin!
21
34
 
22
35
  ## Your First DSPy Program
36
+ ### Installation
37
+
38
+ Add to your Gemfile:
39
+
40
+ ```ruby
41
+ gem 'dspy'
42
+ ```
43
+
44
+ and
45
+
46
+ ```bash
47
+ bundle install
48
+ ```
49
+
50
+ ### Optional Sibling Gems
51
+
52
+ DSPy.rb ships multiple gems from this monorepo so you only install what you need. Add these alongside `dspy`:
53
+
54
+ | Gem | Description | Status |
55
+ | --- | --- | --- |
56
+ | `dspy-schema` | Exposes `DSPy::TypeSystem::SorbetJsonSchema` for downstream reuse. | **Stable** (v1.0.0) |
57
+ | `dspy-code_act` | Think-Code-Observe agents that synthesize and execute Ruby safely. | Preview (0.x) |
58
+ | `dspy-datasets` | Dataset helpers plus Parquet/Polars tooling for richer evaluation corpora. | Preview (0.x) |
59
+ | `dspy-evals` | High-throughput evaluation harness with metrics, callbacks, and regression fixtures. | Preview (0.x) |
60
+ | `dspy-miprov2` | Bayesian optimization + Gaussian Process backend for the MIPROv2 teleprompter. | Preview (0.x) |
61
+ | `dspy-gepa` | `DSPy::Teleprompt::GEPA`, reflection loops, experiment tracking, telemetry adapters. | Preview (mirrors `dspy` version) |
62
+ | `gepa` | GEPA optimizer core (Pareto engine, telemetry, reflective proposer). | Preview (mirrors `dspy` version) |
63
+ | `dspy-o11y` | Core observability APIs: `DSPy::Observability`, async span processor, observation types. | **Stable** (v1.0.0) |
64
+ | `dspy-o11y-langfuse` | Auto-configures DSPy observability to stream spans to Langfuse via OTLP. | **Stable** (v1.0.0) |
65
+
66
+ Set the matching `DSPY_WITH_*` environment variables (see `Gemfile`) to include or exclude each sibling gem when running Bundler locally (for example `DSPY_WITH_GEPA=1` or `DSPY_WITH_O11Y_LANGFUSE=1`). Refer to `docs/core-concepts/dependency-tree.md` for the full dependency map and roadmap.
67
+ ### Your First Reliable Predictor
23
68
 
24
69
  ```ruby
25
- # Define a signature for sentiment classification
70
+
71
+ # Configure DSPy globablly to use your fave LLM - you can override this on an instance levle.
72
+ DSPy.configure do |c|
73
+ c.lm = DSPy::LM.new('openai/gpt-4o-mini',
74
+ api_key: ENV['OPENAI_API_KEY'],
75
+ structured_outputs: true) # Enable OpenAI's native JSON mode
76
+ end
77
+
78
+ # Define a signature for sentiment classification - instead of writing a full prompt!
26
79
  class Classify < DSPy::Signature
27
- description "Classify sentiment of a given sentence."
80
+ description "Classify sentiment of a given sentence." # sets the goal of the underlying prompt
28
81
 
29
82
  class Sentiment < T::Enum
30
83
  enums do
@@ -33,26 +86,22 @@ class Classify < DSPy::Signature
33
86
  Neutral = new('neutral')
34
87
  end
35
88
  end
36
-
89
+
90
+ # Structured Inputs: makes sure you are sending only valid prompt inputs to your model
37
91
  input do
38
- const :sentence, String
92
+ const :sentence, String, description: 'The sentence to analyze'
39
93
  end
40
94
 
95
+ # Structured Outputs: your predictor will validate the output of the model too.
41
96
  output do
42
- const :sentiment, Sentiment
43
- const :confidence, Float
97
+ const :sentiment, Sentiment, description: 'The sentiment of the sentence'
98
+ const :confidence, Float, description: 'A number between 0.0 and 1.0'
44
99
  end
45
100
  end
46
101
 
47
- # Configure DSPy with your LLM
48
- DSPy.configure do |c|
49
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
50
- api_key: ENV['OPENAI_API_KEY'],
51
- structured_outputs: true) # Enable OpenAI's native JSON mode
52
- end
53
-
54
- # Create the predictor and run inference
102
+ # Wire it to the simplest prompting technique - a Predictn.
55
103
  classify = DSPy::Predict.new(Classify)
104
+ # it may raise an error if you mess the inputs or your LLM messes the outputs.
56
105
  result = classify.call(sentence: "This book was super fun to read!")
57
106
 
58
107
  puts result.sentiment # => #<Sentiment::Positive>
@@ -99,12 +148,22 @@ end
99
148
 
100
149
  ## What You Get
101
150
 
151
+ **Developer Experience:**
152
+ - LLM provider support using official Ruby clients:
153
+ - [OpenAI Ruby](https://github.com/openai/openai-ruby) with vision model support
154
+ - [Anthropic Ruby SDK](https://github.com/anthropics/anthropic-sdk-ruby) with multimodal capabilities
155
+ - [Google Gemini API](https://ai.google.dev/) with native structured outputs
156
+ - [Ollama](https://ollama.com/) via OpenAI compatibility layer for local models
157
+ - **Multimodal Support** - Complete image analysis with DSPy::Image, type-safe bounding boxes, vision-capable models
158
+ - Runtime type checking with [Sorbet](https://sorbet.org/) including T::Enum and union types
159
+ - Type-safe tool definitions for ReAct agents
160
+ - Comprehensive instrumentation and observability
161
+
102
162
  **Core Building Blocks:**
103
163
  - **Signatures** - Define input/output schemas using Sorbet types with T::Enum and union type support
104
164
  - **Predict** - LLM completion with structured data extraction and multimodal support
105
165
  - **Chain of Thought** - Step-by-step reasoning for complex problems with automatic prompt optimization
106
166
  - **ReAct** - Tool-using agents with type-safe tool definitions and error recovery
107
- - **CodeAct** - Dynamic code execution agents for programming tasks
108
167
  - **Module Composition** - Combine multiple LLM calls into production-ready workflows
109
168
 
110
169
  **Optimization & Evaluation:**
@@ -122,24 +181,40 @@ end
122
181
  - **File-based Storage** - Optimization result persistence with versioning
123
182
  - **Structured Logging** - JSON and key=value formats with span tracking
124
183
 
125
- **Developer Experience:**
126
- - LLM provider support using official Ruby clients:
127
- - [OpenAI Ruby](https://github.com/openai/openai-ruby) with vision model support
128
- - [Anthropic Ruby SDK](https://github.com/anthropics/anthropic-sdk-ruby) with multimodal capabilities
129
- - [Google Gemini API](https://ai.google.dev/) with native structured outputs
130
- - [Ollama](https://ollama.com/) via OpenAI compatibility layer for local models
131
- - **Multimodal Support** - Complete image analysis with DSPy::Image, type-safe bounding boxes, vision-capable models
132
- - Runtime type checking with [Sorbet](https://sorbet.org/) including T::Enum and union types
133
- - Type-safe tool definitions for ReAct agents
134
- - Comprehensive instrumentation and observability
184
+ ## Recent Achievements
135
185
 
136
- ## Development Status
186
+ DSPy.rb has rapidly evolved from experimental to production-ready:
137
187
 
138
- DSPy.rb is actively developed and approaching stability. The core framework is production-ready with
139
- comprehensive documentation, but I'm battle-testing features through the 0.x series before committing
140
- to a stable v1.0 API.
188
+ ### Foundation
189
+ - **JSON Parsing Reliability** - Native OpenAI structured outputs with adaptive retry logic and schema-aware fallbacks
190
+ - **Type-Safe Strategy Configuration** - Provider-optimized strategy selection and enum-backed optimizer presets
191
+ - ✅ **Core Module System** - Predict, ChainOfThought, ReAct with type safety (add `dspy-code_act` for Think-Code-Observe agents)
192
+ - ✅ **Production Observability** - OpenTelemetry, New Relic, and Langfuse integration
193
+ - ✅ **Advanced Optimization** - MIPROv2 with Bayesian optimization, Gaussian Processes, and multi-mode search
194
+
195
+ ### Recent Advances
196
+ - ✅ **MIPROv2 ADE Integrity (v0.29.1)** - Stratified train/val/test splits, honest precision accounting, and enum-driven `--auto` presets with integration coverage
197
+ - ✅ **Instruction Deduplication (v0.29.1)** - Candidate generation now filters repeated programs so optimization logs highlight unique strategies
198
+ - ✅ **GEPA Teleprompter (v0.29.0)** - Genetic-Pareto reflective prompt evolution with merge proposer scheduling, reflective mutation, and ADE demo parity
199
+ - ✅ **Optimizer Utilities Parity (v0.29.0)** - Bootstrap strategies, dataset summaries, and Layer 3 utilities unlock multi-predictor programs on Ruby
200
+ - ✅ **Observability Hardening (v0.29.0)** - OTLP exporter runs on a single-thread executor preventing frozen SSL contexts without blocking spans
201
+ - ✅ **Documentation Refresh (v0.29.x)** - New GEPA guide plus ADE optimization docs covering presets, stratified splits, and error-handling defaults
202
+
203
+ **Current Focus Areas:**
204
+
205
+ ### Production Readiness
206
+ - 🚧 **Production Patterns** - Real-world usage validation and performance optimization
207
+ - 🚧 **Ruby Ecosystem Integration** - Rails integration, Sidekiq compatibility, deployment patterns
208
+
209
+ ### Community & Adoption
210
+ - 🚧 **Community Examples** - Real-world applications and case studies
211
+ - 🚧 **Contributor Experience** - Making it easier to contribute and extend
212
+ - 🚧 **Performance Benchmarks** - Comparative analysis vs other frameworks
213
+
214
+ **v1.0 Philosophy:**
215
+ v1.0 will be released after extensive production battle-testing, not after checking off features.
216
+ The API is already stable - v1.0 represents confidence in production reliability backed by real-world validation.
141
217
 
142
- Real-world usage feedback is invaluable - if you encounter issues or have suggestions, please open a GitHub issue!
143
218
 
144
219
  ## Documentation
145
220
 
@@ -156,92 +231,37 @@ For LLMs and AI assistants working with DSPy.rb:
156
231
  - **[Quick Start Guide](docs/src/getting-started/quick-start.md)** - Your first DSPy programs
157
232
  - **[Core Concepts](docs/src/getting-started/core-concepts.md)** - Understanding signatures, predictors, and modules
158
233
 
159
- ### Core Features
234
+ ### Prompt Engineering
160
235
  - **[Signatures & Types](docs/src/core-concepts/signatures.md)** - Define typed interfaces for LLM operations
161
236
  - **[Predictors](docs/src/core-concepts/predictors.md)** - Predict, ChainOfThought, ReAct, and more
162
237
  - **[Modules & Pipelines](docs/src/core-concepts/modules.md)** - Compose complex multi-stage workflows
163
238
  - **[Multimodal Support](docs/src/core-concepts/multimodal.md)** - Image analysis with vision-capable models
164
239
  - **[Examples & Validation](docs/src/core-concepts/examples.md)** - Type-safe training data
240
+ - **[Rich Types](docs/src/advanced/complex-types.md)** - Sorbet type integration with automatic coercion for structs, enums, and arrays
241
+ - **[Composable Pipelines](docs/src/advanced/pipelines.md)** - Manual module composition patterns
165
242
 
166
- ### Optimization
243
+ ### Prompt Optimization
167
244
  - **[Evaluation Framework](docs/src/optimization/evaluation.md)** - Advanced metrics beyond simple accuracy
168
245
  - **[Prompt Optimization](docs/src/optimization/prompt-optimization.md)** - Manipulate prompts as objects
169
246
  - **[MIPROv2 Optimizer](docs/src/optimization/miprov2.md)** - Advanced Bayesian optimization with Gaussian Processes
170
247
  - **[GEPA Optimizer](docs/src/optimization/gepa.md)** *(beta)* - Reflective mutation with optional reflection LMs
171
248
 
172
- ### Production Features
173
- - **[Storage System](docs/src/production/storage.md)** - Persistence and optimization result storage
174
- - **[Observability](docs/src/production/observability.md)** - Zero-config Langfuse integration with a dedicated export worker that never blocks your LLMs
175
-
176
- ### Advanced Usage
177
- - **[Complex Types](docs/src/advanced/complex-types.md)** - Sorbet type integration with automatic coercion for structs, enums, and arrays
178
- - **[Manual Pipelines](docs/src/advanced/pipelines.md)** - Manual module composition patterns
249
+ ### Context Engineering
250
+ - **[Tools](docs/src/core-concepts/toolsets.md)** - Tool wieldint agents.
251
+ - **[Agentic Memory](docs/src/core-concepts/memory.md)** - Memory Tools & Agentic Loops
179
252
  - **[RAG Patterns](docs/src/advanced/rag.md)** - Manual RAG implementation with external services
180
- - **[Custom Metrics](docs/src/advanced/custom-metrics.md)** - Proc-based evaluation logic
181
-
182
- ## Quick Start
183
-
184
- ### Installation
185
-
186
- Add to your Gemfile:
187
-
188
- ```ruby
189
- gem 'dspy'
190
- ```
191
-
192
- Then run:
193
-
194
- ```bash
195
- bundle install
196
- ```
197
-
198
- ## Recent Achievements
199
-
200
- DSPy.rb has rapidly evolved from experimental to production-ready:
201
-
202
- ### Foundation
203
- - ✅ **JSON Parsing Reliability** - Native OpenAI structured outputs, strategy selection, retry logic
204
- - ✅ **Type-Safe Strategy Configuration** - Provider-optimized automatic strategy selection
205
- - ✅ **Core Module System** - Predict, ChainOfThought, ReAct, CodeAct with type safety
206
- - ✅ **Production Observability** - OpenTelemetry, New Relic, and Langfuse integration
207
- - ✅ **Advanced Optimization** - MIPROv2 with Bayesian optimization, Gaussian Processes, and multiple strategies
208
253
 
209
- ### Recent Advances
210
- - **Enhanced Langfuse Integration (v0.25.0)** - Comprehensive OpenTelemetry span reporting with proper input/output, hierarchical nesting, accurate timing, and observation types
211
- - **Comprehensive Multimodal Framework** - Complete image analysis with `DSPy::Image`, type-safe bounding boxes, vision model integration
212
- - **Advanced Type System** - `T::Enum` integration, union types for agentic workflows, complex type coercion
213
- - ✅ **Production-Ready Evaluation** - Multi-factor metrics beyond accuracy, error-resilient evaluation pipelines
214
- - ✅ **Documentation Ecosystem** - `llms.txt` for AI assistants, ADRs, blog articles, comprehensive examples
215
- - ✅ **API Maturation** - Simplified idiomatic patterns, better error handling, production-proven designs
254
+ ### Production Features
255
+ - **[Observability](docs/src/production/observability.md)** - Zero-config Langfuse integration with a dedicated export worker that never blocks your LLMs
256
+ - **[Storage System](docs/src/production/storage.md)** - Persistence and optimization result storage
257
+ - **[Custom Metrics](docs/src/advanced/custom-metrics.md)** - Proc-based evaluation logic
216
258
 
217
- ## Roadmap - Production Battle-Testing Toward v1.0
218
259
 
219
- DSPy.rb has transitioned from **feature building** to **production validation**. The core framework is
220
- feature-complete and stable - now I'm focusing on real-world usage patterns, performance optimization,
221
- and ecosystem integration.
222
260
 
223
- **Current Focus Areas:**
224
261
 
225
- ### Production Readiness
226
- - 🚧 **Production Patterns** - Real-world usage validation and performance optimization
227
- - 🚧 **Ruby Ecosystem Integration** - Rails integration, Sidekiq compatibility, deployment patterns
228
- - 🚧 **Scale Testing** - High-volume usage, memory management, connection pooling
229
- - 🚧 **Error Recovery** - Robust failure handling patterns for production environments
230
262
 
231
- ### Ecosystem Expansion
232
- - 🚧 **Model Context Protocol (MCP)** - Integration with MCP ecosystem
233
- - 🚧 **Additional Provider Support** - Azure OpenAI, local models beyond Ollama
234
- - 🚧 **Tool Ecosystem** - Expanded tool integrations for ReAct agents
235
263
 
236
- ### Community & Adoption
237
- - 🚧 **Community Examples** - Real-world applications and case studies
238
- - 🚧 **Contributor Experience** - Making it easier to contribute and extend
239
- - 🚧 **Performance Benchmarks** - Comparative analysis vs other frameworks
240
264
 
241
- **v1.0 Philosophy:**
242
- v1.0 will be released after extensive production battle-testing, not after checking off features.
243
- The API is already stable - v1.0 represents confidence in production reliability backed by real-world validation.
244
265
 
245
266
  ## License
246
-
247
267
  This project is licensed under the MIT License.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
3
4
  require 'sorbet-runtime'
4
5
 
5
6
  module DSPy
@@ -42,48 +43,60 @@ module DSPy
42
43
  end
43
44
 
44
45
  module ClassMethods
46
+ # Configures a method to expose before callbacks.
47
+ #
48
+ # @param method_name [Symbol] the target method.
49
+ # @param wrap [Boolean] When true (default), the method is transparently wrapped so callbacks
50
+ # run automatically on every invocation. Pass false when you plan to trigger callbacks
51
+ # manually (e.g., to interleave custom spans or thread management). Manual targets are
52
+ # never re-wrapped, so execution order stays entirely in your control.
45
53
  # Creates a before callback hook for the specified method
46
54
  #
47
55
  # @param method_name [Symbol] the method to add callback support to
48
- def create_before_callback(method_name)
49
- mark_method_has_callbacks(method_name)
56
+ def create_before_callback(method_name, wrap: true)
57
+ mark_method_has_callbacks(method_name, wrap: wrap)
50
58
  ensure_callback_method_defined(:before, method_name)
51
- wrap_method_with_callbacks(method_name)
59
+ wrap_method_with_callbacks(method_name) if wrap
52
60
  end
53
61
 
54
62
  # Creates an after callback hook for the specified method
55
63
  #
56
64
  # @param method_name [Symbol] the method to add callback support to
57
- def create_after_callback(method_name)
58
- mark_method_has_callbacks(method_name)
65
+ def create_after_callback(method_name, wrap: true)
66
+ mark_method_has_callbacks(method_name, wrap: wrap)
59
67
  ensure_callback_method_defined(:after, method_name)
60
- wrap_method_with_callbacks(method_name)
68
+ wrap_method_with_callbacks(method_name) if wrap
61
69
  end
62
70
 
63
71
  # Creates an around callback hook for the specified method
64
72
  #
65
73
  # @param method_name [Symbol] the method to add callback support to
66
- def create_around_callback(method_name)
67
- mark_method_has_callbacks(method_name)
74
+ def create_around_callback(method_name, wrap: true)
75
+ mark_method_has_callbacks(method_name, wrap: wrap)
68
76
  ensure_callback_method_defined(:around, method_name)
69
- wrap_method_with_callbacks(method_name)
77
+ wrap_method_with_callbacks(method_name) if wrap
70
78
  end
71
79
 
72
80
  private
73
81
 
74
82
  # Ensures the callback registration method exists
75
83
  def ensure_callback_method_defined(type, target_method_name)
84
+ set_default_callback_target(type, target_method_name)
85
+
76
86
  return if singleton_class.method_defined?(type)
77
87
 
78
- define_singleton_method(type) do |callback_method|
79
- register_callback(type, target_method_name, callback_method)
88
+ define_singleton_method(type) do |callback_method = nil, target: default_callback_target(type), &block|
89
+ callback = callback_method || block
90
+ raise ArgumentError, "No callback provided for #{type}" unless callback
91
+
92
+ register_callback(type, target || default_callback_target(type), callback)
80
93
  end
81
94
  end
82
95
 
83
96
  # Registers a callback for execution
84
- def register_callback(type, method_name, callback_method)
97
+ def register_callback(type, method_name, callback)
85
98
  own_callbacks_for(method_name)[type] ||= []
86
- own_callbacks_for(method_name)[type] << callback_method
99
+ own_callbacks_for(method_name)[type] << callback
87
100
  end
88
101
 
89
102
  # Returns own callbacks (not including parent)
@@ -93,8 +106,9 @@ module DSPy
93
106
  end
94
107
 
95
108
  # Marks that a method has callback support (even if no callbacks registered yet)
96
- def mark_method_has_callbacks(method_name)
109
+ def mark_method_has_callbacks(method_name, wrap: true)
97
110
  own_callbacks_for(method_name)
111
+ manual_callback_targets << method_name unless wrap
98
112
  end
99
113
 
100
114
  # Returns the callback registry for a method
@@ -164,6 +178,7 @@ module DSPy
164
178
 
165
179
  return unless has_callback_support
166
180
  return if method_wrapped?(method_name)
181
+ return if manual_callback_targets.include?(method_name)
167
182
 
168
183
  wrap_method_with_callbacks(method_name)
169
184
  end
@@ -191,17 +206,53 @@ module DSPy
191
206
  def method_wrapped?(method_name)
192
207
  @wrapped_methods&.include?(method_name)
193
208
  end
209
+
210
+ def manual_callback_targets
211
+ @manual_callback_targets ||= Set.new
212
+ end
213
+
214
+ def callback_defaults
215
+ @callback_defaults ||= {}
216
+ end
217
+
218
+ def set_default_callback_target(type, method_name)
219
+ callback_defaults[type] ||= method_name
220
+ end
221
+
222
+ def default_callback_target(type)
223
+ callback_defaults[type] || begin
224
+ if superclass.respond_to?(:default_callback_target, true)
225
+ superclass.send(:default_callback_target, type)
226
+ end
227
+ end
228
+ end
194
229
  end
195
230
 
196
231
  private
197
232
 
198
233
  # Executes callbacks of a specific type
199
- def run_callbacks(type, method_name)
234
+ def run_callbacks(type, method_name, payload = nil)
200
235
  callbacks = self.class.send(:callbacks_for, method_name)[type]
201
236
  return unless callbacks
202
237
 
203
- callbacks.each do |callback_method|
204
- send(callback_method)
238
+ callbacks.each do |callback|
239
+ case callback
240
+ when Symbol
241
+ method_obj = method(callback)
242
+ if method_obj.arity.zero?
243
+ send(callback)
244
+ else
245
+ send(callback, payload)
246
+ end
247
+ when Proc
248
+ if callback.arity.zero?
249
+ instance_exec(&callback)
250
+ else
251
+ instance_exec(payload, &callback)
252
+ end
253
+ else
254
+ callback.call(payload)
255
+ end
205
256
  end
206
257
  end
207
258
 
@@ -212,8 +263,12 @@ module DSPy
212
263
  # Build callback chain from innermost (original method) to outermost
213
264
  chain = callbacks.reverse.inject(
214
265
  -> { original_method.bind(self).call(*args, **kwargs, &block) }
215
- ) do |inner, callback_method|
216
- -> { send(callback_method) { inner.call } }
266
+ ) do |inner, callback|
267
+ if callback.is_a?(Proc)
268
+ -> { instance_exec(-> { inner.call }, &callback) }
269
+ else
270
+ -> { send(callback) { inner.call } }
271
+ end
217
272
  end
218
273
 
219
274
  chain.call
data/lib/dspy/context.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'securerandom'
4
+ require 'json'
4
5
 
5
6
  module DSPy
6
7
  class Context
@@ -47,14 +48,15 @@ module DSPy
47
48
  span_id = SecureRandom.uuid
48
49
  parent_span_id = current[:span_stack].last
49
50
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
50
-
51
+ sanitized_attributes = sanitize_span_attributes(attributes)
52
+
51
53
  # Prepare attributes with context information
52
54
  span_attributes = {
53
55
  trace_id: current[:trace_id],
54
56
  span_id: span_id,
55
57
  parent_span_id: parent_span_id,
56
58
  operation: operation,
57
- **attributes
59
+ **sanitized_attributes
58
60
  }
59
61
 
60
62
  # Log span start with proper hierarchy (internal logging only)
@@ -67,7 +69,7 @@ module DSPy
67
69
  # Use OpenTelemetry's proper context management for nesting
68
70
  if DSPy::Observability.enabled? && DSPy::Observability.tracer
69
71
  # Prepare attributes and add trace name for root spans
70
- span_attributes = attributes.transform_keys(&:to_s).reject { |k, v| v.nil? }
72
+ span_attributes = sanitized_attributes.transform_keys(&:to_s).reject { |k, v| v.nil? }
71
73
 
72
74
  # Set trace name if this is likely a root span (no parent in our stack)
73
75
  if current[:span_stack].length == 1 # This will be the first span
@@ -173,6 +175,49 @@ module DSPy
173
175
  rescue
174
176
  false
175
177
  end
178
+
179
+ def sanitize_span_attributes(attributes)
180
+ attributes.each_with_object({}) do |(key, value), acc|
181
+ sanitized_value = sanitize_attribute_value(value)
182
+ acc[key] = sanitized_value
183
+ end
184
+ end
185
+
186
+ def sanitize_attribute_value(value)
187
+ case value
188
+ when nil, String, Integer, Float, TrueClass, FalseClass
189
+ value
190
+ when Time
191
+ value.iso8601(3)
192
+ when Array
193
+ begin
194
+ JSON.generate(value.map { |item| sanitize_attribute_value(item) })
195
+ rescue StandardError
196
+ value.map(&:to_s).to_s
197
+ end
198
+ when Hash
199
+ begin
200
+ sanitized_hash = value.each_with_object({}) do |(k, v), hash|
201
+ sanitized = sanitize_attribute_value(v)
202
+ hash[k.to_s] = sanitized unless sanitized.nil?
203
+ end
204
+ JSON.generate(sanitized_hash)
205
+ rescue StandardError
206
+ value.to_s
207
+ end
208
+ else
209
+ if defined?(T::Struct) && value.is_a?(T::Struct)
210
+ begin
211
+ struct_hash = value.to_h.transform_keys(&:to_s).transform_values { |v| sanitize_attribute_value(v) }
212
+ JSON.generate(struct_hash)
213
+ rescue StandardError
214
+ value.to_s
215
+ end
216
+ else
217
+ value.respond_to?(:to_json) ? value.to_json : value.to_s
218
+ end
219
+ end
220
+ end
176
221
  end
177
222
  end
178
- end
223
+ end
data/lib/dspy/errors.rb CHANGED
@@ -28,4 +28,22 @@ module DSPy
28
28
  MESSAGE
29
29
  end
30
30
  end
31
- end
31
+
32
+ class InstructionUpdateError < Error
33
+ def self.missing_instruction_capability(module_class)
34
+ new(<<~MESSAGE)
35
+ #{module_class} must implement `with_instruction(new_instruction)` to support DSPy teleprompters.
36
+
37
+ Update the module to return a new instance with the provided instruction, or opt out of teleprompter optimizers.
38
+ MESSAGE
39
+ end
40
+
41
+ def self.missing_examples_capability(module_class)
42
+ new(<<~MESSAGE)
43
+ #{module_class} must implement `with_examples(few_shot_examples)` to support DSPy teleprompters.
44
+
45
+ Update the module to return a new instance with the provided examples, or opt out of example optimization flows.
46
+ MESSAGE
47
+ end
48
+ end
49
+ end
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'datasets/ade'
4
-
5
3
  module DSPy
6
- module Datasets
4
+ class Evals
5
+ VERSION = '1.0.0'
7
6
  end
8
7
  end