dspy-anthropic 1.0.2 → 1.0.4

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: 31b0fc4e9033f850fe60e3bea0f99c90d055069d89736cdde636d4bd0e340d72
4
- data.tar.gz: fcd89c5568e9227cc56aff466a0c3ec9f500fc7d288ae8ef48cc20bc31d814a9
3
+ metadata.gz: 2b563f892087546b395c4c279b09684339170eb112898941d3129ac48f804cbf
4
+ data.tar.gz: 54f15263e44abd8b6feca18a9569973ee952c6dd9373e4302db9ed66e46811c3
5
5
  SHA512:
6
- metadata.gz: 699456b578d3f003c41c06bc106d52bcb8405569c48dfa11a5edbdc09e6522cae73f0c2b46453b5f213626ec4c6dcb3b3da8614f9f26f7a1f33f1076356dee8e
7
- data.tar.gz: 4e3fb2612ae552b00cff15219e7a0e1b10a6be838586a9e4bc2b747e4737f0cf1197a357a0632c31b235f73620f04eca3595c5aa2d46a99fef48f34d66fa9af7
6
+ metadata.gz: e78bbfb62abdd747b2dbf0d31da401078eeab71c09a6411d78097769baae4a8d96f1c142617741b203031ba9494e6b99581d5c4a9dbc81e1c40fe8ca221bffa7
7
+ data.tar.gz: 9369dadfb40f324a0f27cf8cdb912245ec214a0e13c676865b561a8886f4a12b0059c4e3b0b67b75b923df7e4a3604462b48d0b8a7807508e1641e0f044173e5
data/README.md CHANGED
@@ -137,26 +137,18 @@ result.answer # => "60 km/h"
137
137
  Build agents that use tools to accomplish tasks:
138
138
 
139
139
  ```ruby
140
- class SearchTool < DSPy::Tools::Tool
140
+ class SearchTool < DSPy::Tools::Base
141
141
  tool_name "search"
142
- description "Search for information"
143
-
144
- input do
145
- const :query, String
146
- end
147
-
148
- output do
149
- const :results, T::Array[String]
150
- end
142
+ tool_description "Search for information"
151
143
 
144
+ sig { params(query: String).returns(String) }
152
145
  def call(query:)
153
146
  # Your search implementation
154
- { results: ["Result 1", "Result 2"] }
147
+ "Result 1, Result 2"
155
148
  end
156
149
  end
157
150
 
158
- toolset = DSPy::Tools::Toolset.new(tools: [SearchTool.new])
159
- agent = DSPy::ReAct.new(signature: ResearchTask, tools: toolset, max_iterations: 5)
151
+ agent = DSPy::ReAct.new(ResearchTask, tools: [SearchTool.new], max_iterations: 5)
160
152
  result = agent.call(question: "What's the latest on Ruby 3.4?")
161
153
  ```
162
154
 
@@ -185,8 +177,8 @@ result = agent.call(question: "What's the latest on Ruby 3.4?")
185
177
  A [Claude Skill](https://github.com/vicentereig/dspy-rb-skill) is available to help you build DSPy.rb applications:
186
178
 
187
179
  ```bash
188
- # Claude Code
189
- git clone https://github.com/vicentereig/dspy-rb-skill ~/.claude/skills/dspy-rb
180
+ # Claude Code — install from the vicentereig/engineering marketplace
181
+ claude install-skill vicentereig/engineering --skill dspy-rb
190
182
  ```
191
183
 
192
184
  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.
@@ -201,7 +193,7 @@ The [examples/](examples/) directory has runnable code for common patterns:
201
193
  - Prompt optimization
202
194
 
203
195
  ```bash
204
- bundle exec ruby examples/first_predictor.rb
196
+ bundle exec ruby examples/basic_search_agent.rb
205
197
  ```
206
198
 
207
199
  ## Optional Gems
@@ -26,17 +26,21 @@ module DSPy
26
26
  if contains_images?(normalized_messages)
27
27
  DSPy::LM::VisionModels.validate_vision_support!('anthropic', model)
28
28
  # Convert messages to Anthropic format with proper image handling
29
- normalized_messages = format_multimodal_messages(normalized_messages)
29
+ normalized_messages = format_multimodal_messages(normalized_messages, 'anthropic')
30
30
  end
31
31
 
32
32
  # Anthropic requires system message to be separate from messages
33
33
  system_message, user_messages = extract_system_message(normalized_messages)
34
34
 
35
+ # Extract Beta API parameters if present
36
+ output_format = extra_params.delete(:output_format)
37
+ betas = extra_params.delete(:betas)
38
+
35
39
  # Check if this is a tool use request
36
40
  has_tools = extra_params.key?(:tools) && !extra_params[:tools].empty?
37
41
 
38
- # Apply JSON prefilling if needed for better Claude JSON compliance (but not for tool use)
39
- unless has_tools || contains_images?(normalized_messages)
42
+ # Apply JSON prefilling if needed for better Claude JSON compliance (but not for tool use or Beta API)
43
+ unless has_tools || output_format || contains_images?(normalized_messages)
40
44
  user_messages = prepare_messages_for_json(user_messages, system_message)
41
45
  end
42
46
 
@@ -58,11 +62,21 @@ module DSPy
58
62
  begin
59
63
  if block_given?
60
64
  content = ""
61
- @client.messages.stream(**request_params) do |chunk|
62
- if chunk.respond_to?(:delta) && chunk.delta.respond_to?(:text)
63
- chunk_text = chunk.delta.text
64
- content += chunk_text
65
- block.call(chunk)
65
+ if output_format
66
+ @client.beta.messages.stream(**request_params, output_format: output_format, betas: betas) do |chunk|
67
+ if chunk.respond_to?(:delta) && chunk.delta.respond_to?(:text)
68
+ chunk_text = chunk.delta.text
69
+ content += chunk_text
70
+ block.call(chunk)
71
+ end
72
+ end
73
+ else
74
+ @client.messages.stream(**request_params) do |chunk|
75
+ if chunk.respond_to?(:delta) && chunk.delta.respond_to?(:text)
76
+ chunk_text = chunk.delta.text
77
+ content += chunk_text
78
+ block.call(chunk)
79
+ end
66
80
  end
67
81
  end
68
82
 
@@ -78,7 +92,11 @@ module DSPy
78
92
  metadata: metadata
79
93
  )
80
94
  else
81
- response = @client.messages.create(**request_params)
95
+ response = if output_format
96
+ @client.beta.messages.create(**request_params, output_format: output_format, betas: betas)
97
+ else
98
+ @client.messages.create(**request_params)
99
+ end
82
100
 
83
101
  if response.respond_to?(:error) && response.error
84
102
  raise DSPy::LM::AdapterError, "Anthropic API error: #{response.error}"
@@ -127,7 +145,7 @@ module DSPy
127
145
  metadata: typed_metadata
128
146
  )
129
147
  end
130
- rescue => e
148
+ rescue StandardError => e
131
149
  # Check for specific image-related errors in the message
132
150
  error_msg = e.message.to_s
133
151
 
@@ -267,33 +285,6 @@ module DSPy
267
285
 
268
286
  [system_message, user_messages]
269
287
  end
270
-
271
- def format_multimodal_messages(messages)
272
- messages.map do |msg|
273
- if msg[:content].is_a?(Array)
274
- # Convert multimodal content to Anthropic format
275
- formatted_content = msg[:content].map do |item|
276
- case item[:type]
277
- when 'text'
278
- { type: 'text', text: item[:text] }
279
- when 'image'
280
- # Validate image compatibility before formatting
281
- item[:image].validate_for_provider!('anthropic')
282
- item[:image].to_anthropic_format
283
- else
284
- item
285
- end
286
- end
287
-
288
- {
289
- role: msg[:role],
290
- content: formatted_content
291
- }
292
- else
293
- msg
294
- end
295
- end
296
- end
297
288
  end
298
289
  end
299
290
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sorbet-runtime"
4
+
5
+ module DSPy
6
+ module Anthropic
7
+ module LM
8
+ # Converts DSPy signatures to Anthropic Beta API structured output format
9
+ module SchemaConverter
10
+ extend T::Sig
11
+
12
+ sig { params(signature_class: T.class_of(DSPy::Signature)).returns(T::Hash[Symbol, T.untyped]) }
13
+ def self.to_beta_format(signature_class)
14
+ schema = signature_class.output_json_schema.except(:$schema)
15
+ add_additional_properties_false(schema)
16
+ end
17
+
18
+ sig { params(schema: T.untyped).returns(T.untyped) }
19
+ def self.add_additional_properties_false(schema)
20
+ return schema unless schema.is_a?(Hash)
21
+
22
+ result = schema.dup
23
+
24
+ # Add additionalProperties: false to any object type
25
+ result[:additionalProperties] = false if result[:type] == "object"
26
+
27
+ # Process nested properties
28
+ if result[:properties].is_a?(Hash)
29
+ result[:properties] = result[:properties].transform_values do |v|
30
+ add_additional_properties_false(v)
31
+ end
32
+ end
33
+
34
+ # Process array items
35
+ if result[:items].is_a?(Hash)
36
+ result[:items] = add_additional_properties_false(result[:items])
37
+ elsif result[:items].is_a?(Array)
38
+ result[:items] = result[:items].map { |item| add_additional_properties_false(item) }
39
+ end
40
+
41
+ # Process oneOf, anyOf, allOf arrays
42
+ [:oneOf, :anyOf, :allOf].each do |key|
43
+ if result[key].is_a?(Array)
44
+ result[key] = result[key].map { |item| add_additional_properties_false(item) }
45
+ end
46
+ end
47
+
48
+ # Process definitions
49
+ [:definitions, :$defs].each do |key|
50
+ if result[key].is_a?(Hash)
51
+ result[key] = result[key].transform_values { |v| add_additional_properties_false(v) }
52
+ end
53
+ end
54
+
55
+ result
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module DSPy
4
4
  module Anthropic
5
- VERSION = '1.0.2'
5
+ VERSION = '1.0.4'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy-anthropic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig Rincón de Arellano
@@ -51,6 +51,7 @@ files:
51
51
  - lib/dspy/anthropic/errors.rb
52
52
  - lib/dspy/anthropic/guardrails.rb
53
53
  - lib/dspy/anthropic/lm/adapters/anthropic_adapter.rb
54
+ - lib/dspy/anthropic/lm/schema_converter.rb
54
55
  - lib/dspy/anthropic/version.rb
55
56
  homepage: https://github.com/vicentereig/dspy.rb
56
57
  licenses: