anthropic-rb 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47751095db1f8f6ad2e30c5161f0137dd8eba0838a2c0a2ccce8cb51aefa278e
4
- data.tar.gz: 5de9082d8b07c9f19ec20eeb4dee0525f7797585dbcb208563b85ef040bd2f0f
3
+ metadata.gz: 0e743c686b28964af68a40c2e322caf6986284e2922d93d5acc0e2783128a5e5
4
+ data.tar.gz: 6ae03121d9cae81f15abdacb69a119428a9780f629fde7985a1a22be60192ea4
5
5
  SHA512:
6
- metadata.gz: 4d5be01a84c735f0bf815cba09cd591da94085cf65cf5387c839eba1f3b0c52ed1af63f26b7044d0d8a1c968bfb10bb7efe030f560e378c4ca7b1e70a5e37d44
7
- data.tar.gz: c8dd64d95f97c348839e12cc52bbdbdbbaccab310eeff232db99120bcc74584c31781296686d3a9fac6077f94492311d6c49c22b9f330f8754038b8ff958f180
6
+ metadata.gz: 352230790e752e44670cb9328b6f93139c1b5c6be9717b65996f1571a34acc9fd7fb28047bddcb32e5ce0c1826faeecb8364d9fd52ace649df868aad86428f9d
7
+ data.tar.gz: 2d94f8ac749b7d87d2081d2733a5fa52a0771a3dac3a7347af36ae3c40451b9c76362e8dc45540fb060b8c66d79c3fb3c7b351e08fedb489378758c84f9fc81e
data/.reek.yml CHANGED
@@ -5,21 +5,18 @@ detectors:
5
5
  TooManyStatements:
6
6
  exclude:
7
7
  - 'Anthropic::Api::Concerns::Validatable#schema'
8
- - 'Anthropic::BaseApi#beta_config'
9
- - 'Anthropic::BaseApi#version_config'
8
+ - 'Anthropic::Api::Base#beta_config'
9
+ - 'Anthropic::Api::Base#version_config'
10
10
  - 'Anthropic::Bootstrapper#self.load_betas'
11
11
  - 'Anthropic::Bootstrapper#self.load_versions'
12
- - 'Anthropic::Client#self.post'
13
- BooleanParameter:
14
- exclude:
15
- - 'Anthropic::Messages#initialize'
12
+ - 'Anthropic::Client::Standard#self.post'
13
+ - 'Anthropic::Client::Streaming#self.post'
16
14
  DuplicateMethodCall:
17
15
  exclude:
18
- - 'Anthropic::BaseApi#version_config'
19
- - 'Anthropic::Messages#create'
16
+ - 'Anthropic::Api::Base#version_config'
20
17
  InstanceVariableAssumption:
21
18
  exclude:
22
- - 'Anthropic::BaseApi'
19
+ - 'Anthropic::Api::Base'
23
20
  NestedIterators:
24
21
  exclude:
25
22
  - 'Anthropic::Bootstrapper#self.load_versions'
data/.rubocop.yml CHANGED
@@ -4,7 +4,7 @@ require:
4
4
  - rubocop-rspec
5
5
 
6
6
  AllCops:
7
- TargetRubyVersion: 3.1
7
+ TargetRubyVersion: 3.2
8
8
  NewCops: enable
9
9
  Naming/RescuedExceptionsVariableName:
10
10
  Enabled: false
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.1.4
1
+ ruby 3.2.4
data/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.6.0] - 2024-06-25
6
+
7
+ ### Breaking Changes
8
+
9
+ - Responses from the Anthropic API are now encapsulated as data objects.
10
+ - Dropped support for Ruby versions prior to 3.2.4.
11
+
12
+ ### Added
13
+
14
+ - Added link to `anthropic-rb-cookbook`.
15
+
16
+ ### Removed
17
+
18
+ - Removed streaming contraint for tools use.
19
+
20
+ ### Updated
21
+
22
+ - Refactored project for maintainability.
23
+ - Updated project funding.
24
+
5
25
  ## [0.5.0] - 2024-04-22
6
26
 
7
27
  ### Updated
@@ -9,6 +29,7 @@
9
29
  - Refactored project for maintainability.
10
30
 
11
31
  ### Breaking Changes
32
+
12
33
  - You must now pass the beta ID when enabling a beta feature. The only current beta is for Tools (id: tools-2024-04-04). Previously, you would pass `true` to enable the beta.
13
34
 
14
35
  ## [0.4.0] - 2024-04-20
@@ -66,7 +87,8 @@
66
87
 
67
88
  - Initial release
68
89
 
69
- [Unreleased]: https://github.com/dickdavis/anthropic-rb/compare/v0.5.0...HEAD
90
+ [Unreleased]: https://github.com/dickdavis/anthropic-rb/compare/v0.6.0...HEAD
91
+ [0.6.0]: https://github.com/dickdavis/anthropic-rb/compare/v0.5.0...v0.6.0
70
92
  [0.5.0]: https://github.com/dickdavis/anthropic-rb/compare/v0.4.0...v0.5.0
71
93
  [0.4.0]: https://github.com/dickdavis/anthropic-rb/compare/v0.3.0...v0.4.0
72
94
  [0.3.0]: https://github.com/dickdavis/anthropic-rb/compare/v0.2.5...v0.3.0
data/README.md CHANGED
@@ -4,6 +4,8 @@ Ruby bindings for the Anthropic API. This library is unofficial and is not affil
4
4
 
5
5
  The goal of this project is feature parity with Anthropic's Python SDK until an official Ruby SDK is available.
6
6
 
7
+ You can find examples of usage in the [anthropic-rb-cookbook](https://github.com/dickdavis/anthropic-rb-cookbook/).
8
+
7
9
  ## Usage
8
10
 
9
11
  anthropic-rb will default to the value of the `ANTHROPIC_API_KEY` environment variable. However, you may initialize the library with your API key. You must set your API key before using the library.
@@ -34,35 +36,61 @@ You can send a request to the Messages API.
34
36
  Anthropic.messages.create(model: 'claude-2.1', max_tokens: 200, messages: [{role: 'user', content: 'Yo what up?'}])
35
37
 
36
38
  # Output =>
37
- # {
38
- # id: "msg_013ePdwEkb4RMC1hCE61Hbm8",
39
- # type: "message",
40
- # role: "assistant",
41
- # content: [{type: "text", text: "Hello! Not much up with me, just chatting. How about you?"}],
42
- # model: "claude-2.1",
43
- # stop_reason: "end_turn",
44
- # stop_sequence: nil
45
- # }
39
+ #<data Anthropic::Client::Response status="success", body={:id=>"msg_01UqHiw6oFLjMYiLV8hkXsrR", :type=>"message", :role=>"assistant", :model=>"claude-2.1", :content=>[{:type=>"text", :text=>"Hello! Not much up with me, just chatting with you. How's it going?"}], :stop_reason=>"end_turn", :stop_sequence=>nil, :usage=>{:input_tokens=>13, :output_tokens=>22}}>
46
40
  ```
47
41
 
48
42
  Alternatively, you can stream the response:
49
43
 
50
44
  ```ruby
51
- Anthropic.messages.create(model: 'claude-2.1', max_tokens: 200, messages: [{role: 'user', content: 'Yo what up?'}], stream: true) do |event|
45
+ options = {
46
+ model: 'claude-2.1',
47
+ max_tokens: 200,
48
+ messages: [{role: 'user', content: 'Yo what up?'}],
49
+ stream: true
50
+ }
51
+
52
+ Anthropic.messages.create(**options) do |event|
52
53
  puts event
53
54
  end
54
55
 
55
56
  # Output =>
56
- # { type: 'message_start', message: { id: 'msg_012pkeozZynwyNvSagwL7kMw', type: 'message', role: 'assistant', content: [], model: 'claude-2.1', stop_reason: nil, stop_sequence: nil } }
57
- # { type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } }
58
- # { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: 'Hello' } }
59
- # { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: '.' } }
60
- # { type: 'content_block_stop', index: 0 }
61
- # { type: 'message_delta', delta: { stop_reason: 'end_turn', stop_sequence: nil } }
62
- # { type: 'message_stop' }
57
+ #<data Anthropic::Client::Response status="success", body={:type=>"message_start", :message=>{:id=>"msg_01EsYcQkBJrHrtgpY5ZcLzvf", :type=>"message", :role=>"assistant", :model=>"claude-2.1", :content=>[], :stop_reason=>nil, :stop_sequence=>nil, :usage=>{:input_tokens=>13, :output_tokens=>1}}}>
58
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_start", :index=>0, :content_block=>{:type=>"text", :text=>""}}>
59
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>"Hello"}}>
60
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>"!"}}>
61
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" Not"}}>
62
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" much"}}>
63
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" up"}}>
64
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" with"}}>
65
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" me"}}>
66
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>","}}>
67
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" I"}}>
68
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>"'m"}}>
69
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" an"}}>
70
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" AI"}}>
71
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" assistant"}}>
72
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" create"}}>
73
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>"d by"}}>
74
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>" An"}}>
75
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>"throp"}}>
76
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>"ic"}}>
77
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_delta", :index=>0, :delta=>{:type=>"text_delta", :text=>"."}}>
78
+ #<data Anthropic::Client::Response status="success", body={:type=>"content_block_stop", :index=>0}>
79
+ #<data Anthropic::Client::Response status="success", body={:type=>"message_delta", :delta=>{:stop_reason=>"end_turn", :stop_sequence=>nil}, :usage=>{:output_tokens=>23}}>
80
+ #<data Anthropic::Client::Response status="success", body={:type=>"message_stop"}>
81
+
82
+ # Or, if you just want to print the text content:
83
+ Anthropic.messages.create(**options) do |event|
84
+ next unless event.body[:type] == 'content_block_delta'
85
+
86
+ print event.body[:delta][:text]
87
+ end
88
+
89
+ # Output =>
90
+ # Hello! Not much up with me, I'm an AI assistant created by Anthropic.
63
91
  ```
64
92
 
65
- You can also experiment with the new tools beta by passing the `beta` name when calling the API. This will ensure each request includes the correct beta header and validate properly.
93
+ You can also pass in a list of tools the assistant can use. You can find out more information about tools in the [documentation](https://docs.anthropic.com/claude/docs/tool-use).
66
94
 
67
95
  ```ruby
68
96
  tools = [
@@ -79,7 +107,7 @@ tools = [
79
107
  }
80
108
  ]
81
109
 
82
- Anthropic.messages(beta: 'tools-2024-04-04').create(
110
+ Anthropic.messages.create(
83
111
  model: 'claude-3-opus-20240229',
84
112
  max_tokens: 200,
85
113
  tools:,
@@ -87,8 +115,6 @@ Anthropic.messages(beta: 'tools-2024-04-04').create(
87
115
  )
88
116
  ```
89
117
 
90
- Streaming is currently not supported by the tools beta. You can find out more information about tools in the [documentation](https://docs.anthropic.com/claude/docs/tool-use).
91
-
92
118
  ### Completions API
93
119
 
94
120
  To make a request to the Completions API:
@@ -101,38 +127,41 @@ Anthropic.completions.create(
101
127
  )
102
128
 
103
129
  # Output =>
104
- # {
105
- # completion: "Hello! Not much going on with me, just chatting. How about you?",
106
- # stop_reason: "stop_sequence",
107
- # model: "claude-2.1",
108
- # stop: "\n\nHuman:",
109
- # log_id: "2496914137c520ec2b4ae8315864bcf3a4c6ce9f2e3c96e13be4c004587313ca"
110
- # }
130
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_01Y9ptPR7xGHaH9rC3ffJExU", :completion=>" Hello! Not much going on here. How about you?", :stop_reason=>"stop_sequence", :model=>"claude-2.1", :stop=>"\n\nHuman:", :log_id=>"compl_01Y9ptPR7xGHaH9rC3ffJExU"}>
111
131
  ```
112
132
 
113
133
  Alternatively, you can stream the response:
114
134
 
115
135
  ```ruby
116
- Anthropic.completions.create(model: 'claude-2', max_tokens_to_sample: 200, prompt: 'Human: Yo what up?\n\nAssistant:', stream: true) do |event|
136
+ options = {
137
+ model: 'claude-2',
138
+ max_tokens_to_sample: 200,
139
+ prompt: 'Human: Yo what up?\n\nAssistant:',
140
+ stream: true
141
+ }
142
+
143
+ Anthropic.completions.create(**options) do |event|
117
144
  puts event
118
145
  end
119
146
 
120
147
  # Output =>
121
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' Hello', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
122
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: '!', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
123
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' Not', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
124
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' much', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
125
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ',', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
126
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' just', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
127
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' chatting', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
128
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' with', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
129
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' people', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
130
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: '.', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
131
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' How', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
132
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' about', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
133
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: ' you', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
134
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: '?', stop_reason: nil, model: 'claude-2.1', stop: nil, log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
135
- # { type: 'completion', id: 'compl_01G6cEfdZtLEEJVRzwUShiDY', completion: '', stop_reason: 'stop_sequence', model: 'claude-2.1', stop: "\n\nHuman:", log_id: 'compl_01G6cEfdZtLEEJVRzwUShiDY' }
148
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_015AktggW7tcM4w11YpkuMbP", :completion=>" Hello", :stop_reason=>nil, :model=>"claude-2.1", :stop=>nil, :log_id=>"compl_015AktggW7tcM4w11YpkuMbP"}>
149
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_015AktggW7tcM4w11YpkuMbP", :completion=>"!", :stop_reason=>nil, :model=>"claude-2.1", :stop=>nil, :log_id=>"compl_015AktggW7tcM4w11YpkuMbP"}>
150
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_015AktggW7tcM4w11YpkuMbP", :completion=>" Not", :stop_reason=>nil, :model=>"claude-2.1", :stop=>nil, :log_id=>"compl_015AktggW7tcM4w11YpkuMbP"}>
151
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_015AktggW7tcM4w11YpkuMbP", :completion=>" much", :stop_reason=>nil, :model=>"claude-2.1", :stop=>nil, :log_id=>"compl_015AktggW7tcM4w11YpkuMbP"}>
152
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_015AktggW7tcM4w11YpkuMbP", :completion=>" going", :stop_reason=>nil, :model=>"claude-2.1", :stop=>nil, :log_id=>"compl_015AktggW7tcM4w11YpkuMbP"}>
153
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_015AktggW7tcM4w11YpkuMbP", :completion=>" on", :stop_reason=>nil, :model=>"claude-2.1", :stop=>nil, :log_id=>"compl_015AktggW7tcM4w11YpkuMbP"}>
154
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_015AktggW7tcM4w11YpkuMbP", :completion=>" here", :stop_reason=>nil, :model=>"claude-2.1", :stop=>nil, :log_id=>"compl_015AktggW7tcM4w11YpkuMbP"}>
155
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_015AktggW7tcM4w11YpkuMbP", :completion=>".", :stop_reason=>nil, :model=>"claude-2.1", :stop=>nil, :log_id=>"compl_015AktggW7tcM4w11YpkuMbP"}>
156
+ #<data Anthropic::Client::Response status="success", body={:type=>"completion", :id=>"compl_015AktggW7tcM4w11YpkuMbP", :completion=>"", :stop_reason=>"stop_sequence", :model=>"claude-2.1", :stop=>"\n\nHuman:", :log_id=>"compl_015AktggW7tcM4w11YpkuMbP"}>
157
+
158
+ # Or, if you just want to print the text content:
159
+ Anthropic.completions.create(**options) do |event|
160
+ print event.body[:completion]
161
+ end
162
+
163
+ # Output =>
164
+ # Hello! Not much, just chatting with you. How's it going?
136
165
  ```
137
166
 
138
167
  ## Installation
@@ -161,6 +190,8 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
161
190
 
162
191
  To install this gem onto your local machine, run `bundle exec rake install`.
163
192
 
193
+ To run a sanity check, run `ruby sanity_check.rb`.
194
+
164
195
  To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
165
196
 
166
197
  ## Contributing
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'concerns/requestable'
4
+ require_relative 'concerns/validatable'
5
+
6
+ module Anthropic
7
+ module Api
8
+ # Error for when a beta feature is configured incorrectly.
9
+ class InvalidBetaConfigurationError < StandardError; end
10
+
11
+ # Error for when API version is missing a schema.
12
+ class MissingSchemaError < StandardError; end
13
+
14
+ # Error for when the provided params do not match the API schema
15
+ class SchemaValidationError < StandardError; end
16
+
17
+ # Error for when the API version is not supported.
18
+ class UnsupportedApiVersionError < StandardError; end
19
+
20
+ # Error for when the provided beta is not supported.
21
+ class UnsupportedBetaError < StandardError; end
22
+
23
+ ##
24
+ # Provides a base class for APIs
25
+ class Base
26
+ include Anthropic::Api::Concerns::Requestable
27
+ include Anthropic::Api::Concerns::Validatable
28
+
29
+ def initialize(beta: nil)
30
+ @beta = beta
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :beta
36
+
37
+ def api
38
+ self.class.name.split('::').last.downcase
39
+ end
40
+
41
+ def version_config
42
+ return @version_config if defined?(@version_config)
43
+
44
+ @version_config ||= catch(:version_found) do
45
+ found_config = Anthropic.versions[api.to_sym].find { |config| config['version'] == Anthropic.api_version }
46
+ unless found_config
47
+ raise Anthropic::Api::UnsupportedApiVersionError, "Unsupported API version: #{Anthropic.api_version}"
48
+ end
49
+
50
+ throw :version_found, found_config
51
+ end
52
+ end
53
+
54
+ def beta_config
55
+ return @beta_config if defined?(@beta_config)
56
+
57
+ @beta_config = catch(:beta_found) do
58
+ found_config = Anthropic.betas.find { |config| config['id'] == beta }
59
+ raise Anthropic::Api::UnsupportedBetaError, "#{beta} not supported" unless found_config
60
+
61
+ throw :beta_found, found_config
62
+ end
63
+ end
64
+
65
+ def beta_loaded?(name)
66
+ return false unless beta
67
+
68
+ beta_config['id'] == name
69
+ end
70
+ end
71
+ end
72
+ end
@@ -4,7 +4,7 @@ module Anthropic
4
4
  module Api
5
5
  ##
6
6
  # Provides bindings for the Anthropic completions API
7
- class Completions < BaseApi
7
+ class Completions < Base
8
8
  def create(**params, &)
9
9
  validate!(params)
10
10
  return post(params) unless params[:stream]
@@ -7,11 +7,11 @@ module Anthropic
7
7
  # Provides helpers for sending API requests
8
8
  module Requestable
9
9
  def post(params)
10
- Anthropic::Client.post(uri, params, additional_headers)
10
+ Anthropic::Client::Standard.post(uri, params, additional_headers)
11
11
  end
12
12
 
13
13
  def post_as_stream(params, &)
14
- Anthropic::Client.post_as_stream(uri, params, additional_headers, &)
14
+ Anthropic::Client::Streaming.post(uri, params, additional_headers, &)
15
15
  end
16
16
 
17
17
  def uri
@@ -26,7 +26,7 @@ module Anthropic
26
26
  return {} unless beta
27
27
 
28
28
  header = beta_config['header']
29
- raise Anthropic::Errors::InvalidBetaConfigurationError, "Missing header: #{beta}" unless header
29
+ raise Anthropic::Api::InvalidBetaConfigurationError, "Missing header: #{beta}" unless header
30
30
 
31
31
  header
32
32
  end
@@ -9,19 +9,19 @@ module Anthropic
9
9
  def validate!(params)
10
10
  JSON::Validator.validate!(schema, params)
11
11
  rescue JSON::Schema::ValidationError => error
12
- raise Anthropic::Errors::SchemaValidationError, error.message
12
+ raise Anthropic::Api::SchemaValidationError, error.message
13
13
  end
14
14
 
15
15
  def schema
16
16
  api_schema = version_config['schema']
17
17
 
18
18
  unless api_schema
19
- raise Anthropic::Errors::MissingSchemaError, "Missing schema for API version: #{Anthropic.api_version}"
19
+ raise Anthropic::Api::MissingSchemaError, "Missing schema for API version: #{Anthropic.api_version}"
20
20
  end
21
21
 
22
22
  if beta
23
23
  beta_schema = beta_config['schema']
24
- raise Anthropic::Errors::InvalidBetaConfigurationError, "Missing beta schema: #{beta}" unless beta_schema
24
+ raise Anthropic::Api::InvalidBetaConfigurationError, "Missing beta schema: #{beta}" unless beta_schema
25
25
 
26
26
  api_schema['properties'].merge!(beta_schema)
27
27
  end
@@ -4,15 +4,10 @@ module Anthropic
4
4
  module Api
5
5
  ##
6
6
  # Provides bindings for the Anthropic messages API
7
- class Messages < BaseApi
7
+ class Messages < Base
8
8
  def create(**params, &)
9
- streaming = params[:stream]
10
- if streaming && beta_loaded?('tools-2024-04-04')
11
- raise Anthropic::Errors::UnsupportedBetaUseError, 'Tool use is not yet supported in streaming mode'
12
- end
13
-
14
9
  validate!(params)
15
- return post(params) unless streaming
10
+ return post(params) unless params[:stream]
16
11
 
17
12
  post_as_stream(params, &)
18
13
  end
@@ -7,9 +7,7 @@ module Anthropic
7
7
  # Provides methods for bootstrapping the Anthropic gem
8
8
  module Bootstrapper
9
9
  def self.load_betas
10
- current_dir = File.dirname(__FILE__)
11
- directory_path = File.join(current_dir, 'betas')
12
-
10
+ directory_path = File.expand_path('../../schemas/betas', __dir__)
13
11
  raise "Directory not found: #{directory_path}" unless Dir.exist?(directory_path)
14
12
 
15
13
  file_paths = Dir.glob(File.join(directory_path, '*.json'))
@@ -21,9 +19,7 @@ module Anthropic
21
19
 
22
20
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
23
21
  def self.load_versions
24
- current_dir = File.dirname(__FILE__)
25
- directory_path = File.join(current_dir, 'versions')
26
-
22
+ directory_path = File.expand_path('../../schemas/versions', __dir__)
27
23
  raise "Directory not found: #{directory_path}" unless Dir.exist?(directory_path)
28
24
 
29
25
  versions = {}
@@ -1,30 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'httpx'
4
+
3
5
  module Anthropic
4
- module Errors
6
+ module Client
5
7
  ##
6
- # Error when the request is malformed.
7
- class BadRequestError < StandardError; end
8
+ # Error when the server experienced an internal error.
9
+ class ApiError < StandardError; end
10
+
8
11
  ##
9
12
  # Error when the API key is invalid.
10
13
  class AuthenticationError < StandardError; end
14
+
11
15
  ##
12
- # Error when the account does not have permission for the operation.
13
- class PermissionDeniedError < StandardError; end
16
+ # Error when the resource already exists.
17
+ class ConflictError < StandardError; end
18
+
19
+ ##
20
+ # Error when the request is malformed.
21
+ class InvalidRequestError < StandardError; end
22
+
14
23
  ##
15
24
  # Error when the resource is not found.
16
25
  class NotFoundError < StandardError; end
26
+
17
27
  ##
18
- # Error when the resource already exists.
19
- class ConflictError < StandardError; end
28
+ # Error when the API servers are overloaded.
29
+ class OverloadedError < StandardError; end
30
+
20
31
  ##
21
- # Error when the resource cannot be processed.
22
- class UnprocessableEntityError < StandardError; end
32
+ # Error when the account does not have permission for the operation.
33
+ class PermissionError < StandardError; end
34
+
23
35
  ##
24
36
  # Error when the request exceeds the rate limit.
25
37
  class RateLimitError < StandardError; end
38
+
26
39
  ##
27
- # Error when the server experienced an internal error.
28
- class InternalServerError < StandardError; end
40
+ # Error when the resource cannot be processed.
41
+ class UnprocessableEntityError < StandardError; end
42
+
43
+ ##
44
+ # Defines a data object for responses
45
+ Response = Data.define(:status, :body)
46
+
47
+ ##
48
+ # Provides a base class for clients
49
+ class Base
50
+ class << self
51
+ private
52
+
53
+ def build_response(response)
54
+ response_hash = JSON.parse(response, symbolize_names: true)
55
+ status = response_hash[:type] == 'error' ? 'failure' : 'success'
56
+ Anthropic::Client::Response.new(status:, body: response_hash)
57
+ end
58
+ end
59
+ end
29
60
  end
30
61
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anthropic
4
+ module Client
5
+ ##
6
+ # Provides a client for sending standard HTTP requests.
7
+ class Standard < Base
8
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
9
+ def self.post(url, data, headers = {})
10
+ response = HTTPX.with(
11
+ headers: {
12
+ 'Content-Type' => 'application/json',
13
+ 'x-api-key' => Anthropic.api_key,
14
+ 'anthropic-version' => Anthropic.api_version
15
+ }.merge(headers)
16
+ ).post(url, json: data)
17
+
18
+ response_data = build_response(response.body)
19
+
20
+ case response.status
21
+ when 200
22
+ response_data
23
+ when 400
24
+ raise Anthropic::Client::InvalidRequestError, response_data
25
+ when 401
26
+ raise Anthropic::Client::AuthenticationError, response_data
27
+ when 403
28
+ raise Anthropic::Client::PermissionError, response_data
29
+ when 404
30
+ raise Anthropic::Client::NotFoundError, response_data
31
+ when 409
32
+ raise Anthropic::Client::ConflictError, response_data
33
+ when 422
34
+ raise Anthropic::Client::UnprocessableEntityError, response_data
35
+ when 429
36
+ raise Anthropic::Client::RateLimitError, response_data
37
+ when 500
38
+ raise Anthropic::Client::ApiError, response_data
39
+ when 529
40
+ raise Anthropic::Client::OverloadedError, response_data
41
+ end
42
+ end
43
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anthropic
4
+ module Client
5
+ ##
6
+ # Provides a client for sending streaming HTTP requests.
7
+ class Streaming < Base
8
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
9
+ def self.post(url, data, headers = {})
10
+ response = HTTPX.plugin(:stream).with(
11
+ headers: {
12
+ 'Content-Type' => 'application/json',
13
+ 'x-api-key' => Anthropic.api_key,
14
+ 'anthropic-version' => Anthropic.api_version
15
+ }.merge(headers)
16
+ ).post(url, json: data, stream: true)
17
+
18
+ response.each_line do |line|
19
+ type, event = line.split(/(\w+\b:\s)/)[1..2]
20
+ next unless type&.start_with?('data') && event
21
+
22
+ response_data = build_response(event)
23
+ yield response_data unless %w[ping error].include?(response_data.body[:type])
24
+ end
25
+ rescue HTTPX::HTTPError => error
26
+ case error.response.status
27
+ when 400
28
+ raise Anthropic::Client::InvalidRequestError
29
+ when 401
30
+ raise Anthropic::Client::AuthenticationError
31
+ when 403
32
+ raise Anthropic::Client::PermissionError
33
+ when 404
34
+ raise Anthropic::Client::NotFoundError
35
+ when 409
36
+ raise Anthropic::Client::ConflictError
37
+ when 422
38
+ raise Anthropic::Client::UnprocessableEntityError
39
+ when 429
40
+ raise Anthropic::Client::RateLimitError
41
+ when 500
42
+ raise Anthropic::Client::ApiError
43
+ when 529
44
+ raise Anthropic::Client::OverloadedError
45
+ end
46
+ end
47
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Anthropic
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.0'
5
5
  end
data/sanity_check.rb CHANGED
@@ -21,7 +21,7 @@ Anthropic.messages.create(
21
21
  stream: true
22
22
  ) { |event| puts event }
23
23
 
24
- puts "\nTesting tools beta"
24
+ puts "\nTesting tools"
25
25
  tools = [
26
26
  {
27
27
  name: 'get_weather',
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "tools-2024-04-04",
3
- "name": "Tools",
4
- "description": "Equips Claude with custom tools for interacting with clients for a wide variety of tasks.",
3
+ "name": "[Deprecated] Tools",
4
+ "description": "[Deprecated] Equips Claude with custom tools for interacting with clients for a wide variety of tasks.",
5
5
  "documentation": "https://docs.anthropic.com/claude/docs/tool-use",
6
6
  "header" : { "anthropic-beta": "tools-2024-04-04" },
7
7
  "schema": {
@@ -30,6 +30,23 @@
30
30
  "temperature": {
31
31
  "type": "number"
32
32
  },
33
+ "tools": {
34
+ "type": "array",
35
+ "items": {
36
+ "type": "object",
37
+ "properties": {
38
+ "name": {
39
+ "type": "string"
40
+ },
41
+ "description": {
42
+ "type": "string"
43
+ },
44
+ "input_schema": {
45
+ "type": "object"
46
+ }
47
+ }
48
+ }
49
+ },
33
50
  "top_k": {
34
51
  "type": "integer"
35
52
  },
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anthropic-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dick Davis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-23 00:00:00.000000000 Z
11
+ date: 2024-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpx
@@ -54,23 +54,21 @@ files:
54
54
  - LICENSE.txt
55
55
  - README.md
56
56
  - Rakefile
57
- - anthropic-rb.gemspec
58
57
  - lib/anthropic.rb
59
- - lib/anthropic/api/base_api.rb
58
+ - lib/anthropic/api/base.rb
60
59
  - lib/anthropic/api/completions.rb
61
60
  - lib/anthropic/api/concerns/requestable.rb
62
61
  - lib/anthropic/api/concerns/validatable.rb
63
62
  - lib/anthropic/api/messages.rb
64
- - lib/anthropic/betas/tools-2024-04-04.json
65
63
  - lib/anthropic/bootstrapper.rb
66
- - lib/anthropic/client.rb
67
- - lib/anthropic/errors/betas.rb
68
- - lib/anthropic/errors/requests.rb
69
- - lib/anthropic/errors/versions.rb
64
+ - lib/anthropic/client/base.rb
65
+ - lib/anthropic/client/standard.rb
66
+ - lib/anthropic/client/streaming.rb
70
67
  - lib/anthropic/version.rb
71
- - lib/anthropic/versions/completions/2023-06-01.json
72
- - lib/anthropic/versions/messages/2023-06-01.json
73
68
  - sanity_check.rb
69
+ - schemas/betas/tools-2024-04-04.json
70
+ - schemas/versions/completions/2023-06-01.json
71
+ - schemas/versions/messages/2023-06-01.json
74
72
  - sig/anthropic/rb.rbs
75
73
  homepage: https://github.com/dickdavis/anthropic-rb
76
74
  licenses:
@@ -89,14 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
89
87
  requirements:
90
88
  - - ">="
91
89
  - !ruby/object:Gem::Version
92
- version: '3.1'
90
+ version: 3.2.4
93
91
  required_rubygems_version: !ruby/object:Gem::Requirement
94
92
  requirements:
95
93
  - - ">="
96
94
  - !ruby/object:Gem::Version
97
95
  version: '0'
98
96
  requirements: []
99
- rubygems_version: 3.3.26
97
+ rubygems_version: 3.4.19
100
98
  signing_key:
101
99
  specification_version: 4
102
100
  summary: Ruby bindings for the Anthropic API
data/anthropic-rb.gemspec DELETED
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lib/anthropic/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'anthropic-rb'
7
- spec.version = Anthropic::VERSION
8
- spec.authors = ['Dick Davis']
9
- spec.email = ['dick@hey.com']
10
-
11
- spec.summary = 'Ruby bindings for the Anthropic API'
12
- spec.homepage = 'https://github.com/dickdavis/anthropic-rb'
13
- spec.license = 'MIT'
14
-
15
- spec.metadata['allowed_push_host'] = 'https://rubygems.org'
16
- spec.metadata['homepage_uri'] = spec.homepage
17
- spec.metadata['source_code_uri'] = 'https://github.com/dickdavis/anthropic-rb'
18
- spec.metadata['changelog_uri'] = 'https://github.com/dickdavis/anthropic-rb/blob/main/CHANGELOG.md'
19
-
20
- # Specify which files should be added to the gem when it is released.
21
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(__dir__) do
23
- `git ls-files -z`.split("\x0").reject do |f|
24
- (File.expand_path(f) == __FILE__) ||
25
- f.start_with?(*%w[bin/ spec/ .git .github/ Gemfile])
26
- end
27
- end + Dir.glob('lib/**/*.json')
28
- spec.bindir = 'exe'
29
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
- spec.require_paths = ['lib']
31
-
32
- # Uncomment to register a new dependency of your gem
33
- # spec.add_dependency "example-gem", "~> 1.0"
34
-
35
- # For more information and examples about making a new gem, check out our
36
- # guide at: https://bundler.io/guides/creating_gem.html
37
- spec.metadata['rubygems_mfa_required'] = 'true'
38
-
39
- spec.required_ruby_version = '>= 3.1'
40
- spec.add_dependency 'httpx', '>= 1.1.5'
41
- spec.add_dependency 'json-schema', '>= 4.1.1'
42
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'concerns/requestable'
4
- require_relative 'concerns/validatable'
5
-
6
- module Anthropic
7
- ##
8
- # Provides a base class for APIs
9
- class BaseApi
10
- include Anthropic::Api::Concerns::Requestable
11
- include Anthropic::Api::Concerns::Validatable
12
-
13
- def initialize(beta: nil)
14
- @beta = beta
15
- end
16
-
17
- private
18
-
19
- attr_reader :beta
20
-
21
- def api
22
- self.class.name.split('::').last.downcase
23
- end
24
-
25
- def version_config
26
- return @version_config if defined?(@version_config)
27
-
28
- @version_config ||= catch(:version_found) do
29
- found_config = Anthropic.versions[api.to_sym].find { |config| config['version'] == Anthropic.api_version }
30
- unless found_config
31
- raise Anthropic::Errors::UnsupportedApiVersionError, "Unsupported API version: #{Anthropic.api_version}"
32
- end
33
-
34
- throw :version_found, found_config
35
- end
36
- end
37
-
38
- def beta_config
39
- return @beta_config if defined?(@beta_config)
40
-
41
- @beta_config = catch(:beta_found) do
42
- found_config = Anthropic.betas.find { |config| config['id'] == beta }
43
- raise Anthropic::Errors::UnsupportedBetaError, "#{beta} not supported" unless found_config
44
-
45
- throw :beta_found, found_config
46
- end
47
- end
48
-
49
- def beta_loaded?(name)
50
- return false unless beta
51
-
52
- beta_config['id'] == name
53
- end
54
- end
55
- end
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'httpx'
4
-
5
- module Anthropic
6
- ##
7
- # Provides a client for sending HTTP requests.
8
- class Client
9
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
10
- def self.post(url, data, headers = {})
11
- response = HTTPX.with(
12
- headers: {
13
- 'Content-Type' => 'application/json',
14
- 'x-api-key' => Anthropic.api_key,
15
- 'anthropic-version' => Anthropic.api_version
16
- }.merge(headers)
17
- ).post(url, json: data)
18
-
19
- response_body = JSON.parse(response.body, symbolize_names: true)
20
-
21
- case response.status
22
- when 200
23
- response_body
24
- when 400
25
- raise Anthropic::Errors::BadRequestError, response_body
26
- when 401
27
- raise Anthropic::Errors::AuthenticationError, response_body
28
- when 403
29
- raise Anthropic::Errors::PermissionDeniedError, response_body
30
- when 404
31
- raise Anthropic::Errors::NotFoundError, response_body
32
- when 409
33
- raise Anthropic::Errors::ConflictError, response_body
34
- when 422
35
- raise Anthropic::Errors::UnprocessableEntityError, response_body
36
- when 429
37
- raise Anthropic::Errors::RateLimitError, response_body
38
- when 500
39
- raise Anthropic::Errors::InternalServerError, response_body
40
- end
41
- end
42
-
43
- def self.post_as_stream(url, data, headers = {})
44
- response = HTTPX.plugin(:stream).with(
45
- headers: {
46
- 'Content-Type' => 'application/json',
47
- 'x-api-key' => Anthropic.api_key,
48
- 'anthropic-version' => Anthropic.api_version
49
- }.merge(headers)
50
- ).post(url, json: data, stream: true)
51
-
52
- response.each_line do |line|
53
- event, data = line.split(/(\w+\b:\s)/)[1..2]
54
- next unless event && data
55
-
56
- if event.start_with?('data')
57
- formatted_data = JSON.parse(data, symbolize_names: true)
58
- yield formatted_data unless %w[ping error].include?(formatted_data[:type])
59
- end
60
- end
61
- rescue HTTPX::HTTPError => error
62
- case error.response.status
63
- when 400
64
- raise Anthropic::Errors::BadRequestError
65
- when 401
66
- raise Anthropic::Errors::AuthenticationError
67
- when 403
68
- raise Anthropic::Errors::PermissionDeniedError
69
- when 404
70
- raise Anthropic::Errors::NotFoundError
71
- when 409
72
- raise Anthropic::Errors::ConflictError
73
- when 422
74
- raise Anthropic::Errors::UnprocessableEntityError
75
- when 429
76
- raise Anthropic::Errors::RateLimitError
77
- when 500
78
- raise Anthropic::Errors::InternalServerError
79
- end
80
- end
81
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
82
- end
83
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Anthropic
4
- module Errors
5
- # Error for when the provided beta is not supported
6
- class UnsupportedBetaError < StandardError; end
7
-
8
- # Error for when a beta feature is configured incorrectly
9
- class InvalidBetaConfigurationError < StandardError; end
10
-
11
- # Error for when a beta feature is not used correctly
12
- class UnsupportedBetaUseError < StandardError; end
13
- end
14
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Anthropic
4
- module Errors
5
- # Error for when the API version is not supported.
6
- class UnsupportedApiVersionError < StandardError; end
7
-
8
- # Error for when API version is missing a schema.
9
- class MissingSchemaError < StandardError; end
10
-
11
- # Error for when the provided params do not match the API schema
12
- class SchemaValidationError < StandardError; end
13
- end
14
- end