@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.
Files changed (50) hide show
  1. package/{plugins/compound-engineering → .claude}/commands/release-docs.md +0 -1
  2. package/.claude-plugin/marketplace.json +2 -2
  3. package/.github/workflows/ci.yml +1 -1
  4. package/.github/workflows/deploy-docs.yml +3 -3
  5. package/.github/workflows/publish.yml +37 -0
  6. package/README.md +12 -3
  7. package/docs/index.html +13 -13
  8. package/docs/pages/changelog.html +39 -0
  9. package/docs/plans/2026-02-08-feat-convert-local-md-settings-for-opencode-codex-plan.md +143 -0
  10. package/docs/plans/2026-02-08-feat-simplify-plugin-settings-plan.md +195 -0
  11. package/docs/plans/2026-02-09-refactor-dspy-ruby-skill-update-plan.md +104 -0
  12. package/docs/plans/2026-02-12-feat-add-cursor-cli-target-provider-plan.md +306 -0
  13. package/docs/specs/cursor.md +85 -0
  14. package/package.json +1 -1
  15. package/plugins/compound-engineering/.claude-plugin/plugin.json +2 -2
  16. package/plugins/compound-engineering/CHANGELOG.md +38 -0
  17. package/plugins/compound-engineering/README.md +5 -3
  18. package/plugins/compound-engineering/commands/workflows/brainstorm.md +6 -1
  19. package/plugins/compound-engineering/commands/workflows/compound.md +1 -0
  20. package/plugins/compound-engineering/commands/workflows/review.md +23 -21
  21. package/plugins/compound-engineering/commands/workflows/work.md +29 -15
  22. package/plugins/compound-engineering/skills/dspy-ruby/SKILL.md +539 -396
  23. package/plugins/compound-engineering/skills/dspy-ruby/assets/config-template.rb +159 -331
  24. package/plugins/compound-engineering/skills/dspy-ruby/assets/module-template.rb +210 -236
  25. package/plugins/compound-engineering/skills/dspy-ruby/assets/signature-template.rb +173 -95
  26. package/plugins/compound-engineering/skills/dspy-ruby/references/core-concepts.md +552 -143
  27. package/plugins/compound-engineering/skills/dspy-ruby/references/observability.md +366 -0
  28. package/plugins/compound-engineering/skills/dspy-ruby/references/optimization.md +440 -460
  29. package/plugins/compound-engineering/skills/dspy-ruby/references/providers.md +305 -225
  30. package/plugins/compound-engineering/skills/dspy-ruby/references/toolsets.md +502 -0
  31. package/plugins/compound-engineering/skills/setup/SKILL.md +168 -0
  32. package/src/commands/convert.ts +10 -5
  33. package/src/commands/install.ts +18 -10
  34. package/src/converters/claude-to-codex.ts +7 -2
  35. package/src/converters/claude-to-cursor.ts +166 -0
  36. package/src/converters/claude-to-droid.ts +174 -0
  37. package/src/converters/claude-to-opencode.ts +8 -2
  38. package/src/targets/cursor.ts +48 -0
  39. package/src/targets/droid.ts +50 -0
  40. package/src/targets/index.ts +18 -0
  41. package/src/types/cursor.ts +29 -0
  42. package/src/types/droid.ts +20 -0
  43. package/tests/cli.test.ts +62 -0
  44. package/tests/codex-converter.test.ts +62 -0
  45. package/tests/converter.test.ts +61 -0
  46. package/tests/cursor-converter.test.ts +347 -0
  47. package/tests/cursor-writer.test.ts +137 -0
  48. package/tests/droid-converter.test.ts +277 -0
  49. package/tests/droid-writer.test.ts +100 -0
  50. package/plugins/compound-engineering/commands/technical_review.md +0 -8
@@ -1,338 +1,418 @@
1
1
  # DSPy.rb LLM Providers
2
2
 
3
- ## Supported Providers
3
+ ## Adapter Architecture
4
4
 
5
- DSPy.rb provides unified support across multiple LLM providers through adapter gems that automatically load when installed.
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
- ### Provider Overview
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
- - **OpenAI**: GPT-4, GPT-4o, GPT-4o-mini, GPT-3.5-turbo
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
- ## Configuration
22
+ Covers any endpoint that speaks the OpenAI chat-completions protocol: OpenAI itself, OpenRouter, and Ollama.
16
23
 
17
- ### Basic Setup
24
+ **SDK dependency:** `openai ~> 0.17`
18
25
 
19
26
  ```ruby
20
- require 'dspy'
27
+ # OpenAI
28
+ lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
21
29
 
22
- DSPy.configure do |c|
23
- c.lm = DSPy::LM.new('provider/model-name', api_key: ENV['API_KEY'])
24
- end
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
- ### OpenAI Configuration
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
- **Required gem**: `dspy-openai`
47
+ For OpenRouter models that lack native structured-output support, disable it explicitly:
30
48
 
31
49
  ```ruby
32
- DSPy.configure do |c|
33
- # GPT-4o Mini (recommended for development)
34
- c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
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
- # GPT-4o (more capable)
37
- c.lm = DSPy::LM.new('openai/gpt-4o', api_key: ENV['OPENAI_API_KEY'])
56
+ ### dspy-anthropic
38
57
 
39
- # GPT-4 Turbo
40
- c.lm = DSPy::LM.new('openai/gpt-4-turbo', api_key: ENV['OPENAI_API_KEY'])
41
- end
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
- **Environment variable**: `OPENAI_API_KEY`
84
+ ### dspy-gemini
45
85
 
46
- ### Anthropic Configuration
86
+ Provides the Gemini adapter. Install it for any `gemini/*` model id.
47
87
 
48
- **Required gem**: `dspy-anthropic`
88
+ **SDK dependency:** `gemini-ai ~> 4.3`
49
89
 
50
90
  ```ruby
51
- DSPy.configure do |c|
52
- # Claude 3.5 Sonnet (latest, most capable)
53
- c.lm = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
54
- api_key: ENV['ANTHROPIC_API_KEY'])
91
+ lm = DSPy::LM.new('gemini/gemini-2.5-flash',
92
+ api_key: ENV['GEMINI_API_KEY']
93
+ )
94
+ ```
55
95
 
56
- # Claude 3 Opus (most capable in Claude 3 family)
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
- # Claude 3 Sonnet (balanced)
61
- c.lm = DSPy::LM.new('anthropic/claude-3-sonnet-20240229',
62
- api_key: ENV['ANTHROPIC_API_KEY'])
98
+ ---
63
99
 
64
- # Claude 3 Haiku (fast, cost-effective)
65
- c.lm = DSPy::LM.new('anthropic/claude-3-haiku-20240307',
66
- api_key: ENV['ANTHROPIC_API_KEY'])
67
- end
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
- **Environment variable**: `ANTHROPIC_API_KEY`
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
- ### Google Gemini Configuration
126
+ ### Using Existing RubyLLM Configuration
73
127
 
74
- **Required gem**: `dspy-gemini`
128
+ When RubyLLM is already configured globally, omit the `api_key:` argument. DSPy reuses the global config automatically:
75
129
 
76
130
  ```ruby
77
- DSPy.configure do |c|
78
- # Gemini 1.5 Pro (most capable)
79
- c.lm = DSPy::LM.new('gemini/gemini-1.5-pro',
80
- api_key: ENV['GOOGLE_API_KEY'])
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
- # Gemini 1.5 Flash (faster, cost-effective)
83
- c.lm = DSPy::LM.new('gemini/gemini-1.5-flash',
84
- api_key: ENV['GOOGLE_API_KEY'])
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
- **Environment variable**: `GOOGLE_API_KEY` or `GEMINI_API_KEY`
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
- ### Ollama Configuration
144
+ ### Cloud-Hosted Providers (Bedrock, VertexAI)
91
145
 
92
- **Required gem**: None (uses OpenAI compatibility layer)
146
+ Configure RubyLLM globally first, then reference the model:
93
147
 
94
148
  ```ruby
95
- DSPy.configure do |c|
96
- # Local Ollama instance
97
- c.lm = DSPy::LM.new('ollama/llama3.1',
98
- base_url: 'http://localhost:11434')
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
- # Other Ollama models
101
- c.lm = DSPy::LM.new('ollama/mistral')
102
- c.lm = DSPy::LM.new('ollama/codellama')
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
- **Note**: Ensure Ollama is running locally: `ollama serve`
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
- ### OpenRouter Configuration
183
+ ## Rails Initializer Pattern
109
184
 
110
- **Required gem**: `dspy-openai` (uses OpenAI adapter)
185
+ Configure DSPy inside an `after_initialize` block so Rails credentials and environment are fully loaded:
111
186
 
112
187
  ```ruby
113
- DSPy.configure do |c|
114
- # Access 200+ models through OpenRouter
115
- c.lm = DSPy::LM.new('openrouter/anthropic/claude-3.5-sonnet',
116
- api_key: ENV['OPENROUTER_API_KEY'],
117
- base_url: 'https://openrouter.ai/api/v1')
118
-
119
- # Other examples
120
- c.lm = DSPy::LM.new('openrouter/google/gemini-pro')
121
- c.lm = DSPy::LM.new('openrouter/meta-llama/llama-3.1-70b-instruct')
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
- **Environment variable**: `OPENROUTER_API_KEY`
212
+ Key points:
126
213
 
127
- ## Provider Compatibility Matrix
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
- ### Feature Support
219
+ ---
130
220
 
131
- | Feature | OpenAI | Anthropic | Gemini | Ollama |
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
- **Legend**: Full support | ⚠️ Partial support | Not supported
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
- ### Vision Capabilities
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
- **Image URLs**: Only OpenAI supports direct URL references. For other providers, load images as base64 or from files.
229
+ classifier = Classifier.new
145
230
 
146
- ```ruby
147
- # OpenAI - supports URLs
148
- DSPy::Image.from_url("https://example.com/image.jpg")
231
+ # Uses the global LM
232
+ result = classifier.call(text: "Hello")
149
233
 
150
- # Anthropic, Gemini - use file or base64
151
- DSPy::Image.from_file("path/to/image.jpg")
152
- DSPy::Image.from_base64(base64_data, mime_type: "image/jpeg")
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
- **Ollama**: Limited multimodal functionality. Check specific model capabilities.
245
+ ### LM Resolution Hierarchy
156
246
 
157
- ## Advanced Configuration
247
+ DSPy resolves the active language model in this order:
158
248
 
159
- ### Custom Parameters
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
- Pass provider-specific parameters during configuration:
253
+ Instance-level configuration always wins, even inside a `DSPy.with_lm` block:
162
254
 
163
255
  ```ruby
164
- DSPy.configure do |c|
165
- c.lm = DSPy::LM.new('openai/gpt-4o',
166
- api_key: ENV['OPENAI_API_KEY'],
167
- temperature: 0.7,
168
- max_tokens: 2000,
169
- top_p: 0.9
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
- ### Multiple Providers
266
+ ### configure_predictor for Fine-Grained Agent Control
175
267
 
176
- Use different models for different tasks:
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
- # Fast model for simple tasks
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
- # Powerful model for complex tasks
183
- powerful_lm = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
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
- # Use different models in different modules
187
- class SimpleClassifier < DSPy::Module
188
- def initialize
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
- class ComplexAnalyzer < DSPy::Module
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
- ### Per-Request Configuration
205
-
206
- Override configuration for specific predictions:
284
+ Both methods support chaining:
207
285
 
208
286
  ```ruby
209
- predictor = DSPy::Predict.new(MySignature)
287
+ agent
288
+ .configure { |c| c.lm = cheap_model }
289
+ .configure_predictor('thought_generator') { |c| c.lm = expensive_model }
290
+ ```
210
291
 
211
- # Use default configuration
212
- result1 = predictor.forward(input: "data")
292
+ #### Available Predictors by Agent Type
213
293
 
214
- # Override temperature for this request
215
- result2 = predictor.forward(
216
- input: "data",
217
- config: { temperature: 0.2 } # More deterministic
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
- ## Cost Optimization
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
- ### Model Selection Strategy
307
+ ---
224
308
 
225
- 1. **Development**: Use cheaper, faster models (gpt-4o-mini, claude-3-haiku, gemini-1.5-flash)
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
- ### Example Cost-Conscious Setup
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
- # Development environment
234
- if Rails.env.development?
235
- DSPy.configure do |c|
236
- c.lm = DSPy::LM.new('ollama/llama3.1') # Free, local
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
- elsif Rails.env.test?
239
- DSPy.configure do |c|
240
- c.lm = DSPy::LM.new('openai/gpt-4o-mini', # Cheap for testing
241
- api_key: ENV['OPENAI_API_KEY'])
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
- else # production
244
- DSPy.configure do |c|
245
- c.lm = DSPy::LM.new('anthropic/claude-3-5-sonnet-20241022',
246
- api_key: ENV['ANTHROPIC_API_KEY'])
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
- ## Provider-Specific Best Practices
252
-
253
- ### OpenAI
337
+ ### Per-Tool Model Override
254
338
 
255
- - Use `gpt-4o-mini` for development and simple tasks
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
- ### Anthropic
341
+ ```bash
342
+ # .env
343
+ DSPY_DEFAULT_MODEL=openai/gpt-4o-mini
344
+ DSPY_DEFAULT_API_KEY=sk-...
261
345
 
262
- - Claude 3.5 Sonnet is currently the most capable model
263
- - Excellent for complex reasoning and analysis
264
- - Strong safety features and helpful outputs
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
- ### Google Gemini
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
- - Gemini 1.5 Pro for complex tasks, Flash for speed
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
- ### Ollama
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
- - Best for privacy-sensitive applications
277
- - Zero API costs
278
- - Requires local hardware resources
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
- ## Troubleshooting
368
+ def forward(text:)
369
+ @predictor.call(text: text)
370
+ end
371
+ end
372
+ ```
283
373
 
284
- ### API Key Issues
374
+ This pattern keeps model routing declarative and avoids scattering `DSPy::LM.new` calls across the codebase.
285
375
 
286
- ```ruby
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
- # Test connection
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
- ### Rate Limiting
380
+ Feature support across direct adapter gems. All features listed assume `structured_outputs: true` (the default).
305
381
 
306
- Handle rate limits gracefully:
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
- ```ruby
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
- ### Model Not Found
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
- Ensure the correct gem is installed:
401
+ ### Choosing an Adapter Strategy
328
402
 
329
- ```bash
330
- # For OpenAI
331
- gem install dspy-openai
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
- # For Anthropic
334
- gem install dspy-anthropic
411
+ ### Current Recommended Models
335
412
 
336
- # For Gemini
337
- gem install dspy-gemini
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 |