anthropic 1.13.0 → 1.14.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +31 -1
- data/lib/anthropic/helpers/input_schema/base_model.rb +6 -3
- data/lib/anthropic/helpers/input_schema/json_schema_converter.rb +9 -3
- data/lib/anthropic/helpers/input_schema/supported_schemas.rb +106 -0
- data/lib/anthropic/helpers/input_schema/union_of.rb +3 -1
- data/lib/anthropic/helpers/messages.rb +107 -0
- data/lib/anthropic/helpers/streaming/message_stream.rb +54 -43
- data/lib/anthropic/helpers/tools/base_tool.rb +82 -0
- data/lib/anthropic/helpers/tools/runner.rb +156 -0
- data/lib/anthropic/helpers/tools.rb +5 -0
- data/lib/anthropic/internal/transport/base_client.rb +7 -1
- data/lib/anthropic/internal/transport/pooled_net_requester.rb +6 -2
- data/lib/anthropic/models/beta/beta_tool_use_block.rb +14 -0
- data/lib/anthropic/models/tool_use_block.rb +6 -6
- data/lib/anthropic/resources/beta/messages.rb +23 -5
- data/lib/anthropic/resources/messages.rb +7 -81
- data/lib/anthropic/version.rb +1 -1
- data/lib/anthropic.rb +15 -10
- data/manifest.yaml +1 -0
- data/rbi/anthropic/helpers/input_schema/base_model.rbi +7 -2
- data/rbi/anthropic/helpers/tools/base_tool.rbi +51 -0
- data/rbi/anthropic/helpers/tools/runner.rbi +40 -0
- data/rbi/anthropic/helpers/tools.rbi +5 -0
- data/rbi/anthropic/internal/transport/base_client.rbi +5 -0
- data/rbi/anthropic/internal/transport/pooled_net_requester.rbi +6 -2
- data/rbi/anthropic/internal/type/base_model.rbi +8 -4
- data/rbi/anthropic/models/tool_use_block.rbi +3 -0
- data/rbi/anthropic/resources/beta/messages.rbi +296 -0
- data/sig/anthropic/internal/transport/base_client.rbs +2 -0
- data/sig/anthropic/internal/transport/pooled_net_requester.rbs +4 -1
- metadata +11 -4
- data/lib/anthropic/helpers/input_schema/property_mapping.rb +0 -47
- /data/rbi/anthropic/helpers/{structured_output.rbi → input_schema.rbi} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd00e27834064b69a4c340776b3a6dcc50be9cde619c86fca93663c421cb7dff
|
|
4
|
+
data.tar.gz: 8a07090aaa4fc943673bba3a67411ef3d2dede001e8a597487c6be61b17a6d36
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f50ad464f5deeaf0be1635ca3fe270b82efd88332c145472ca653048b3e970925b531f9e652401d8f5cfe6e055083e540aadd2f3f6ae18b743acb2e58c0a513c
|
|
7
|
+
data.tar.gz: 0706ceb70895b9cfedf683a8edbc183a73b45234c62531c15a281860891de1ed2160245f104a3f4f752eb7d82235de0d6ade340bdf66e1a5de99801cf755a951
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.14.0 (2025-11-07)
|
|
4
|
+
|
|
5
|
+
Full Changelog: [v1.13.0...v1.14.0](https://github.com/anthropics/anthropic-sdk-ruby/compare/v1.13.0...v1.14.0)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* run-tools implementation ([#714](https://github.com/anthropics/anthropic-sdk-ruby/issues/714)) ([5cf7298](https://github.com/anthropics/anthropic-sdk-ruby/commit/5cf72982d25f97052cb87270293434e16a330818))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* better thread safety via early initializing SSL store during HTTP client creation ([3a9531c](https://github.com/anthropics/anthropic-sdk-ruby/commit/3a9531c5b46da4d77647b14a3e8b20e3713c6da3))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Chores
|
|
18
|
+
|
|
19
|
+
* **client:** send user-agent header ([57b22be](https://github.com/anthropics/anthropic-sdk-ruby/commit/57b22bebbc1b5c1bf676103c997e0aabdc065838))
|
|
20
|
+
* **internal:** codegen related update ([15188e3](https://github.com/anthropics/anthropic-sdk-ruby/commit/15188e3963c05e4594d449a0ef871f9ae6b41ed3))
|
|
21
|
+
|
|
3
22
|
## 1.13.0 (2025-10-29)
|
|
4
23
|
|
|
5
24
|
Full Changelog: [v1.12.0...v1.13.0](https://github.com/anthropics/anthropic-sdk-ruby/compare/v1.12.0...v1.13.0)
|
data/README.md
CHANGED
|
@@ -15,7 +15,7 @@ To use this gem, install via Bundler by adding the following to your application
|
|
|
15
15
|
<!-- x-release-please-start-version -->
|
|
16
16
|
|
|
17
17
|
```ruby
|
|
18
|
-
gem "anthropic", "~> 1.
|
|
18
|
+
gem "anthropic", "~> 1.14.0"
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
<!-- x-release-please-end -->
|
|
@@ -79,6 +79,36 @@ end
|
|
|
79
79
|
|
|
80
80
|
Streaming with `anthropic.messages.stream(...)` exposes [various helpers for your convenience](helpers.md) including accumulation & SDK-specific events.
|
|
81
81
|
|
|
82
|
+
### Input Schema & Tool Calling
|
|
83
|
+
|
|
84
|
+
We have helper mechanisms to define structured data classes for tools and let Claude automatically execute them.
|
|
85
|
+
|
|
86
|
+
Please refer to [helpers.md](helpers.md) for more detailed usage information.
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
class CalculatorInput < Anthropic::BaseModel
|
|
90
|
+
required :lhs, Float
|
|
91
|
+
required :rhs, Float
|
|
92
|
+
required :operator, Anthropic::InputSchema::EnumOf[:+, :-, :*, :/]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class Calculator < Anthropic::BaseTool
|
|
96
|
+
input_schema CalculatorInput
|
|
97
|
+
|
|
98
|
+
def call(expr)
|
|
99
|
+
expr.lhs.public_send(expr.operator, expr.rhs)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Automatically handles tool execution loop
|
|
104
|
+
client.beta.messages.tool_runner(
|
|
105
|
+
model: "claude-sonnet-4-5-20250929",
|
|
106
|
+
max_tokens: 1024,
|
|
107
|
+
messages: [{role: "user", content: "What's 15 * 7?"}],
|
|
108
|
+
tools: [Calculator.new]
|
|
109
|
+
).each_message { puts _1.content }
|
|
110
|
+
```
|
|
111
|
+
|
|
82
112
|
### Pagination
|
|
83
113
|
|
|
84
114
|
List methods in the Anthropic API are paginated.
|
|
@@ -68,12 +68,15 @@ module Anthropic
|
|
|
68
68
|
super(name_sym, type_info, spec)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
# @return [String]
|
|
71
72
|
attr_reader :doc_string
|
|
72
73
|
|
|
74
|
+
# @api public
|
|
75
|
+
#
|
|
73
76
|
# @param description [String]
|
|
74
|
-
def
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
def description(description) = (@doc_string = description)
|
|
78
|
+
|
|
79
|
+
alias_method :doc, :description
|
|
77
80
|
|
|
78
81
|
private def process_field_args(args)
|
|
79
82
|
# Only accept hash format for descriptions.
|
|
@@ -51,7 +51,7 @@ module Anthropic
|
|
|
51
51
|
{anyOf: [schema, {type: null}]}
|
|
52
52
|
in {anyOf: schemas}
|
|
53
53
|
null = {type: null}
|
|
54
|
-
schemas.any? { _1 == null || _1 == {type: [
|
|
54
|
+
schemas.any? { _1 == null || _1 == {type: [null]} } ? schema : {anyOf: [*schemas, null]}
|
|
55
55
|
in {type: String => type}
|
|
56
56
|
type == null ? schema : schema.update(type: [type, null])
|
|
57
57
|
in {type: Array => types}
|
|
@@ -63,7 +63,12 @@ module Anthropic
|
|
|
63
63
|
#
|
|
64
64
|
# @param schema [Hash{Symbol=>Object}]
|
|
65
65
|
def assoc_meta!(schema, meta:)
|
|
66
|
-
xformed = meta.transform_keys(
|
|
66
|
+
xformed = meta.transform_keys(doc: :description) do
|
|
67
|
+
_1.to_s.gsub(/_\w/, &:upcase).tr("_", "").to_sym
|
|
68
|
+
end
|
|
69
|
+
if schema.key?(:$ref) && !xformed.empty?
|
|
70
|
+
schema.merge!(Anthropic::Helpers::InputSchema::JsonSchemaConverter::NO_REF => true)
|
|
71
|
+
end
|
|
67
72
|
schema.merge!(xformed)
|
|
68
73
|
end
|
|
69
74
|
|
|
@@ -138,7 +143,8 @@ module Anthropic
|
|
|
138
143
|
end
|
|
139
144
|
|
|
140
145
|
xformed = reused_defs.transform_keys { _1.delete_prefix("#/$defs/") }
|
|
141
|
-
xformed.empty? ? schema : {"$defs": xformed}.update(schema)
|
|
146
|
+
unconformed = xformed.empty? ? schema : {"$defs": xformed}.update(schema)
|
|
147
|
+
unconformed.tap { Anthropic::Helpers::InputSchema::SupportedSchemas.transform_schema!(_1) }
|
|
142
148
|
end
|
|
143
149
|
|
|
144
150
|
# @api private
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Anthropic
|
|
4
|
+
module Helpers
|
|
5
|
+
module InputSchema
|
|
6
|
+
module SupportedSchemas
|
|
7
|
+
class << self
|
|
8
|
+
# @api private
|
|
9
|
+
#
|
|
10
|
+
# @param x [Object]
|
|
11
|
+
#
|
|
12
|
+
# @yieldparam [Object]
|
|
13
|
+
private def walk_once(x, &blk)
|
|
14
|
+
seen = Set.new
|
|
15
|
+
rec = ->(x) do
|
|
16
|
+
return unless seen.add?(x)
|
|
17
|
+
|
|
18
|
+
blk.call(x)
|
|
19
|
+
case x
|
|
20
|
+
in Hash
|
|
21
|
+
x.each_value(&rec)
|
|
22
|
+
in Array
|
|
23
|
+
x.each(&rec)
|
|
24
|
+
else
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
rec.call(x)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @api private
|
|
32
|
+
#
|
|
33
|
+
# @param schema [Hash{Symbol=>Object}]
|
|
34
|
+
#
|
|
35
|
+
# @return [String]
|
|
36
|
+
private def describe!(schema, unsupported:)
|
|
37
|
+
[:$defs, :default].each { unsupported.delete(_1) }
|
|
38
|
+
|
|
39
|
+
doc = unsupported.delete(:description)
|
|
40
|
+
return if unsupported.empty?
|
|
41
|
+
|
|
42
|
+
addendum = unsupported.map { "#{_1}=#{_2.to_json}" }.join(",")
|
|
43
|
+
unsupported.each_key { schema.delete(_1) }
|
|
44
|
+
|
|
45
|
+
schema[:description] =
|
|
46
|
+
[
|
|
47
|
+
doc,
|
|
48
|
+
"Please also conform to these set of constraints: #{addendum}"
|
|
49
|
+
].compact.join("\n")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @api private
|
|
53
|
+
#
|
|
54
|
+
# @param schema [Hash{Symbol=>Object}]
|
|
55
|
+
#
|
|
56
|
+
# @return [Hash{Symbol=>Object}]
|
|
57
|
+
def transform_schema!(schema)
|
|
58
|
+
defs = schema[:$defs].to_h
|
|
59
|
+
|
|
60
|
+
# rubocop:disable Metrics/BlockLength
|
|
61
|
+
xform = ->(s) do
|
|
62
|
+
case s
|
|
63
|
+
in {type: "string" | ["string", "null"], format: "date-time" | "time" | "date" | "duration" | "email" | "hostname" | "uri" | "ipv4" | "ipv6" | "uuid"} | {type: "array", minItems: 0 | 1}
|
|
64
|
+
# these are the currently supported cases
|
|
65
|
+
next
|
|
66
|
+
|
|
67
|
+
in {oneOf: Array => schemas, **rest}
|
|
68
|
+
{anyOf: schemas, **rest}
|
|
69
|
+
|
|
70
|
+
in {allOf: Array => schemas}
|
|
71
|
+
derefed = schemas.lazy.grep(Hash).map do
|
|
72
|
+
ref = _1[:$ref].to_s.delete_prefix("#/$defs/")
|
|
73
|
+
defs.fetch(ref, _1)
|
|
74
|
+
end
|
|
75
|
+
merged = {}.merge!(*derefed)
|
|
76
|
+
if (doc = xform.call(merged))
|
|
77
|
+
merged.store(:$ref, true)
|
|
78
|
+
|
|
79
|
+
schemas.reject! do |s|
|
|
80
|
+
next unless s.is_a?(Hash)
|
|
81
|
+
s.delete(:description)
|
|
82
|
+
s.select! { merged.key?(_1) }
|
|
83
|
+
s.empty?
|
|
84
|
+
end
|
|
85
|
+
schemas << {description: doc}
|
|
86
|
+
end
|
|
87
|
+
in {type: "integer" | "number" | "string" | ["integer", "null"] | ["number", "null"] | ["string", "null"], **unsupported}
|
|
88
|
+
describe!(s, unsupported: unsupported.except(:enum))
|
|
89
|
+
in {type: "array", **unsupported}
|
|
90
|
+
describe!(s, unsupported: unsupported.except(:items))
|
|
91
|
+
in {type: "object", **unsupported}
|
|
92
|
+
unsupported.delete(:additionalProperties) if unsupported[:additionalProperties] == false
|
|
93
|
+
describe!(s, unsupported: unsupported.except(:required, :properties))
|
|
94
|
+
else
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
# rubocop:enable Metrics/BlockLength
|
|
98
|
+
|
|
99
|
+
walk_once(schema, &xform)
|
|
100
|
+
schema
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -33,7 +33,9 @@ module Anthropic
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
schemas.each do |schema|
|
|
36
|
-
mergeable_keys.each_key
|
|
36
|
+
mergeable_keys.each_key do
|
|
37
|
+
mergeable_keys[_1] += 1 if schema.keys == _1 && schema[_1].is_a?(Array)
|
|
38
|
+
end
|
|
37
39
|
end
|
|
38
40
|
mergeable = mergeable_keys.any? { _1.last == schemas.length }
|
|
39
41
|
mergeable ? Anthropic::Internal::Util.deep_merge(*schemas, concat: true) : {anyOf: schemas}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Anthropic
|
|
4
|
+
module Helpers
|
|
5
|
+
module Messages
|
|
6
|
+
class << self
|
|
7
|
+
# @api private
|
|
8
|
+
#
|
|
9
|
+
# Extract tool models from the request and convert them to JSON Schema
|
|
10
|
+
# Returns a hash mapping tool name to Ruby model.
|
|
11
|
+
#
|
|
12
|
+
# @param data [Hash{Sybmol=>Object}]
|
|
13
|
+
#
|
|
14
|
+
# @param strict [Boolean, nil]
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash{String=>Class}, Hash{String=>Class}]
|
|
17
|
+
def distill_input_schema_models!(data, strict:)
|
|
18
|
+
tools = {}
|
|
19
|
+
models = {}
|
|
20
|
+
|
|
21
|
+
case data
|
|
22
|
+
in {tools: Array => tool_array}
|
|
23
|
+
# rubocop:disable Metrics/BlockLength
|
|
24
|
+
mapped = tool_array.map do |tool|
|
|
25
|
+
case tool
|
|
26
|
+
# Direct tool class:
|
|
27
|
+
in Anthropic::Helpers::InputSchema::JsonSchemaConverter
|
|
28
|
+
classname = tool.is_a?(Anthropic::Helpers::Tools::BaseTool) ? tool.class.name : tool.name
|
|
29
|
+
name = classname
|
|
30
|
+
.split("::")
|
|
31
|
+
.last
|
|
32
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
33
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
34
|
+
.downcase
|
|
35
|
+
description =
|
|
36
|
+
case tool
|
|
37
|
+
in Anthropic::Helpers::Tools::BaseTool
|
|
38
|
+
tool.class.doc_string || name
|
|
39
|
+
in Class if tool <= Anthropic::Helpers::InputSchema::BaseModel
|
|
40
|
+
tool.doc_string || name
|
|
41
|
+
else
|
|
42
|
+
name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
tools.store(name, tool)
|
|
46
|
+
input_schema = Anthropic::Helpers::InputSchema::JsonSchemaConverter.to_json_schema(tool)
|
|
47
|
+
{name:, description:, input_schema:}.tap { _1.update(strict:) if strict }
|
|
48
|
+
# Tool with explicit name/description and BaseModel as input_schema:
|
|
49
|
+
in {name: String => name,
|
|
50
|
+
input_schema: Anthropic::Helpers::InputSchema::JsonSchemaConverter => tool,
|
|
51
|
+
**rest}
|
|
52
|
+
tools.store(name, tool)
|
|
53
|
+
input_schema = Anthropic::Helpers::InputSchema::JsonSchemaConverter.to_json_schema(tool)
|
|
54
|
+
rest.merge(name:, input_schema:).tap { _1.update(strict:) if strict }
|
|
55
|
+
else
|
|
56
|
+
# Any other format (pass through unchanged)
|
|
57
|
+
# This includes raw JSON schemas and any other tool definitions.
|
|
58
|
+
tool
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
tool_array.replace(mapped)
|
|
62
|
+
else
|
|
63
|
+
end
|
|
64
|
+
# rubocop:enable Metrics/BlockLength
|
|
65
|
+
|
|
66
|
+
[tools, models]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @api private
|
|
70
|
+
#
|
|
71
|
+
# @param raw [Hash{Sybmol=>Object}]
|
|
72
|
+
#
|
|
73
|
+
# @param tools [Hash{String=>Class}]
|
|
74
|
+
#
|
|
75
|
+
# @param models [Hash{String=>Class}]
|
|
76
|
+
#
|
|
77
|
+
# @return [Hash{Sybmol=>Object}]
|
|
78
|
+
def parse_input_schemas!(raw, tools:, models:)
|
|
79
|
+
raw[:content]&.each do |content|
|
|
80
|
+
case content
|
|
81
|
+
in {type: "tool_use", name:, input:} if (tool = tools[name])
|
|
82
|
+
begin
|
|
83
|
+
coerced = Anthropic::Internal::Type::Converter.coerce(tool, input)
|
|
84
|
+
|
|
85
|
+
content.store(:parsed, coerced)
|
|
86
|
+
rescue StandardError => e
|
|
87
|
+
content.store(:parsed, {error: e.message})
|
|
88
|
+
end
|
|
89
|
+
in {type: "text", text:} if (model = models.first&.last)
|
|
90
|
+
begin
|
|
91
|
+
json = JSON.parse(text, symbolize_names: true)
|
|
92
|
+
coerced = Anthropic::Internal::Type::Converter.coerce(model, json)
|
|
93
|
+
|
|
94
|
+
content.store(:parsed, coerced)
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
content.store(:parsed, {error: e.message})
|
|
97
|
+
end
|
|
98
|
+
else
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
raw
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -60,10 +60,10 @@ module Anthropic
|
|
|
60
60
|
#
|
|
61
61
|
# Returns the complete accumulated Message object after stream completion.
|
|
62
62
|
#
|
|
63
|
-
# @return [Anthropic::Models::Message]
|
|
63
|
+
# @return [Anthropic::Models::Message, Anthropic::Models::Beta::BetaMessage]
|
|
64
64
|
def accumulated_message
|
|
65
65
|
until_done
|
|
66
|
-
parse_tool_uses!(@accumated_message_snapshot)
|
|
66
|
+
parse_tool_uses!(@accumated_message_snapshot)
|
|
67
67
|
@accumated_message_snapshot
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -76,18 +76,7 @@ module Anthropic
|
|
|
76
76
|
# @return [String]
|
|
77
77
|
def accumulated_text
|
|
78
78
|
message = accumulated_message
|
|
79
|
-
|
|
80
|
-
message.content.each do |block|
|
|
81
|
-
if block.type == :text
|
|
82
|
-
text_blocks << block.text
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
if text_blocks.empty?
|
|
87
|
-
raise RuntimeError.new("Expected to have received at least 1 text block")
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
text_blocks.join
|
|
79
|
+
message.content.map { _1.type == :text ? _1.text : nil }.join
|
|
91
80
|
end
|
|
92
81
|
|
|
93
82
|
# @api private
|
|
@@ -99,7 +88,10 @@ module Anthropic
|
|
|
99
88
|
#
|
|
100
89
|
# @return [Anthropic::Models::Message] updated message snapshot with event applied
|
|
101
90
|
private def accumulate_event(event:, current_snapshot:)
|
|
102
|
-
|
|
91
|
+
case event
|
|
92
|
+
in Anthropic::Models::RawMessageStreamEvent | Anthropic::Models::BetaRawMessageStreamEvent
|
|
93
|
+
nil
|
|
94
|
+
else
|
|
103
95
|
message = "Expected event to be a variant of RawMessageStreamEvent, got #{event.class}"
|
|
104
96
|
raise ArgumentError.new(message)
|
|
105
97
|
end
|
|
@@ -112,36 +104,37 @@ module Anthropic
|
|
|
112
104
|
end
|
|
113
105
|
|
|
114
106
|
case event
|
|
115
|
-
in Anthropic::Models::RawMessageStartEvent
|
|
116
|
-
# Use the converter to create a new, isolated copy of the message object.
|
|
107
|
+
in Anthropic::Models::RawMessageStartEvent # Use the converter to create a new, isolated copy of the message object.
|
|
117
108
|
# This ensures proper type validation and prevents shared object references
|
|
118
109
|
# that could lead to unintended mutations during streaming accumulation.
|
|
119
110
|
# Matches the Python SDK's approach of explicitly constructing Message objects.
|
|
120
111
|
return Anthropic::Internal::Type::Converter.coerce(Anthropic::Models::Message, event.message)
|
|
121
|
-
in Anthropic::Models::
|
|
112
|
+
in Anthropic::Models::BetaRawMessageStartEvent
|
|
113
|
+
return Anthropic::Internal::Type::Converter.coerce(Anthropic::Models::BetaMessage, event.message)
|
|
114
|
+
in Anthropic::Models::RawContentBlockStartEvent | Anthropic::Models::BetaRawContentBlockStartEvent
|
|
122
115
|
current_snapshot.content = (current_snapshot.content || []) + [event.content_block]
|
|
123
|
-
in Anthropic::Models::RawContentBlockDeltaEvent
|
|
116
|
+
in Anthropic::Models::RawContentBlockDeltaEvent | Anthropic::Models::BetaRawContentBlockDeltaEvent
|
|
124
117
|
content = current_snapshot.content[event.index]
|
|
125
118
|
|
|
126
119
|
case (delta = event.delta)
|
|
127
|
-
in Anthropic::Models::TextDelta if content.type == :text
|
|
120
|
+
in Anthropic::Models::TextDelta | Anthropic::Models::BetaTextDelta if content.type == :text
|
|
128
121
|
content.text += delta.text
|
|
129
|
-
in Anthropic::Models::InputJSONDelta if content.type == :tool_use
|
|
130
|
-
json_buf = content.
|
|
122
|
+
in Anthropic::Models::InputJSONDelta | Anthropic::Models::BetaInputJSONDelta if content.type == :tool_use
|
|
123
|
+
json_buf = content._json_buf.to_s
|
|
131
124
|
json_buf += delta.partial_json
|
|
132
125
|
|
|
133
126
|
content.input = json_buf
|
|
134
|
-
content.
|
|
135
|
-
in Anthropic::Models::CitationsDelta if content.type == :text
|
|
127
|
+
content._json_buf = json_buf
|
|
128
|
+
in Anthropic::Models::CitationsDelta | Anthropic::Models::BetaCitationsDelta if content.type == :text
|
|
136
129
|
content.citations ||= []
|
|
137
130
|
content.citations << delta.citation
|
|
138
|
-
in Anthropic::Models::ThinkingDelta if content.type == :thinking
|
|
131
|
+
in Anthropic::Models::ThinkingDelta | Anthropic::Models::BetaThinkingDelta if content.type == :thinking
|
|
139
132
|
content.thinking += delta.thinking
|
|
140
|
-
in Anthropic::Models::SignatureDelta if content.type == :thinking
|
|
133
|
+
in Anthropic::Models::SignatureDelta | Anthropic::Models::BetaSignatureDelta if content.type == :thinking
|
|
141
134
|
content.signature = delta.signature
|
|
142
135
|
else
|
|
143
136
|
end
|
|
144
|
-
in Anthropic::Models::RawMessageDeltaEvent
|
|
137
|
+
in Anthropic::Models::RawMessageDeltaEvent | Anthropic::Models::BetaRawMessageDeltaEvent
|
|
145
138
|
current_snapshot.stop_reason = event.delta.stop_reason
|
|
146
139
|
current_snapshot.stop_sequence = event.delta.stop_sequence
|
|
147
140
|
current_snapshot.usage.output_tokens = event.usage.output_tokens
|
|
@@ -166,51 +159,51 @@ module Anthropic
|
|
|
166
159
|
events_to_yield = []
|
|
167
160
|
|
|
168
161
|
case event
|
|
169
|
-
in Anthropic::Models::RawMessageStopEvent
|
|
162
|
+
in Anthropic::Models::RawMessageStopEvent | Anthropic::Models::BetaRawMessageStopEvent
|
|
170
163
|
events_to_yield << MessageStopEvent.new(
|
|
171
164
|
type: :message_stop,
|
|
172
165
|
message: message_snapshot
|
|
173
166
|
)
|
|
174
|
-
in Anthropic::Models::RawContentBlockDeltaEvent
|
|
167
|
+
in Anthropic::Models::RawContentBlockDeltaEvent | Anthropic::Models::BetaRawContentBlockDeltaEvent
|
|
175
168
|
events_to_yield << event
|
|
176
169
|
content_block = message_snapshot.content[event.index]
|
|
177
170
|
|
|
178
171
|
case (delta = event.delta)
|
|
179
|
-
in Anthropic::Models::TextDelta if content_block.type == :text
|
|
172
|
+
in Anthropic::Models::TextDelta | Anthropic::Models::BetaTextDelta if content_block.type == :text
|
|
180
173
|
events_to_yield << Anthropic::Streaming::TextEvent.new(
|
|
181
174
|
type: :text,
|
|
182
175
|
text: delta.text,
|
|
183
176
|
snapshot: content_block.text
|
|
184
177
|
)
|
|
185
|
-
in Anthropic::Models::InputJSONDelta if content_block.type == :tool_use
|
|
178
|
+
in Anthropic::Models::InputJSONDelta | Anthropic::Models::BetaInputJSONDelta if content_block.type == :tool_use
|
|
186
179
|
events_to_yield << Anthropic::Streaming::InputJsonEvent.new(
|
|
187
180
|
type: :input_json,
|
|
188
181
|
partial_json: delta.partial_json,
|
|
189
182
|
snapshot: content_block.input
|
|
190
183
|
)
|
|
191
|
-
in Anthropic::Models::CitationsDelta if content_block.type == :text
|
|
184
|
+
in Anthropic::Models::CitationsDelta | Anthropic::Models::BetaCitationsDelta if content_block.type == :text
|
|
192
185
|
events_to_yield << Anthropic::Streaming::CitationEvent.new(
|
|
193
186
|
type: :citation,
|
|
194
187
|
citation: delta.citation,
|
|
195
188
|
snapshot: content_block.citations || []
|
|
196
189
|
)
|
|
197
|
-
in Anthropic::Models::ThinkingDelta if content_block.type == :thinking
|
|
190
|
+
in Anthropic::Models::ThinkingDelta | Anthropic::Models::BetaThinkingDelta if content_block.type == :thinking
|
|
198
191
|
events_to_yield << Anthropic::Streaming::ThinkingEvent.new(
|
|
199
192
|
type: :thinking,
|
|
200
193
|
thinking: delta.thinking,
|
|
201
194
|
snapshot: content_block.thinking
|
|
202
195
|
)
|
|
203
|
-
in Anthropic::Models::SignatureDelta if content_block.type == :thinking
|
|
196
|
+
in Anthropic::Models::SignatureDelta | Anthropic::Models::BetaSignatureDelta if content_block.type == :thinking
|
|
204
197
|
events_to_yield << Anthropic::Streaming::SignatureEvent.new(
|
|
205
198
|
type: :signature,
|
|
206
199
|
signature: content_block.signature
|
|
207
200
|
)
|
|
208
201
|
else
|
|
209
202
|
end
|
|
210
|
-
in Anthropic::Models::RawContentBlockStopEvent
|
|
203
|
+
in Anthropic::Models::RawContentBlockStopEvent | Anthropic::Models::BetaRawContentBlockStopEvent
|
|
211
204
|
content_block = message_snapshot.content[event.index]
|
|
212
205
|
|
|
213
|
-
events_to_yield << ContentBlockStopEvent.new(
|
|
206
|
+
events_to_yield << Anthropic::Streaming::ContentBlockStopEvent.new(
|
|
214
207
|
type: :content_block_stop,
|
|
215
208
|
index: event.index,
|
|
216
209
|
content_block: content_block
|
|
@@ -232,21 +225,33 @@ module Anthropic
|
|
|
232
225
|
private def parse_tool_uses!(message)
|
|
233
226
|
return message unless message&.content
|
|
234
227
|
|
|
228
|
+
# rubocop:disable Metrics/BlockLength
|
|
235
229
|
message.content.each_with_index do |content, index|
|
|
236
230
|
next unless content.type == :tool_use
|
|
237
231
|
|
|
238
|
-
next unless (
|
|
232
|
+
next unless (tool = @tools[content.name])
|
|
239
233
|
|
|
240
234
|
parsed =
|
|
241
235
|
begin
|
|
242
|
-
parsed_input = content.input.is_a?(String)
|
|
236
|
+
parsed_input = if content.input.is_a?(String)
|
|
237
|
+
JSON.parse(content.input, symbolize_names: true)
|
|
238
|
+
else
|
|
239
|
+
content.input
|
|
240
|
+
end
|
|
243
241
|
|
|
244
|
-
Anthropic::Internal::Type::Converter.coerce(
|
|
242
|
+
Anthropic::Internal::Type::Converter.coerce(tool, parsed_input)
|
|
245
243
|
rescue StandardError => e
|
|
246
244
|
e
|
|
247
245
|
end
|
|
248
246
|
|
|
249
|
-
|
|
247
|
+
cls =
|
|
248
|
+
case content
|
|
249
|
+
in Anthropic::ContentBlock
|
|
250
|
+
Anthropic::Models::ToolUseBlock
|
|
251
|
+
else
|
|
252
|
+
Anthropic::Models::BetaToolUseBlock
|
|
253
|
+
end
|
|
254
|
+
message.content[index] = cls.new(
|
|
250
255
|
id: content.id,
|
|
251
256
|
input: content.input,
|
|
252
257
|
name: content.name,
|
|
@@ -254,6 +259,7 @@ module Anthropic
|
|
|
254
259
|
parsed: parsed
|
|
255
260
|
)
|
|
256
261
|
end
|
|
262
|
+
# rubocop:enable Metrics/BlockLength
|
|
257
263
|
|
|
258
264
|
message
|
|
259
265
|
end
|
|
@@ -261,14 +267,19 @@ module Anthropic
|
|
|
261
267
|
# @api private
|
|
262
268
|
#
|
|
263
269
|
# @param raw_stream [Anthropic::Internal::Type::BaseStream]
|
|
264
|
-
#
|
|
265
|
-
|
|
270
|
+
#
|
|
271
|
+
# @param tools [Hash{String=>Class}] Mapping of tool names to model classes
|
|
272
|
+
#
|
|
273
|
+
# @param models [Hash{String=>Class}] Mapping of tool names to model classes
|
|
274
|
+
def initialize(raw_stream:, tools: {}, models: {})
|
|
266
275
|
# The underlying Server-Sent Event stream from the Anthropic API.
|
|
267
276
|
@raw_stream = raw_stream
|
|
268
277
|
# Accumulated message state that builds up as events are processed.
|
|
269
278
|
@accumated_message_snapshot = nil
|
|
270
279
|
# Mapping of tool names to model classes for parsing.
|
|
271
|
-
@
|
|
280
|
+
@tools = tools
|
|
281
|
+
# Mapping of tool names to model classes for parsing.
|
|
282
|
+
@models = models
|
|
272
283
|
# Lazy enumerable that transforms raw events into consumable events.
|
|
273
284
|
@iterator = iterator
|
|
274
285
|
@status = raw_stream.status
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Anthropic
|
|
4
|
+
module Helpers
|
|
5
|
+
module Tools
|
|
6
|
+
class BaseTool
|
|
7
|
+
include Anthropic::Internal::Type::Converter
|
|
8
|
+
include Anthropic::Helpers::InputSchema::JsonSchemaConverter
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# @api public
|
|
12
|
+
#
|
|
13
|
+
# @return [Class<Anthropic::Helpers::InputSchema::BaseModel>]
|
|
14
|
+
attr_reader :model
|
|
15
|
+
|
|
16
|
+
# @return [String]
|
|
17
|
+
attr_reader :doc_string
|
|
18
|
+
|
|
19
|
+
# @api public
|
|
20
|
+
#
|
|
21
|
+
# @param description [String]
|
|
22
|
+
def description(description) = (@doc_string = description)
|
|
23
|
+
|
|
24
|
+
alias_method :doc, :description
|
|
25
|
+
|
|
26
|
+
# @api public
|
|
27
|
+
#
|
|
28
|
+
# @model [Class<Anthropic::Helpers::InputSchema::BaseModel>]
|
|
29
|
+
def input_schema(model) = (@model = model)
|
|
30
|
+
|
|
31
|
+
# @api private
|
|
32
|
+
#
|
|
33
|
+
# @param depth [Integer]
|
|
34
|
+
def inspect(depth: 0) = "#{name}[#{model.inspect(depth:)}]"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @api private
|
|
38
|
+
#
|
|
39
|
+
def to_json_schema_inner(state:) = self.class.model&.to_json_schema_inner(state:)
|
|
40
|
+
|
|
41
|
+
# @api private
|
|
42
|
+
#
|
|
43
|
+
def to_json_schema = self.class.model&.to_json_schema
|
|
44
|
+
|
|
45
|
+
# @api private
|
|
46
|
+
#
|
|
47
|
+
def dump(value, state:)
|
|
48
|
+
Anthropic::Internal::Type::Converter.dump(self.class.model, value, state:)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @api private
|
|
52
|
+
#
|
|
53
|
+
def coerce(value, state:)
|
|
54
|
+
parsed = parse(value)
|
|
55
|
+
Anthropic::Internal::Type::Converter.coerce(self.class.model, parsed, state:)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
|
59
|
+
|
|
60
|
+
# @api public
|
|
61
|
+
#
|
|
62
|
+
# Override the `#parse` method to customize the pre-processing of the tool call argument
|
|
63
|
+
#
|
|
64
|
+
# @param value [Object]
|
|
65
|
+
#
|
|
66
|
+
# @return [Object]
|
|
67
|
+
def parse(value) = value
|
|
68
|
+
|
|
69
|
+
# @api public
|
|
70
|
+
#
|
|
71
|
+
# @param parsed [Anthropic::Helpers::InputSchema::BaseModel]
|
|
72
|
+
def call(parsed) = raise NotImplementedError.new
|
|
73
|
+
|
|
74
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
|
75
|
+
|
|
76
|
+
# @api private
|
|
77
|
+
#
|
|
78
|
+
def inspect = "#<#{self.class.inspect(depth: 1)}:0x#{object_id.to_s(16)}>"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|