dspy 0.29.0 → 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.
- checksums.yaml +4 -4
- data/LICENSE +45 -0
- data/README.md +121 -101
- data/lib/dspy/callbacks.rb +74 -19
- data/lib/dspy/context.rb +49 -4
- data/lib/dspy/errors.rb +19 -1
- data/lib/dspy/{datasets.rb → evals/version.rb} +2 -3
- data/lib/dspy/{evaluate.rb → evals.rb} +373 -110
- data/lib/dspy/mixins/instruction_updatable.rb +22 -0
- data/lib/dspy/observability.rb +40 -182
- data/lib/dspy/predict.rb +10 -2
- data/lib/dspy/propose/dataset_summary_generator.rb +28 -18
- data/lib/dspy/re_act.rb +21 -0
- data/lib/dspy/schema/sorbet_json_schema.rb +302 -0
- data/lib/dspy/schema/version.rb +7 -0
- data/lib/dspy/schema.rb +4 -0
- data/lib/dspy/structured_outputs_prompt.rb +48 -0
- data/lib/dspy/support/warning_filters.rb +27 -0
- data/lib/dspy/teleprompt/gepa.rb +9 -588
- data/lib/dspy/teleprompt/instruction_updates.rb +94 -0
- data/lib/dspy/teleprompt/teleprompter.rb +6 -6
- data/lib/dspy/teleprompt/utils.rb +5 -65
- data/lib/dspy/type_system/sorbet_json_schema.rb +2 -299
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +33 -7
- metadata +14 -60
- data/lib/dspy/code_act.rb +0 -477
- data/lib/dspy/datasets/ade.rb +0 -90
- data/lib/dspy/observability/async_span_processor.rb +0 -250
- data/lib/dspy/observability/observation_type.rb +0 -65
- data/lib/dspy/optimizers/gaussian_process.rb +0 -141
- data/lib/dspy/teleprompt/mipro_v2.rb +0 -1423
- data/lib/gepa/api.rb +0 -61
- data/lib/gepa/core/engine.rb +0 -226
- data/lib/gepa/core/evaluation_batch.rb +0 -26
- data/lib/gepa/core/result.rb +0 -92
- data/lib/gepa/core/state.rb +0 -231
- data/lib/gepa/logging/experiment_tracker.rb +0 -54
- data/lib/gepa/logging/logger.rb +0 -57
- data/lib/gepa/logging.rb +0 -9
- data/lib/gepa/proposer/base.rb +0 -27
- data/lib/gepa/proposer/merge_proposer.rb +0 -424
- data/lib/gepa/proposer/reflective_mutation/base.rb +0 -48
- data/lib/gepa/proposer/reflective_mutation/reflective_mutation.rb +0 -188
- data/lib/gepa/strategies/batch_sampler.rb +0 -91
- data/lib/gepa/strategies/candidate_selector.rb +0 -97
- data/lib/gepa/strategies/component_selector.rb +0 -57
- data/lib/gepa/strategies/instruction_proposal.rb +0 -120
- data/lib/gepa/telemetry.rb +0 -122
- data/lib/gepa/utils/pareto.rb +0 -119
- data/lib/gepa.rb +0 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e59fde98eb918cca54822bc6af704100c51b71081d8ac77e17e8df3cddcb016
|
|
4
|
+
data.tar.gz: f8ff81ef1873fc1bffac8a209ddb4172586295e6ef1f46d6bb598045148fc3eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
[](https://github.com/vicentereig/dspy.rb/actions/workflows/ruby.yml)
|
|
6
6
|
[](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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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,19 +148,29 @@ 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:**
|
|
111
170
|
- **Prompt Objects** - Manipulate prompts as first-class objects instead of strings
|
|
112
171
|
- **Typed Examples** - Type-safe training data with automatic validation
|
|
113
172
|
- **Evaluation Framework** - Advanced metrics beyond simple accuracy with error-resilient pipelines
|
|
114
|
-
- **MIPROv2 Optimization** - Advanced Bayesian optimization with Gaussian Processes, multiple optimization strategies, and storage persistence
|
|
173
|
+
- **MIPROv2 Optimization** - Advanced Bayesian optimization with Gaussian Processes, multiple optimization strategies, auto-config presets, and storage persistence
|
|
115
174
|
|
|
116
175
|
**Production Features:**
|
|
117
176
|
- **Reliable JSON Extraction** - Native structured outputs for OpenAI and Gemini, Anthropic tool-based extraction, and automatic strategy selection with fallback
|
|
@@ -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
|
-
|
|
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
|
-
|
|
186
|
+
DSPy.rb has rapidly evolved from experimental to production-ready:
|
|
137
187
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
173
|
-
- **[
|
|
174
|
-
- **[
|
|
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
|
-
###
|
|
210
|
-
-
|
|
211
|
-
-
|
|
212
|
-
-
|
|
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.
|
data/lib/dspy/callbacks.rb
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
97
|
+
def register_callback(type, method_name, callback)
|
|
85
98
|
own_callbacks_for(method_name)[type] ||= []
|
|
86
|
-
own_callbacks_for(method_name)[type] <<
|
|
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 |
|
|
204
|
-
|
|
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,
|
|
216
|
-
|
|
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
|
-
**
|
|
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 =
|
|
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
|
-
|
|
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
|