cloudflare-ai 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +78 -34
- data/lib/cloudflare/ai/client.rb +19 -32
- data/lib/cloudflare/ai/clients/text_generation_helpers.rb +28 -0
- data/lib/cloudflare/ai/models.rb +43 -0
- data/lib/cloudflare/ai/result.rb +29 -16
- data/lib/cloudflare/ai/results/text_classification.rb +3 -0
- data/lib/cloudflare/ai/results/text_embedding.rb +9 -0
- data/lib/cloudflare/ai/results/text_generation.rb +5 -0
- data/lib/cloudflare/ai/version.rb +1 -1
- data/lib/cloudflare/ai.rb +0 -3
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8c78786e06d2a3cc05b177aedc99672b4be56016c33d6be2e718533b6b3f507
|
4
|
+
data.tar.gz: 535f23f9e6ffedbe0e090ded97459e021e57b84025456dab77d3d85dd2da194d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d35d01ae35a9407b97acfc0643c66c33b26d608d90373daf3c00001f86d7d2d2893162b370c417e2db7a96b43cec74d66e2652caa11ed89bbf31b968b4fd34d
|
7
|
+
data.tar.gz: 5af4ef84fdfc0ed649f951f18c10105b97ee4ae55b5d951f90551f7b0e5e452ca98ce408ded86980e67da05bfaf4e42321097b0b3f1cb2cfd224d67da74f8d44
|
data/README.md
CHANGED
@@ -1,35 +1,40 @@
|
|
1
|
-
|
1
|
+
Cloudflare Workers AI API client for ruby
|
2
2
|
---
|
3
3
|
Cloudflare is testing its [Workers AI](https://blog.cloudflare.com/workers-ai) API.
|
4
4
|
Hopefully this project makes it easier for ruby-first developers to consume
|
5
5
|
Cloudflare's latest and greatest.
|
6
6
|
|
7
|
-
|
7
|
+
|
8
|
+

|
9
|
+
[](https://badge.fury.io/rb/cloudflare-ai)
|
10
|
+

|
11
|
+
|
12
|
+
I'm really interested in applying retrieval-augmented
|
8
13
|
generation to make legal services more accessible. [Email me](mailto:cloudflare-ai@krishnan.ca).
|
9
14
|
|
10
15
|
If you're looking for legal help, it's best to book a slot via https://www.krishnan.ca.
|
11
16
|
|
12
|
-
|
17
|
+
# Todo
|
13
18
|
It's still early days, and here are my immediate priorities:
|
14
|
-
* [
|
15
|
-
* [
|
19
|
+
* [x] Support for streamed responses
|
20
|
+
* [x] CI pipeline
|
16
21
|
* [ ] Support for more AI model categories
|
17
22
|
* [x] [Text Generation](https://developers.cloudflare.com/workers-ai/models/text-generation/)
|
18
|
-
* [
|
23
|
+
* [x] [Text Embeddings](https://developers.cloudflare.com/workers-ai/models/text-embeddings/)
|
19
24
|
* [ ] [Text Classification](https://developers.cloudflare.com/workers-ai/models/text-classification/)
|
20
25
|
* [ ] [Image Classification](https://developers.cloudflare.com/workers-ai/models/image-classification/)
|
21
26
|
* [ ] [Translation](https://developers.cloudflare.com/workers-ai/models/translation/)
|
22
27
|
* [ ] [Text-to-Image](https://developers.cloudflare.com/workers-ai/models/text-to-image/)
|
23
28
|
* [ ] [Automatic Speech Recognition](https://developers.cloudflare.com/workers-ai/models/speech-recognition/)
|
24
29
|
|
25
|
-
|
30
|
+
# Table of Contents
|
26
31
|
|
27
32
|
- [Installation](#installation)
|
28
33
|
- [Usage](#usage)
|
29
34
|
- [Logging](#logging)
|
30
35
|
- [Development](#development)
|
31
36
|
|
32
|
-
|
37
|
+
# Installation
|
33
38
|
|
34
39
|
Install the gem and add to the application's Gemfile by executing:
|
35
40
|
|
@@ -39,45 +44,38 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
39
44
|
|
40
45
|
gem install cloudflare-ai
|
41
46
|
|
42
|
-
|
47
|
+
# Usage
|
43
48
|
|
44
49
|
```ruby
|
45
50
|
require "cloudflare/ai"
|
46
51
|
```
|
47
52
|
|
48
|
-
|
53
|
+
## Cloudflare Workers AI
|
49
54
|
Please visit the [Cloudflare Workers AI website](https://developers.cloudflare.com/workers-ai/) for more details.
|
50
55
|
Thiis gem provides a client that wraps around [Cloudflare's REST API](https://developers.cloudflare.com/workers-ai/get-started/rest-api/).
|
51
56
|
|
52
57
|
|
53
|
-
|
58
|
+
## Client
|
54
59
|
|
55
60
|
```ruby
|
56
61
|
client = Cloudflare::AI::Client.new(account_id: ENV["CLOUDFLARE_ACCOUNT_ID"], api_token: ENV["CLOUDFLARE_API_TOKEN"])
|
57
62
|
```
|
58
63
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
64
|
+
### Model selection
|
65
|
+
The model name is an optional parameter to every one of the client methods described below.
|
66
|
+
For example, if an example is documented as
|
63
67
|
```ruby
|
64
|
-
result = client.complete(prompt: "
|
65
|
-
|
66
|
-
# Successful
|
67
|
-
puts result.response # => "My name is John."
|
68
|
-
puts result.success? # => true
|
69
|
-
puts result.failure? # => false
|
70
|
-
puts result.to_json # => {"result":{"response":"My name is John"},"success":true,"errors":[],"messages":[]}
|
71
|
-
|
72
|
-
# Unsuccessful
|
73
|
-
puts result.response # => nil
|
74
|
-
puts result.success? # => false
|
75
|
-
puts result.failure? # => true
|
76
|
-
puts result.to_json # => {"result":null,"success":false,"errors":[{"code":7009,"message":"Upstream service unavailable"}],"messages":[]}
|
68
|
+
result = client.complete(prompt: "Hello my name is")
|
77
69
|
```
|
70
|
+
this is implicitly the same as
|
71
|
+
```ruby
|
72
|
+
result = client.complete(prompt: "Hello my name is", model: "@cf/meta/llama-2-7b-chat-fp16")
|
73
|
+
```
|
74
|
+
The full list of supported models is available here: [models.rb](lib/cloudflare/ai/models.rb).
|
75
|
+
More information is available [in the cloudflare documentation](https://developers.cloudflare.com/workers-ai/models/).
|
76
|
+
The default model used is the first enumerated model in the applicable set in [models.rb](lib/cloudflare/ai/models.rb).
|
78
77
|
|
79
|
-
|
80
|
-
#### Text generation (chat / scoped prompt)
|
78
|
+
### Text generation (chat / scoped prompt)
|
81
79
|
```ruby
|
82
80
|
messages = [
|
83
81
|
Cloudflare::AI::Message.new(role: "system", content: "You are a big fan of Cloudflare and Ruby."),
|
@@ -89,7 +87,7 @@ result = client.chat(messages: messages)
|
|
89
87
|
puts result.response # => "Yes, I love Cloudflare!"
|
90
88
|
```
|
91
89
|
|
92
|
-
|
90
|
+
#### Streaming responses
|
93
91
|
Responses will be streamed back to the client using Server Side Events (SSE) if a block is passed to the `chat` or `complete` method.
|
94
92
|
```ruby
|
95
93
|
result = client.complete(prompt: "Hi!") { |data| puts data}
|
@@ -99,8 +97,54 @@ result = client.complete(prompt: "Hi!") { |data| puts data}
|
|
99
97
|
# {"response":"!"}
|
100
98
|
# {"response":""}
|
101
99
|
# [DONE]
|
100
|
+
|
102
101
|
```
|
103
|
-
|
102
|
+
#### Result object
|
103
|
+
All invocations of the `prompt` and `chat` methods return a `Cloudflare::AI::Results::TextGeneration` object. This object's serializable JSON output is
|
104
|
+
based on the raw response from the Cloudflare API.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
result = client.complete(prompt: "What is your name?")
|
108
|
+
|
109
|
+
# Successful
|
110
|
+
puts result.response # => "My name is John."
|
111
|
+
puts result.success? # => true
|
112
|
+
puts result.failure? # => false
|
113
|
+
puts result.to_json # => {"result":{"response":"My name is John"},"success":true,"errors":[],"messages":[]}
|
114
|
+
|
115
|
+
# Unsuccessful
|
116
|
+
puts result.response # => nil
|
117
|
+
puts result.success? # => false
|
118
|
+
puts result.failure? # => true
|
119
|
+
puts result.to_json # => {"result":null,"success":false,"errors":[{"code":7009,"message":"Upstream service unavailable"}],"messages":[]}
|
120
|
+
```
|
121
|
+
|
122
|
+
|
123
|
+
### Text embedding
|
124
|
+
```ruby
|
125
|
+
result = client.embed(text: "Hello")
|
126
|
+
p result.shape # => [1, 768] # (1 embedding, 768 dimensions per embedding)
|
127
|
+
p result.embedding # => [[-0.008496830239892006, 0.001376907923258841, -0.0323275662958622, ...]]
|
128
|
+
```
|
129
|
+
|
130
|
+
The input can be either a string (as above) or an array of strings:
|
131
|
+
```ruby
|
132
|
+
result = client.embed(text: ["Hello", "World"])
|
133
|
+
```
|
134
|
+
|
135
|
+
#### Result object
|
136
|
+
All invocations of the `embed` methods return a `Cloudflare::AI::Results::TextEmbedding`.
|
137
|
+
|
138
|
+
### Text classification
|
139
|
+
```ruby
|
140
|
+
result = client.classify(text: "You meanie!")
|
141
|
+
p result.result # => [{"label"=>"NEGATIVE", "score"=>0.6647962927818298}, {"label"=>"POSITIVE", "score"=>0.3352036774158478}]
|
142
|
+
```
|
143
|
+
|
144
|
+
#### Result object
|
145
|
+
All invocations of the `classify` methods return a `Cloudflare::AI::Results::TextClassification`.
|
146
|
+
|
147
|
+
# Logging
|
104
148
|
|
105
149
|
This gem uses standard logging mechanisms and defaults to `:warn` level. Most messages are at info level, but we will add debug or warn statements as needed.
|
106
150
|
To show all log messages:
|
@@ -113,12 +157,12 @@ You can use this logger as you would the default ruby logger. For example:
|
|
113
157
|
```ruby
|
114
158
|
Cloudflare::AI.logger = Logger.new($stdout)
|
115
159
|
```
|
116
|
-
|
160
|
+
# Development
|
117
161
|
|
118
162
|
1. `git clone https://github.com/ajaynomics/cloudflare-ai.git`
|
119
163
|
2. `bundle exec rake` to ensure that the tests pass and to run standardrb
|
120
164
|
|
121
|
-
|
165
|
+
# Contributing
|
122
166
|
|
123
167
|
Bug reports and pull requests are welcome on GitHub at https://github.com/ajaynomics/cloudflare-ai.
|
124
168
|
|
data/lib/cloudflare/ai/client.rb
CHANGED
@@ -2,6 +2,8 @@ require "event_stream_parser"
|
|
2
2
|
require "faraday"
|
3
3
|
|
4
4
|
class Cloudflare::AI::Client
|
5
|
+
include Cloudflare::AI::Clients::TextGenerationHelpers
|
6
|
+
|
5
7
|
attr_reader :url, :account_id, :api_token
|
6
8
|
|
7
9
|
def initialize(account_id:, api_token:)
|
@@ -9,50 +11,35 @@ class Cloudflare::AI::Client
|
|
9
11
|
@api_token = api_token
|
10
12
|
end
|
11
13
|
|
12
|
-
def
|
14
|
+
def chat(messages:, model_name: default_text_generation_model_name, &block)
|
13
15
|
url = service_url_for(account_id: account_id, model_name: model_name)
|
14
16
|
stream = block ? true : false
|
15
|
-
payload =
|
16
|
-
|
17
|
+
payload = create_streamable_payload({messages: messages.map(&:serializable_hash)}, stream: stream)
|
18
|
+
post_streamable_request(url, payload, &block)
|
17
19
|
end
|
18
20
|
|
19
|
-
def
|
21
|
+
def classify(text:, model_name: Cloudflare::AI::Models.text_classification.first)
|
20
22
|
url = service_url_for(account_id: account_id, model_name: model_name)
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
payload = {text: text}.to_json
|
24
|
+
|
25
|
+
Cloudflare::AI::Results::TextClassification.new(connection.post(url, payload).body)
|
24
26
|
end
|
25
27
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
text_classification: %w[@cf/huggingface/distilbert-sst-2-int8],
|
32
|
-
image_classification: %w[@cf/huggingface/distilbert-sst-2-int8],
|
33
|
-
text_to_image: %w[@cf/stabilityai/stable-diffusion-xl-base-1.0],
|
34
|
-
text_embeddings: %w[@cf/baai/bge-base-en-v1.5 @cf/baai/bge-large-en-v1.5 @cf/baai/bge-small-en-v1.5]
|
35
|
-
}.freeze
|
28
|
+
def complete(prompt:, model_name: default_text_generation_model_name, &block)
|
29
|
+
url = service_url_for(account_id: account_id, model_name: model_name)
|
30
|
+
stream = block ? true : false
|
31
|
+
payload = create_streamable_payload({prompt: prompt}, stream: stream)
|
32
|
+
post_streamable_request(url, payload, &block)
|
36
33
|
end
|
37
34
|
|
38
|
-
|
35
|
+
def embed(text:, model_name: Cloudflare::AI::Models.text_embedding.first)
|
36
|
+
url = service_url_for(account_id: account_id, model_name: model_name)
|
37
|
+
payload = {text: text}.to_json
|
39
38
|
|
40
|
-
|
41
|
-
data.merge({stream: stream}).to_json
|
39
|
+
Cloudflare::AI::Results::TextEmbedding.new(connection.post(url, payload).body)
|
42
40
|
end
|
43
41
|
|
44
|
-
|
45
|
-
if block
|
46
|
-
parser = EventStreamParser::Parser.new
|
47
|
-
connection.post(url, payload) do |response|
|
48
|
-
response.options.on_data = parser.stream do |_type, data, _id, _reconnection_time, _size|
|
49
|
-
yield data
|
50
|
-
end
|
51
|
-
end
|
52
|
-
else
|
53
|
-
Cloudflare::AI::Result.new(connection.post(url, payload).body)
|
54
|
-
end
|
55
|
-
end
|
42
|
+
private
|
56
43
|
|
57
44
|
def connection
|
58
45
|
@connection ||= ::Faraday.new(headers: {Authorization: "Bearer #{api_token}"})
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Cloudflare
|
2
|
+
module AI
|
3
|
+
module Clients
|
4
|
+
module TextGenerationHelpers
|
5
|
+
def default_text_generation_model_name
|
6
|
+
Cloudflare::AI::Models.text_generation.first
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_streamable_payload(data, stream: false)
|
10
|
+
data.merge({stream: stream}).to_json
|
11
|
+
end
|
12
|
+
|
13
|
+
def post_streamable_request(url, payload, &block)
|
14
|
+
if block
|
15
|
+
parser = EventStreamParser::Parser.new
|
16
|
+
connection.post(url, payload) do |response|
|
17
|
+
response.options.on_data = parser.stream do |_type, data, _id, _reconnection_time, _size|
|
18
|
+
yield data
|
19
|
+
end
|
20
|
+
end
|
21
|
+
else
|
22
|
+
Cloudflare::AI::Results::TextGeneration.new(connection.post(url, payload).body)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Cloudflare::AI::Models
|
2
|
+
class << self
|
3
|
+
def text_generation
|
4
|
+
%w[@cf/meta/llama-2-7b-chat-fp16 @cf/meta/llama-2-7b-chat-int8 @cf/mistral/mistral-7b-instruct-v0.1 @hf/thebloke/codellama-7b-instruct-awq]
|
5
|
+
end
|
6
|
+
|
7
|
+
def speech_recognition
|
8
|
+
%w[@cf/openai/whisper]
|
9
|
+
end
|
10
|
+
|
11
|
+
def translation
|
12
|
+
%w[@cf/meta/m2m100-1.2b]
|
13
|
+
end
|
14
|
+
|
15
|
+
def text_classification
|
16
|
+
%w[@cf/huggingface/distilbert-sst-2-int8]
|
17
|
+
end
|
18
|
+
|
19
|
+
def image_classification
|
20
|
+
%w[@cf/huggingface/distilbert-sst-2-int8]
|
21
|
+
end
|
22
|
+
|
23
|
+
def text_to_image
|
24
|
+
%w[@cf/stabilityai/stable-diffusion-xl-base-1.0]
|
25
|
+
end
|
26
|
+
|
27
|
+
def text_embedding
|
28
|
+
%w[@cf/baai/bge-base-en-v1.5 @cf/baai/bge-large-en-v1.5 @cf/baai/bge-small-en-v1.5]
|
29
|
+
end
|
30
|
+
|
31
|
+
def all
|
32
|
+
{
|
33
|
+
text_generation: text_generation,
|
34
|
+
speech_recognition: speech_recognition,
|
35
|
+
translation: translation,
|
36
|
+
text_classification: text_classification,
|
37
|
+
image_classification: image_classification,
|
38
|
+
text_to_image: text_to_image,
|
39
|
+
text_embeddings: text_embedding
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/cloudflare/ai/result.rb
CHANGED
@@ -1,32 +1,45 @@
|
|
1
1
|
require "active_support/core_ext/hash/indifferent_access"
|
2
|
+
require "json"
|
2
3
|
|
3
4
|
class Cloudflare::AI::Result
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
@json = json
|
8
|
-
@json = JSON.parse(@json) unless @json.is_a?(Hash)
|
9
|
-
@json = @json.with_indifferent_access
|
5
|
+
def initialize(json_string_or_ruby_hash)
|
6
|
+
@result_data = parse_data(json_string_or_ruby_hash)
|
7
|
+
end
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
@errors = @json["errors"]
|
14
|
-
@messages = @json["messages"]
|
9
|
+
def result
|
10
|
+
result_data[:result]
|
15
11
|
end
|
16
12
|
|
17
|
-
def
|
18
|
-
|
13
|
+
def success?
|
14
|
+
success == true
|
19
15
|
end
|
20
16
|
|
21
17
|
def failure?
|
22
18
|
!success?
|
23
19
|
end
|
24
20
|
|
25
|
-
def
|
26
|
-
|
21
|
+
def errors
|
22
|
+
result_data.dig(:errors)
|
23
|
+
end
|
24
|
+
|
25
|
+
def messages
|
26
|
+
result_data.dig(:messages)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_json
|
30
|
+
result_data.to_json
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :result_data
|
36
|
+
|
37
|
+
def success
|
38
|
+
result_data[:success]
|
27
39
|
end
|
28
40
|
|
29
|
-
def
|
30
|
-
|
41
|
+
def parse_data(input)
|
42
|
+
input = JSON.parse(input) if input.is_a?(String)
|
43
|
+
input.with_indifferent_access
|
31
44
|
end
|
32
45
|
end
|
data/lib/cloudflare/ai.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudflare-ai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ajay Krishnan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -93,9 +93,14 @@ files:
|
|
93
93
|
- README.md
|
94
94
|
- lib/cloudflare/ai.rb
|
95
95
|
- lib/cloudflare/ai/client.rb
|
96
|
+
- lib/cloudflare/ai/clients/text_generation_helpers.rb
|
96
97
|
- lib/cloudflare/ai/contextual_logger.rb
|
97
98
|
- lib/cloudflare/ai/message.rb
|
99
|
+
- lib/cloudflare/ai/models.rb
|
98
100
|
- lib/cloudflare/ai/result.rb
|
101
|
+
- lib/cloudflare/ai/results/text_classification.rb
|
102
|
+
- lib/cloudflare/ai/results/text_embedding.rb
|
103
|
+
- lib/cloudflare/ai/results/text_generation.rb
|
99
104
|
- lib/cloudflare/ai/version.rb
|
100
105
|
homepage: https://rubygems.org/gems/cloudflare-ai
|
101
106
|
licenses:
|