dspy 0.3.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64e8b7011ea06273772d2ef8a985d61aa1ee30d5d6fb3c559dc22ed81e345b16
4
- data.tar.gz: a16fab394ee1db1bcaddc0baaa3636590a2ca00c1ce60eb7dc1a00355750009f
3
+ metadata.gz: fd8c98014ede76d7f232ebbb1e46789efa57fdca83203548d0e9b564054d859d
4
+ data.tar.gz: f654cf96ac2976fbdd101e6d9d3b5b69346a5a32857cfcff2683e659173c9483
5
5
  SHA512:
6
- metadata.gz: ce4ab780cce89c2c3680e6c5703e853bbd901ac206993af1cd0660cc25e9796ef1dd5adb488d575bbc97ea75a71b563e82eb4cc521b5c84291ff9b1e106216e1
7
- data.tar.gz: be313a08f282eb7a08638879298742baf5ec26bcb48b946ac068892c2ad542003d99df575ecc85bb5220c637368f02adf042164831cf5d48fd7139bb3f5424a7
6
+ metadata.gz: 425bf005503285726f84aff2009e53cfcad853afc5b11cce7b600a978e5546ddf8cddd8d9e50d9e54701c26834ff47511d34719bf1a2f78c60f8e8b17a5d92e2
7
+ data.tar.gz: 6c2f6eeb7686a9e116642f8097154aee5610cfe75542973e973b4ba851f1c7428e71f6241a9502b7489fad40a59f35921fc377aa3af008fd506a17d18043320a
data/README.md CHANGED
@@ -2,14 +2,9 @@
2
2
 
3
3
  **Build reliable LLM applications in Ruby using composable, type-safe modules.**
4
4
 
5
- DSPy.rb brings structured LLM programming to Ruby developers.
6
- Instead of wrestling with prompt strings and parsing responses,
7
- you define typed signatures and compose them into pipelines that just work.
5
+ DSPy.rb brings structured LLM programming to Ruby developers. Instead of wrestling with prompt strings and parsing responses, you define typed signatures and compose them into pipelines that just work.
8
6
 
9
- Traditional prompting is like writing code with string concatenation: it works until
10
- it doesn't. DSPy.rb brings you the programming approach pioneered
11
- by [dspy.ai](https://dspy.ai/): instead of crafting fragile prompts, you define
12
- modular signatures and let the framework handle the messy details.
7
+ Traditional prompting is like writing code with string concatenation: it works until it doesn't. DSPy.rb brings you the programming approach pioneered by [dspy.ai](https://dspy.ai/): instead of crafting fragile prompts, you define modular signatures and let the framework handle the messy details.
13
8
 
14
9
  The result? LLM applications that actually scale and don't break when you sneeze.
15
10
 
@@ -19,36 +14,43 @@ The result? LLM applications that actually scale and don't break when you sneeze
19
14
  - **Signatures** - Define input/output schemas using Sorbet types
20
15
  - **Predict** - Basic LLM completion with structured data
21
16
  - **Chain of Thought** - Step-by-step reasoning for complex problems
22
- - **ReAct** - Tool-using agents that can actually get things done
23
- - **RAG** - Context-enriched responses from your data
24
- - **Multi-stage Pipelines** - Compose multiple LLM calls into workflows
25
- - OpenAI and Anthropic support via [Ruby LLM](https://github.com/crmne/ruby_llm)
17
+ - **ReAct** - Tool-using agents with basic tool integration
18
+ - **Manual Composition** - Combine multiple LLM calls into workflows
19
+
20
+ **Optimization & Evaluation:**
21
+ - **Prompt Objects** - Manipulate prompts as first-class objects instead of strings
22
+ - **Typed Examples** - Type-safe training data with automatic validation
23
+ - **Evaluation Framework** - Basic testing with simple metrics
24
+ - **Basic Optimization** - Simple prompt optimization techniques
25
+
26
+ **Production Features:**
27
+ - **File-based Storage** - Basic optimization result persistence
28
+ - **Multi-Platform Observability** - OpenTelemetry, New Relic, and Langfuse integration
29
+ - **Basic Instrumentation** - Event tracking and logging
30
+
31
+ **Developer Experience:**
32
+ - LLM provider support using official Ruby clients:
33
+ - [OpenAI Ruby](https://github.com/openai/openai-ruby)
34
+ - [Anthropic Ruby SDK](https://github.com/anthropics/anthropic-sdk-ruby)
26
35
  - Runtime type checking with [Sorbet](https://sorbet.org/)
27
36
  - Type-safe tool definitions for ReAct agents
37
+ - Comprehensive instrumentation and observability
28
38
 
29
39
  ## Fair Warning
30
40
 
31
- This is fresh off the oven and evolving fast.
32
- I'm actively building this as a Ruby port of the [DSPy library](https://dspy.ai/).
33
- If you hit bugs or want to contribute, just email me directly!
41
+ This is fresh off the oven and evolving fast. I'm actively building this as a Ruby port of the [DSPy library](https://dspy.ai/). If you hit bugs or want to contribute, just email me directly!
34
42
 
35
- ## What's Next
36
- These are my goals to release v1.0.
37
-
38
- - Solidify prompt optimization
39
- - OTel Integration
40
- - Ollama support
43
+ ## Quick Start
41
44
 
42
- ## Installation
45
+ ### Installation
43
46
 
44
47
  Skip the gem for now - install straight from this repo while I prep the first release:
48
+
45
49
  ```ruby
46
50
  gem 'dspy', github: 'vicentereig/dspy.rb'
47
51
  ```
48
52
 
49
- ## Usage Examples
50
-
51
- ### Simple Prediction
53
+ ### Your First DSPy Program
52
54
 
53
55
  ```ruby
54
56
  # Define a signature for sentiment classification
@@ -80,380 +82,60 @@ end
80
82
 
81
83
  # Create the predictor and run inference
82
84
  classify = DSPy::Predict.new(Classify)
83
- result = classify.call(sentence: "This book was super fun to read, though not the last chapter.")
85
+ result = classify.call(sentence: "This book was super fun to read!")
84
86
 
85
- # result is a properly typed T::Struct instance
86
87
  puts result.sentiment # => #<Sentiment::Positive>
87
88
  puts result.confidence # => 0.85
88
89
  ```
89
90
 
90
- ### Chain of Thought Reasoning
91
-
92
- ```ruby
93
- class AnswerPredictor < DSPy::Signature
94
- description "Provides a concise answer to the question"
95
-
96
- input do
97
- const :question, String
98
- end
99
-
100
- output do
101
- const :answer, String
102
- end
103
- end
104
-
105
- # Chain of thought automatically adds a 'reasoning' field to the output
106
- qa_cot = DSPy::ChainOfThought.new(AnswerPredictor)
107
- result = qa_cot.call(question: "Two dice are tossed. What is the probability that the sum equals two?")
108
-
109
- puts result.reasoning # => "There is only one way to get a sum of 2..."
110
- puts result.answer # => "1/36"
111
- ```
112
-
113
- ### ReAct Agents with Tools
114
-
115
- ```ruby
116
-
117
- class DeepQA < DSPy::Signature
118
- description "Answer questions with consideration for the context"
119
-
120
- input do
121
- const :question, String
122
- end
123
-
124
- output do
125
- const :answer, String
126
- end
127
- end
91
+ ## Documentation
128
92
 
129
- # Define tools for the agent
130
- class CalculatorTool < DSPy::Tools::Base
131
-
132
- tool_name 'calculator'
133
- tool_description 'Performs basic arithmetic operations'
134
-
135
- sig { params(operation: String, num1: Float, num2: Float).returns(T.any(Float, String)) }
136
- def call(operation:, num1:, num2:)
137
- case operation.downcase
138
- when 'add' then num1 + num2
139
- when 'subtract' then num1 - num2
140
- when 'multiply' then num1 * num2
141
- when 'divide'
142
- return "Error: Cannot divide by zero" if num2 == 0
143
- num1 / num2
144
- else
145
- "Error: Unknown operation '#{operation}'. Use add, subtract, multiply, or divide"
146
- end
147
- end
93
+ ### Getting Started
94
+ - **[Installation & Setup](docs/getting-started/installation.md)** - Detailed installation and configuration
95
+ - **[Quick Start Guide](docs/getting-started/quick-start.md)** - Your first DSPy programs
96
+ - **[Core Concepts](docs/getting-started/core-concepts.md)** - Understanding signatures, predictors, and modules
148
97
 
149
- # Create ReAct agent with tools
150
- agent = DSPy::ReAct.new(DeepQA, tools: [CalculatorTool.new])
98
+ ### Core Features
99
+ - **[Signatures & Types](docs/core-concepts/signatures.md)** - Define typed interfaces for LLM operations
100
+ - **[Predictors](docs/core-concepts/predictors.md)** - Predict, ChainOfThought, ReAct, and more
101
+ - **[Modules & Pipelines](docs/core-concepts/modules.md)** - Compose complex multi-stage workflows
102
+ - **[Examples & Validation](docs/core-concepts/examples.md)** - Type-safe training data
151
103
 
152
- # Run the agent
153
- result = agent.forward(question: "What is 42 plus 58?")
154
- puts result.answer # => "100"
155
- puts result.history # => Array of reasoning steps and tool calls
156
- ```
104
+ ### Optimization
105
+ - **[Evaluation Framework](docs/optimization/evaluation.md)** - Basic testing with simple metrics
106
+ - **[Prompt Optimization](docs/optimization/prompt-optimization.md)** - Manipulate prompts as objects
107
+ - **[MIPROv2 Optimizer](docs/optimization/miprov2.md)** - Basic automatic optimization
108
+ - **[Simple Optimizer](docs/optimization/simple-optimizer.md)** - Random search experimentation
157
109
 
158
- ### Multi-stage Pipelines
159
- Outline the sections of an article and draft them out.
160
-
161
- ```ruby
110
+ ### Production Features
111
+ - **[Storage System](docs/production/storage.md)** - Basic file-based persistence
112
+ - **[Observability](docs/production/observability.md)** - Multi-platform monitoring and metrics
162
113
 
163
- # write an article!
164
- drafter = ArticleDrafter.new
165
- article = drafter.forward(topic: "The impact of AI on software development") # { title: '....', sections: [{content: '....'}]}
166
-
167
- class Outline < DSPy::Signature
168
- description "Outline a thorough overview of a topic."
169
-
170
- input do
171
- const :topic, String
172
- end
173
-
174
- output do
175
- const :title, String
176
- const :sections, T::Array[String]
177
- end
178
- end
179
-
180
- class DraftSection < DSPy::Signature
181
- description "Draft a section of an article"
182
-
183
- input do
184
- const :topic, String
185
- const :title, String
186
- const :section, String
187
- end
188
-
189
- output do
190
- const :content, String
191
- end
192
- end
193
-
194
- class ArticleDrafter < DSPy::Module
195
- def initialize
196
- @build_outline = DSPy::ChainOfThought.new(Outline)
197
- @draft_section = DSPy::ChainOfThought.new(DraftSection)
198
- end
199
-
200
- def forward(topic:)
201
- outline = @build_outline.call(topic: topic)
202
-
203
- sections = outline.sections.map do |section|
204
- @draft_section.call(
205
- topic: topic,
206
- title: outline.title,
207
- section: section
208
- )
209
- end
210
-
211
- {
212
- title: outline.title,
213
- sections: sections.map(&:content)
214
- }
215
- end
216
- end
217
-
218
- ```
219
-
220
- ## Working with Complex Types
221
-
222
- ### Enums
223
-
224
- ```ruby
225
- class Color < T::Enum
226
- enums do
227
- Red = new
228
- Green = new
229
- Blue = new
230
- end
231
- end
232
-
233
- class ColorSignature < DSPy::Signature
234
- description "Identify the dominant color in a description"
235
-
236
- input do
237
- const :description, String,
238
- description: 'Description of an object or scene'
239
- end
240
-
241
- output do
242
- const :color, Color,
243
- description: 'The dominant color (Red, Green, or Blue)'
244
- end
245
- end
114
+ ### Advanced Usage
115
+ - **[Complex Types](docs/advanced/complex-types.md)** - Basic Sorbet type integration
116
+ - **[Manual Pipelines](docs/advanced/pipelines.md)** - Manual module composition patterns
117
+ - **[RAG Patterns](docs/advanced/rag.md)** - Manual RAG implementation with external services
118
+ - **[Custom Metrics](docs/advanced/custom-metrics.md)** - Proc-based evaluation logic
246
119
 
247
- predictor = DSPy::Predict.new(ColorSignature)
248
- result = predictor.call(description: "A red apple on a wooden table")
249
- puts result.color # => #<Color::Red>
250
- ```
251
-
252
- ### Optional Fields and Defaults
253
-
254
- ```ruby
255
- class AnalysisSignature < DSPy::Signature
256
- description "Analyze text with optional metadata"
257
-
258
- input do
259
- const :text, String,
260
- description: 'Text to analyze'
261
- const :include_metadata, T::Boolean,
262
- description: 'Whether to include metadata in analysis',
263
- default: false
264
- end
265
-
266
- output do
267
- const :summary, String,
268
- description: 'Summary of the text'
269
- const :word_count, Integer,
270
- description: 'Number of words (optional)',
271
- default: 0
272
- end
273
- end
274
- ```
275
-
276
- ## Advanced Usage Patterns
277
-
278
- ### Multi-stage Pipelines
279
-
280
- ```ruby
281
- class TopicSignature < DSPy::Signature
282
- description "Extract main topic from text"
283
-
284
- input do
285
- const :content, String,
286
- description: 'Text content to analyze'
287
- end
288
-
289
- output do
290
- const :topic, String,
291
- description: 'Main topic of the content'
292
- end
293
- end
294
-
295
- class SummarySignature < DSPy::Signature
296
- description "Create summary focusing on specific topic"
297
-
298
- input do
299
- const :content, String,
300
- description: 'Original text content'
301
- const :topic, String,
302
- description: 'Topic to focus on'
303
- end
304
-
305
- output do
306
- const :summary, String,
307
- description: 'Topic-focused summary'
308
- end
309
- end
310
-
311
- class ArticlePipeline < DSPy::Signature
312
- extend T::Sig
313
-
314
- def initialize
315
- @topic_extractor = DSPy::Predict.new(TopicSignature)
316
- @summarizer = DSPy::ChainOfThought.new(SummarySignature)
317
- end
318
-
319
- sig { params(content: String).returns(T.untyped) }
320
- def forward(content:)
321
- # Extract topic
322
- topic_result = @topic_extractor.call(content: content)
323
-
324
- # Create focused summary
325
- summary_result = @summarizer.call(
326
- content: content,
327
- topic: topic_result.topic
328
- )
329
-
330
- {
331
- topic: topic_result.topic,
332
- summary: summary_result.summary,
333
- reasoning: summary_result.reasoning
334
- }
335
- end
336
- end
337
-
338
- # Usage
339
- pipeline = ArticlePipeline.new
340
- result = pipeline.call(content: "Long article content...")
341
- ```
342
-
343
- ### Retrieval Augmented Generation
344
-
345
- ```ruby
346
- class ContextualQA < DSPy::Signature
347
- description "Answer questions using relevant context"
348
-
349
- input do
350
- const :question, String,
351
- description: 'The question to answer'
352
- const :context, T::Array[String],
353
- description: 'Relevant context passages'
354
- end
355
-
356
- output do
357
- const :answer, String,
358
- description: 'Answer based on the provided context'
359
- const :confidence, Float,
360
- description: 'Confidence in the answer (0.0 to 1.0)'
361
- end
362
- end
363
-
364
- # Usage with retriever
365
- retriever = YourRetrieverClass.new
366
- qa = DSPy::ChainOfThought.new(ContextualQA)
367
-
368
- question = "What is the capital of France?"
369
- context = retriever.retrieve(question) # Returns array of strings
370
-
371
- result = qa.call(question: question, context: context)
372
- puts result.reasoning # Step-by-step reasoning
373
- puts result.answer # "Paris"
374
- puts result.confidence # 0.95
375
- ```
376
-
377
- ## Instrumentation & Observability
378
-
379
- DSPy.rb includes built-in instrumentation that captures detailed events and
380
- performance metrics from your LLM operations. Perfect for monitoring your
381
- applications and integrating with observability tools.
382
-
383
- ### Available Events
384
-
385
- Subscribe to these events to monitor different aspects of your LLM operations:
386
-
387
- | Event Name | Triggered When | Key Payload Fields |
388
- |------------|----------------|-------------------|
389
- | `dspy.lm.request` | LLM API request lifecycle | `gen_ai_system`, `model`, `provider`, `duration_ms`, `status` |
390
- | `dspy.lm.tokens` | Token usage tracking | `tokens_input`, `tokens_output`, `tokens_total` |
391
- | `dspy.predict` | Prediction operations | `signature_class`, `input_size`, `duration_ms`, `status` |
392
- | `dspy.chain_of_thought` | CoT reasoning | `signature_class`, `model`, `duration_ms`, `status` |
393
- | `dspy.react` | Agent operations | `max_iterations`, `tools_used`, `duration_ms`, `status` |
394
- | `dspy.react.tool_call` | Tool execution | `tool_name`, `tool_input`, `tool_output`, `duration_ms` |
395
-
396
- ### Event Payloads
397
-
398
- The instrumentation emits events with structured payloads you can process:
399
-
400
- ```ruby
401
- # Example event payload for dspy.predict
402
- {
403
- signature_class: "QuestionAnswering",
404
- model: "gpt-4o-mini",
405
- provider: "openai",
406
- input_size: 45,
407
- duration_ms: 1234.56,
408
- cpu_time_ms: 89.12,
409
- status: "success",
410
- timestamp: "2024-01-15T10:30:00Z"
411
- }
412
-
413
- # Example token usage payload
414
- {
415
- tokens_input: 150,
416
- tokens_output: 45,
417
- tokens_total: 195,
418
- gen_ai_system: "openai",
419
- signature_class: "QuestionAnswering"
420
- }
421
- ```
422
-
423
- Events are emitted via dry-monitor notifications, giving you flexibility to
424
- process them however you need - logging, metrics, alerts, or custom monitoring.
425
-
426
- ### Token Tracking
427
-
428
- Token usage is extracted from actual API responses (OpenAI and Anthropic only),
429
- giving you precise cost tracking:
430
-
431
- ```ruby
432
- # Token events include:
433
- {
434
- tokens_input: 150, # From API response
435
- tokens_output: 45, # From API response
436
- tokens_total: 195, # From API response
437
- gen_ai_system: "openai",
438
- gen_ai_request_model: "gpt-4o-mini"
439
- }
440
- ```
441
-
442
- ### Integration with Monitoring Tools
443
-
444
- Subscribe to events for custom processing:
120
+ ## What's Next
445
121
 
446
- ```ruby
447
- # Subscribe to all LM events
448
- DSPy::Instrumentation.subscribe('dspy.lm.*') do |event|
449
- puts "#{event.id}: #{event.payload[:duration_ms]}ms"
450
- end
122
+ These are my goals to release v1.0.
451
123
 
452
- # Subscribe to specific events
453
- DSPy::Instrumentation.subscribe('dspy.predict') do |event|
454
- MyMetrics.histogram('dspy.predict.duration', event.payload[:duration_ms])
455
- end
456
- ```
124
+ - Prompt objects foundation - *Done*
125
+ - Evaluation framework - *Done*
126
+ - ✅ Teleprompter base classes - *Done*
127
+ - ✅ MIPROv2 optimization algorithm - *Done*
128
+ - ✅ Storage & persistence system - *Done*
129
+ - ✅ Registry & version management - *Done*
130
+ - ✅ OpenTelemetry integration - *Done*
131
+ - ✅ New Relic integration - *Done*
132
+ - ✅ Langfuse integration - *Done*
133
+ - 🚧 Ollama support
134
+ - Context Engineering (see recent research: [How Contexts Fail](https://www.dbreunig.com/2025/06/22/how-contexts-fail-and-how-to-fix-them.html), [How to Fix Your Context](https://www.dbreunig.com/2025/06/26/how-to-fix-your-context.html), [Context Engineering](https://simonwillison.net/2025/Jun/27/context-engineering/))
135
+ - Agentic Memory support
136
+ - MCP Support
137
+ - Documentation website
138
+ - Performance benchmarks
457
139
 
458
140
  ## License
459
141