anthropic-rb 0.4.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: b5e066105f3157621c27e2f974ed43bde9f14e6e55187d1d7a4f095b79a5a1ba
4
- data.tar.gz: f32d479f7ec06c29a0347f69084a4378a64d69233d78ed280f5a1c924e15d708
3
+ metadata.gz: 0e743c686b28964af68a40c2e322caf6986284e2922d93d5acc0e2783128a5e5
4
+ data.tar.gz: 6ae03121d9cae81f15abdacb69a119428a9780f629fde7985a1a22be60192ea4
5
5
  SHA512:
6
- metadata.gz: 90040c86504a3dfd28e0093d1b8525252387f4e978271908aa158d1cffe10a3dd5766aadc243bc31f45ca6dd38ecf1259d009b2da9f2f78670fcc114c76574a3
7
- data.tar.gz: 8ca46c62fe204b3b2403cce1267b6661f43c52159c8d31877b9d2522be604427996eedcbac67ba22a1b6df703259ededd98ea2b86d49755d7aa057835458321c
6
+ metadata.gz: 352230790e752e44670cb9328b6f93139c1b5c6be9717b65996f1571a34acc9fd7fb28047bddcb32e5ce0c1826faeecb8364d9fd52ace649df868aad86428f9d
7
+ data.tar.gz: 2d94f8ac749b7d87d2081d2733a5fa52a0771a3dac3a7347af36ae3c40451b9c76362e8dc45540fb060b8c66d79c3fb3c7b351e08fedb489378758c84f9fc81e
data/.reek.yml CHANGED
@@ -4,10 +4,19 @@ detectors:
4
4
  - Anthropic#self.api_key=
5
5
  TooManyStatements:
6
6
  exclude:
7
- - 'Anthropic::Client#self.post'
8
- BooleanParameter:
9
- exclude:
10
- - 'Anthropic::Messages#initialize'
7
+ - 'Anthropic::Api::Concerns::Validatable#schema'
8
+ - 'Anthropic::Api::Base#beta_config'
9
+ - 'Anthropic::Api::Base#version_config'
10
+ - 'Anthropic::Bootstrapper#self.load_betas'
11
+ - 'Anthropic::Bootstrapper#self.load_versions'
12
+ - 'Anthropic::Client::Standard#self.post'
13
+ - 'Anthropic::Client::Streaming#self.post'
11
14
  DuplicateMethodCall:
12
15
  exclude:
13
- - 'Anthropic::Messages#create'
16
+ - 'Anthropic::Api::Base#version_config'
17
+ InstanceVariableAssumption:
18
+ exclude:
19
+ - 'Anthropic::Api::Base'
20
+ NestedIterators:
21
+ exclude:
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,36 @@
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
+
25
+ ## [0.5.0] - 2024-04-22
26
+
27
+ ### Updated
28
+
29
+ - Refactored project for maintainability.
30
+
31
+ ### Breaking Changes
32
+
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.
34
+
5
35
  ## [0.4.0] - 2024-04-20
6
36
 
7
37
  ### Added
@@ -57,7 +87,9 @@
57
87
 
58
88
  - Initial release
59
89
 
60
- [Unreleased]: https://github.com/dickdavis/anthropic-rb/compare/v0.4.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
92
+ [0.5.0]: https://github.com/dickdavis/anthropic-rb/compare/v0.4.0...v0.5.0
61
93
  [0.4.0]: https://github.com/dickdavis/anthropic-rb/compare/v0.3.0...v0.4.0
62
94
  [0.3.0]: https://github.com/dickdavis/anthropic-rb/compare/v0.2.5...v0.3.0
63
95
  [0.2.5]: https://github.com/dickdavis/anthropic-rb/compare/v0.2.3...v0.2.5
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` flag when calling the API. This will ensure each request includes the correct beta header.
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: true).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: true).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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anthropic
4
+ module Api
5
+ ##
6
+ # Provides bindings for the Anthropic completions API
7
+ class Completions < Base
8
+ def create(**params, &)
9
+ validate!(params)
10
+ return post(params) unless params[:stream]
11
+
12
+ post_as_stream(params, &)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anthropic
4
+ module Api
5
+ module Concerns
6
+ ##
7
+ # Provides helpers for sending API requests
8
+ module Requestable
9
+ def post(params)
10
+ Anthropic::Client::Standard.post(uri, params, additional_headers)
11
+ end
12
+
13
+ def post_as_stream(params, &)
14
+ Anthropic::Client::Streaming.post(uri, params, additional_headers, &)
15
+ end
16
+
17
+ def uri
18
+ "#{Anthropic.api_host}#{version_config['endpoint']}"
19
+ end
20
+
21
+ def additional_headers
22
+ {}.merge(beta_headers)
23
+ end
24
+
25
+ def beta_headers
26
+ return {} unless beta
27
+
28
+ header = beta_config['header']
29
+ raise Anthropic::Api::InvalidBetaConfigurationError, "Missing header: #{beta}" unless header
30
+
31
+ header
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anthropic
4
+ module Api
5
+ module Concerns
6
+ ##
7
+ # Provides helpers for validating params against the API schema
8
+ module Validatable
9
+ def validate!(params)
10
+ JSON::Validator.validate!(schema, params)
11
+ rescue JSON::Schema::ValidationError => error
12
+ raise Anthropic::Api::SchemaValidationError, error.message
13
+ end
14
+
15
+ def schema
16
+ api_schema = version_config['schema']
17
+
18
+ unless api_schema
19
+ raise Anthropic::Api::MissingSchemaError, "Missing schema for API version: #{Anthropic.api_version}"
20
+ end
21
+
22
+ if beta
23
+ beta_schema = beta_config['schema']
24
+ raise Anthropic::Api::InvalidBetaConfigurationError, "Missing beta schema: #{beta}" unless beta_schema
25
+
26
+ api_schema['properties'].merge!(beta_schema)
27
+ end
28
+
29
+ api_schema
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anthropic
4
+ module Api
5
+ ##
6
+ # Provides bindings for the Anthropic messages API
7
+ class Messages < Base
8
+ def create(**params, &)
9
+ validate!(params)
10
+ return post(params) unless params[:stream]
11
+
12
+ post_as_stream(params, &)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httpx'
4
+
5
+ module Anthropic
6
+ ##
7
+ # Provides methods for bootstrapping the Anthropic gem
8
+ module Bootstrapper
9
+ def self.load_betas
10
+ directory_path = File.expand_path('../../schemas/betas', __dir__)
11
+ raise "Directory not found: #{directory_path}" unless Dir.exist?(directory_path)
12
+
13
+ file_paths = Dir.glob(File.join(directory_path, '*.json'))
14
+
15
+ file_paths.map do |file_path|
16
+ JSON.parse(File.read(file_path))
17
+ end
18
+ end
19
+
20
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
21
+ def self.load_versions
22
+ directory_path = File.expand_path('../../schemas/versions', __dir__)
23
+ raise "Directory not found: #{directory_path}" unless Dir.exist?(directory_path)
24
+
25
+ versions = {}
26
+
27
+ Dir.glob(File.join(directory_path, '*')).each do |subdirectory_path|
28
+ next unless File.directory?(subdirectory_path)
29
+
30
+ subdirectory_name = File.basename(subdirectory_path)
31
+ file_paths = Dir.glob(File.join(subdirectory_path, '*.json'))
32
+
33
+ versions[subdirectory_name.to_sym] = file_paths.map do |file_path|
34
+ JSON.parse(File.read(file_path))
35
+ end
36
+ end
37
+
38
+ versions
39
+ end
40
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
41
+ end
42
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httpx'
4
+
5
+ module Anthropic
6
+ module Client
7
+ ##
8
+ # Error when the server experienced an internal error.
9
+ class ApiError < StandardError; end
10
+
11
+ ##
12
+ # Error when the API key is invalid.
13
+ class AuthenticationError < StandardError; end
14
+
15
+ ##
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
+
23
+ ##
24
+ # Error when the resource is not found.
25
+ class NotFoundError < StandardError; end
26
+
27
+ ##
28
+ # Error when the API servers are overloaded.
29
+ class OverloadedError < StandardError; end
30
+
31
+ ##
32
+ # Error when the account does not have permission for the operation.
33
+ class PermissionError < StandardError; end
34
+
35
+ ##
36
+ # Error when the request exceeds the rate limit.
37
+ class RateLimitError < StandardError; end
38
+
39
+ ##
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
60
+ end
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.4.0'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/anthropic.rb CHANGED
@@ -4,24 +4,20 @@ require 'httpx'
4
4
  require 'json'
5
5
  require 'json-schema'
6
6
 
7
- require_relative 'anthropic/client'
8
- require_relative 'anthropic/completions'
9
- require_relative 'anthropic/messages'
10
- require_relative 'anthropic/version'
7
+ Dir[File.join(__dir__, 'anthropic/', '**', '*.rb')].each { |file| require_relative file }
11
8
 
12
9
  ##
13
10
  # Namespace for anthropic-rb gem
14
11
  module Anthropic
15
- ##
16
- # Default error class
17
- class Error < StandardError; end
18
-
19
12
  def self.setup
20
13
  yield self
14
+ @betas = Bootstrapper.load_betas
15
+ @versions = Bootstrapper.load_versions
21
16
  end
22
17
 
23
18
  def self.reset
24
19
  @api_key = nil
20
+ @api_host = nil
25
21
  @api_version = nil
26
22
  end
27
23
 
@@ -33,6 +29,14 @@ module Anthropic
33
29
  @api_key = api_key
34
30
  end
35
31
 
32
+ def self.api_host
33
+ @api_host || ENV.fetch('ANTHROPIC_API_HOST', 'https://api.anthropic.com')
34
+ end
35
+
36
+ def self.api_host=(api_host = nil)
37
+ @api_host = api_host
38
+ end
39
+
36
40
  def self.api_version
37
41
  @api_version || ENV.fetch('ANTHROPIC_API_VERSION', '2023-06-01')
38
42
  end
@@ -41,11 +45,19 @@ module Anthropic
41
45
  @api_version = api_version
42
46
  end
43
47
 
48
+ def self.betas
49
+ @betas
50
+ end
51
+
52
+ def self.versions
53
+ @versions
54
+ end
55
+
44
56
  def self.completions
45
- Completions.new
57
+ Anthropic::Api::Completions.new
46
58
  end
47
59
 
48
60
  def self.messages(...)
49
- Messages.new(...)
61
+ Anthropic::Api::Messages.new(...)
50
62
  end
51
63
  end
data/sanity_check.rb ADDED
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'anthropic'
4
+
5
+ Anthropic.setup do |config|
6
+ config.api_key = ENV.fetch('ANTHROPIC_API_KEY')
7
+ end
8
+
9
+ puts "\nTesting non-streaming messages API"
10
+ puts Anthropic.messages.create(
11
+ model: 'claude-2.1',
12
+ max_tokens: 200,
13
+ messages: [{ role: 'user', content: 'Yo what up?' }]
14
+ )
15
+
16
+ puts "\nTesting streaming messages API"
17
+ Anthropic.messages.create(
18
+ model: 'claude-2.1',
19
+ max_tokens: 200,
20
+ messages: [{ role: 'user', content: 'Yo what up?' }],
21
+ stream: true
22
+ ) { |event| puts event }
23
+
24
+ puts "\nTesting tools"
25
+ tools = [
26
+ {
27
+ name: 'get_weather',
28
+ description: 'Get the current weather in a given location',
29
+ input_schema: {
30
+ type: 'object',
31
+ properties: {
32
+ location: { type: 'string' }
33
+ },
34
+ required: ['location']
35
+ }
36
+ }
37
+ ]
38
+
39
+ puts Anthropic.messages(beta: 'tools-2024-04-04').create(
40
+ model: 'claude-3-opus-20240229',
41
+ max_tokens: 200,
42
+ tools:,
43
+ messages: [{ role: 'user', content: 'What is the weather like in Nashville?' }]
44
+ )
45
+
46
+ puts "\nTesting completions API"
47
+ puts Anthropic.completions.create(
48
+ model: 'claude-2',
49
+ max_tokens_to_sample: 200,
50
+ prompt: 'Human: Yo what up?\n\nAssistant:'
51
+ )
52
+
53
+ puts "\nTesting streaming completions API"
54
+ Anthropic.completions.create(
55
+ model: 'claude-2',
56
+ max_tokens_to_sample: 200,
57
+ prompt: 'Human: Yo what up?\n\nAssistant:',
58
+ stream: true
59
+ ) { |event| puts event }
@@ -0,0 +1,26 @@
1
+ {
2
+ "id": "tools-2024-04-04",
3
+ "name": "[Deprecated] Tools",
4
+ "description": "[Deprecated] Equips Claude with custom tools for interacting with clients for a wide variety of tasks.",
5
+ "documentation": "https://docs.anthropic.com/claude/docs/tool-use",
6
+ "header" : { "anthropic-beta": "tools-2024-04-04" },
7
+ "schema": {
8
+ "tools": {
9
+ "type": "array",
10
+ "items": {
11
+ "type": "object",
12
+ "properties": {
13
+ "name": {
14
+ "type": "string"
15
+ },
16
+ "description": {
17
+ "type": "string"
18
+ },
19
+ "input_schema": {
20
+ "type": "object"
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "version": "2023-06-01",
3
+ "endpoint": "/v1/complete",
4
+ "schema": {
5
+ "type": "object",
6
+ "required": [
7
+ "model",
8
+ "prompt",
9
+ "max_tokens_to_sample"
10
+ ],
11
+ "properties": {
12
+ "model": {
13
+ "type": "string"
14
+ },
15
+ "prompt": {
16
+ "type": "string"
17
+ },
18
+ "max_tokens_to_sample": {
19
+ "type": "integer"
20
+ },
21
+ "stop_sequences": {
22
+ "type": "array",
23
+ "items": {
24
+ "type": "string"
25
+ }
26
+ },
27
+ "temperature": {
28
+ "type": "number"
29
+ },
30
+ "top_k": {
31
+ "type": "integer"
32
+ },
33
+ "top_p": {
34
+ "type": "number"
35
+ },
36
+ "metadata": {
37
+ "type": "object"
38
+ },
39
+ "stream": {
40
+ "type": "boolean"
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ }
45
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "version": "2023-06-01",
3
+ "endpoint": "/v1/messages",
4
+ "schema": {
5
+ "type": "object",
6
+ "required": [
7
+ "model",
8
+ "messages",
9
+ "max_tokens"
10
+ ],
11
+ "properties": {
12
+ "model": {
13
+ "type": "string"
14
+ },
15
+ "messages": {
16
+ "type": "array"
17
+ },
18
+ "max_tokens": {
19
+ "type": "integer"
20
+ },
21
+ "system": {
22
+ "type": "string"
23
+ },
24
+ "stop_sequences": {
25
+ "type": "array",
26
+ "items": {
27
+ "type": "string"
28
+ }
29
+ },
30
+ "temperature": {
31
+ "type": "number"
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
+ },
50
+ "top_k": {
51
+ "type": "integer"
52
+ },
53
+ "top_p": {
54
+ "type": "number"
55
+ },
56
+ "metadata": {
57
+ "type": "object"
58
+ },
59
+ "stream": {
60
+ "type": "boolean"
61
+ }
62
+ },
63
+ "additionalProperties": false
64
+ }
65
+ }
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.4.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-20 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,12 +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/client.rb
60
- - lib/anthropic/completions.rb
61
- - lib/anthropic/messages.rb
58
+ - lib/anthropic/api/base.rb
59
+ - lib/anthropic/api/completions.rb
60
+ - lib/anthropic/api/concerns/requestable.rb
61
+ - lib/anthropic/api/concerns/validatable.rb
62
+ - lib/anthropic/api/messages.rb
63
+ - lib/anthropic/bootstrapper.rb
64
+ - lib/anthropic/client/base.rb
65
+ - lib/anthropic/client/standard.rb
66
+ - lib/anthropic/client/streaming.rb
62
67
  - lib/anthropic/version.rb
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
63
72
  - sig/anthropic/rb.rbs
64
73
  homepage: https://github.com/dickdavis/anthropic-rb
65
74
  licenses:
@@ -78,14 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
78
87
  requirements:
79
88
  - - ">="
80
89
  - !ruby/object:Gem::Version
81
- version: '3.1'
90
+ version: 3.2.4
82
91
  required_rubygems_version: !ruby/object:Gem::Requirement
83
92
  requirements:
84
93
  - - ">="
85
94
  - !ruby/object:Gem::Version
86
95
  version: '0'
87
96
  requirements: []
88
- rubygems_version: 3.3.26
97
+ rubygems_version: 3.4.19
89
98
  signing_key:
90
99
  specification_version: 4
91
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
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,108 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'httpx'
4
-
5
- module Anthropic
6
- ##
7
- # Error when the request is malformed.
8
- class BadRequestError < StandardError; end
9
- ##
10
- # Error when the API key is invalid.
11
- class AuthenticationError < StandardError; end
12
- ##
13
- # Error when the account does not have permission for the operation.
14
- class PermissionDeniedError < StandardError; end
15
- ##
16
- # Error when the resource is not found.
17
- class NotFoundError < StandardError; end
18
- ##
19
- # Error when the resource already exists.
20
- class ConflictError < StandardError; end
21
- ##
22
- # Error when the resource cannot be processed.
23
- class UnprocessableEntityError < StandardError; end
24
- ##
25
- # Error when the request exceeds the rate limit.
26
- class RateLimitError < StandardError; end
27
- ##
28
- # Error when the server experienced an internal error.
29
- class InternalServerError < StandardError; end
30
-
31
- ##
32
- # Provides a client for sending HTTP requests.
33
- class Client
34
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
35
- def self.post(url, data, headers = {})
36
- response = HTTPX.with(
37
- headers: {
38
- 'Content-Type' => 'application/json',
39
- 'x-api-key' => Anthropic.api_key,
40
- 'anthropic-version' => Anthropic.api_version
41
- }.merge(headers)
42
- ).post(url, json: data)
43
-
44
- response_body = JSON.parse(response.body, symbolize_names: true)
45
-
46
- case response.status
47
- when 200
48
- response_body
49
- when 400
50
- raise Anthropic::BadRequestError, response_body
51
- when 401
52
- raise Anthropic::AuthenticationError, response_body
53
- when 403
54
- raise Anthropic::PermissionDeniedError, response_body
55
- when 404
56
- raise Anthropic::NotFoundError, response_body
57
- when 409
58
- raise Anthropic::ConflictError, response_body
59
- when 422
60
- raise Anthropic::UnprocessableEntityError, response_body
61
- when 429
62
- raise Anthropic::RateLimitError, response_body
63
- when 500
64
- raise Anthropic::InternalServerError, response_body
65
- end
66
- end
67
-
68
- def self.post_as_stream(url, data, headers = {})
69
- response = HTTPX.plugin(:stream).with(
70
- headers: {
71
- 'Content-Type' => 'application/json',
72
- 'x-api-key' => Anthropic.api_key,
73
- 'anthropic-version' => Anthropic.api_version
74
- }.merge(headers)
75
- ).post(url, json: data, stream: true)
76
-
77
- response.each_line do |line|
78
- event, data = line.split(/(\w+\b:\s)/)[1..2]
79
- next unless event && data
80
-
81
- if event.start_with?('data')
82
- formatted_data = JSON.parse(data, symbolize_names: true)
83
- yield formatted_data unless %w[ping error].include?(formatted_data[:type])
84
- end
85
- end
86
- rescue HTTPX::HTTPError => error
87
- case error.response.status
88
- when 400
89
- raise Anthropic::BadRequestError
90
- when 401
91
- raise Anthropic::AuthenticationError
92
- when 403
93
- raise Anthropic::PermissionDeniedError
94
- when 404
95
- raise Anthropic::NotFoundError
96
- when 409
97
- raise Anthropic::ConflictError
98
- when 422
99
- raise Anthropic::UnprocessableEntityError
100
- when 429
101
- raise Anthropic::RateLimitError
102
- when 500
103
- raise Anthropic::InternalServerError
104
- end
105
- end
106
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
107
- end
108
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Anthropic
4
- ##
5
- # Provides bindings for the Anthropic completions API
6
- class Completions
7
- # Error for when the API version is not supported.
8
- class UnsupportedApiVersionError < StandardError; end
9
-
10
- V1_SCHEMA = {
11
- type: 'object',
12
- required: %w[model prompt max_tokens_to_sample],
13
- properties: {
14
- model: { type: 'string' },
15
- prompt: { type: 'string' },
16
- max_tokens_to_sample: { type: 'integer' },
17
- stop_sequences: { type: 'array', items: { type: 'string' } },
18
- temperature: { type: 'number' },
19
- top_k: { type: 'integer' },
20
- top_p: { type: 'number' },
21
- metadata: { type: 'object' },
22
- stream: { type: 'boolean' }
23
- },
24
- additionalProperties: false
25
- }.freeze
26
-
27
- def initialize
28
- @endpoint = 'https://api.anthropic.com/v1/complete'
29
- end
30
-
31
- def create(**params, &)
32
- JSON::Validator.validate!(schema_for_api_version, params)
33
- return Anthropic::Client.post(endpoint, params) unless params[:stream]
34
-
35
- Anthropic::Client.post_as_stream(endpoint, params, &)
36
- rescue JSON::Schema::ValidationError => error
37
- raise ArgumentError, error.message
38
- end
39
-
40
- private
41
-
42
- attr_reader :endpoint
43
-
44
- def schema_for_api_version
45
- api_version = Anthropic.api_version
46
- case api_version
47
- when '2023-06-01'
48
- V1_SCHEMA
49
- else
50
- raise UnsupportedApiVersionError, "Unsupported API version: #{api_version}"
51
- end
52
- end
53
- end
54
- end
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Anthropic
4
- ##
5
- # Provides bindings for the Anthropic messages API
6
- class Messages
7
- # Error for when the API version is not supported.
8
- class UnsupportedApiVersionError < StandardError; end
9
-
10
- # Error for when a beta feature is not used correctly
11
- class UnsupportedBetaOptionError < StandardError; end
12
-
13
- ENDPOINT = 'https://api.anthropic.com/v1/messages'
14
- V1_SCHEMA = {
15
- type: 'object',
16
- required: %w[model messages max_tokens],
17
- properties: {
18
- model: { type: 'string' },
19
- messages: { type: 'array' },
20
- max_tokens: { type: 'integer' },
21
- system: { type: 'string' },
22
- stop_sequences: { type: 'array', items: { type: 'string' } },
23
- temperature: { type: 'number' },
24
- tools: {
25
- type: 'array',
26
- items: {
27
- name: { type: 'string' },
28
- description: { type: 'string' },
29
- input_schema: { type: 'object' }
30
- }
31
- },
32
- top_k: { type: 'integer' },
33
- top_p: { type: 'number' },
34
- metadata: { type: 'object' },
35
- stream: { type: 'boolean' }
36
- },
37
- additionalProperties: false
38
- }.freeze
39
-
40
- def initialize(beta: false)
41
- @beta = beta
42
- end
43
-
44
- def create(**params, &)
45
- raise UnsupportedBetaOptionError, 'Tool use is not yet supported in streaming mode' if params[:stream] && beta
46
-
47
- JSON::Validator.validate!(schema_for_api_version, params)
48
-
49
- return Anthropic::Client.post(ENDPOINT, params, additional_headers) unless params[:stream]
50
-
51
- Anthropic::Client.post_as_stream(ENDPOINT, params, additional_headers, &)
52
- rescue JSON::Schema::ValidationError => error
53
- raise ArgumentError, error.message
54
- end
55
-
56
- private
57
-
58
- attr_reader :beta
59
-
60
- def schema_for_api_version
61
- api_version = Anthropic.api_version
62
- case api_version
63
- when '2023-06-01'
64
- V1_SCHEMA
65
- else
66
- raise UnsupportedApiVersionError, "Unsupported API version: #{api_version}"
67
- end
68
- end
69
-
70
- def additional_headers
71
- return {} unless beta
72
-
73
- { 'anthropic-beta' => 'tools-2024-04-04' }
74
- end
75
- end
76
- end