dspy-code_act 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41e384ba71082ee6532f168cccd6103a46797153d47ed2cfde122d6c2b3dae30
4
- data.tar.gz: 86ec69fbc323d38513179adf541cbc7ff60600616eeffafae0db9a9055eb7f2a
3
+ metadata.gz: 1e81ad2629b9d652887180a6209dc803301c39a8989317907fd92e4b0bd08c40
4
+ data.tar.gz: 41f24b4c59be3ee80d3eaaa329fa0d4c1ded4b44ba62f337fd36328271cb886b
5
5
  SHA512:
6
- metadata.gz: 7a89397a45fe0e63a66ebe993ab26e4a7f244de52e19faaf8ae0f40b9ede20be861d0267cc029f4f5034ecd2d2f7f83229b0428dad4edeb3160400540a8739f0
7
- data.tar.gz: 0f3ac179289084396771adea5c624fa71b9a029001ad5c1baa99cfa03103ba92fd1aefbfe0ed6b9356447349a1dee6239dcf20027466cfe0b4d6b78d6f40e726
6
+ metadata.gz: 52355d8730ca9f9f3455bfc87e6fba782a934feece0ccc418cd1adec8c4bd597191cf9975c66afded7dbfb5c41bcbc751c1bee6c2c4e0d72b569fc92d04b6ed6
7
+ data.tar.gz: ed2f38b62d55cf52a8f93755c432138aa6686e644b2175d74c03ab79c0812e77a62a078938b3a0e7d7908469f00e431731d77b8a75833fd1d94c096f11acd0fc
data/README.md CHANGED
@@ -3,60 +3,19 @@
3
3
  [![Gem Version](https://img.shields.io/gem/v/dspy)](https://rubygems.org/gems/dspy)
4
4
  [![Total Downloads](https://img.shields.io/gem/dt/dspy)](https://rubygems.org/gems/dspy)
5
5
  [![Build Status](https://img.shields.io/github/actions/workflow/status/vicentereig/dspy.rb/ruby.yml?branch=main&label=build)](https://github.com/vicentereig/dspy.rb/actions/workflows/ruby.yml)
6
- [![Documentation](https://img.shields.io/badge/docs-vicentereig.github.io%2Fdspy.rb-blue)](https://vicentereig.github.io/dspy.rb/)
6
+ [![Documentation](https://img.shields.io/badge/docs-oss.vicente.services%2Fdspy.rb-blue)](https://oss.vicente.services/dspy.rb/)
7
7
  [![Discord](https://img.shields.io/discord/1161519468141355160?label=discord&logo=discord&logoColor=white)](https://discord.gg/zWBhrMqn)
8
8
 
9
- > [!NOTE]
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
- **Build reliable LLM applications in idiomatic Ruby using composable, type-safe modules.**
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
- DSPy.rb is the Ruby-first surgical port of Stanford's [DSPy framework](https://github.com/stanfordnlp/dspy). It delivers structured LLM programming, prompt engineering, and context engineering in the language we love. Instead of wrestling with brittle prompt strings, you define typed signatures in idiomatic Ruby and compose workflows and agents that actually behave.
13
+ The `1.x` series is the current stable release line.
20
14
 
21
- **Prompts are just functions.** Traditional prompting is like writing code with string concatenation: it works until it doesn't. DSPy.rb brings you the programming approach pioneered by [dspy.ai](https://dspy.ai/): define modular signatures and let the framework deal with the messy bits.
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,233 +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, description: 'The sentence to analyze'
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, description: 'The sentiment of the sentence'
76
- const :confidence, Float, description: 'A number between 0.0 and 1.0'
32
+ const :sentiment, Sentiment
33
+ const :confidence, Float
77
34
  end
78
35
  end
79
36
 
80
- # Wire it to the simplest prompting technique: a prediction loop.
81
- classify = DSPy::Predict.new(Classify)
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-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) |
106
- | `dspy-datasets` | Dataset helpers plus Parquet/Polars tooling for richer evaluation corpora. (Toggle via `DSPY_WITH_DATASETS`.) | **Stable** (v1.0.0) |
107
- | `dspy-evals` | High-throughput evaluation harness with metrics, callbacks, and regression fixtures. (Toggle via `DSPY_WITH_EVALS`.) | **Stable** (v1.0.0) |
108
- | `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) |
109
- | `dspy-gepa` | `DSPy::Teleprompt::GEPA`, reflection loops, experiment tracking, telemetry adapters. (Install or set `DSPY_WITH_GEPA=1`.) | **Stable** (v1.0.0) |
110
- | `gepa` | GEPA optimizer core (Pareto engine, telemetry, reflective proposer). | **Stable** (v1.0.0) |
111
- | `dspy-o11y` | Core observability APIs: `DSPy::Observability`, async span processor, observation types. (Install or set `DSPY_WITH_O11Y=1`.) | **Stable** (v1.0.0) |
112
- | `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) |
113
- | `dspy-deep_search` | Production DeepSearch loop with Exa-backed search/read, token budgeting, and instrumentation (Issue #163). | **Stable** (v1.0.0) |
114
- | `dspy-deep_research` | Planner/QA orchestration atop DeepSearch plus the memory supervisor used by the CLI example. | **Stable** (v1.0.0) |
115
- | `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) |
116
-
117
- **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.
118
-
119
- 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.
120
- ### Access to 200+ Models Across 5 Providers
121
-
122
- DSPy.rb provides unified access to major LLM providers with provider-specific optimizations:
123
-
124
- ```ruby
125
- # OpenAI (GPT-4, GPT-4o, GPT-4o-mini, GPT-5, etc.)
126
- DSPy.configure do |c|
127
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
128
- api_key: ENV['OPENAI_API_KEY'],
129
- structured_outputs: true) # Native JSON mode
130
- end
131
-
132
- # Google Gemini (Gemini 1.5 Pro, Flash, Gemini 2.0, etc.)
133
- DSPy.configure do |c|
134
- c.lm = DSPy::LM.new('gemini/gemini-2.5-flash',
135
- api_key: ENV['GEMINI_API_KEY'],
136
- structured_outputs: true) # Native structured outputs
137
- end
138
-
139
- # Anthropic Claude (Claude 3.5, Claude 4, etc.)
140
- DSPy.configure do |c|
141
- c.lm = DSPy::LM.new('anthropic/claude-sonnet-4-5-20250929',
142
- api_key: ENV['ANTHROPIC_API_KEY'],
143
- structured_outputs: true) # Tool-based extraction (default)
144
- end
145
-
146
- # Ollama - Run any local model (Llama, Mistral, Gemma, etc.)
147
- DSPy.configure do |c|
148
- c.lm = DSPy::LM.new('ollama/llama3.2') # Free, runs locally, no API key needed
149
- end
150
-
151
- # OpenRouter - Access to 200+ models from multiple providers
152
- DSPy.configure do |c|
153
- c.lm = DSPy::LM.new('openrouter/deepseek/deepseek-chat-v3.1:free',
154
- api_key: ENV['OPENROUTER_API_KEY'])
155
- end
37
+ classifier = DSPy::Predict.new(Classify)
38
+ result = classifier.call(sentence: "This book was fun to read!")
156
39
  ```
157
40
 
158
- ## What You Get
159
-
160
- **Developer Experience:** Official clients, multimodal coverage, and observability baked in.
161
- <details>
162
- <summary>Expand for everything included</summary>
163
-
164
- - LLM provider support using official Ruby clients:
165
- - [OpenAI Ruby](https://github.com/openai/openai-ruby) with vision model support
166
- - [Anthropic Ruby SDK](https://github.com/anthropics/anthropic-sdk-ruby) with multimodal capabilities
167
- - [Google Gemini API](https://ai.google.dev/) with native structured outputs
168
- - [Ollama](https://ollama.com/) via OpenAI compatibility layer for local models
169
- - **Multimodal Support** - Complete image analysis with DSPy::Image, type-safe bounding boxes, vision-capable models
170
- - Runtime type checking with [Sorbet](https://sorbet.org/) including T::Enum and union types
171
- - Type-safe tool definitions for ReAct agents
172
- - Comprehensive instrumentation and observability
173
- </details>
174
-
175
- **Core Building Blocks:** Predictors, agents, and pipelines wired through type-safe signatures.
176
- <details>
177
- <summary>Expand for everything included</summary>
178
-
179
- - **Signatures** - Define input/output schemas using Sorbet types with T::Enum and union type support
180
- - **Predict** - LLM completion with structured data extraction and multimodal support
181
- - **Chain of Thought** - Step-by-step reasoning for complex problems with automatic prompt optimization
182
- - **ReAct** - Tool-using agents with type-safe tool definitions and error recovery
183
- - **Module Composition** - Combine multiple LLM calls into production-ready workflows
184
- </details>
185
-
186
- **Optimization & Evaluation:** Treat prompt optimization like a real ML workflow.
187
- <details>
188
- <summary>Expand for everything included</summary>
189
-
190
- - **Prompt Objects** - Manipulate prompts as first-class objects instead of strings
191
- - **Typed Examples** - Type-safe training data with automatic validation
192
- - **Evaluation Framework** - Advanced metrics beyond simple accuracy with error-resilient pipelines
193
- - **MIPROv2 Optimization** - Advanced Bayesian optimization with Gaussian Processes, multiple optimization strategies, auto-config presets, and storage persistence
194
- </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.
195
42
 
196
- **Production Features:** Hardened behaviors for teams shipping actual products.
197
- <details>
198
- <summary>Expand for everything included</summary>
43
+ ## Start Here
199
44
 
200
- - **Reliable JSON Extraction** - Native structured outputs for OpenAI and Gemini, Anthropic tool-based extraction, and automatic strategy selection with fallback
201
- - **Type-Safe Configuration** - Strategy enums with automatic provider optimization (Strict/Compatible modes)
202
- - **Smart Retry Logic** - Progressive fallback with exponential backoff for handling transient failures
203
- - **Zero-Config Langfuse Integration** - Set env vars and get automatic OpenTelemetry traces in Langfuse
204
- - **Performance Caching** - Schema and capability caching for faster repeated operations
205
- - **File-based Storage** - Optimization result persistence with versioning
206
- - **Structured Logging** - JSON and key=value formats with span tracking
207
- </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.
208
46
 
209
- ## Recent Achievements
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.
210
48
 
211
- DSPy.rb has gone from experimental to production-ready in three fast releases.
212
- <details>
213
- <summary>Expand for the full changelog highlights</summary>
49
+ ## Mental Model
214
50
 
215
- ### Foundation
216
- - **JSON Parsing Reliability** - Native OpenAI structured outputs with adaptive retry logic and schema-aware fallbacks
217
- - **Type-Safe Strategy Configuration** - Provider-optimized strategy selection and enum-backed optimizer presets
218
- - **Core Module System** - Predict, ChainOfThought, ReAct with type safety (add `dspy-code_act` for Think-Code-Observe agents)
219
- - ✅ **Production Observability** - OpenTelemetry, New Relic, and Langfuse integration
220
- - ✅ **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.
221
55
 
222
- ### Recent Advances
223
- - ✅ **MIPROv2 ADE Integrity (v0.29.1)** - Stratified train/val/test splits, honest precision accounting, and enum-driven `--auto` presets with integration coverage
224
- - ✅ **Instruction Deduplication (v0.29.1)** - Candidate generation now filters repeated programs so optimization logs highlight unique strategies
225
- - ✅ **GEPA Teleprompter (v0.29.0)** - Genetic-Pareto reflective prompt evolution with merge proposer scheduling, reflective mutation, and ADE demo parity
226
- - ✅ **Optimizer Utilities Parity (v0.29.0)** - Bootstrap strategies, dataset summaries, and Layer 3 utilities unlock multi-predictor programs on Ruby
227
- - ✅ **Observability Hardening (v0.29.0)** - OTLP exporter runs on a single-thread executor preventing frozen SSL contexts without blocking spans
228
- - ✅ **Documentation Refresh (v0.29.x)** - New GEPA guide plus ADE optimization docs covering presets, stratified splits, and error-handling defaults
229
- </details>
230
-
231
- **Current Focus Areas:** Closing the loop on production patterns and community adoption ahead of v1.0.
232
- <details>
233
- <summary>Expand for the roadmap</summary>
234
-
235
- ### Production Readiness
236
- - 🚧 **Production Patterns** - Real-world usage validation and performance optimization
237
- - 🚧 **Ruby Ecosystem Integration** - Rails integration, Sidekiq compatibility, deployment patterns
238
-
239
- ### Community & Adoption
240
- - 🚧 **Community Examples** - Real-world applications and case studies
241
- - 🚧 **Contributor Experience** - Making it easier to contribute and extend
242
- - 🚧 **Performance Benchmarks** - Comparative analysis vs other frameworks
243
- </details>
244
-
245
- **v1.0 Philosophy:** v1.0 lands after battle-testing, not checkbox bingo. The API is already stable; the milestone marks production confidence.
246
-
247
-
248
- ## Documentation
249
-
250
- 📖 **[Complete Documentation Website](https://vicentereig.github.io/dspy.rb/)**
251
-
252
- ### LLM-Friendly Documentation
253
-
254
- For LLMs and AI assistants working with DSPy.rb:
255
- - **[llms.txt](https://vicentereig.github.io/dspy.rb/llms.txt)** - Concise reference optimized for LLMs
256
- - **[llms-full.txt](https://vicentereig.github.io/dspy.rb/llms-full.txt)** - Comprehensive API documentation
257
-
258
- ### Getting Started
259
- - **[Installation & Setup](docs/src/getting-started/installation.md)** - Detailed installation and configuration
260
- - **[Quick Start Guide](docs/src/getting-started/quick-start.md)** - Your first DSPy programs
261
- - **[Core Concepts](docs/src/getting-started/core-concepts.md)** - Understanding signatures, predictors, and modules
262
-
263
- ### Prompt Engineering
264
- - **[Signatures & Types](docs/src/core-concepts/signatures.md)** - Define typed interfaces for LLM operations
265
- - **[Predictors](docs/src/core-concepts/predictors.md)** - Predict, ChainOfThought, ReAct, and more
266
- - **[Modules & Pipelines](docs/src/core-concepts/modules.md)** - Compose complex multi-stage workflows
267
- - **[Multimodal Support](docs/src/core-concepts/multimodal.md)** - Image analysis with vision-capable models
268
- - **[Examples & Validation](docs/src/core-concepts/examples.md)** - Type-safe training data
269
- - **[Rich Types](docs/src/advanced/complex-types.md)** - Sorbet type integration with automatic coercion for structs, enums, and arrays
270
- - **[Composable Pipelines](docs/src/advanced/pipelines.md)** - Manual module composition patterns
271
-
272
- ### Prompt Optimization
273
- - **[Evaluation Framework](docs/src/optimization/evaluation.md)** - Advanced metrics beyond simple accuracy
274
- - **[Prompt Optimization](docs/src/optimization/prompt-optimization.md)** - Manipulate prompts as objects
275
- - **[MIPROv2 Optimizer](docs/src/optimization/miprov2.md)** - Advanced Bayesian optimization with Gaussian Processes
276
- - **[GEPA Optimizer](docs/src/optimization/gepa.md)** *(beta)* - Reflective mutation with optional reflection LMs
277
-
278
- ### Context Engineering
279
- - **[Tools](docs/src/core-concepts/toolsets.md)** - Tool wieldint agents.
280
- - **[Agentic Memory](docs/src/core-concepts/memory.md)** - Memory Tools & Agentic Loops
281
- - **[RAG Patterns](docs/src/advanced/rag.md)** - Manual RAG implementation with external services
282
-
283
- ### Production Features
284
- - **[Observability](docs/src/production/observability.md)** - Zero-config Langfuse integration with a dedicated export worker that never blocks your LLMs
285
- - **[Storage System](docs/src/production/storage.md)** - Persistence and optimization result storage
286
- - **[Custom Metrics](docs/src/advanced/custom-metrics.md)** - Proc-based evaluation logic
56
+ ### ReAct Agents
287
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/).
288
59
 
60
+ ## Explore
289
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
290
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.
291
67
 
68
+ ## Contributing
292
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).
293
71
 
72
+ Want to contribute code? Reach out: hey at vicente.services
294
73
 
295
74
  ## License
296
- This project is licensed under the MIT License.
75
+
76
+ MIT License.
@@ -1,152 +1,77 @@
1
1
  # CodeAct: Dynamic Code Generation for DSPy.rb
2
2
 
3
- CodeAct is a DSPy.rb module that enables agents to write and execute Ruby code dynamically. Unlike ReAct agents that rely on predefined tools, CodeAct generates tailored Ruby code on the fly to solve complex tasks.
3
+ `dspy-code_act` is for controlled experiments with trusted input. The current implementation evaluates model-generated Ruby inside the application process. It does not provide a sandbox, permission boundary, resource isolation, or safe handling of untrusted or multi-tenant input.
4
4
 
5
- ## When to Use CodeAct
5
+ See the [package and capability matrix](https://oss.vicente.services/dspy.rb/getting-started/packages/) for canonical package status and the execution-safety boundary.
6
6
 
7
- - Choose CodeAct when you need creative problem solving, custom data transformations, or bespoke algorithms.
8
- - Prefer ReAct when you have well-defined tools, must call external services, or need stricter safety guarantees.
7
+ ## Prerequisites
9
8
 
10
- ## Quick Start
9
+ - Ruby 3.3 or newer and Bundler
10
+ - `dspy`, one provider adapter, and `dspy-code_act`
11
+ - a provider key and model that can produce the required structured responses
12
+ - trusted input and a controlled execution environment
13
+ - outbound network access for the repository research example; generated Ruby calls a live HTTP API
11
14
 
12
- ```ruby
13
- require 'dspy'
14
- require 'dspy/code_act'
15
-
16
- DSPy.configure do |config|
17
- config.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV.fetch('OPENAI_API_KEY'))
18
- end
19
-
20
- agent = DSPy::CodeAct.new
21
-
22
- result = agent.forward(
23
- task: "Calculate the Fibonacci sequence up to the 10th number",
24
- context: "You have access to standard Ruby libraries"
25
- )
26
-
27
- puts result.final_answer
28
- # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
29
-
30
- result.history.each do |step|
31
- puts "Step #{step.step}"
32
- puts "Thought: #{step.thought}"
33
- puts "Code: #{step.ruby_code}"
34
- puts "Result: #{step.execution_result}"
35
- end
36
- ```
37
-
38
- ## Advanced Usage
39
-
40
- ### Custom Execution Context
41
-
42
- Provide structured data or helper methods with the `context` argument so generated code can reference them:
15
+ ## Install and Run the Example
43
16
 
44
17
  ```ruby
45
- sales_data = {
46
- "January" => [100, 150, 200],
47
- "February" => [120, 180, 190],
48
- "March" => [140, 210, 220]
49
- }
50
-
51
- agent = DSPy::CodeAct.new
52
-
53
- result = agent.forward(
54
- task: "Calculate the average sales for each month and report the best performer",
55
- context: "You have access to `sales_data`, a hash keyed by month with numeric arrays",
56
- data: { sales_data: sales_data }
57
- )
58
-
59
- puts result.final_answer
60
- # => "March is the best performing month with an average of 190.0"
18
+ gem "dspy"
19
+ gem "dspy-openai"
20
+ gem "dspy-code_act"
61
21
  ```
62
22
 
63
- ### Arbitrary Data Processing
23
+ In an application, run `bundle install` after adding those gems. From this repository, enable the optional package while installing the monorepo bundle, then run the example:
64
24
 
65
- ```ruby
66
- csv_content = <<~CSV
67
- name,email,department
68
- John Doe,john@gmail.com,Engineering
69
- Jane Smith,jane@company.com,Marketing
70
- Bob Johnson,bob@gmail.com,Sales
71
- CSV
72
-
73
- result = agent.forward(
74
- task: "Parse the CSV data and list gmail.com addresses",
75
- context: "CSV data is available in `csv_content`",
76
- data: { csv_content: csv_content }
77
- )
25
+ ```bash
26
+ DSPY_WITH_CODE_ACT=1 bundle install
27
+ export OPENAI_API_KEY="your-key"
28
+ bundle exec ruby examples/codeact_research_agent.rb \
29
+ "How many stars does rails/rails have?"
78
30
  ```
79
31
 
80
- ## Safety Checklist
32
+ The script prints the final answer and records the generated code and observations in `result.history`. The answer can change with the live API; inspect the generated code and observation rather than treating an old count as a fixture.
81
33
 
82
- Executing arbitrary Ruby code is powerful but risky. Consider:
34
+ ## Define a CodeAct Task
83
35
 
84
- 1. **Sandboxing & Timeouts** – wrap execution in `Timeout.timeout` and restrict accessible objects.
85
- 2. **Input Sanitization** – scrub user input to remove shell-outs and dangerous methods.
86
- 3. **Resource Monitoring** – track memory and CPU usage to abort runaway code.
36
+ CodeAct requires a signature and an iteration bound:
87
37
 
88
38
  ```ruby
89
- class SafeCodeAct < DSPy::CodeAct
90
- def execute_code(ruby_code)
91
- Timeout.timeout(5) { super }
92
- rescue Timeout::Error
93
- "Code execution timed out"
94
- end
39
+ class Calculate < DSPy::Signature
40
+ input { const :question, String }
41
+ output { const :answer, String }
95
42
  end
96
- ```
97
-
98
- ## Example: Sales Analysis Pipeline
99
43
 
100
- ```ruby
101
- class SalesAnalyzer < DSPy::Module
102
- def initialize
103
- @agent = DSPy::CodeAct.new
104
- end
105
-
106
- def analyze_trends(sales_data)
107
- result = @agent.forward(
108
- task: <<~TASK,
109
- Analyze the sales data to:
110
- 1. Compute month-over-month growth
111
- 2. Identify seasonal patterns
112
- 3. Predict next month's sales with simple linear regression
113
- TASK
114
- context: "You have access to `sales_data` and standard Ruby libraries",
115
- data: { sales_data: sales_data }
116
- )
117
-
118
- {
119
- analysis: result.final_answer,
120
- code_steps: result.history.map(&:ruby_code),
121
- execution_time: result.metadata[:total_time]
122
- }
123
- end
44
+ DSPy.configure do |config|
45
+ config.lm = DSPy::LM.new(
46
+ "openai/gpt-4o-mini",
47
+ api_key: ENV.fetch("OPENAI_API_KEY")
48
+ )
124
49
  end
50
+
51
+ agent = DSPy::CodeAct.new(Calculate, max_iterations: 4)
52
+ result = agent.call(question: "Return the first ten Fibonacci numbers")
53
+
54
+ puts result.answer
55
+ puts result.history.map(&:ruby_code)
125
56
  ```
126
57
 
127
- ## Debugging
58
+ The model chooses Ruby code, CodeAct evaluates it, and a second prediction chooses whether to continue or finish. The application owns the iteration limit and execution boundary.
128
59
 
129
- Enable structured logging to observe each iteration:
60
+ ## Choose CodeAct or ReAct
130
61
 
131
- ```ruby
132
- DSPy.configure do |config|
133
- config.logger = Dry.Logger(:dspy, formatter: :json) do |logger|
134
- logger.add_backend(level: :debug, stream: $stdout)
135
- end
136
- end
137
- ```
62
+ Prefer `DSPy::ReAct` when the allowed operations can be exposed as narrow typed tools. Use CodeAct only when open-ended computation is necessary and the execution environment can carry the larger authority boundary.
138
63
 
139
- Inspect `result.history` entry-by-entry to review generated code, observations, and errors.
64
+ ## Safety Checklist
140
65
 
141
- ## Limitations & Roadmap
66
+ Timeouts and forbidden-string checks do not make Ruby `eval` safe. Generated code can reach loaded constants, files, environment variables, network clients, and process APIs through many equivalent expressions.
142
67
 
143
- - Basic sandboxing—do not run untrusted input without additional guards.
144
- - No support for external gem loading during execution.
145
- - Future roadmap targets hardened sandboxing, async execution, and richer explanations.
68
+ Before accepting untrusted input, move execution outside the application process and provide:
146
69
 
147
- ## Best Practices
70
+ - a disposable process, container, or virtual machine;
71
+ - no inherited application secrets;
72
+ - allowlisted network access;
73
+ - read-only or ephemeral storage;
74
+ - CPU, memory, wall-clock, and output limits; and
75
+ - a narrow protocol for returning observations.
148
76
 
149
- 1. Start with simple prompts before moving to complex tasks.
150
- 2. Provide precise context and structured data for better code generation.
151
- 3. Always validate outputs and enforce timeouts or resource caps.
152
- 4. Capture telemetry (Observability, Langfuse) for production usage.
77
+ The gem does not supply those controls. A provider error, invalid generated program, execution exception, or maximum-iteration result can stop or degrade a run; inspect `result.history` and handle those outcomes in the application.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module DSPy
4
4
  module CodeActVersion
5
- VERSION = '1.0.1'
5
+ VERSION = '1.0.3'
6
6
  end
7
7
  end
data/lib/dspy/code_act.rb CHANGED
@@ -118,6 +118,18 @@ module DSPy
118
118
  # Create enhanced output struct with CodeAct fields
119
119
  @enhanced_output_struct = create_enhanced_output_struct(signature_class)
120
120
  enhanced_output_struct = @enhanced_output_struct
121
+ input_descriptors = signature_class.input_field_descriptors
122
+ output_descriptors = input_descriptors.merge(signature_class.output_field_descriptors).merge(
123
+ history: DSPy::Signature::FieldDescriptor.new(
124
+ T::Array[CodeActHistoryEntry],
125
+ "CodeAct execution history"
126
+ ),
127
+ iterations: DSPy::Signature::FieldDescriptor.new(Integer, "Number of iterations executed"),
128
+ execution_context: DSPy::Signature::FieldDescriptor.new(
129
+ T::Hash[Symbol, T.untyped],
130
+ "Variables and context from code execution"
131
+ )
132
+ )
121
133
 
122
134
  # Create enhanced signature class
123
135
  enhanced_signature = Class.new(DSPy::Signature) do
@@ -126,9 +138,11 @@ module DSPy
126
138
 
127
139
  # Use the same input struct
128
140
  @input_struct_class = signature_class.input_struct_class
141
+ @input_field_descriptors = input_descriptors
129
142
 
130
143
  # Use the enhanced output struct with CodeAct fields
131
144
  @output_struct_class = enhanced_output_struct
145
+ @output_field_descriptors = output_descriptors
132
146
 
133
147
  # Store original signature name
134
148
  @original_signature_name = signature_class.name
@@ -287,7 +301,7 @@ module DSPy
287
301
  build_enhanced_struct(
288
302
  { input: input_props, output: output_props },
289
303
  {
290
- history: [T::Array[T::Hash[Symbol, T.untyped]], "CodeAct execution history"],
304
+ history: [T::Array[CodeActHistoryEntry], "CodeAct execution history"],
291
305
  iterations: [Integer, "Number of iterations executed"],
292
306
  execution_context: [T::Hash[Symbol, T.untyped], "Variables and context from code execution"]
293
307
  }
@@ -300,12 +314,17 @@ module DSPy
300
314
  output_field_name = @original_signature_class.output_struct_class.props.keys.first
301
315
 
302
316
  output_data = input_kwargs.merge({
303
- history: reasoning_result[:history].map(&:to_h),
317
+ history: reasoning_result[:history],
304
318
  iterations: reasoning_result[:iterations],
305
319
  execution_context: reasoning_result[:execution_context]
306
320
  })
307
321
  output_data[output_field_name] = reasoning_result[:final_answer]
308
322
 
323
+ @signature_class.validate_required_fields!(
324
+ output_data,
325
+ @signature_class.output_field_descriptors
326
+ )
327
+
309
328
  @enhanced_output_struct.new(**output_data)
310
329
  end
311
330
 
@@ -420,41 +439,13 @@ module DSPy
420
439
  [final_result, ""]
421
440
  rescue SyntaxError => e
422
441
  [nil, "Error: #{e.message}"]
423
- rescue => e
442
+ rescue StandardError => e
424
443
  [nil, "Error: #{e.message}"]
425
444
  ensure
426
445
  $stdout = original_stdout if original_stdout
427
446
  end
428
447
  end
429
448
 
430
- sig { params(output: T.untyped).void }
431
- def validate_output_schema!(output)
432
- # Validate that output is an instance of the enhanced output struct
433
- unless output.is_a?(@enhanced_output_struct)
434
- raise "Output must be an instance of #{@enhanced_output_struct}, got #{output.class}"
435
- end
436
-
437
- # Validate original signature output fields are present
438
- @original_signature_class.output_struct_class.props.each do |field_name, _prop|
439
- unless output.respond_to?(field_name)
440
- raise "Missing required field: #{field_name}"
441
- end
442
- end
443
-
444
- # Validate CodeAct-specific fields
445
- unless output.respond_to?(:history) && output.history.is_a?(Array)
446
- raise "Missing or invalid history field"
447
- end
448
-
449
- unless output.respond_to?(:iterations) && output.iterations.is_a?(Integer)
450
- raise "Missing or invalid iterations field"
451
- end
452
-
453
- unless output.respond_to?(:execution_context) && output.execution_context.is_a?(Hash)
454
- raise "Missing or invalid execution_context field"
455
- end
456
- end
457
-
458
449
  sig { returns(T::Hash[Symbol, T.untyped]) }
459
450
  def generate_example_output
460
451
  # Create a base example structure
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy-code_act
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig Rincón de Arellano
@@ -15,14 +15,20 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '0.30'
18
+ version: 1.0.2
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
- version: '0.30'
28
+ version: 1.0.2
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '2.0'
26
32
  description: CodeAct provides Think-Code-Observe agents that synthesize and execute
27
33
  Ruby code dynamically. Ship DSPy.rb workflows that write custom Ruby code while
28
34
  tracking execution history, observations, and safety signals.