cloudflare-ai 0.2.0 → 0.3.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: 4a98e10b8688a9f4a0853534b104c9a05ceaa91e4405c71d4dde52fdd1953ce7
4
- data.tar.gz: 5921d2c627b91dfcf58375b39e9d0781f839e880d8c996146d7002f9ba48f365
3
+ metadata.gz: b0a6b80e669a5c4d64ec87433ed1c3426ee0a2a1087016773c5648c799b34912
4
+ data.tar.gz: fe1d3b2afb5f70cbe476b1b8e006819b2e6e469184169993a15c7c4c34b89ca8
5
5
  SHA512:
6
- metadata.gz: a2c1f4389bea67083ef6e19e12620b29850bdf818e4bbce1e3dcd4e5916175fabf69f04fe33b36ae539ec43f622aeb3456f3fff34ec9f236e745fe7f853cdbdc
7
- data.tar.gz: c3e48f425b89449cae0bcbc480ee23c94d6494972f950070f3bb18e013711da95b45d7b6e3b2c2e7c9069ef458cc384ddf76ef3ea3a55cc61e46453bd190e189
6
+ metadata.gz: 84dcec88bcd44e4a7b004235b15b559d924c1e8dfce552234b2d9286762ab2a12d282954e8d863523b1c9170c24f49a772da7320b5b475b66b79b141ac2c532d
7
+ data.tar.gz: 0f676b2accb65edc63b2b9b1c2b8a532ebfedba588dfdcb1ae38ccfdf7edb4869c1ebee2060e887e5ac166772cda91d3e43e2ca650e878eb63b8c964d7fc19e1
data/README.md CHANGED
@@ -1,35 +1,40 @@
1
- cloudflare-ai: a Cloudflare Workers AI client for ruby developers
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
- I'm a lawyer who codes, and I'm really interested in applying retrieval-augmented
7
+
8
+ ![Tests status](https://github.com/ajaynomics/cloudflare-ai/actions/workflows/ci.yml/badge.svg?branch=main)
9
+ [![Gem Version](https://badge.fury.io/rb/cloudflare-ai.svg)](https://badge.fury.io/rb/cloudflare-ai)
10
+ ![GitHub License](https://img.shields.io/github/license/ajaynomics/cloudflare-ai)
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
- ## Todo
17
+ # Todo
13
18
  It's still early days, and here are my immediate priorities:
14
- * [ ] Support for streamed responses
15
- * [ ] CI pipeline and guidance for contributors
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
- * [ ] [Text Embeddings](https://developers.cloudflare.com/workers-ai/models/text-embeddings/)
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
- ## Table of Contents
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
- ## Installation
37
+ # Installation
33
38
 
34
39
  Install the gem and add to the application's Gemfile by executing:
35
40
 
@@ -39,26 +44,51 @@ 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
- ## Usage
47
+ # Usage
43
48
 
44
49
  ```ruby
45
50
  require "cloudflare/ai"
46
51
  ```
47
52
 
48
- ### Cloudflare Workers AI
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
- ### Client
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
 
64
+
65
+ ### Text generation (chat / scoped prompt)
66
+ ```ruby
67
+ messages = [
68
+ Cloudflare::AI::Message.new(role: "system", content: "You are a big fan of Cloudflare and Ruby."),
69
+ Cloudflare::AI::Message.new(role: "user", content: "What is your favourite tech stack?"),
70
+ Cloudflare::AI::Message.new(role: "assistant", content: "I love building with Ruby on Rails and Cloudflare!"),
71
+ Cloudflare::AI::Message.new(role: "user", content: "Really? You like Cloudflare even though there isn't great support for Ruby?"),
72
+ ]
73
+ result = client.chat(messages: messages)
74
+ puts result.response # => "Yes, I love Cloudflare!"
75
+ ```
76
+
77
+ #### Streaming responses
78
+ Responses will be streamed back to the client using Server Side Events (SSE) if a block is passed to the `chat` or `complete` method.
79
+ ```ruby
80
+ result = client.complete(prompt: "Hi!") { |data| puts data}
81
+ # {"response":" "}
82
+ # {"response":" Hello"}
83
+ # {"response":" there"}
84
+ # {"response":"!"}
85
+ # {"response":""}
86
+ # [DONE]
87
+
88
+ ```
59
89
  #### Result object
60
- All invocations of the client return a `Cloudflare::AI::Result` object. This object's serializable JSON output is
61
- based on the raw response from the Cloudflare API.
90
+ All invocations of the `prompt` and `chat` methods return a `Cloudflare::AI::Results::TextGeneration` object. This object's serializable JSON output is
91
+ based on the raw response from the Cloudflare API.
62
92
 
63
93
  ```ruby
64
94
  result = client.complete(prompt: "What is your name?")
@@ -77,30 +107,22 @@ puts result.to_json # => {"result":null,"success":false,"errors":[{"code":7009,"
77
107
  ```
78
108
 
79
109
 
80
- #### Text generation (chat / scoped prompt)
110
+ ### Text embedding
81
111
  ```ruby
82
- messages = [
83
- Cloudflare::AI::Message.new(role: "system", content: "You are a big fan of Cloudflare and Ruby."),
84
- Cloudflare::AI::Message.new(role: "user", content: "What is your favourite tech stack?"),
85
- Cloudflare::AI::Message.new(role: "assistant", content: "I love building with Ruby on Rails and Cloudflare!"),
86
- Cloudflare::AI::Message.new(role: "user", content: "Really? You like Cloudflare even though there isn't great support for Ruby?"),
87
- ]
88
- result = client.chat(messages: messages)
89
- puts result.response # => "Yes, I love Cloudflare!"
112
+ result = client.embed(text: "Hello")
113
+ p result.shape # => [1, 768] # (1 embedding, 768 dimensions per embedding)
114
+ p result.embedding # => [[-0.008496830239892006, 0.001376907923258841, -0.0323275662958622, ...]]
90
115
  ```
91
116
 
92
- ##### Streaming responses
93
- Responses will be streamed back to the client using Server Side Events (SSE) if a block is passed to the `chat` or `complete` method.
117
+ The input can be either a string (as above) or an array of strings:
94
118
  ```ruby
95
- result = client.complete(prompt: "Hi!") { |data| puts data}
96
- # {"response":" "}
97
- # {"response":" Hello"}
98
- # {"response":" there"}
99
- # {"response":"!"}
100
- # {"response":""}
101
- # [DONE]
119
+ result = client.embed(text: ["Hello", "World"])
102
120
  ```
103
- ## Logging
121
+
122
+ #### Result object
123
+ All invocations of the `embedding` methods return a `Cloudflare::AI::Results::TextEmbedding`.
124
+
125
+ # Logging
104
126
 
105
127
  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
128
  To show all log messages:
@@ -113,12 +135,12 @@ You can use this logger as you would the default ruby logger. For example:
113
135
  ```ruby
114
136
  Cloudflare::AI.logger = Logger.new($stdout)
115
137
  ```
116
- ## Development
138
+ # Development
117
139
 
118
140
  1. `git clone https://github.com/ajaynomics/cloudflare-ai.git`
119
141
  2. `bundle exec rake` to ensure that the tests pass and to run standardrb
120
142
 
121
- ## Contributing
143
+ # Contributing
122
144
 
123
145
  Bug reports and pull requests are welcome on GitHub at https://github.com/ajaynomics/cloudflare-ai.
124
146
 
@@ -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,28 @@ class Cloudflare::AI::Client
9
11
  @api_token = api_token
10
12
  end
11
13
 
12
- def complete(prompt:, model_name: models[:text_generation].first, &block)
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 = create_payload({prompt: prompt}, stream: stream)
16
- post_request(url, payload, &block)
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 chat(messages:, model_name: models[:text_generation].first, &block)
21
+ def complete(prompt:, model_name: default_text_generation_model_name, &block)
20
22
  url = service_url_for(account_id: account_id, model_name: model_name)
21
23
  stream = block ? true : false
22
- payload = create_payload({messages: messages.map(&:serializable_hash)}, stream: stream)
23
- post_request(url, payload, &block)
24
- end
25
-
26
- def models
27
- {
28
- text_generation: %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],
29
- speech_recognition: %w[@cf/openai/whisper],
30
- translation: %w[@cf/meta/m2m100-1.2b],
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
24
+ payload = create_streamable_payload({prompt: prompt}, stream: stream)
25
+ post_streamable_request(url, payload, &block)
36
26
  end
37
27
 
38
- private
28
+ def embed(text:, model_name: Cloudflare::AI::Models.text_embedding.first)
29
+ url = service_url_for(account_id: account_id, model_name: model_name)
30
+ payload = {text: text}.to_json
39
31
 
40
- def create_payload(data, stream: false)
41
- data.merge({stream: stream}).to_json
32
+ Cloudflare::AI::Results::TextEmbedding.new(connection.post(url, payload).body)
42
33
  end
43
34
 
44
- def post_request(url, payload, &block)
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
35
+ private
56
36
 
57
37
  def connection
58
38
  @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
@@ -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
- attr_reader :result, :success, :errors, :messages
5
-
6
- def initialize(json)
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
- @result = @json["result"]
12
- @success = @json["success"]
13
- @errors = @json["errors"]
14
- @messages = @json["messages"]
9
+ def result
10
+ result_data[:result]
15
11
  end
16
12
 
17
- def to_json
18
- @json.to_json
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 success?
26
- success
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 response
30
- result.with_indifferent_access["response"]
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
@@ -0,0 +1,9 @@
1
+ class Cloudflare::AI::Results::TextEmbedding < Cloudflare::AI::Result
2
+ def shape
3
+ result&.dig(:shape) # nil if no shape
4
+ end
5
+
6
+ def data
7
+ result&.dig(:data) # nil if no data
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class Cloudflare::AI::Results::TextGeneration < Cloudflare::AI::Result
2
+ def response
3
+ result&.dig(:response) # nil if no response
4
+ end
5
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cloudflare
4
4
  module AI
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
data/lib/cloudflare/ai.rb CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require_relative "ai/version"
4
2
  require "logger"
5
3
  require "pathname"
@@ -27,6 +25,5 @@ module Cloudflare
27
25
  @root = Pathname.new(__dir__)
28
26
 
29
27
  class Error < StandardError; end
30
- # Your code goes here...
31
28
  end
32
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudflare-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ajay Krishnan
@@ -93,9 +93,13 @@ 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_embedding.rb
102
+ - lib/cloudflare/ai/results/text_generation.rb
99
103
  - lib/cloudflare/ai/version.rb
100
104
  homepage: https://rubygems.org/gems/cloudflare-ai
101
105
  licenses: