@every-env/compound-plugin 0.2.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-08-refactor-reduce-plugin-context-token-usage-plan.md +212 -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 +64 -0
- package/plugins/compound-engineering/README.md +5 -3
- package/plugins/compound-engineering/agents/design/design-implementation-reviewer.md +16 -1
- package/plugins/compound-engineering/agents/design/design-iterator.md +28 -1
- package/plugins/compound-engineering/agents/design/figma-design-sync.md +19 -1
- package/plugins/compound-engineering/agents/docs/ankane-readme-writer.md +16 -1
- package/plugins/compound-engineering/agents/research/best-practices-researcher.md +16 -1
- package/plugins/compound-engineering/agents/research/framework-docs-researcher.md +16 -1
- package/plugins/compound-engineering/agents/research/git-history-analyzer.md +16 -1
- package/plugins/compound-engineering/agents/research/learnings-researcher.md +22 -1
- package/plugins/compound-engineering/agents/research/repo-research-analyst.md +22 -1
- package/plugins/compound-engineering/agents/review/agent-native-reviewer.md +16 -1
- package/plugins/compound-engineering/agents/review/architecture-strategist.md +16 -1
- package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +16 -1
- package/plugins/compound-engineering/agents/review/data-integrity-guardian.md +16 -1
- package/plugins/compound-engineering/agents/review/data-migration-expert.md +16 -1
- package/plugins/compound-engineering/agents/review/deployment-verification-agent.md +16 -1
- package/plugins/compound-engineering/agents/review/dhh-rails-reviewer.md +22 -1
- package/plugins/compound-engineering/agents/review/julik-frontend-races-reviewer.md +20 -21
- package/plugins/compound-engineering/agents/review/kieran-python-reviewer.md +30 -1
- package/plugins/compound-engineering/agents/review/kieran-rails-reviewer.md +30 -1
- package/plugins/compound-engineering/agents/review/kieran-typescript-reviewer.md +30 -1
- package/plugins/compound-engineering/agents/review/pattern-recognition-specialist.md +16 -1
- package/plugins/compound-engineering/agents/review/performance-oracle.md +28 -1
- package/plugins/compound-engineering/agents/review/schema-drift-detector.md +16 -1
- package/plugins/compound-engineering/agents/review/security-sentinel.md +22 -1
- package/plugins/compound-engineering/agents/workflow/bug-reproduction-validator.md +16 -1
- package/plugins/compound-engineering/agents/workflow/every-style-editor.md +1 -1
- package/plugins/compound-engineering/agents/workflow/pr-comment-resolver.md +16 -1
- package/plugins/compound-engineering/agents/workflow/spec-flow-analyzer.md +22 -1
- package/plugins/compound-engineering/commands/agent-native-audit.md +1 -0
- package/plugins/compound-engineering/commands/changelog.md +1 -0
- package/plugins/compound-engineering/commands/create-agent-skill.md +1 -0
- package/plugins/compound-engineering/commands/deploy-docs.md +1 -0
- package/plugins/compound-engineering/commands/generate_command.md +1 -0
- package/plugins/compound-engineering/commands/heal-skill.md +1 -0
- package/plugins/compound-engineering/commands/lfg.md +1 -0
- package/plugins/compound-engineering/commands/report-bug.md +1 -0
- package/plugins/compound-engineering/commands/reproduce-bug.md +1 -0
- package/plugins/compound-engineering/commands/resolve_parallel.md +1 -0
- package/plugins/compound-engineering/commands/slfg.md +1 -0
- package/plugins/compound-engineering/commands/{xcode-test.md → test-xcode.md} +2 -1
- package/plugins/compound-engineering/commands/triage.md +1 -0
- 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/compound-docs/SKILL.md +1 -0
- 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/file-todos/SKILL.md +1 -0
- package/plugins/compound-engineering/skills/orchestrating-swarms/SKILL.md +1 -0
- package/plugins/compound-engineering/skills/setup/SKILL.md +168 -0
- package/plugins/compound-engineering/skills/skill-creator/SKILL.md +1 -0
- package/src/commands/convert.ts +10 -5
- package/src/commands/install.ts +10 -5
- package/src/converters/claude-to-codex.ts +9 -3
- 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 +9 -2
- package/src/parsers/claude.ts +4 -0
- package/src/targets/cursor.ts +48 -0
- package/src/targets/droid.ts +50 -0
- package/src/targets/index.ts +18 -0
- package/src/types/claude.ts +2 -0
- package/src/types/cursor.ts +29 -0
- package/src/types/droid.ts +20 -0
- package/tests/claude-parser.test.ts +24 -2
- package/tests/codex-converter.test.ts +100 -0
- package/tests/converter.test.ts +76 -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/tests/fixtures/sample-plugin/commands/disabled-command.md +7 -0
- package/tests/fixtures/sample-plugin/skills/disabled-skill/SKILL.md +7 -0
- package/plugins/compound-engineering/commands/technical_review.md +0 -7
- /package/{plugins/compound-engineering → .claude}/commands/release-docs.md +0 -0
|
@@ -1,326 +1,300 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# DSPy.rb Module Template — v0.34.3 API
|
|
5
|
+
#
|
|
6
|
+
# Modules orchestrate predictors, tools, and business logic.
|
|
7
|
+
#
|
|
8
|
+
# Key patterns:
|
|
9
|
+
# - Use .call() to invoke (not .forward())
|
|
10
|
+
# - Access results with result.field (not result[:field])
|
|
11
|
+
# - Use DSPy::Tools::Base for tools (not DSPy::Tool)
|
|
12
|
+
# - Use lifecycle callbacks (before/around/after) for cross-cutting concerns
|
|
13
|
+
# - Use DSPy.with_lm for temporary model overrides
|
|
14
|
+
# - Use configure_predictor for fine-grained agent control
|
|
15
|
+
# =============================================================================
|
|
16
|
+
|
|
17
|
+
# --- Basic Module ---
|
|
18
|
+
|
|
19
|
+
class BasicClassifier < DSPy::Module
|
|
8
20
|
def initialize
|
|
9
21
|
super
|
|
10
|
-
|
|
11
|
-
@predictor = DSPy::Predict.new(ExampleSignature)
|
|
22
|
+
@predictor = DSPy::Predict.new(ClassificationSignature)
|
|
12
23
|
end
|
|
13
24
|
|
|
14
|
-
def forward(
|
|
15
|
-
|
|
16
|
-
@predictor.forward(input_hash)
|
|
25
|
+
def forward(text:)
|
|
26
|
+
@predictor.call(text: text)
|
|
17
27
|
end
|
|
18
28
|
end
|
|
19
29
|
|
|
20
|
-
#
|
|
21
|
-
|
|
30
|
+
# Usage:
|
|
31
|
+
# classifier = BasicClassifier.new
|
|
32
|
+
# result = classifier.call(text: "This is a test")
|
|
33
|
+
# result.category # => "technical"
|
|
34
|
+
# result.confidence # => 0.95
|
|
35
|
+
|
|
36
|
+
# --- Module with Chain of Thought ---
|
|
37
|
+
|
|
38
|
+
class ReasoningClassifier < DSPy::Module
|
|
22
39
|
def initialize
|
|
23
40
|
super
|
|
24
|
-
|
|
25
|
-
@predictor = DSPy::ChainOfThought.new(EmailClassificationSignature)
|
|
41
|
+
@predictor = DSPy::ChainOfThought.new(ClassificationSignature)
|
|
26
42
|
end
|
|
27
43
|
|
|
28
|
-
def forward(
|
|
29
|
-
result = @predictor.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
def forward(text:)
|
|
45
|
+
result = @predictor.call(text: text)
|
|
46
|
+
# ChainOfThought adds result.reasoning automatically
|
|
47
|
+
result
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# --- Module with Lifecycle Callbacks ---
|
|
52
|
+
|
|
53
|
+
class InstrumentedModule < DSPy::Module
|
|
54
|
+
before :setup_metrics
|
|
55
|
+
around :manage_context
|
|
56
|
+
after :log_completion
|
|
33
57
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
58
|
+
def initialize
|
|
59
|
+
super
|
|
60
|
+
@predictor = DSPy::Predict.new(AnalysisSignature)
|
|
61
|
+
@start_time = nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def forward(query:)
|
|
65
|
+
@predictor.call(query: query)
|
|
41
66
|
end
|
|
42
67
|
|
|
43
68
|
private
|
|
44
69
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
70
|
+
# Runs before forward
|
|
71
|
+
def setup_metrics
|
|
72
|
+
@start_time = Time.now
|
|
73
|
+
Rails.logger.info "Starting prediction"
|
|
49
74
|
end
|
|
75
|
+
|
|
76
|
+
# Wraps forward — must call yield
|
|
77
|
+
def manage_context
|
|
78
|
+
load_user_context
|
|
79
|
+
result = yield
|
|
80
|
+
save_updated_context(result)
|
|
81
|
+
result
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Runs after forward completes
|
|
85
|
+
def log_completion
|
|
86
|
+
duration = Time.now - @start_time
|
|
87
|
+
Rails.logger.info "Prediction completed in #{duration}s"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def load_user_context = nil
|
|
91
|
+
def save_updated_context(_result) = nil
|
|
50
92
|
end
|
|
51
93
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
94
|
+
# Execution order: before → around (before yield) → forward → around (after yield) → after
|
|
95
|
+
# Callbacks are inherited from parent classes and execute in registration order.
|
|
96
|
+
|
|
97
|
+
# --- Module with Tools ---
|
|
98
|
+
|
|
99
|
+
class SearchTool < DSPy::Tools::Base
|
|
100
|
+
tool_name "search"
|
|
101
|
+
tool_description "Search for information by query"
|
|
102
|
+
|
|
103
|
+
sig { params(query: String, max_results: Integer).returns(T::Array[T::Hash[Symbol, String]]) }
|
|
104
|
+
def call(query:, max_results: 5)
|
|
105
|
+
# Implementation here
|
|
106
|
+
[{ title: "Result 1", url: "https://example.com" }]
|
|
60
107
|
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class FinishTool < DSPy::Tools::Base
|
|
111
|
+
tool_name "finish"
|
|
112
|
+
tool_description "Submit the final answer"
|
|
61
113
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
result2 = @step2.forward(result1)
|
|
66
|
-
result3 = @step3.forward(result2)
|
|
67
|
-
|
|
68
|
-
# Combine results as needed
|
|
69
|
-
{
|
|
70
|
-
step1_output: result1,
|
|
71
|
-
step2_output: result2,
|
|
72
|
-
final_result: result3
|
|
73
|
-
}
|
|
114
|
+
sig { params(answer: String).returns(String) }
|
|
115
|
+
def call(answer:)
|
|
116
|
+
answer
|
|
74
117
|
end
|
|
75
118
|
end
|
|
76
119
|
|
|
77
|
-
|
|
78
|
-
class ConditionalModule < DSPy::Module
|
|
120
|
+
class ResearchAgent < DSPy::Module
|
|
79
121
|
def initialize
|
|
80
122
|
super
|
|
81
|
-
|
|
82
|
-
@
|
|
123
|
+
tools = [SearchTool.new, FinishTool.new]
|
|
124
|
+
@agent = DSPy::ReAct.new(
|
|
125
|
+
ResearchSignature,
|
|
126
|
+
tools: tools,
|
|
127
|
+
max_iterations: 5
|
|
128
|
+
)
|
|
83
129
|
end
|
|
84
130
|
|
|
85
|
-
def forward(
|
|
86
|
-
|
|
87
|
-
if text.length < complexity_threshold
|
|
88
|
-
@simple_classifier.forward(text: text)
|
|
89
|
-
else
|
|
90
|
-
@complex_analyzer.forward(text: text)
|
|
91
|
-
end
|
|
131
|
+
def forward(question:)
|
|
132
|
+
@agent.call(question: question)
|
|
92
133
|
end
|
|
93
134
|
end
|
|
94
135
|
|
|
95
|
-
# Module with
|
|
96
|
-
class RobustModule < DSPy::Module
|
|
97
|
-
MAX_RETRIES = 3
|
|
136
|
+
# --- Module with Per-Task Model Selection ---
|
|
98
137
|
|
|
138
|
+
class SmartRouter < DSPy::Module
|
|
99
139
|
def initialize
|
|
100
140
|
super
|
|
101
|
-
@
|
|
102
|
-
@
|
|
141
|
+
@classifier = DSPy::Predict.new(RouteSignature)
|
|
142
|
+
@analyzer = DSPy::ChainOfThought.new(AnalysisSignature)
|
|
103
143
|
end
|
|
104
144
|
|
|
105
|
-
def forward(
|
|
106
|
-
|
|
145
|
+
def forward(text:)
|
|
146
|
+
# Use fast model for classification
|
|
147
|
+
DSPy.with_lm(fast_model) do
|
|
148
|
+
route = @classifier.call(text: text)
|
|
107
149
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@logger.error "Validation error: #{e.message}"
|
|
114
|
-
|
|
115
|
-
if retry_count < MAX_RETRIES
|
|
116
|
-
@logger.info "Retrying (#{retry_count + 1}/#{MAX_RETRIES})..."
|
|
117
|
-
sleep(2 ** retry_count) # Exponential backoff
|
|
118
|
-
forward(input, retry_count: retry_count + 1)
|
|
150
|
+
if route.requires_deep_analysis
|
|
151
|
+
# Switch to powerful model for analysis
|
|
152
|
+
DSPy.with_lm(powerful_model) do
|
|
153
|
+
@analyzer.call(text: text)
|
|
154
|
+
end
|
|
119
155
|
else
|
|
120
|
-
|
|
121
|
-
raise
|
|
156
|
+
route
|
|
122
157
|
end
|
|
123
158
|
end
|
|
124
159
|
end
|
|
125
160
|
|
|
126
161
|
private
|
|
127
162
|
|
|
128
|
-
def
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
163
|
+
def fast_model
|
|
164
|
+
@fast_model ||= DSPy::LM.new(
|
|
165
|
+
ENV.fetch("DSPY_SELECTOR_MODEL", "ruby_llm/gemini-2.5-flash-lite"),
|
|
166
|
+
structured_outputs: true
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def powerful_model
|
|
171
|
+
@powerful_model ||= DSPy::LM.new(
|
|
172
|
+
ENV.fetch("DSPY_SYNTHESIZER_MODEL", "ruby_llm/gemini-2.5-flash"),
|
|
173
|
+
structured_outputs: true
|
|
174
|
+
)
|
|
132
175
|
end
|
|
133
176
|
end
|
|
134
177
|
|
|
135
|
-
# Module with
|
|
136
|
-
|
|
178
|
+
# --- Module with configure_predictor ---
|
|
179
|
+
|
|
180
|
+
class ConfiguredAgent < DSPy::Module
|
|
137
181
|
def initialize
|
|
138
182
|
super
|
|
183
|
+
tools = [SearchTool.new, FinishTool.new]
|
|
184
|
+
@agent = DSPy::ReAct.new(ResearchSignature, tools: tools)
|
|
139
185
|
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
SearchTool.new,
|
|
143
|
-
CalculatorTool.new,
|
|
144
|
-
DatabaseQueryTool.new
|
|
145
|
-
]
|
|
186
|
+
# Set default model for all internal predictors
|
|
187
|
+
@agent.configure { |c| c.lm = DSPy::LM.new('ruby_llm/gemini-2.5-flash', structured_outputs: true) }
|
|
146
188
|
|
|
147
|
-
#
|
|
148
|
-
@agent
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
max_iterations: 5
|
|
152
|
-
)
|
|
189
|
+
# Override specific predictor with a more capable model
|
|
190
|
+
@agent.configure_predictor('thought_generator') do |c|
|
|
191
|
+
c.lm = DSPy::LM.new('ruby_llm/claude-sonnet-4-20250514', structured_outputs: true)
|
|
192
|
+
end
|
|
153
193
|
end
|
|
154
194
|
|
|
155
|
-
def forward(
|
|
156
|
-
|
|
157
|
-
@agent.forward(task: task)
|
|
195
|
+
def forward(question:)
|
|
196
|
+
@agent.call(question: question)
|
|
158
197
|
end
|
|
159
198
|
end
|
|
160
199
|
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
results = perform_search(query)
|
|
166
|
-
{ results: results }
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
private
|
|
200
|
+
# Available internal predictors by agent type:
|
|
201
|
+
# DSPy::ReAct → thought_generator, observation_processor
|
|
202
|
+
# DSPy::CodeAct → code_generator, observation_processor
|
|
203
|
+
# DSPy::DeepSearch → seed_predictor, search_predictor, reader_predictor, reason_predictor
|
|
170
204
|
|
|
171
|
-
|
|
172
|
-
# Actual search implementation
|
|
173
|
-
# Could call external API, database, etc.
|
|
174
|
-
["result1", "result2", "result3"]
|
|
175
|
-
end
|
|
176
|
-
end
|
|
205
|
+
# --- Module with Event Subscriptions ---
|
|
177
206
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
attr_reader :history
|
|
207
|
+
class TokenTrackingModule < DSPy::Module
|
|
208
|
+
subscribe 'lm.tokens', :track_tokens, scope: :descendants
|
|
181
209
|
|
|
182
210
|
def initialize
|
|
183
211
|
super
|
|
184
|
-
@predictor = DSPy::
|
|
185
|
-
@
|
|
212
|
+
@predictor = DSPy::Predict.new(AnalysisSignature)
|
|
213
|
+
@total_tokens = 0
|
|
186
214
|
end
|
|
187
215
|
|
|
188
|
-
def forward(
|
|
189
|
-
|
|
190
|
-
context = build_context_from_history
|
|
191
|
-
result = @predictor.forward(
|
|
192
|
-
input: input,
|
|
193
|
-
context: context
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
# Store in history
|
|
197
|
-
@history << {
|
|
198
|
-
input: input,
|
|
199
|
-
result: result,
|
|
200
|
-
timestamp: Time.now
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
result
|
|
216
|
+
def forward(query:)
|
|
217
|
+
@predictor.call(query: query)
|
|
204
218
|
end
|
|
205
219
|
|
|
206
|
-
def
|
|
207
|
-
@
|
|
220
|
+
def track_tokens(_event, attrs)
|
|
221
|
+
@total_tokens += attrs.fetch(:total_tokens, 0)
|
|
208
222
|
end
|
|
209
223
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def build_context_from_history
|
|
213
|
-
@history.last(5).map { |h| h[:result][:summary] }.join("\n")
|
|
224
|
+
def token_usage
|
|
225
|
+
@total_tokens
|
|
214
226
|
end
|
|
215
227
|
end
|
|
216
228
|
|
|
217
|
-
# Module
|
|
218
|
-
|
|
219
|
-
def initialize
|
|
220
|
-
super
|
|
229
|
+
# Module-scoped subscriptions automatically scope to the module instance and descendants.
|
|
230
|
+
# Use scope: :self_only to restrict delivery to the module itself (ignoring children).
|
|
221
231
|
|
|
222
|
-
|
|
223
|
-
@fast_predictor = create_predictor(
|
|
224
|
-
'openai/gpt-4o-mini',
|
|
225
|
-
SimpleClassificationSignature
|
|
226
|
-
)
|
|
232
|
+
# --- Tool That Wraps a Prediction ---
|
|
227
233
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
ComplexAnalysisSignature
|
|
232
|
-
)
|
|
233
|
-
end
|
|
234
|
+
class RerankTool < DSPy::Tools::Base
|
|
235
|
+
tool_name "rerank"
|
|
236
|
+
tool_description "Score and rank search results by relevance"
|
|
234
237
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
@powerful_predictor.forward(input)
|
|
238
|
-
else
|
|
239
|
-
@fast_predictor.forward(input)
|
|
240
|
-
end
|
|
241
|
-
end
|
|
238
|
+
MAX_ITEMS = 200
|
|
239
|
+
MIN_ITEMS_FOR_LLM = 5
|
|
242
240
|
|
|
243
|
-
|
|
241
|
+
sig { params(query: String, items: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Hash[Symbol, T.untyped]) }
|
|
242
|
+
def call(query:, items: [])
|
|
243
|
+
# Short-circuit: skip LLM for small sets
|
|
244
|
+
return { scored_items: items, reranked: false } if items.size < MIN_ITEMS_FOR_LLM
|
|
245
|
+
|
|
246
|
+
# Cap to prevent token overflow
|
|
247
|
+
capped_items = items.first(MAX_ITEMS)
|
|
248
|
+
|
|
249
|
+
predictor = DSPy::Predict.new(RerankSignature)
|
|
250
|
+
predictor.configure { |c| c.lm = DSPy::LM.new("ruby_llm/gemini-2.5-flash", structured_outputs: true) }
|
|
244
251
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
252
|
+
result = predictor.call(query: query, items: capped_items)
|
|
253
|
+
{ scored_items: result.scored_items, reranked: true }
|
|
254
|
+
rescue => e
|
|
255
|
+
Rails.logger.warn "[RerankTool] LLM rerank failed: #{e.message}"
|
|
256
|
+
{ error: "Rerank failed: #{e.message}", scored_items: items, reranked: false }
|
|
248
257
|
end
|
|
249
258
|
end
|
|
250
259
|
|
|
251
|
-
#
|
|
252
|
-
|
|
260
|
+
# Key patterns for tools wrapping predictions:
|
|
261
|
+
# - Short-circuit LLM calls when unnecessary (small data, trivial cases)
|
|
262
|
+
# - Cap input size to prevent token overflow
|
|
263
|
+
# - Per-tool model selection via configure
|
|
264
|
+
# - Graceful error handling with fallback data
|
|
265
|
+
|
|
266
|
+
# --- Multi-Step Pipeline ---
|
|
267
|
+
|
|
268
|
+
class AnalysisPipeline < DSPy::Module
|
|
253
269
|
def initialize
|
|
254
270
|
super
|
|
255
|
-
@
|
|
256
|
-
@
|
|
271
|
+
@classifier = DSPy::Predict.new(ClassifySignature)
|
|
272
|
+
@analyzer = DSPy::ChainOfThought.new(AnalyzeSignature)
|
|
273
|
+
@summarizer = DSPy::Predict.new(SummarizeSignature)
|
|
257
274
|
end
|
|
258
275
|
|
|
259
|
-
def forward(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
# Return cached result if available
|
|
264
|
-
if @cache.key?(cache_key)
|
|
265
|
-
puts "Cache hit for #{cache_key}"
|
|
266
|
-
return @cache[cache_key]
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# Compute and cache result
|
|
270
|
-
result = @predictor.forward(input)
|
|
271
|
-
@cache[cache_key] = result
|
|
272
|
-
result
|
|
276
|
+
def forward(text:)
|
|
277
|
+
classification = @classifier.call(text: text)
|
|
278
|
+
analysis = @analyzer.call(text: text, category: classification.category)
|
|
279
|
+
@summarizer.call(analysis: analysis.reasoning, category: classification.category)
|
|
273
280
|
end
|
|
281
|
+
end
|
|
274
282
|
|
|
275
|
-
|
|
276
|
-
@cache.clear
|
|
277
|
-
end
|
|
283
|
+
# --- Observability with Spans ---
|
|
278
284
|
|
|
279
|
-
|
|
285
|
+
class TracedModule < DSPy::Module
|
|
286
|
+
def initialize
|
|
287
|
+
super
|
|
288
|
+
@predictor = DSPy::Predict.new(AnalysisSignature)
|
|
289
|
+
end
|
|
280
290
|
|
|
281
|
-
def
|
|
282
|
-
|
|
283
|
-
|
|
291
|
+
def forward(query:)
|
|
292
|
+
DSPy::Context.with_span(
|
|
293
|
+
operation: "traced_module.analyze",
|
|
294
|
+
"dspy.module" => self.class.name,
|
|
295
|
+
"query.length" => query.length.to_s
|
|
296
|
+
) do
|
|
297
|
+
@predictor.call(query: query)
|
|
298
|
+
end
|
|
284
299
|
end
|
|
285
300
|
end
|
|
286
|
-
|
|
287
|
-
# Usage Examples:
|
|
288
|
-
#
|
|
289
|
-
# Basic usage:
|
|
290
|
-
# module = BasicModule.new
|
|
291
|
-
# result = module.forward(field_name: "value")
|
|
292
|
-
#
|
|
293
|
-
# Chain of Thought:
|
|
294
|
-
# module = ChainOfThoughtModule.new
|
|
295
|
-
# result = module.forward(
|
|
296
|
-
# email_subject: "Can't log in",
|
|
297
|
-
# email_body: "I'm unable to access my account"
|
|
298
|
-
# )
|
|
299
|
-
# puts result[:reasoning]
|
|
300
|
-
#
|
|
301
|
-
# Multi-step pipeline:
|
|
302
|
-
# pipeline = MultiStepPipeline.new
|
|
303
|
-
# result = pipeline.forward(input_data)
|
|
304
|
-
#
|
|
305
|
-
# With error handling:
|
|
306
|
-
# module = RobustModule.new
|
|
307
|
-
# begin
|
|
308
|
-
# result = module.forward(input_data)
|
|
309
|
-
# rescue DSPy::ValidationError => e
|
|
310
|
-
# puts "Failed after retries: #{e.message}"
|
|
311
|
-
# end
|
|
312
|
-
#
|
|
313
|
-
# Agent with tools:
|
|
314
|
-
# agent = AgentModule.new
|
|
315
|
-
# result = agent.forward(task: "Find the population of Tokyo")
|
|
316
|
-
#
|
|
317
|
-
# Stateful processing:
|
|
318
|
-
# module = StatefulModule.new
|
|
319
|
-
# result1 = module.forward("First input")
|
|
320
|
-
# result2 = module.forward("Second input") # Has context from first
|
|
321
|
-
# module.reset! # Clear history
|
|
322
|
-
#
|
|
323
|
-
# With caching:
|
|
324
|
-
# module = CachedModule.new
|
|
325
|
-
# result1 = module.forward(input) # Computes result
|
|
326
|
-
# result2 = module.forward(input) # Returns cached result
|