dspy-ruby_llm 0.1.0 → 0.1.2
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 +32 -253
- data/lib/dspy/ruby_llm/README.md +29 -150
- data/lib/dspy/ruby_llm/lm/adapters/ruby_llm_adapter.rb +14 -30
- data/lib/dspy/ruby_llm/version.rb +1 -1
- data/lib/dspy/ruby_llm.rb +0 -3
- metadata +20 -9
- data/lib/dspy/ruby_llm/guardrails.rb +0 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 954de47d2c188958e0f761dc8d4aa62d6c33feacc049f38eb9d3ddd31ba48f18
|
|
4
|
+
data.tar.gz: 2ccfa1e9a88a273627f717d9b5af87a64e04b478e6e2407e32e2b6a5c71ea50c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5816b736e227cf2661923d57485b4e1c75d447234523daa07890a1455464590846957505e43be7f3baefecbfc89ac3a910b83b5b984ac0fae5a4b98784e6ee10
|
|
7
|
+
data.tar.gz: 1b1787a237e8184298635afe70f5b9ba2e99151e3826a715d8805c246996967c0ed4e9cef2a341347d7c5e29b4579ad3e54aa5413452d6fe280bc373c468856a
|
data/README.md
CHANGED
|
@@ -3,60 +3,19 @@
|
|
|
3
3
|
[](https://rubygems.org/gems/dspy)
|
|
4
4
|
[](https://rubygems.org/gems/dspy)
|
|
5
5
|
[](https://github.com/vicentereig/dspy.rb/actions/workflows/ruby.yml)
|
|
6
|
-
[](https://oss.vicente.services/dspy.rb/)
|
|
7
7
|
[](https://discord.gg/zWBhrMqn)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
> The core Prompt Engineering Framework is production-ready with
|
|
11
|
-
> comprehensive documentation. I am focusing now on educational content on systematic Prompt Optimization and Context Engineering.
|
|
12
|
-
> 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).
|
|
13
|
-
>
|
|
14
|
-
> If you want to contribute, feel free to reach out to me to coordinate efforts: hey at vicente.services
|
|
15
|
-
>
|
|
9
|
+
**Build typed agents and model-backed programs in Ruby.**
|
|
16
10
|
|
|
17
|
-
|
|
11
|
+
DSPy.rb brings [DSPy](https://dspy.ai)'s signature, module, agent, and optimizer model to Ruby, with Sorbet types and Ruby-native integrations. A signature declares a task as typed inputs and outputs. A module chooses how to execute it. Ruby composes modules into programs, while `ReAct` adds a bounded loop when the model should choose the next action. DSPy.rb builds the provider request and validates the returned shape.
|
|
18
12
|
|
|
19
|
-
|
|
13
|
+
The `1.x` series is the current stable release line.
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
While we implement the same signatures, predictors, and optimization algorithms as the original library, DSPy.rb leans hard into Ruby conventions with Sorbet-based typing, ReAct loops, and production-ready integrations like non-blocking OpenTelemetry instrumentation.
|
|
24
|
-
|
|
25
|
-
**What you get?** Ruby LLM applications that scale and don't break when you sneeze.
|
|
26
|
-
|
|
27
|
-
Check the [examples](examples/) and take them for a spin!
|
|
28
|
-
|
|
29
|
-
## Your First DSPy Program
|
|
30
|
-
### Installation
|
|
31
|
-
|
|
32
|
-
Add to your Gemfile:
|
|
15
|
+
In a configured application, the task contract and call look like this:
|
|
33
16
|
|
|
34
17
|
```ruby
|
|
35
|
-
gem 'dspy'
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
and
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
bundle install
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Your First Reliable Predictor
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
require 'dspy'
|
|
48
|
-
|
|
49
|
-
# Configure DSPy globally to use your fave LLM (you can override per predictor).
|
|
50
|
-
DSPy.configure do |c|
|
|
51
|
-
c.lm = DSPy::LM.new('openai/gpt-4o-mini',
|
|
52
|
-
api_key: ENV['OPENAI_API_KEY'],
|
|
53
|
-
structured_outputs: true) # Enable OpenAI's native JSON mode
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Define a signature for sentiment classification - instead of writing a full prompt!
|
|
57
18
|
class Classify < DSPy::Signature
|
|
58
|
-
description "Classify sentiment of a given sentence." # sets the goal of the underlying prompt
|
|
59
|
-
|
|
60
19
|
class Sentiment < T::Enum
|
|
61
20
|
enums do
|
|
62
21
|
Positive = new('positive')
|
|
@@ -64,234 +23,54 @@ class Classify < DSPy::Signature
|
|
|
64
23
|
Neutral = new('neutral')
|
|
65
24
|
end
|
|
66
25
|
end
|
|
67
|
-
|
|
68
|
-
# Structured Inputs: makes sure you are sending only valid prompt inputs to your model
|
|
26
|
+
|
|
69
27
|
input do
|
|
70
|
-
const :sentence, String
|
|
28
|
+
const :sentence, String
|
|
71
29
|
end
|
|
72
30
|
|
|
73
|
-
# Structured Outputs: your predictor will validate the output of the model too.
|
|
74
31
|
output do
|
|
75
|
-
const :sentiment, Sentiment
|
|
76
|
-
const :confidence, Float
|
|
32
|
+
const :sentiment, Sentiment
|
|
33
|
+
const :confidence, Float
|
|
77
34
|
end
|
|
78
35
|
end
|
|
79
36
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# it may raise an error if you mess the inputs or your LLM messes the outputs.
|
|
83
|
-
result = classify.call(sentence: "This book was super fun to read!")
|
|
84
|
-
|
|
85
|
-
puts result.sentiment # => #<Sentiment::Positive>
|
|
86
|
-
puts result.confidence # => 0.85
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Save this as `examples/first_predictor.rb` and run it with:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
bundle exec ruby examples/first_predictor.rb
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Sibling Gems
|
|
96
|
-
|
|
97
|
-
DSPy.rb ships multiple gems from this monorepo so you can opt into features with heavier dependency trees (e.g., datasets pull in Polars/Arrow, MIPROv2 requires `numo-*` BLAS bindings) only when you need them. Add these alongside `dspy`:
|
|
98
|
-
|
|
99
|
-
| Gem | Description | Status |
|
|
100
|
-
| --- | --- | --- |
|
|
101
|
-
| `dspy-schema` | Exposes `DSPy::TypeSystem::SorbetJsonSchema` for downstream reuse. (Still required by the core `dspy` gem; extraction lets other projects depend on it directly.) | **Stable** (v1.0.0) |
|
|
102
|
-
| `dspy-openai` | Packages the OpenAI/OpenRouter/Ollama adapters plus the official SDK guardrails. Install whenever you call `openai/*`, `openrouter/*`, or `ollama/*`. [Adapter README](https://github.com/vicentereig/dspy.rb/blob/main/lib/dspy/openai/README.md) | **Stable** (v1.0.0) |
|
|
103
|
-
| `dspy-anthropic` | Claude adapters, streaming, and structured-output helpers behind the official `anthropic` SDK. [Adapter README](https://github.com/vicentereig/dspy.rb/blob/main/lib/dspy/anthropic/README.md) | **Stable** (v1.0.0) |
|
|
104
|
-
| `dspy-gemini` | Gemini adapters with multimodal + tool-call support via `gemini-ai`. [Adapter README](https://github.com/vicentereig/dspy.rb/blob/main/lib/dspy/gemini/README.md) | **Stable** (v1.0.0) |
|
|
105
|
-
| `dspy-ruby_llm` | Unified access to 12+ LLM providers (OpenAI, Anthropic, Gemini, Bedrock, Ollama, DeepSeek, etc.) via [RubyLLM](https://rubyllm.com). [Adapter README](https://github.com/vicentereig/dspy.rb/blob/main/lib/dspy/ruby_llm/README.md) | **Stable** (v0.1.0) |
|
|
106
|
-
| `dspy-code_act` | Think-Code-Observe agents that synthesize and execute Ruby safely. (Add the gem or set `DSPY_WITH_CODE_ACT=1` before requiring `dspy/code_act`.) | **Stable** (v1.0.0) |
|
|
107
|
-
| `dspy-datasets` | Dataset helpers plus Parquet/Polars tooling for richer evaluation corpora. (Toggle via `DSPY_WITH_DATASETS`.) | **Stable** (v1.0.0) |
|
|
108
|
-
| `dspy-evals` | High-throughput evaluation harness with metrics, callbacks, and regression fixtures. (Toggle via `DSPY_WITH_EVALS`.) | **Stable** (v1.0.0) |
|
|
109
|
-
| `dspy-miprov2` | Bayesian optimization + Gaussian Process backend for the MIPROv2 teleprompter. (Install or export `DSPY_WITH_MIPROV2=1` before requiring the teleprompter.) | **Stable** (v1.0.0) |
|
|
110
|
-
| `dspy-gepa` | `DSPy::Teleprompt::GEPA`, reflection loops, experiment tracking, telemetry adapters. (Install or set `DSPY_WITH_GEPA=1`.) | **Stable** (v1.0.0) |
|
|
111
|
-
| `gepa` | GEPA optimizer core (Pareto engine, telemetry, reflective proposer). | **Stable** (v1.0.0) |
|
|
112
|
-
| `dspy-o11y` | Core observability APIs: `DSPy::Observability`, async span processor, observation types. (Install or set `DSPY_WITH_O11Y=1`.) | **Stable** (v1.0.0) |
|
|
113
|
-
| `dspy-o11y-langfuse` | Auto-configures DSPy observability to stream spans to Langfuse via OTLP. (Install or set `DSPY_WITH_O11Y_LANGFUSE=1`.) | **Stable** (v1.0.0) |
|
|
114
|
-
| `dspy-deep_search` | Production DeepSearch loop with Exa-backed search/read, token budgeting, and instrumentation (Issue #163). | **Stable** (v1.0.0) |
|
|
115
|
-
| `dspy-deep_research` | Planner/QA orchestration atop DeepSearch plus the memory supervisor used by the CLI example. | **Stable** (v1.0.0) |
|
|
116
|
-
| `sorbet-toon` | Token-Oriented Object Notation (TOON) codec, prompt formatter, and Sorbet mixins for BAML/TOON Enhanced Prompting. [Sorbet::Toon README](https://github.com/vicentereig/dspy.rb/blob/main/lib/sorbet/toon/README.md) | **Alpha** (v0.1.0) |
|
|
117
|
-
|
|
118
|
-
**Provider adapters:** Add `dspy-openai`, `dspy-anthropic`, and/or `dspy-gemini` next to `dspy` in your Gemfile depending on which `DSPy::LM` providers you call. Each gem already depends on the official SDK (`openai`, `anthropic`, `gemini-ai`), and DSPy auto-loads the adapters when the gem is present—no extra `require` needed.
|
|
119
|
-
|
|
120
|
-
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 `adr/013-dependency-tree.md` for the full dependency map and roadmap.
|
|
121
|
-
### Access to 200+ Models Across 5 Providers
|
|
122
|
-
|
|
123
|
-
DSPy.rb provides unified access to major LLM providers with provider-specific optimizations:
|
|
124
|
-
|
|
125
|
-
```ruby
|
|
126
|
-
# OpenAI (GPT-4, GPT-4o, GPT-4o-mini, GPT-5, etc.)
|
|
127
|
-
DSPy.configure do |c|
|
|
128
|
-
c.lm = DSPy::LM.new('openai/gpt-4o-mini',
|
|
129
|
-
api_key: ENV['OPENAI_API_KEY'],
|
|
130
|
-
structured_outputs: true) # Native JSON mode
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Google Gemini (Gemini 1.5 Pro, Flash, Gemini 2.0, etc.)
|
|
134
|
-
DSPy.configure do |c|
|
|
135
|
-
c.lm = DSPy::LM.new('gemini/gemini-2.5-flash',
|
|
136
|
-
api_key: ENV['GEMINI_API_KEY'],
|
|
137
|
-
structured_outputs: true) # Native structured outputs
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Anthropic Claude (Claude 3.5, Claude 4, etc.)
|
|
141
|
-
DSPy.configure do |c|
|
|
142
|
-
c.lm = DSPy::LM.new('anthropic/claude-sonnet-4-5-20250929',
|
|
143
|
-
api_key: ENV['ANTHROPIC_API_KEY'],
|
|
144
|
-
structured_outputs: true) # Tool-based extraction (default)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# Ollama - Run any local model (Llama, Mistral, Gemma, etc.)
|
|
148
|
-
DSPy.configure do |c|
|
|
149
|
-
c.lm = DSPy::LM.new('ollama/llama3.2') # Free, runs locally, no API key needed
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# OpenRouter - Access to 200+ models from multiple providers
|
|
153
|
-
DSPy.configure do |c|
|
|
154
|
-
c.lm = DSPy::LM.new('openrouter/deepseek/deepseek-chat-v3.1:free',
|
|
155
|
-
api_key: ENV['OPENROUTER_API_KEY'])
|
|
156
|
-
end
|
|
37
|
+
classifier = DSPy::Predict.new(Classify)
|
|
38
|
+
result = classifier.call(sentence: "This book was fun to read!")
|
|
157
39
|
```
|
|
158
40
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
**Developer Experience:** Official clients, multimodal coverage, and observability baked in.
|
|
162
|
-
<details>
|
|
163
|
-
<summary>Expand for everything included</summary>
|
|
164
|
-
|
|
165
|
-
- LLM provider support using official Ruby clients:
|
|
166
|
-
- [OpenAI Ruby](https://github.com/openai/openai-ruby) with vision model support
|
|
167
|
-
- [Anthropic Ruby SDK](https://github.com/anthropics/anthropic-sdk-ruby) with multimodal capabilities
|
|
168
|
-
- [Google Gemini API](https://ai.google.dev/) with native structured outputs
|
|
169
|
-
- [Ollama](https://ollama.com/) via OpenAI compatibility layer for local models
|
|
170
|
-
- **Multimodal Support** - Complete image analysis with DSPy::Image, type-safe bounding boxes, vision-capable models
|
|
171
|
-
- Runtime type checking with [Sorbet](https://sorbet.org/) including T::Enum and union types
|
|
172
|
-
- Type-safe tool definitions for ReAct agents
|
|
173
|
-
- Comprehensive instrumentation and observability
|
|
174
|
-
</details>
|
|
175
|
-
|
|
176
|
-
**Core Building Blocks:** Predictors, agents, and pipelines wired through type-safe signatures.
|
|
177
|
-
<details>
|
|
178
|
-
<summary>Expand for everything included</summary>
|
|
179
|
-
|
|
180
|
-
- **Signatures** - Define input/output schemas using Sorbet types with T::Enum and union type support
|
|
181
|
-
- **Predict** - LLM completion with structured data extraction and multimodal support
|
|
182
|
-
- **Chain of Thought** - Step-by-step reasoning for complex problems with automatic prompt optimization
|
|
183
|
-
- **ReAct** - Tool-using agents with type-safe tool definitions and error recovery
|
|
184
|
-
- **Module Composition** - Combine multiple LLM calls into production-ready workflows
|
|
185
|
-
</details>
|
|
186
|
-
|
|
187
|
-
**Optimization & Evaluation:** Treat prompt optimization like a real ML workflow.
|
|
188
|
-
<details>
|
|
189
|
-
<summary>Expand for everything included</summary>
|
|
190
|
-
|
|
191
|
-
- **Prompt Objects** - Manipulate prompts as first-class objects instead of strings
|
|
192
|
-
- **Typed Examples** - Type-safe training data with automatic validation
|
|
193
|
-
- **Evaluation Framework** - Advanced metrics beyond simple accuracy with error-resilient pipelines
|
|
194
|
-
- **MIPROv2 Optimization** - Advanced Bayesian optimization with Gaussian Processes, multiple optimization strategies, auto-config presets, and storage persistence
|
|
195
|
-
</details>
|
|
41
|
+
Define a typed task contract instead of maintaining an output template. Receive validated Ruby values instead of parsing provider JSON. Handle configuration, transport, and validation errors explicitly.
|
|
196
42
|
|
|
197
|
-
|
|
198
|
-
<details>
|
|
199
|
-
<summary>Expand for everything included</summary>
|
|
43
|
+
## Start Here
|
|
200
44
|
|
|
201
|
-
-
|
|
202
|
-
- **Type-Safe Configuration** - Strategy enums with automatic provider optimization (Strict/Compatible modes)
|
|
203
|
-
- **Smart Retry Logic** - Progressive fallback with exponential backoff for handling transient failures
|
|
204
|
-
- **Zero-Config Langfuse Integration** - Set env vars and get automatic OpenTelemetry traces in Langfuse
|
|
205
|
-
- **Performance Caching** - Schema and capability caching for faster repeated operations
|
|
206
|
-
- **File-based Storage** - Optimization result persistence with versioning
|
|
207
|
-
- **Structured Logging** - JSON and key=value formats with span tracking
|
|
208
|
-
</details>
|
|
45
|
+
The [Quick Start](https://oss.vicente.services/dspy.rb/getting-started/quick-start/) is the complete supported path: install the core and provider gems, configure a key, save a program, and run it.
|
|
209
46
|
|
|
210
|
-
|
|
47
|
+
Use [Installation](https://oss.vicente.services/dspy.rb/getting-started/installation/) to choose a provider. The [package and capability matrix](https://oss.vicente.services/dspy.rb/getting-started/packages/) records exact gem names, require behavior, support labels, and model or SDK boundaries.
|
|
211
48
|
|
|
212
|
-
|
|
213
|
-
<details>
|
|
214
|
-
<summary>Expand for the full changelog highlights</summary>
|
|
49
|
+
## Mental Model
|
|
215
50
|
|
|
216
|
-
|
|
217
|
-
-
|
|
218
|
-
-
|
|
219
|
-
-
|
|
220
|
-
- ✅ **Production Observability** - OpenTelemetry, New Relic, and Langfuse integration
|
|
221
|
-
- ✅ **Advanced Optimization** - MIPROv2 with Bayesian optimization, Gaussian Processes, and multi-mode search
|
|
51
|
+
- A `DSPy::Signature` defines the task contract.
|
|
52
|
+
- `DSPy::Predict` and other modules choose an execution strategy.
|
|
53
|
+
- Ordinary Ruby owns fixed sequencing, branching, persistence, permissions, and failure policy.
|
|
54
|
+
- Results cross a runtime validation boundary; validation establishes shape, not factual correctness.
|
|
222
55
|
|
|
223
|
-
###
|
|
224
|
-
- ✅ **MIPROv2 ADE Integrity (v0.29.1)** - Stratified train/val/test splits, honest precision accounting, and enum-driven `--auto` presets with integration coverage
|
|
225
|
-
- ✅ **Instruction Deduplication (v0.29.1)** - Candidate generation now filters repeated programs so optimization logs highlight unique strategies
|
|
226
|
-
- ✅ **GEPA Teleprompter (v0.29.0)** - Genetic-Pareto reflective prompt evolution with merge proposer scheduling, reflective mutation, and ADE demo parity
|
|
227
|
-
- ✅ **Optimizer Utilities Parity (v0.29.0)** - Bootstrap strategies, dataset summaries, and Layer 3 utilities unlock multi-predictor programs on Ruby
|
|
228
|
-
- ✅ **Observability Hardening (v0.29.0)** - OTLP exporter runs on a single-thread executor preventing frozen SSL contexts without blocking spans
|
|
229
|
-
- ✅ **Documentation Refresh (v0.29.x)** - New GEPA guide plus ADE optimization docs covering presets, stratified splits, and error-handling defaults
|
|
230
|
-
</details>
|
|
231
|
-
|
|
232
|
-
**Current Focus Areas:** Closing the loop on production patterns and community adoption ahead of v1.0.
|
|
233
|
-
<details>
|
|
234
|
-
<summary>Expand for the roadmap</summary>
|
|
235
|
-
|
|
236
|
-
### Production Readiness
|
|
237
|
-
- 🚧 **Production Patterns** - Real-world usage validation and performance optimization
|
|
238
|
-
- 🚧 **Ruby Ecosystem Integration** - Rails integration, Sidekiq compatibility, deployment patterns
|
|
239
|
-
|
|
240
|
-
### Community & Adoption
|
|
241
|
-
- 🚧 **Community Examples** - Real-world applications and case studies
|
|
242
|
-
- 🚧 **Contributor Experience** - Making it easier to contribute and extend
|
|
243
|
-
- 🚧 **Performance Benchmarks** - Comparative analysis vs other frameworks
|
|
244
|
-
</details>
|
|
245
|
-
|
|
246
|
-
**v1.0 Philosophy:** v1.0 lands after battle-testing, not checkbox bingo. The API is already stable; the milestone marks production confidence.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
## Documentation
|
|
250
|
-
|
|
251
|
-
📖 **[Complete Documentation Website](https://vicentereig.github.io/dspy.rb/)**
|
|
252
|
-
|
|
253
|
-
### LLM-Friendly Documentation
|
|
254
|
-
|
|
255
|
-
For LLMs and AI assistants working with DSPy.rb:
|
|
256
|
-
- **[llms.txt](https://vicentereig.github.io/dspy.rb/llms.txt)** - Concise reference optimized for LLMs
|
|
257
|
-
- **[llms-full.txt](https://vicentereig.github.io/dspy.rb/llms-full.txt)** - Comprehensive API documentation
|
|
258
|
-
|
|
259
|
-
### Getting Started
|
|
260
|
-
- **[Installation & Setup](docs/src/getting-started/installation.md)** - Detailed installation and configuration
|
|
261
|
-
- **[Quick Start Guide](docs/src/getting-started/quick-start.md)** - Your first DSPy programs
|
|
262
|
-
- **[Core Concepts](docs/src/getting-started/core-concepts.md)** - Understanding signatures, predictors, and modules
|
|
263
|
-
|
|
264
|
-
### Prompt Engineering
|
|
265
|
-
- **[Signatures & Types](docs/src/core-concepts/signatures.md)** - Define typed interfaces for LLM operations
|
|
266
|
-
- **[Predictors](docs/src/core-concepts/predictors.md)** - Predict, ChainOfThought, ReAct, and more
|
|
267
|
-
- **[Modules & Pipelines](docs/src/core-concepts/modules.md)** - Compose complex multi-stage workflows
|
|
268
|
-
- **[Multimodal Support](docs/src/core-concepts/multimodal.md)** - Image analysis with vision-capable models
|
|
269
|
-
- **[Examples & Validation](docs/src/core-concepts/examples.md)** - Type-safe training data
|
|
270
|
-
- **[Rich Types](docs/src/advanced/complex-types.md)** - Sorbet type integration with automatic coercion for structs, enums, and arrays
|
|
271
|
-
- **[Composable Pipelines](docs/src/advanced/pipelines.md)** - Manual module composition patterns
|
|
272
|
-
|
|
273
|
-
### Prompt Optimization
|
|
274
|
-
- **[Evaluation Framework](docs/src/optimization/evaluation.md)** - Advanced metrics beyond simple accuracy
|
|
275
|
-
- **[Prompt Optimization](docs/src/optimization/prompt-optimization.md)** - Manipulate prompts as objects
|
|
276
|
-
- **[MIPROv2 Optimizer](docs/src/optimization/miprov2.md)** - Advanced Bayesian optimization with Gaussian Processes
|
|
277
|
-
- **[GEPA Optimizer](docs/src/optimization/gepa.md)** *(beta)* - Reflective mutation with optional reflection LMs
|
|
278
|
-
|
|
279
|
-
### Context Engineering
|
|
280
|
-
- **[Tools](docs/src/core-concepts/toolsets.md)** - Tool wieldint agents.
|
|
281
|
-
- **[Agentic Memory](docs/src/core-concepts/memory.md)** - Memory Tools & Agentic Loops
|
|
282
|
-
- **[RAG Patterns](docs/src/advanced/rag.md)** - Manual RAG implementation with external services
|
|
283
|
-
|
|
284
|
-
### Production Features
|
|
285
|
-
- **[Observability](docs/src/production/observability.md)** - Zero-config Langfuse integration with a dedicated export worker that never blocks your LLMs
|
|
286
|
-
- **[Storage System](docs/src/production/storage.md)** - Persistence and optimization result storage
|
|
287
|
-
- **[Custom Metrics](docs/src/advanced/custom-metrics.md)** - Proc-based evaluation logic
|
|
56
|
+
### ReAct Agents
|
|
288
57
|
|
|
58
|
+
Use `ReAct` when the model needs to choose among typed Ruby tools inside an iteration bound. The application still owns tool authorization, side effects, budgets, and errors. See [Predictors](https://oss.vicente.services/dspy.rb/core-concepts/predictors/) and [Toolsets](https://oss.vicente.services/dspy.rb/core-concepts/toolsets/).
|
|
289
59
|
|
|
60
|
+
## Explore
|
|
290
61
|
|
|
62
|
+
- [Documentation](https://oss.vicente.services/dspy.rb/) — task-oriented guides and reference
|
|
63
|
+
- [Examples](examples/) — repository demos indexed by capability and prerequisites
|
|
64
|
+
- [llms.txt](https://oss.vicente.services/dspy.rb/llms.txt) — generated reference for AI assistants
|
|
291
65
|
|
|
66
|
+
Provider adapters, optimizers, observability exporters, datasets, and code-executing agents are separate packages when they add dependencies. Check the package matrix before selecting one; package availability does not guarantee uniform provider or model behavior.
|
|
292
67
|
|
|
68
|
+
## Contributing
|
|
293
69
|
|
|
70
|
+
For bugs, [open an issue](https://github.com/vicentereig/dspy.rb/issues). For suggestions, [start a discussion](https://github.com/vicentereig/dspy.rb/discussions).
|
|
294
71
|
|
|
72
|
+
Want to contribute code? Reach out: hey at vicente.services
|
|
295
73
|
|
|
296
74
|
## License
|
|
297
|
-
|
|
75
|
+
|
|
76
|
+
MIT License.
|
data/lib/dspy/ruby_llm/README.md
CHANGED
|
@@ -1,174 +1,53 @@
|
|
|
1
|
-
# DSPy RubyLLM Adapter
|
|
1
|
+
# DSPy RubyLLM Adapter (Preview)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`dspy-ruby_llm` delegates model lookup, provider selection, request transport, and capability discovery to [RubyLLM](https://rubyllm.com). The package is preview: provider coverage is not a uniform capability promise.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
See the [package and capability matrix](https://oss.vicente.services/dspy.rb/getting-started/packages/) for canonical status and the files shared with the core gem.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Prerequisites
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
## Usage
|
|
9
|
+
- Ruby, Bundler, and `gem "dspy-ruby_llm"`
|
|
10
|
+
- RubyLLM credentials and configuration for the selected provider
|
|
11
|
+
- a model ID recognized by RubyLLM, or an explicit `provider:` override
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
If you already have RubyLLM configured, DSPy will use your existing setup automatically:
|
|
13
|
+
## Install and Run
|
|
18
14
|
|
|
19
15
|
```ruby
|
|
20
|
-
|
|
21
|
-
RubyLLM.configure do |config|
|
|
22
|
-
config.openai_api_key = ENV['OPENAI_API_KEY']
|
|
23
|
-
config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# DSPy uses your existing config - no api_key needed!
|
|
27
|
-
lm = DSPy::LM.new("ruby_llm/gpt-4o")
|
|
28
|
-
lm = DSPy::LM.new("ruby_llm/claude-sonnet-4")
|
|
16
|
+
gem "dspy-ruby_llm"
|
|
29
17
|
```
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Use `ruby_llm/{model_id}` format where `model_id` is the RubyLLM model identifier:
|
|
19
|
+
Save this as `ruby_llm_smoke.rb`:
|
|
34
20
|
|
|
35
21
|
```ruby
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# Or use global RubyLLM config (no api_key needed)
|
|
40
|
-
lm = DSPy::LM.new("ruby_llm/gpt-4o")
|
|
41
|
-
lm = DSPy::LM.new("ruby_llm/claude-sonnet-4")
|
|
42
|
-
lm = DSPy::LM.new("ruby_llm/gemini-1.5-pro")
|
|
22
|
+
require "dspy"
|
|
23
|
+
require "dspy/ruby_llm"
|
|
43
24
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
The adapter detects the provider from RubyLLM's model registry. For models not in the registry, use the `provider:` option.
|
|
49
|
-
|
|
50
|
-
### Provider Override
|
|
51
|
-
|
|
52
|
-
For custom deployments or models not in the registry, explicitly specify the provider:
|
|
53
|
-
|
|
54
|
-
```ruby
|
|
55
|
-
# OpenRouter
|
|
56
|
-
lm = DSPy::LM.new("ruby_llm/anthropic/claude-3-opus",
|
|
57
|
-
api_key: ENV['OPENROUTER_API_KEY'],
|
|
58
|
-
provider: 'openrouter'
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
# Custom model with explicit provider
|
|
62
|
-
lm = DSPy::LM.new("ruby_llm/my-custom-model",
|
|
63
|
-
api_key: ENV['OPENAI_API_KEY'],
|
|
64
|
-
provider: 'openai',
|
|
65
|
-
base_url: 'https://custom-endpoint.com/v1'
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
# AWS Bedrock - configure RubyLLM globally first
|
|
69
|
-
RubyLLM.configure do |c|
|
|
70
|
-
c.bedrock_api_key = ENV['AWS_ACCESS_KEY_ID']
|
|
71
|
-
c.bedrock_secret_key = ENV['AWS_SECRET_ACCESS_KEY']
|
|
72
|
-
c.bedrock_region = 'us-east-1'
|
|
25
|
+
RubyLLM.configure do |config|
|
|
26
|
+
config.openai_api_key = ENV.fetch("OPENAI_API_KEY")
|
|
73
27
|
end
|
|
74
|
-
lm = DSPy::LM.new("ruby_llm/anthropic.claude-3-5-sonnet", provider: 'bedrock')
|
|
75
28
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
c.vertexai_location = 'us-central1'
|
|
80
|
-
end
|
|
81
|
-
lm = DSPy::LM.new("ruby_llm/gemini-pro", provider: 'vertexai')
|
|
29
|
+
lm = DSPy::LM.new("ruby_llm/gpt-4o")
|
|
30
|
+
response = lm.raw_chat([{ role: "user", content: "Reply with: adapter ready" }])
|
|
31
|
+
puts response.content
|
|
82
32
|
```
|
|
83
33
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
| OpenAI | `ruby_llm/gpt-4o` | In RubyLLM registry |
|
|
89
|
-
| Anthropic | `ruby_llm/claude-sonnet-4` | In RubyLLM registry |
|
|
90
|
-
| Google Gemini | `ruby_llm/gemini-1.5-pro` | In RubyLLM registry |
|
|
91
|
-
| DeepSeek | `ruby_llm/deepseek-chat` | In RubyLLM registry |
|
|
92
|
-
| Mistral | `ruby_llm/mistral-large` | In RubyLLM registry |
|
|
93
|
-
| Ollama | `ruby_llm/llama3.2` | Use `provider: 'ollama'`, no API key needed |
|
|
94
|
-
| AWS Bedrock | `ruby_llm/anthropic.claude-3-5-sonnet` | Configure RubyLLM globally |
|
|
95
|
-
| VertexAI | `ruby_llm/gemini-pro` | Configure RubyLLM globally |
|
|
96
|
-
| OpenRouter | `ruby_llm/anthropic/claude-3-opus` | Use `provider: 'openrouter'` |
|
|
97
|
-
| Perplexity | `ruby_llm/llama-3.1-sonar-large` | Use `provider: 'perplexity'` |
|
|
98
|
-
| GPUStack | `ruby_llm/model-name` | Use `provider: 'gpustack'` |
|
|
99
|
-
|
|
100
|
-
### Configuration Options
|
|
101
|
-
|
|
102
|
-
```ruby
|
|
103
|
-
lm = DSPy::LM.new("ruby_llm/gpt-4o",
|
|
104
|
-
api_key: ENV['OPENAI_API_KEY'], # API key (or use global RubyLLM config)
|
|
105
|
-
base_url: 'https://custom.com/v1', # Custom endpoint
|
|
106
|
-
timeout: 120, # Request timeout in seconds
|
|
107
|
-
max_retries: 3, # Retry count
|
|
108
|
-
structured_outputs: true # Enable JSON schema (default: true)
|
|
109
|
-
)
|
|
34
|
+
```bash
|
|
35
|
+
bundle install
|
|
36
|
+
export OPENAI_API_KEY="your-key"
|
|
37
|
+
bundle exec ruby ruby_llm_smoke.rb
|
|
110
38
|
```
|
|
111
39
|
|
|
112
|
-
|
|
40
|
+
The command prints the provider response. DSPy reuses RubyLLM's global configuration only when the `DSPy::LM` call does not supply `api_key`, `base_url`, `timeout`, or `max_retries` overrides. Supplying any of those options creates a scoped adapter configuration.
|
|
113
41
|
|
|
114
|
-
|
|
42
|
+
For a model absent from RubyLLM's registry, specify the provider explicitly:
|
|
115
43
|
|
|
116
44
|
```ruby
|
|
117
|
-
|
|
118
|
-
description "Summarize the given text"
|
|
119
|
-
|
|
120
|
-
input do
|
|
121
|
-
const :text, String
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
output do
|
|
125
|
-
const :summary, String
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
DSPy.configure do |config|
|
|
130
|
-
config.lm = DSPy::LM.new("ruby_llm/claude-sonnet-4")
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
summarizer = DSPy::Predict.new(Summarize)
|
|
134
|
-
result = summarizer.call(text: "Long article text here...")
|
|
135
|
-
puts result.summary
|
|
45
|
+
lm = DSPy::LM.new("ruby_llm/my-model", provider: "ollama")
|
|
136
46
|
```
|
|
137
47
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
```ruby
|
|
141
|
-
lm = DSPy::LM.new("ruby_llm/gpt-4o", api_key: ENV['OPENAI_API_KEY'])
|
|
142
|
-
|
|
143
|
-
response = lm.chat(messages: [{ role: 'user', content: 'Tell me a story' }]) do |chunk|
|
|
144
|
-
print chunk # Print each chunk as it arrives
|
|
145
|
-
end
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Dependencies
|
|
149
|
-
|
|
150
|
-
This gem depends on:
|
|
151
|
-
- `dspy` (>= 0.32)
|
|
152
|
-
- `ruby_llm` (~> 1.3)
|
|
153
|
-
|
|
154
|
-
RubyLLM itself has minimal dependencies (Faraday, Zeitwerk, Marcel).
|
|
155
|
-
|
|
156
|
-
## Why Use This Adapter?
|
|
157
|
-
|
|
158
|
-
1. **Unified interface** - One API for all providers
|
|
159
|
-
2. **Lightweight** - RubyLLM has only 3 dependencies
|
|
160
|
-
3. **Provider coverage** - Access Bedrock, VertexAI, DeepSeek without separate adapters
|
|
161
|
-
4. **Built-in retries** - Automatic retry with exponential backoff
|
|
162
|
-
5. **Model registry** - 500+ models with capability detection and auto provider resolution
|
|
163
|
-
|
|
164
|
-
## Error Handling
|
|
165
|
-
|
|
166
|
-
The adapter maps RubyLLM errors to DSPy error types:
|
|
48
|
+
## Capability and Failure Boundaries
|
|
167
49
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
| `ModelNotFoundError` | `AdapterError` |
|
|
173
|
-
| `BadRequestError` | `AdapterError` |
|
|
174
|
-
| `ConfigurationError` | `ConfigurationError` |
|
|
50
|
+
- A missing RubyLLM credential or unknown model/provider fails before or during the request.
|
|
51
|
+
- Registry data and capability detection vary with the installed RubyLLM version.
|
|
52
|
+
- Authentication, attachments, schemas, streaming, retries, and request options vary by underlying provider and model; document input is currently restricted to Anthropic.
|
|
53
|
+
- Error mapping preserves a DSPy boundary, but provider-specific details still come from RubyLLM and its SDKs. Test the exact model and request your application will use.
|
|
@@ -5,9 +5,6 @@ require 'ruby_llm'
|
|
|
5
5
|
require 'dspy/lm/adapter'
|
|
6
6
|
require 'dspy/lm/vision_models'
|
|
7
7
|
|
|
8
|
-
require 'dspy/ruby_llm/guardrails'
|
|
9
|
-
DSPy::RubyLLM::Guardrails.ensure_ruby_llm_installed!
|
|
10
|
-
|
|
11
8
|
module DSPy
|
|
12
9
|
module RubyLLM
|
|
13
10
|
module LM
|
|
@@ -49,10 +46,12 @@ module DSPy
|
|
|
49
46
|
def chat(messages:, signature: nil, &block)
|
|
50
47
|
normalized_messages = normalize_messages(messages)
|
|
51
48
|
|
|
49
|
+
validate_document_support!(normalized_messages)
|
|
50
|
+
|
|
52
51
|
# Validate vision support if images are present
|
|
53
52
|
if contains_images?(normalized_messages)
|
|
54
53
|
validate_vision_support!
|
|
55
|
-
normalized_messages = format_multimodal_messages(normalized_messages)
|
|
54
|
+
normalized_messages = format_multimodal_messages(normalized_messages, provider)
|
|
56
55
|
end
|
|
57
56
|
|
|
58
57
|
chat_instance = create_chat_instance
|
|
@@ -255,6 +254,9 @@ module DSPy
|
|
|
255
254
|
elsif item[:image_url]
|
|
256
255
|
attachments << item[:image_url][:url]
|
|
257
256
|
end
|
|
257
|
+
when 'document'
|
|
258
|
+
document = item[:document]
|
|
259
|
+
attachments << document.to_ruby_llm_attachment if document
|
|
258
260
|
end
|
|
259
261
|
end
|
|
260
262
|
content = text_parts.join("\n")
|
|
@@ -263,6 +265,14 @@ module DSPy
|
|
|
263
265
|
[content.to_s, attachments]
|
|
264
266
|
end
|
|
265
267
|
|
|
268
|
+
def validate_document_support!(messages)
|
|
269
|
+
return unless contains_documents?(messages)
|
|
270
|
+
return if provider == 'anthropic'
|
|
271
|
+
|
|
272
|
+
raise DSPy::LM::IncompatibleDocumentFeatureError,
|
|
273
|
+
"RubyLLM document inputs are currently supported only when the underlying provider is Anthropic."
|
|
274
|
+
end
|
|
275
|
+
|
|
266
276
|
def map_response(ruby_llm_response)
|
|
267
277
|
DSPy::LM::Response.new(
|
|
268
278
|
content: ruby_llm_response.content.to_s,
|
|
@@ -358,32 +368,6 @@ module DSPy
|
|
|
358
368
|
# If DSPy doesn't know about the model, let RubyLLM handle it
|
|
359
369
|
# RubyLLM has its own model registry with capability detection
|
|
360
370
|
end
|
|
361
|
-
|
|
362
|
-
def format_multimodal_messages(messages)
|
|
363
|
-
messages.map do |msg|
|
|
364
|
-
if msg[:content].is_a?(Array)
|
|
365
|
-
formatted_content = msg[:content].map do |item|
|
|
366
|
-
case item[:type]
|
|
367
|
-
when 'text'
|
|
368
|
-
{ type: 'text', text: item[:text] }
|
|
369
|
-
when 'image'
|
|
370
|
-
# Validate and format image for provider
|
|
371
|
-
image = item[:image]
|
|
372
|
-
if image.respond_to?(:validate_for_provider!)
|
|
373
|
-
image.validate_for_provider!(provider)
|
|
374
|
-
end
|
|
375
|
-
item
|
|
376
|
-
else
|
|
377
|
-
item
|
|
378
|
-
end
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
{ role: msg[:role], content: formatted_content }
|
|
382
|
-
else
|
|
383
|
-
msg
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
371
|
end
|
|
388
372
|
end
|
|
389
373
|
end
|
data/lib/dspy/ruby_llm.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dspy-ruby_llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vicente Reig Rincón de Arellano
|
|
@@ -16,28 +16,40 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 0.30.1
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '2.0'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
27
|
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
29
|
+
version: 0.30.1
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: ruby_llm
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
30
36
|
requirements:
|
|
31
|
-
- - "
|
|
37
|
+
- - ">="
|
|
32
38
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
39
|
+
version: 1.14.1
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '2.0'
|
|
34
43
|
type: :runtime
|
|
35
44
|
prerelease: false
|
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
46
|
requirements:
|
|
38
|
-
- - "
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: 1.14.1
|
|
50
|
+
- - "<"
|
|
39
51
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
52
|
+
version: '2.0'
|
|
41
53
|
description: Provides a unified adapter using RubyLLM to access OpenAI, Anthropic,
|
|
42
54
|
Gemini, Bedrock, Ollama, and more through a single interface in DSPy.rb projects.
|
|
43
55
|
email:
|
|
@@ -51,7 +63,6 @@ files:
|
|
|
51
63
|
- README.md
|
|
52
64
|
- lib/dspy/ruby_llm.rb
|
|
53
65
|
- lib/dspy/ruby_llm/README.md
|
|
54
|
-
- lib/dspy/ruby_llm/guardrails.rb
|
|
55
66
|
- lib/dspy/ruby_llm/lm/adapters/ruby_llm_adapter.rb
|
|
56
67
|
- lib/dspy/ruby_llm/version.rb
|
|
57
68
|
homepage: https://github.com/vicentereig/dspy.rb
|
|
@@ -75,5 +86,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
75
86
|
requirements: []
|
|
76
87
|
rubygems_version: 3.6.9
|
|
77
88
|
specification_version: 4
|
|
78
|
-
summary: RubyLLM adapter for DSPy.rb
|
|
89
|
+
summary: RubyLLM provider adapter for DSPy.rb.
|
|
79
90
|
test_files: []
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'dspy/lm/errors'
|
|
4
|
-
|
|
5
|
-
module DSPy
|
|
6
|
-
module RubyLLM
|
|
7
|
-
class Guardrails
|
|
8
|
-
SUPPORTED_RUBY_LLM_VERSIONS = "~> 1.3".freeze
|
|
9
|
-
|
|
10
|
-
def self.ensure_ruby_llm_installed!
|
|
11
|
-
require 'ruby_llm'
|
|
12
|
-
|
|
13
|
-
spec = Gem.loaded_specs["ruby_llm"]
|
|
14
|
-
unless spec && Gem::Requirement.new(SUPPORTED_RUBY_LLM_VERSIONS).satisfied_by?(spec.version)
|
|
15
|
-
msg = <<~MSG
|
|
16
|
-
DSPy requires the `ruby_llm` gem #{SUPPORTED_RUBY_LLM_VERSIONS}.
|
|
17
|
-
Please install or upgrade it with `bundle add ruby_llm --version "#{SUPPORTED_RUBY_LLM_VERSIONS}"`.
|
|
18
|
-
MSG
|
|
19
|
-
raise DSPy::LM::UnsupportedVersionError, msg
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|