dspy-gemini 1.0.4 → 1.0.5

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: 3c7ede5395b56b65a0bc339369a0d8c998ecf4316d77f2863d39aacb9d550b41
4
- data.tar.gz: 84d6adac01d530c2a907f4f220724f403fb9b078b085b223466287c85db17a39
3
+ metadata.gz: f74542eb4beab2b4b2c3c3931b496c4a0ef8d7e6925fb63dd7bf1b71d770a9c0
4
+ data.tar.gz: 6d8cdd3ffcadbbea59ebc03c0b199fa79e07aac79a07ba9dc29aba1aa39169fa
5
5
  SHA512:
6
- metadata.gz: 7bec96952b3d33201d6c2ad5d7e0351a6bb390f0d35ad689acdde02cebf5aba25dee0152c62c2a3db83f6be64dcf93e336e2f99a05aed92ed624bd1cdf3dd836
7
- data.tar.gz: ec1455e4602379edd52634ce533b1b8bba8807df230f42de7003161804bb0ec987b5e66038d0d000d4fbc470becbe277e0d0ae7871dcda6a37b08f06ba1e0e80
6
+ metadata.gz: f30d06f707b26c85d0497feb334664f553f7f02f5c5fccfd5ecfd9a4ef32d668f6cb1f567a22a772e8f48a5a7cc7ccc462a96abc8ec354161e722717758574d8
7
+ data.tar.gz: aa30c0732139dc720652735bc5f850a5ba1f39cd2e32082dc0d592a7a61a344143f72160737583cb3e15300a958a87084b7f5612b1029e60dc5559b8cb72b53a
data/README.md CHANGED
@@ -6,96 +6,16 @@
6
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
- **Build reliable LLM applications in idiomatic Ruby using composable, type-safe modules.**
9
+ **Build typed agents and model-backed programs in Ruby.**
10
10
 
11
- DSPy.rb is the Ruby port of Stanford's [DSPy](https://dspy.ai). Instead of wrestling with brittle prompt strings, you define typed signatures and let the framework handle the rest. Prompts become functions. LLM calls become predictable.
12
- The `1.x` line is the stable release track for production Ruby LLM applications.
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.
13
12
 
14
- ```ruby
15
- require 'dspy'
16
-
17
- DSPy.configure do |c|
18
- c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY'])
19
- end
20
-
21
- class Summarize < DSPy::Signature
22
- description "Summarize the given text in one sentence."
23
-
24
- input do
25
- const :text, String
26
- end
27
-
28
- output do
29
- const :summary, String
30
- end
31
- end
32
-
33
- summarizer = DSPy::Predict.new(Summarize)
34
- result = summarizer.call(text: "DSPy.rb brings structured LLM programming to Ruby...")
35
- puts result.summary
36
- ```
37
-
38
- That's it. No prompt templates. No JSON parsing. No prayer-based error handling.
39
-
40
- ## Installation
41
-
42
- ```ruby
43
- # Gemfile
44
- gem 'dspy'
45
- gem 'dspy-openai' # For OpenAI, OpenRouter, or Ollama
46
- # gem 'dspy-anthropic' # For Claude
47
- # gem 'dspy-gemini' # For Gemini
48
- # gem 'dspy-ruby_llm' # For 12+ providers via RubyLLM
49
- ```
50
-
51
- ```bash
52
- bundle install
53
- ```
54
-
55
- ## Quick Start
56
-
57
- ### Configure Your LLM
58
-
59
- ```ruby
60
- # OpenAI
61
- DSPy.configure do |c|
62
- c.lm = DSPy::LM.new('openai/gpt-4o-mini',
63
- api_key: ENV['OPENAI_API_KEY'],
64
- structured_outputs: true)
65
- end
66
-
67
- # Anthropic Claude
68
- DSPy.configure do |c|
69
- c.lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514',
70
- api_key: ENV['ANTHROPIC_API_KEY'])
71
- end
72
-
73
- # Google Gemini
74
- DSPy.configure do |c|
75
- c.lm = DSPy::LM.new('gemini/gemini-2.5-flash',
76
- api_key: ENV['GEMINI_API_KEY'])
77
- end
78
-
79
- # Ollama (local, free)
80
- DSPy.configure do |c|
81
- c.lm = DSPy::LM.new('ollama/llama3.2')
82
- end
83
-
84
- # OpenRouter (200+ models)
85
- DSPy.configure do |c|
86
- c.lm = DSPy::LM.new('openrouter/deepseek/deepseek-chat-v3.1:free',
87
- api_key: ENV['OPENROUTER_API_KEY'])
88
- end
89
- ```
90
-
91
- ### Define a Signature
13
+ The `1.x` series is the current stable release line.
92
14
 
93
- Signatures are typed contracts for LLM operations. Define inputs, outputs, and let DSPy handle the prompt:
15
+ In a configured application, the task contract and call look like this:
94
16
 
95
17
  ```ruby
96
18
  class Classify < DSPy::Signature
97
- description "Classify sentiment of a given sentence."
98
-
99
19
  class Sentiment < T::Enum
100
20
  enums do
101
21
  Positive = new('positive')
@@ -105,7 +25,7 @@ class Classify < DSPy::Signature
105
25
  end
106
26
 
107
27
  input do
108
- const :sentence, String, description: 'The sentence to analyze'
28
+ const :sentence, String
109
29
  end
110
30
 
111
31
  output do
@@ -115,107 +35,39 @@ class Classify < DSPy::Signature
115
35
  end
116
36
 
117
37
  classifier = DSPy::Predict.new(Classify)
118
- result = classifier.call(sentence: "This book was super fun to read!")
119
-
120
- result.sentiment # => #<Sentiment::Positive>
121
- result.confidence # => 0.92
38
+ result = classifier.call(sentence: "This book was fun to read!")
122
39
  ```
123
40
 
124
- ### Chain of Thought
125
-
126
- For complex reasoning, use `ChainOfThought` to get step-by-step explanations:
127
-
128
- ```ruby
129
- solver = DSPy::ChainOfThought.new(MathProblem)
130
- result = solver.call(problem: "If a train travels 120km in 2 hours, what's its speed?")
131
-
132
- result.reasoning # => "Speed = Distance / Time = 120km / 2h = 60km/h"
133
- result.answer # => "60 km/h"
134
- ```
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.
135
42
 
136
- ### ReAct Agents
137
-
138
- Build agents that use tools to accomplish tasks:
139
-
140
- ```ruby
141
- class SearchTool < DSPy::Tools::Base
142
- tool_name "search"
143
- tool_description "Search for information"
144
-
145
- sig { params(query: String).returns(String) }
146
- def call(query:)
147
- # Your search implementation
148
- "Result 1, Result 2"
149
- end
150
- end
151
-
152
- agent = DSPy::ReAct.new(ResearchTask, tools: [SearchTool.new], max_iterations: 5)
153
- result = agent.call(question: "What's the latest on Ruby 3.4?")
154
- ```
43
+ ## Start Here
155
44
 
156
- ## What's Included
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.
157
46
 
158
- **Core Modules**: Predict, ChainOfThought, ReAct agents, and composable pipelines.
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.
159
48
 
160
- **Type Safety**: Sorbet-based runtime validation. Enums, unions, nested structs—all work.
49
+ ## Mental Model
161
50
 
162
- **Multimodal**: Image analysis with `DSPy::Image` for vision-capable models.
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.
163
55
 
164
- **Observability**: Zero-config Langfuse integration via OpenTelemetry. Non-blocking, production-ready.
165
-
166
- **Optimization**: MIPROv2 (Bayesian optimization) and GEPA (genetic evolution) for prompt tuning.
167
-
168
- **Provider Support**: OpenAI, Anthropic, Gemini, Ollama, and OpenRouter via official SDKs.
169
-
170
- ## Documentation
171
-
172
- **[Full Documentation](https://oss.vicente.services/dspy.rb/)** — Getting started, core concepts, advanced patterns.
173
-
174
- **[llms.txt](https://oss.vicente.services/dspy.rb/llms.txt)** — LLM-friendly reference for AI assistants.
175
-
176
- ### Claude Skill
177
-
178
- A [Claude Skill](https://github.com/vicentereig/dspy-rb-skill) is available to help you build DSPy.rb applications:
179
-
180
- ```bash
181
- # Claude Code — install from the vicentereig/engineering marketplace
182
- claude install-skill vicentereig/engineering --skill dspy-rb
183
- ```
184
-
185
- For Claude.ai Pro/Max, download the [skill ZIP](https://github.com/vicentereig/dspy-rb-skill/archive/refs/heads/main.zip) and upload via Settings > Skills.
186
-
187
- ## Examples
188
-
189
- The [examples/](examples/) directory has runnable code for common patterns:
190
-
191
- - Sentiment classification
192
- - ReAct agents with tools
193
- - Image analysis
194
- - Prompt optimization
195
-
196
- ```bash
197
- bundle exec ruby examples/basic_search_agent.rb
198
- ```
56
+ ### ReAct Agents
199
57
 
200
- ## Optional Gems
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/).
201
59
 
202
- DSPy.rb ships sibling gems for features with heavier dependencies. Add them as needed:
60
+ ## Explore
203
61
 
204
- | Gem | What it does |
205
- | --- | --- |
206
- | `dspy-datasets` | Dataset helpers, Parquet/Polars tooling |
207
- | `dspy-evals` | Evaluation harness with metrics and callbacks |
208
- | `dspy-miprov2` | Bayesian optimization for prompt tuning |
209
- | `dspy-gepa` | Genetic-Pareto prompt evolution |
210
- | `dspy-o11y-langfuse` | Auto-configure Langfuse tracing |
211
- | `dspy-code_act` | Think-Code-Observe agents |
212
- | `dspy-deep_search` | Production DeepSearch with Exa |
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
213
65
 
214
- See [the full list](https://oss.vicente.services/dspy.rb/getting-started/installation/) in the docs.
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.
215
67
 
216
68
  ## Contributing
217
69
 
218
- Feedback is invaluable. If you encounter issues, [open an issue](https://github.com/vicentereig/dspy.rb/issues). For suggestions, [start a discussion](https://github.com/vicentereig/dspy.rb/discussions).
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).
219
71
 
220
72
  Want to contribute code? Reach out: hey at vicente.services
221
73
 
@@ -1,37 +1,52 @@
1
- # DSPy Gemini adapter gem
1
+ # DSPy Gemini Adapter
2
2
 
3
- `dspy-gemini` provides the Gemini adapter for DSPy.rb so we can rely on Google's API without bloating the core gem. Install it whenever you plan to call `gemini/*` model ids from DSPy.
3
+ `dspy-gemini` lets `DSPy::LM` call model IDs with the `gemini/` prefix. The package is a supported provider adapter; model and SDK capabilities still vary.
4
4
 
5
- ## When you need it
6
- - You call `DSPy::LM.new` with a provider of `gemini`.
7
- - You want structured outputs, multimodal prompts, or streaming responses backed by Gemini's Generative Language API.
5
+ See the [package and capability matrix](https://oss.vicente.services/dspy.rb/getting-started/packages/) for canonical status and model/SDK boundaries.
8
6
 
9
- Projects that only target OpenAI, Anthropic, or other providers can skip this gem.
7
+ ## Prerequisites
10
8
 
11
- ## Installation
12
- Add it beside `dspy` and install dependencies:
9
+ - Ruby 3.3 or newer and Bundler
10
+ - a Gemini API key
11
+ - a Gemini model ID available to your account
13
12
 
14
- ```ruby
15
- # Gemfile
16
- gem 'dspy'
17
- gem 'dspy-gemini'
18
- ```
13
+ ## Install and Run
19
14
 
20
- ```sh
21
- bundle install
15
+ Add both gems to your `Gemfile`:
16
+
17
+ ```ruby
18
+ gem "dspy"
19
+ gem "dspy-gemini"
22
20
  ```
23
21
 
24
- The adapter enforces `gemini-ai ~> 4.3` at runtime (and raises if the dependency is missing or out of range).
22
+ Save this as `gemini_smoke.rb`:
25
23
 
26
- ## Configuration
27
- - Set `ENV['GEMINI_API_KEY']`, or pass `api_key:` directly.
24
+ ```ruby
25
+ require "dspy"
28
26
 
29
- ## Basic usage
27
+ lm = DSPy::LM.new(
28
+ ENV.fetch("GEMINI_MODEL"),
29
+ api_key: ENV.fetch("GEMINI_API_KEY")
30
+ )
30
31
 
31
- ```ruby
32
- require 'dspy'
33
- # No need to explicitly require 'dspy/gemini'
32
+ response = lm.raw_chat([{ role: "user", content: "Reply with: adapter ready" }])
33
+ puts response.content
34
+ ```
34
35
 
35
- lm = DSPy::LM.new('gemini/gemini-1.5-flash', api_key: ENV.fetch('GEMINI_API_KEY'))
36
+ Then install and run it:
36
37
 
38
+ ```bash
39
+ bundle install
40
+ export GEMINI_API_KEY="your-key"
41
+ export GEMINI_MODEL="gemini/your-model-id"
42
+ bundle exec ruby gemini_smoke.rb
37
43
  ```
44
+
45
+ The command prints the model response. Requiring `dspy` loads the installed adapter for a `gemini/` model; explicit `require "dspy/gemini"` is also supported.
46
+
47
+ ## Failure Conditions
48
+
49
+ - A missing key raises from `ENV.fetch` before the request.
50
+ - A missing or incompatible `gemini-ai` dependency prevents the adapter from loading.
51
+ - An unavailable model, safety rejection, or invalid request raises an adapter/provider error.
52
+ - Schema, image, safety, and streaming behavior depend on the selected Gemini model and SDK/API version. Verify the exact request; installing the gem does not guarantee every capability.
@@ -45,8 +45,8 @@ module DSPy
45
45
  # Get the output JSON schema from the signature class
46
46
  output_schema = signature_class.output_json_schema
47
47
 
48
- # Convert to Gemini format (OpenAPI 3.0 Schema subset - not related to OpenAI)
49
- convert_dspy_schema_to_gemini(output_schema)
48
+ # response_json_schema accepts JSON Schema, including nullable type arrays.
49
+ convert_property_to_gemini(output_schema)
50
50
  end
51
51
 
52
52
  sig { params(model: String).returns(T::Boolean) }
@@ -73,87 +73,34 @@ module DSPy
73
73
 
74
74
  private
75
75
 
76
- sig { params(dspy_schema: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
77
- def self.convert_dspy_schema_to_gemini(dspy_schema)
78
- # For Gemini's responseJsonSchema, we need pure JSON Schema format
79
- # Remove OpenAPI-specific fields like "$schema"
80
- result = {
81
- type: "object",
82
- properties: {},
83
- required: []
84
- }
85
-
86
- # Convert properties
87
- properties = dspy_schema[:properties] || {}
88
- properties.each do |prop_name, prop_schema|
89
- result[:properties][prop_name] = convert_property_to_gemini(prop_schema)
90
- end
91
-
92
- # Set required fields
93
- result[:required] = (dspy_schema[:required] || []).map(&:to_s)
94
-
95
- result
96
- end
97
-
98
76
  sig { params(property_schema: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
99
77
  def self.convert_property_to_gemini(property_schema)
100
- # Handle oneOf/anyOf schemas (union types) - Gemini supports these in responseJsonSchema
101
- if property_schema[:oneOf]
102
- return {
103
- oneOf: property_schema[:oneOf].map { |schema| convert_property_to_gemini(schema) },
104
- description: property_schema[:description]
105
- }.compact
106
- end
78
+ # Preserve types and constraints instead of rebuilding nodes from scalar
79
+ # type names: Sorbet represents nilable fields as type: [type, "null"].
80
+ result = property_schema.reject { |key, _| key == :"$schema" || key == :const }
107
81
 
108
- if property_schema[:anyOf]
109
- return {
110
- anyOf: property_schema[:anyOf].map { |schema| convert_property_to_gemini(schema) },
111
- description: property_schema[:description]
112
- }.compact
113
- end
82
+ # Keep the existing const-to-enum conversion, including false/null values.
83
+ result[:enum] = [property_schema[:const]] if property_schema.key?(:const)
114
84
 
115
- case property_schema[:type]
116
- when "string"
117
- result = { type: "string" }
118
- # Gemini responseJsonSchema doesn't support const, so convert to single-value enum
119
- # See: https://ai.google.dev/api/generate-content#FIELDS.response_json_schema
120
- if property_schema[:const]
121
- result[:enum] = [property_schema[:const]]
122
- elsif property_schema[:enum]
123
- result[:enum] = property_schema[:enum]
85
+ [:properties, :"$defs", :definitions].each do |key|
86
+ if property_schema[key].is_a?(Hash)
87
+ result[key] = property_schema[key].transform_values { |schema| convert_property_to_gemini(schema) }
124
88
  end
125
- result
126
- when "integer"
127
- { type: "integer" }
128
- when "number"
129
- { type: "number" }
130
- when "boolean"
131
- { type: "boolean" }
132
- when "array"
133
- {
134
- type: "array",
135
- items: convert_property_to_gemini(property_schema[:items] || { type: "string" })
136
- }
137
- when "object"
138
- result = { type: "object" }
139
-
140
- if property_schema[:properties]
141
- result[:properties] = {}
142
- property_schema[:properties].each do |nested_prop, nested_schema|
143
- result[:properties][nested_prop] = convert_property_to_gemini(nested_schema)
144
- end
89
+ end
145
90
 
146
- # Set required fields for nested objects
147
- if property_schema[:required]
148
- result[:required] = property_schema[:required].map(&:to_s)
149
- end
91
+ [:items, :additionalProperties].each do |key|
92
+ if property_schema[key].is_a?(Hash)
93
+ result[key] = convert_property_to_gemini(property_schema[key])
150
94
  end
95
+ end
151
96
 
152
- result
153
- else
154
- # Default to string for unknown types
155
- { type: "string" }
97
+ [:oneOf, :anyOf, :allOf, :prefixItems].each do |key|
98
+ if property_schema[key].is_a?(Array)
99
+ result[key] = property_schema[key].map { |schema| convert_property_to_gemini(schema) }
100
+ end
156
101
  end
102
+
103
+ result
157
104
  end
158
105
 
159
106
  sig { params(schema: T::Hash[Symbol, T.untyped], current_depth: Integer).returns(Integer) }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module DSPy
4
4
  module Gemini
5
- VERSION = '1.0.4'
5
+ VERSION = '1.0.5'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy-gemini
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig Rincón de Arellano