@every-env/compound-plugin 0.3.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{plugins/compound-engineering → .claude}/commands/release-docs.md +0 -1
- package/.claude-plugin/marketplace.json +2 -2
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/deploy-docs.yml +3 -3
- package/.github/workflows/publish.yml +37 -0
- package/README.md +12 -3
- package/docs/index.html +13 -13
- package/docs/pages/changelog.html +39 -0
- package/docs/plans/2026-02-08-feat-convert-local-md-settings-for-opencode-codex-plan.md +143 -0
- package/docs/plans/2026-02-08-feat-simplify-plugin-settings-plan.md +195 -0
- package/docs/plans/2026-02-09-refactor-dspy-ruby-skill-update-plan.md +104 -0
- package/docs/plans/2026-02-12-feat-add-cursor-cli-target-provider-plan.md +306 -0
- package/docs/specs/cursor.md +85 -0
- package/package.json +1 -1
- package/plugins/compound-engineering/.claude-plugin/plugin.json +2 -2
- package/plugins/compound-engineering/CHANGELOG.md +38 -0
- package/plugins/compound-engineering/README.md +5 -3
- package/plugins/compound-engineering/commands/workflows/brainstorm.md +6 -1
- package/plugins/compound-engineering/commands/workflows/compound.md +1 -0
- package/plugins/compound-engineering/commands/workflows/review.md +23 -21
- package/plugins/compound-engineering/commands/workflows/work.md +29 -15
- package/plugins/compound-engineering/skills/dspy-ruby/SKILL.md +539 -396
- package/plugins/compound-engineering/skills/dspy-ruby/assets/config-template.rb +159 -331
- package/plugins/compound-engineering/skills/dspy-ruby/assets/module-template.rb +210 -236
- package/plugins/compound-engineering/skills/dspy-ruby/assets/signature-template.rb +173 -95
- package/plugins/compound-engineering/skills/dspy-ruby/references/core-concepts.md +552 -143
- package/plugins/compound-engineering/skills/dspy-ruby/references/observability.md +366 -0
- package/plugins/compound-engineering/skills/dspy-ruby/references/optimization.md +440 -460
- package/plugins/compound-engineering/skills/dspy-ruby/references/providers.md +305 -225
- package/plugins/compound-engineering/skills/dspy-ruby/references/toolsets.md +502 -0
- package/plugins/compound-engineering/skills/setup/SKILL.md +168 -0
- package/src/commands/convert.ts +10 -5
- package/src/commands/install.ts +18 -10
- package/src/converters/claude-to-codex.ts +7 -2
- package/src/converters/claude-to-cursor.ts +166 -0
- package/src/converters/claude-to-droid.ts +174 -0
- package/src/converters/claude-to-opencode.ts +8 -2
- package/src/targets/cursor.ts +48 -0
- package/src/targets/droid.ts +50 -0
- package/src/targets/index.ts +18 -0
- package/src/types/cursor.ts +29 -0
- package/src/types/droid.ts +20 -0
- package/tests/cli.test.ts +62 -0
- package/tests/codex-converter.test.ts +62 -0
- package/tests/converter.test.ts +61 -0
- package/tests/cursor-converter.test.ts +347 -0
- package/tests/cursor-writer.test.ts +137 -0
- package/tests/droid-converter.test.ts +277 -0
- package/tests/droid-writer.test.ts +100 -0
- package/plugins/compound-engineering/commands/technical_review.md +0 -8
|
@@ -1,338 +1,418 @@
|
|
|
1
1
|
# DSPy.rb LLM Providers
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Adapter Architecture
|
|
4
4
|
|
|
5
|
-
DSPy.rb
|
|
5
|
+
DSPy.rb ships provider SDKs as separate adapter gems. Install only the adapters the project needs. Each adapter gem depends on the official SDK for its provider and auto-loads when present -- no explicit `require` necessary.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```ruby
|
|
8
|
+
# Gemfile
|
|
9
|
+
gem 'dspy' # core framework (no provider SDKs)
|
|
10
|
+
gem 'dspy-openai' # OpenAI, OpenRouter, Ollama
|
|
11
|
+
gem 'dspy-anthropic' # Claude
|
|
12
|
+
gem 'dspy-gemini' # Gemini
|
|
13
|
+
gem 'dspy-ruby_llm' # RubyLLM unified adapter (12+ providers)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Per-Provider Adapters
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
- **Anthropic**: Claude 3 family (Sonnet, Opus, Haiku), Claude 3.5 Sonnet
|
|
11
|
-
- **Google Gemini**: Gemini 1.5 Pro, Gemini 1.5 Flash, other versions
|
|
12
|
-
- **Ollama**: Local model support via OpenAI compatibility layer
|
|
13
|
-
- **OpenRouter**: Unified multi-provider API for 200+ models
|
|
20
|
+
### dspy-openai
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
Covers any endpoint that speaks the OpenAI chat-completions protocol: OpenAI itself, OpenRouter, and Ollama.
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
**SDK dependency:** `openai ~> 0.17`
|
|
18
25
|
|
|
19
26
|
```ruby
|
|
20
|
-
|
|
27
|
+
# OpenAI
|
|
28
|
+
lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
# OpenRouter -- access 200+ models behind a single key
|
|
31
|
+
lm = DSPy::LM.new('openrouter/x-ai/grok-4-fast:free',
|
|
32
|
+
api_key: ENV['OPENROUTER_API_KEY']
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Ollama -- local models, no API key required
|
|
36
|
+
lm = DSPy::LM.new('ollama/llama3.2')
|
|
37
|
+
|
|
38
|
+
# Remote Ollama instance
|
|
39
|
+
lm = DSPy::LM.new('ollama/llama3.2',
|
|
40
|
+
base_url: 'https://my-ollama.example.com/v1',
|
|
41
|
+
api_key: 'optional-auth-token'
|
|
42
|
+
)
|
|
25
43
|
```
|
|
26
44
|
|
|
27
|
-
|
|
45
|
+
All three sub-adapters share the same request handling, structured-output support, and error reporting. Swap providers without changing higher-level DSPy code.
|
|
28
46
|
|
|
29
|
-
|
|
47
|
+
For OpenRouter models that lack native structured-output support, disable it explicitly:
|
|
30
48
|
|
|
31
49
|
```ruby
|
|
32
|
-
DSPy.
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
lm = DSPy::LM.new('openrouter/deepseek/deepseek-chat-v3.1:free',
|
|
51
|
+
api_key: ENV['OPENROUTER_API_KEY'],
|
|
52
|
+
structured_outputs: false
|
|
53
|
+
)
|
|
54
|
+
```
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
c.lm = DSPy::LM.new('openai/gpt-4o', api_key: ENV['OPENAI_API_KEY'])
|
|
56
|
+
### dspy-anthropic
|
|
38
57
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
Provides the Claude adapter. Install it for any `anthropic/*` model id.
|
|
59
|
+
|
|
60
|
+
**SDK dependency:** `anthropic ~> 1.12`
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514',
|
|
64
|
+
api_key: ENV['ANTHROPIC_API_KEY']
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Structured outputs default to tool-based JSON extraction (`structured_outputs: true`). Set `structured_outputs: false` to use enhanced-prompting extraction instead.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Tool-based extraction (default, most reliable)
|
|
72
|
+
lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514',
|
|
73
|
+
api_key: ENV['ANTHROPIC_API_KEY'],
|
|
74
|
+
structured_outputs: true
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Enhanced prompting extraction
|
|
78
|
+
lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514',
|
|
79
|
+
api_key: ENV['ANTHROPIC_API_KEY'],
|
|
80
|
+
structured_outputs: false
|
|
81
|
+
)
|
|
42
82
|
```
|
|
43
83
|
|
|
44
|
-
|
|
84
|
+
### dspy-gemini
|
|
45
85
|
|
|
46
|
-
|
|
86
|
+
Provides the Gemini adapter. Install it for any `gemini/*` model id.
|
|
47
87
|
|
|
48
|
-
**
|
|
88
|
+
**SDK dependency:** `gemini-ai ~> 4.3`
|
|
49
89
|
|
|
50
90
|
```ruby
|
|
51
|
-
DSPy.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
lm = DSPy::LM.new('gemini/gemini-2.5-flash',
|
|
92
|
+
api_key: ENV['GEMINI_API_KEY']
|
|
93
|
+
)
|
|
94
|
+
```
|
|
55
95
|
|
|
56
|
-
|
|
57
|
-
c.lm = DSPy::LM.new('anthropic/claude-3-opus-20240229',
|
|
58
|
-
api_key: ENV['ANTHROPIC_API_KEY'])
|
|
96
|
+
**Environment variable:** `GEMINI_API_KEY` (also accepts `GOOGLE_API_KEY`).
|
|
59
97
|
|
|
60
|
-
|
|
61
|
-
c.lm = DSPy::LM.new('anthropic/claude-3-sonnet-20240229',
|
|
62
|
-
api_key: ENV['ANTHROPIC_API_KEY'])
|
|
98
|
+
---
|
|
63
99
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
100
|
+
## RubyLLM Unified Adapter
|
|
101
|
+
|
|
102
|
+
The `dspy-ruby_llm` gem provides a single adapter that routes to 12+ providers through [RubyLLM](https://rubyllm.com). Use it when a project talks to multiple providers or needs access to Bedrock, VertexAI, DeepSeek, or Mistral without dedicated adapter gems.
|
|
103
|
+
|
|
104
|
+
**SDK dependency:** `ruby_llm ~> 1.3`
|
|
105
|
+
|
|
106
|
+
### Model ID Format
|
|
107
|
+
|
|
108
|
+
Prefix every model id with `ruby_llm/`:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
lm = DSPy::LM.new('ruby_llm/gpt-4o-mini')
|
|
112
|
+
lm = DSPy::LM.new('ruby_llm/claude-sonnet-4-20250514')
|
|
113
|
+
lm = DSPy::LM.new('ruby_llm/gemini-2.5-flash')
|
|
68
114
|
```
|
|
69
115
|
|
|
70
|
-
|
|
116
|
+
The adapter detects the provider from RubyLLM's model registry automatically. For models not in the registry, pass `provider:` explicitly:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
lm = DSPy::LM.new('ruby_llm/llama3.2', provider: 'ollama')
|
|
120
|
+
lm = DSPy::LM.new('ruby_llm/anthropic/claude-3-opus',
|
|
121
|
+
api_key: ENV['OPENROUTER_API_KEY'],
|
|
122
|
+
provider: 'openrouter'
|
|
123
|
+
)
|
|
124
|
+
```
|
|
71
125
|
|
|
72
|
-
###
|
|
126
|
+
### Using Existing RubyLLM Configuration
|
|
73
127
|
|
|
74
|
-
|
|
128
|
+
When RubyLLM is already configured globally, omit the `api_key:` argument. DSPy reuses the global config automatically:
|
|
75
129
|
|
|
76
130
|
```ruby
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
131
|
+
RubyLLM.configure do |config|
|
|
132
|
+
config.openai_api_key = ENV['OPENAI_API_KEY']
|
|
133
|
+
config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
|
|
134
|
+
end
|
|
81
135
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
136
|
+
# No api_key needed -- picks up the global config
|
|
137
|
+
DSPy.configure do |c|
|
|
138
|
+
c.lm = DSPy::LM.new('ruby_llm/gpt-4o-mini')
|
|
85
139
|
end
|
|
86
140
|
```
|
|
87
141
|
|
|
88
|
-
|
|
142
|
+
When an `api_key:` (or any of `base_url:`, `timeout:`, `max_retries:`) is passed, DSPy creates a **scoped context** instead of reusing the global config.
|
|
89
143
|
|
|
90
|
-
###
|
|
144
|
+
### Cloud-Hosted Providers (Bedrock, VertexAI)
|
|
91
145
|
|
|
92
|
-
|
|
146
|
+
Configure RubyLLM globally first, then reference the model:
|
|
93
147
|
|
|
94
148
|
```ruby
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
c.
|
|
98
|
-
|
|
149
|
+
# AWS Bedrock
|
|
150
|
+
RubyLLM.configure do |c|
|
|
151
|
+
c.bedrock_api_key = ENV['AWS_ACCESS_KEY_ID']
|
|
152
|
+
c.bedrock_secret_key = ENV['AWS_SECRET_ACCESS_KEY']
|
|
153
|
+
c.bedrock_region = 'us-east-1'
|
|
154
|
+
end
|
|
155
|
+
lm = DSPy::LM.new('ruby_llm/anthropic.claude-3-5-sonnet', provider: 'bedrock')
|
|
99
156
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
c.
|
|
157
|
+
# Google VertexAI
|
|
158
|
+
RubyLLM.configure do |c|
|
|
159
|
+
c.vertexai_project_id = 'your-project-id'
|
|
160
|
+
c.vertexai_location = 'us-central1'
|
|
103
161
|
end
|
|
162
|
+
lm = DSPy::LM.new('ruby_llm/gemini-pro', provider: 'vertexai')
|
|
104
163
|
```
|
|
105
164
|
|
|
106
|
-
|
|
165
|
+
### Supported Providers Table
|
|
166
|
+
|
|
167
|
+
| Provider | Example Model ID | Notes |
|
|
168
|
+
|-------------|--------------------------------------------|---------------------------------|
|
|
169
|
+
| OpenAI | `ruby_llm/gpt-4o-mini` | Auto-detected from registry |
|
|
170
|
+
| Anthropic | `ruby_llm/claude-sonnet-4-20250514` | Auto-detected from registry |
|
|
171
|
+
| Gemini | `ruby_llm/gemini-2.5-flash` | Auto-detected from registry |
|
|
172
|
+
| DeepSeek | `ruby_llm/deepseek-chat` | Auto-detected from registry |
|
|
173
|
+
| Mistral | `ruby_llm/mistral-large` | Auto-detected from registry |
|
|
174
|
+
| Ollama | `ruby_llm/llama3.2` | Use `provider: 'ollama'` |
|
|
175
|
+
| AWS Bedrock | `ruby_llm/anthropic.claude-3-5-sonnet` | Configure RubyLLM globally |
|
|
176
|
+
| VertexAI | `ruby_llm/gemini-pro` | Configure RubyLLM globally |
|
|
177
|
+
| OpenRouter | `ruby_llm/anthropic/claude-3-opus` | Use `provider: 'openrouter'` |
|
|
178
|
+
| Perplexity | `ruby_llm/llama-3.1-sonar-large` | Use `provider: 'perplexity'` |
|
|
179
|
+
| GPUStack | `ruby_llm/model-name` | Use `provider: 'gpustack'` |
|
|
180
|
+
|
|
181
|
+
---
|
|
107
182
|
|
|
108
|
-
|
|
183
|
+
## Rails Initializer Pattern
|
|
109
184
|
|
|
110
|
-
|
|
185
|
+
Configure DSPy inside an `after_initialize` block so Rails credentials and environment are fully loaded:
|
|
111
186
|
|
|
112
187
|
```ruby
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
188
|
+
# config/initializers/dspy.rb
|
|
189
|
+
Rails.application.config.after_initialize do
|
|
190
|
+
return if Rails.env.test? # skip in test -- use VCR cassettes instead
|
|
191
|
+
|
|
192
|
+
DSPy.configure do |config|
|
|
193
|
+
config.lm = DSPy::LM.new(
|
|
194
|
+
'openai/gpt-4o-mini',
|
|
195
|
+
api_key: Rails.application.credentials.openai_api_key,
|
|
196
|
+
structured_outputs: true
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
config.logger = if Rails.env.production?
|
|
200
|
+
Dry.Logger(:dspy, formatter: :json) do |logger|
|
|
201
|
+
logger.add_backend(stream: Rails.root.join("log/dspy.log"))
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
Dry.Logger(:dspy) do |logger|
|
|
205
|
+
logger.add_backend(level: :debug, stream: $stdout)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
122
209
|
end
|
|
123
210
|
```
|
|
124
211
|
|
|
125
|
-
|
|
212
|
+
Key points:
|
|
126
213
|
|
|
127
|
-
|
|
214
|
+
- Wrap in `after_initialize` so `Rails.application.credentials` is available.
|
|
215
|
+
- Return early in the test environment. Rely on VCR cassettes for deterministic LLM responses.
|
|
216
|
+
- Set `structured_outputs: true` (the default) for provider-native JSON extraction.
|
|
217
|
+
- Use `Dry.Logger` with `:json` formatter in production for structured log parsing.
|
|
128
218
|
|
|
129
|
-
|
|
219
|
+
---
|
|
130
220
|
|
|
131
|
-
|
|
132
|
-
|---------|--------|-----------|--------|--------|
|
|
133
|
-
| Structured Output | ✅ | ✅ | ✅ | ✅ |
|
|
134
|
-
| Vision (Images) | ✅ | ✅ | ✅ | ⚠️ Limited |
|
|
135
|
-
| Image URLs | ✅ | ❌ | ❌ | ❌ |
|
|
136
|
-
| Tool Calling | ✅ | ✅ | ✅ | Varies |
|
|
137
|
-
| Streaming | ❌ | ❌ | ❌ | ❌ |
|
|
138
|
-
| Function Calling | ✅ | ✅ | ✅ | Varies |
|
|
221
|
+
## Fiber-Local LM Context
|
|
139
222
|
|
|
140
|
-
|
|
223
|
+
`DSPy.with_lm` sets a temporary language-model override scoped to the current Fiber. Every predictor call inside the block uses the override; outside the block the previous LM takes effect again.
|
|
141
224
|
|
|
142
|
-
|
|
225
|
+
```ruby
|
|
226
|
+
fast = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
|
|
227
|
+
powerful = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', api_key: ENV['ANTHROPIC_API_KEY'])
|
|
143
228
|
|
|
144
|
-
|
|
229
|
+
classifier = Classifier.new
|
|
145
230
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
DSPy::Image.from_url("https://example.com/image.jpg")
|
|
231
|
+
# Uses the global LM
|
|
232
|
+
result = classifier.call(text: "Hello")
|
|
149
233
|
|
|
150
|
-
#
|
|
151
|
-
DSPy
|
|
152
|
-
|
|
234
|
+
# Temporarily switch to the fast model
|
|
235
|
+
DSPy.with_lm(fast) do
|
|
236
|
+
result = classifier.call(text: "Hello") # uses gpt-4o-mini
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Temporarily switch to the powerful model
|
|
240
|
+
DSPy.with_lm(powerful) do
|
|
241
|
+
result = classifier.call(text: "Hello") # uses claude-sonnet-4
|
|
242
|
+
end
|
|
153
243
|
```
|
|
154
244
|
|
|
155
|
-
|
|
245
|
+
### LM Resolution Hierarchy
|
|
156
246
|
|
|
157
|
-
|
|
247
|
+
DSPy resolves the active language model in this order:
|
|
158
248
|
|
|
159
|
-
|
|
249
|
+
1. **Instance-level LM** -- set directly on a module instance via `configure`
|
|
250
|
+
2. **Fiber-local LM** -- set via `DSPy.with_lm`
|
|
251
|
+
3. **Global LM** -- set via `DSPy.configure`
|
|
160
252
|
|
|
161
|
-
|
|
253
|
+
Instance-level configuration always wins, even inside a `DSPy.with_lm` block:
|
|
162
254
|
|
|
163
255
|
```ruby
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
256
|
+
classifier = Classifier.new
|
|
257
|
+
classifier.configure { |c| c.lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', api_key: ENV['ANTHROPIC_API_KEY']) }
|
|
258
|
+
|
|
259
|
+
fast = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
|
|
260
|
+
|
|
261
|
+
DSPy.with_lm(fast) do
|
|
262
|
+
classifier.call(text: "Test") # still uses claude-sonnet-4 (instance-level wins)
|
|
171
263
|
end
|
|
172
264
|
```
|
|
173
265
|
|
|
174
|
-
###
|
|
266
|
+
### configure_predictor for Fine-Grained Agent Control
|
|
175
267
|
|
|
176
|
-
Use
|
|
268
|
+
Complex agents (`ReAct`, `CodeAct`, `DeepResearch`, `DeepSearch`) contain internal predictors. Use `configure` for a blanket override and `configure_predictor` to target a specific sub-predictor:
|
|
177
269
|
|
|
178
270
|
```ruby
|
|
179
|
-
|
|
180
|
-
fast_lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
|
|
271
|
+
agent = DSPy::ReAct.new(MySignature, tools: tools)
|
|
181
272
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
api_key: ENV['ANTHROPIC_API_KEY'])
|
|
273
|
+
# Set a default LM for the agent and all its children
|
|
274
|
+
agent.configure { |c| c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']) }
|
|
185
275
|
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
super
|
|
190
|
-
DSPy.configure { |c| c.lm = fast_lm }
|
|
191
|
-
@predictor = DSPy::Predict.new(SimpleSignature)
|
|
192
|
-
end
|
|
276
|
+
# Override just the reasoning predictor with a more capable model
|
|
277
|
+
agent.configure_predictor('thought_generator') do |c|
|
|
278
|
+
c.lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', api_key: ENV['ANTHROPIC_API_KEY'])
|
|
193
279
|
end
|
|
194
280
|
|
|
195
|
-
|
|
196
|
-
def initialize
|
|
197
|
-
super
|
|
198
|
-
DSPy.configure { |c| c.lm = powerful_lm }
|
|
199
|
-
@predictor = DSPy::ChainOfThought.new(ComplexSignature)
|
|
200
|
-
end
|
|
201
|
-
end
|
|
281
|
+
result = agent.call(question: "Summarize the report")
|
|
202
282
|
```
|
|
203
283
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
Override configuration for specific predictions:
|
|
284
|
+
Both methods support chaining:
|
|
207
285
|
|
|
208
286
|
```ruby
|
|
209
|
-
|
|
287
|
+
agent
|
|
288
|
+
.configure { |c| c.lm = cheap_model }
|
|
289
|
+
.configure_predictor('thought_generator') { |c| c.lm = expensive_model }
|
|
290
|
+
```
|
|
210
291
|
|
|
211
|
-
|
|
212
|
-
result1 = predictor.forward(input: "data")
|
|
292
|
+
#### Available Predictors by Agent Type
|
|
213
293
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
294
|
+
| Agent | Internal Predictors |
|
|
295
|
+
|----------------------|------------------------------------------------------------------|
|
|
296
|
+
| `DSPy::ReAct` | `thought_generator`, `observation_processor` |
|
|
297
|
+
| `DSPy::CodeAct` | `code_generator`, `observation_processor` |
|
|
298
|
+
| `DSPy::DeepResearch` | `planner`, `synthesizer`, `qa_reviewer`, `reporter` |
|
|
299
|
+
| `DSPy::DeepSearch` | `seed_predictor`, `search_predictor`, `reader_predictor`, `reason_predictor` |
|
|
300
|
+
|
|
301
|
+
#### Propagation Rules
|
|
220
302
|
|
|
221
|
-
|
|
303
|
+
- Configuration propagates recursively to children and grandchildren.
|
|
304
|
+
- Children with an already-configured LM are **not** overwritten by a later parent `configure` call.
|
|
305
|
+
- Configure the parent first, then override specific children.
|
|
222
306
|
|
|
223
|
-
|
|
307
|
+
---
|
|
224
308
|
|
|
225
|
-
|
|
226
|
-
2. **Production Simple Tasks**: Continue with cheaper models if quality is sufficient
|
|
227
|
-
3. **Production Complex Tasks**: Upgrade to more capable models (gpt-4o, claude-3.5-sonnet, gemini-1.5-pro)
|
|
228
|
-
4. **Local Development**: Use Ollama for privacy and zero API costs
|
|
309
|
+
## Feature-Flagged Model Selection
|
|
229
310
|
|
|
230
|
-
|
|
311
|
+
Use a `FeatureFlags` module backed by ENV vars to centralize model selection. Each tool or agent reads its model from the flags, falling back to a global default.
|
|
231
312
|
|
|
232
313
|
```ruby
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
314
|
+
module FeatureFlags
|
|
315
|
+
module_function
|
|
316
|
+
|
|
317
|
+
def default_model
|
|
318
|
+
ENV.fetch('DSPY_DEFAULT_MODEL', 'openai/gpt-4o-mini')
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def default_api_key
|
|
322
|
+
ENV.fetch('DSPY_DEFAULT_API_KEY') { ENV.fetch('OPENAI_API_KEY', nil) }
|
|
237
323
|
end
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
324
|
+
|
|
325
|
+
def model_for(tool_name)
|
|
326
|
+
env_key = "DSPY_MODEL_#{tool_name.upcase}"
|
|
327
|
+
ENV.fetch(env_key, default_model)
|
|
242
328
|
end
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
329
|
+
|
|
330
|
+
def api_key_for(tool_name)
|
|
331
|
+
env_key = "DSPY_API_KEY_#{tool_name.upcase}"
|
|
332
|
+
ENV.fetch(env_key, default_api_key)
|
|
247
333
|
end
|
|
248
334
|
end
|
|
249
335
|
```
|
|
250
336
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
### OpenAI
|
|
337
|
+
### Per-Tool Model Override
|
|
254
338
|
|
|
255
|
-
|
|
256
|
-
- Use `gpt-4o` for production complex tasks
|
|
257
|
-
- Best vision support including URL loading
|
|
258
|
-
- Excellent function calling capabilities
|
|
339
|
+
Override an individual tool's model without touching application code:
|
|
259
340
|
|
|
260
|
-
|
|
341
|
+
```bash
|
|
342
|
+
# .env
|
|
343
|
+
DSPY_DEFAULT_MODEL=openai/gpt-4o-mini
|
|
344
|
+
DSPY_DEFAULT_API_KEY=sk-...
|
|
261
345
|
|
|
262
|
-
|
|
263
|
-
-
|
|
264
|
-
-
|
|
265
|
-
- Requires base64 for images (no URL support)
|
|
346
|
+
# Override the classifier to use Claude
|
|
347
|
+
DSPY_MODEL_CLASSIFIER=anthropic/claude-sonnet-4-20250514
|
|
348
|
+
DSPY_API_KEY_CLASSIFIER=sk-ant-...
|
|
266
349
|
|
|
267
|
-
|
|
350
|
+
# Override the summarizer to use Gemini
|
|
351
|
+
DSPY_MODEL_SUMMARIZER=gemini/gemini-2.5-flash
|
|
352
|
+
DSPY_API_KEY_SUMMARIZER=...
|
|
353
|
+
```
|
|
268
354
|
|
|
269
|
-
|
|
270
|
-
- Strong multimodal capabilities
|
|
271
|
-
- Good balance of cost and performance
|
|
272
|
-
- Requires base64 for images
|
|
355
|
+
Wire each agent to its flag at initialization:
|
|
273
356
|
|
|
274
|
-
|
|
357
|
+
```ruby
|
|
358
|
+
class ClassifierAgent < DSPy::Module
|
|
359
|
+
def initialize
|
|
360
|
+
super
|
|
361
|
+
model = FeatureFlags.model_for('classifier')
|
|
362
|
+
api_key = FeatureFlags.api_key_for('classifier')
|
|
275
363
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
- Limited multimodal support depending on model
|
|
280
|
-
- Good for development and testing
|
|
364
|
+
@predictor = DSPy::Predict.new(ClassifySignature)
|
|
365
|
+
configure { |c| c.lm = DSPy::LM.new(model, api_key: api_key) }
|
|
366
|
+
end
|
|
281
367
|
|
|
282
|
-
|
|
368
|
+
def forward(text:)
|
|
369
|
+
@predictor.call(text: text)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
```
|
|
283
373
|
|
|
284
|
-
|
|
374
|
+
This pattern keeps model routing declarative and avoids scattering `DSPy::LM.new` calls across the codebase.
|
|
285
375
|
|
|
286
|
-
|
|
287
|
-
# Verify API key is set
|
|
288
|
-
if ENV['OPENAI_API_KEY'].nil?
|
|
289
|
-
raise "OPENAI_API_KEY environment variable not set"
|
|
290
|
-
end
|
|
376
|
+
---
|
|
291
377
|
|
|
292
|
-
|
|
293
|
-
begin
|
|
294
|
-
DSPy.configure { |c| c.lm = DSPy::LM.new('openai/gpt-4o-mini',
|
|
295
|
-
api_key: ENV['OPENAI_API_KEY']) }
|
|
296
|
-
predictor = DSPy::Predict.new(TestSignature)
|
|
297
|
-
predictor.forward(test: "data")
|
|
298
|
-
puts "✅ Connection successful"
|
|
299
|
-
rescue => e
|
|
300
|
-
puts "❌ Connection failed: #{e.message}"
|
|
301
|
-
end
|
|
302
|
-
```
|
|
378
|
+
## Compatibility Matrix
|
|
303
379
|
|
|
304
|
-
|
|
380
|
+
Feature support across direct adapter gems. All features listed assume `structured_outputs: true` (the default).
|
|
305
381
|
|
|
306
|
-
|
|
382
|
+
| Feature | OpenAI | Anthropic | Gemini | Ollama | OpenRouter | RubyLLM |
|
|
383
|
+
|----------------------|--------|-----------|--------|----------|------------|-------------|
|
|
384
|
+
| Structured Output | Native JSON mode | Tool-based extraction | Native JSON schema | OpenAI-compatible JSON | Varies by model | Via `with_schema` |
|
|
385
|
+
| Vision (Images) | File + URL | File + Base64 | File + Base64 | Limited | Varies | Delegates to underlying provider |
|
|
386
|
+
| Image URLs | Yes | No | No | No | Varies | Depends on provider |
|
|
387
|
+
| Tool Calling | Yes | Yes | Yes | Varies | Varies | Yes |
|
|
388
|
+
| Streaming | Yes | Yes | Yes | Yes | Yes | Yes |
|
|
307
389
|
|
|
308
|
-
|
|
309
|
-
def call_with_retry(predictor, input, max_retries: 3)
|
|
310
|
-
retries = 0
|
|
311
|
-
begin
|
|
312
|
-
predictor.forward(input)
|
|
313
|
-
rescue RateLimitError => e
|
|
314
|
-
retries += 1
|
|
315
|
-
if retries < max_retries
|
|
316
|
-
sleep(2 ** retries) # Exponential backoff
|
|
317
|
-
retry
|
|
318
|
-
else
|
|
319
|
-
raise
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
```
|
|
390
|
+
**Notes:**
|
|
324
391
|
|
|
325
|
-
|
|
392
|
+
- **Structured Output** is enabled by default on every adapter. Set `structured_outputs: false` to fall back to enhanced-prompting extraction.
|
|
393
|
+
- **Vision / Image URLs:** Only OpenAI supports passing a URL directly. For Anthropic and Gemini, load images from file or Base64:
|
|
394
|
+
```ruby
|
|
395
|
+
DSPy::Image.from_url("https://example.com/img.jpg") # OpenAI only
|
|
396
|
+
DSPy::Image.from_file("path/to/image.jpg") # all providers
|
|
397
|
+
DSPy::Image.from_base64(data, mime_type: "image/jpeg") # all providers
|
|
398
|
+
```
|
|
399
|
+
- **RubyLLM** delegates to the underlying provider, so feature support matches the provider column in the table.
|
|
326
400
|
|
|
327
|
-
|
|
401
|
+
### Choosing an Adapter Strategy
|
|
328
402
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
gem
|
|
403
|
+
| Scenario | Recommended Adapter |
|
|
404
|
+
|-------------------------------------------|--------------------------------|
|
|
405
|
+
| Single provider (OpenAI, Claude, or Gemini) | Dedicated gem (`dspy-openai`, `dspy-anthropic`, `dspy-gemini`) |
|
|
406
|
+
| Multi-provider with per-agent model routing | `dspy-ruby_llm` |
|
|
407
|
+
| AWS Bedrock or Google VertexAI | `dspy-ruby_llm` |
|
|
408
|
+
| Local development with Ollama | `dspy-openai` (Ollama sub-adapter) or `dspy-ruby_llm` |
|
|
409
|
+
| OpenRouter for cost optimization | `dspy-openai` (OpenRouter sub-adapter) |
|
|
332
410
|
|
|
333
|
-
|
|
334
|
-
gem install dspy-anthropic
|
|
411
|
+
### Current Recommended Models
|
|
335
412
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
413
|
+
| Provider | Model ID | Use Case |
|
|
414
|
+
|-----------|---------------------------------------|-----------------------|
|
|
415
|
+
| OpenAI | `openai/gpt-4o-mini` | Fast, cost-effective |
|
|
416
|
+
| Anthropic | `anthropic/claude-sonnet-4-20250514` | Balanced reasoning |
|
|
417
|
+
| Gemini | `gemini/gemini-2.5-flash` | Fast, cost-effective |
|
|
418
|
+
| Ollama | `ollama/llama3.2` | Local, zero API cost |
|