anthropic-rb 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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