cloudflare-ai 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4a98e10b8688a9f4a0853534b104c9a05ceaa91e4405c71d4dde52fdd1953ce7
4
+ data.tar.gz: 5921d2c627b91dfcf58375b39e9d0781f839e880d8c996146d7002f9ba48f365
5
+ SHA512:
6
+ metadata.gz: a2c1f4389bea67083ef6e19e12620b29850bdf818e4bbce1e3dcd4e5916175fabf69f04fe33b36ae539ec43f622aeb3456f3fff34ec9f236e745fe7f853cdbdc
7
+ data.tar.gz: c3e48f425b89449cae0bcbc480ee23c94d6494972f950070f3bb18e013711da95b45d7b6e3b2c2e7c9069ef458cc384ddf76ef3ea3a55cc61e46453bd190e189
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.1] - 2024-01-21
4
+ - Support for server-side streaming responses
5
+
6
+ ## [0.1.0] - 2024-01-20
7
+
8
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Ajay Krishnan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ cloudflare-ai: a Cloudflare Workers AI client for ruby developers
2
+ ---
3
+ Cloudflare is testing its [Workers AI](https://blog.cloudflare.com/workers-ai) API.
4
+ Hopefully this project makes it easier for ruby-first developers to consume
5
+ Cloudflare's latest and greatest.
6
+
7
+ I'm a lawyer who codes, and I'm really interested in applying retrieval-augmented
8
+ generation to make legal services more accessible. [Email me](mailto:cloudflare-ai@krishnan.ca).
9
+
10
+ If you're looking for legal help, it's best to book a slot via https://www.krishnan.ca.
11
+
12
+ ## Todo
13
+ It's still early days, and here are my immediate priorities:
14
+ * [ ] Support for streamed responses
15
+ * [ ] CI pipeline and guidance for contributors
16
+ * [ ] Support for more AI model categories
17
+ * [x] [Text Generation](https://developers.cloudflare.com/workers-ai/models/text-generation/)
18
+ * [ ] [Text Embeddings](https://developers.cloudflare.com/workers-ai/models/text-embeddings/)
19
+ * [ ] [Text Classification](https://developers.cloudflare.com/workers-ai/models/text-classification/)
20
+ * [ ] [Image Classification](https://developers.cloudflare.com/workers-ai/models/image-classification/)
21
+ * [ ] [Translation](https://developers.cloudflare.com/workers-ai/models/translation/)
22
+ * [ ] [Text-to-Image](https://developers.cloudflare.com/workers-ai/models/text-to-image/)
23
+ * [ ] [Automatic Speech Recognition](https://developers.cloudflare.com/workers-ai/models/speech-recognition/)
24
+
25
+ ## Table of Contents
26
+
27
+ - [Installation](#installation)
28
+ - [Usage](#usage)
29
+ - [Logging](#logging)
30
+ - [Development](#development)
31
+
32
+ ## Installation
33
+
34
+ Install the gem and add to the application's Gemfile by executing:
35
+
36
+ bundle add cloudflare-ai
37
+
38
+ If bundler is not being used to manage dependencies, install the gem by executing:
39
+
40
+ gem install cloudflare-ai
41
+
42
+ ## Usage
43
+
44
+ ```ruby
45
+ require "cloudflare/ai"
46
+ ```
47
+
48
+ ### Cloudflare Workers AI
49
+ Please visit the [Cloudflare Workers AI website](https://developers.cloudflare.com/workers-ai/) for more details.
50
+ Thiis gem provides a client that wraps around [Cloudflare's REST API](https://developers.cloudflare.com/workers-ai/get-started/rest-api/).
51
+
52
+
53
+ ### Client
54
+
55
+ ```ruby
56
+ client = Cloudflare::AI::Client.new(account_id: ENV["CLOUDFLARE_ACCOUNT_ID"], api_token: ENV["CLOUDFLARE_API_TOKEN"])
57
+ ```
58
+
59
+ #### 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.
62
+
63
+ ```ruby
64
+ result = client.complete(prompt: "What is your name?")
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":[]}
77
+ ```
78
+
79
+
80
+ #### Text generation (chat / scoped prompt)
81
+ ```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!"
90
+ ```
91
+
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.
94
+ ```ruby
95
+ result = client.complete(prompt: "Hi!") { |data| puts data}
96
+ # {"response":" "}
97
+ # {"response":" Hello"}
98
+ # {"response":" there"}
99
+ # {"response":"!"}
100
+ # {"response":""}
101
+ # [DONE]
102
+ ```
103
+ ## Logging
104
+
105
+ 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
+ To show all log messages:
107
+
108
+ ```ruby
109
+ Cloudflare::AI.logger.level = :debug
110
+ ```
111
+
112
+ You can use this logger as you would the default ruby logger. For example:
113
+ ```ruby
114
+ Cloudflare::AI.logger = Logger.new($stdout)
115
+ ```
116
+ ## Development
117
+
118
+ 1. `git clone https://github.com/ajaynomics/cloudflare-ai.git`
119
+ 2. `bundle exec rake` to ensure that the tests pass and to run standardrb
120
+
121
+ ## Contributing
122
+
123
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ajaynomics/cloudflare-ai.
124
+
125
+ ## License
126
+
127
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). A special thanks to the team at [langchainrb](https://github.com/andreibondarev/langchainrb) – I learnt a lot reading your codebase as I muddled my way through the initial effort.
@@ -0,0 +1,64 @@
1
+ require "event_stream_parser"
2
+ require "faraday"
3
+
4
+ class Cloudflare::AI::Client
5
+ attr_reader :url, :account_id, :api_token
6
+
7
+ def initialize(account_id:, api_token:)
8
+ @account_id = account_id
9
+ @api_token = api_token
10
+ end
11
+
12
+ def complete(prompt:, model_name: models[:text_generation].first, &block)
13
+ url = service_url_for(account_id: account_id, model_name: model_name)
14
+ stream = block ? true : false
15
+ payload = create_payload({prompt: prompt}, stream: stream)
16
+ post_request(url, payload, &block)
17
+ end
18
+
19
+ def chat(messages:, model_name: models[:text_generation].first, &block)
20
+ url = service_url_for(account_id: account_id, model_name: model_name)
21
+ 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
36
+ end
37
+
38
+ private
39
+
40
+ def create_payload(data, stream: false)
41
+ data.merge({stream: stream}).to_json
42
+ end
43
+
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
56
+
57
+ def connection
58
+ @connection ||= ::Faraday.new(headers: {Authorization: "Bearer #{api_token}"})
59
+ end
60
+
61
+ def service_url_for(account_id:, model_name:)
62
+ "https://api.cloudflare.com/client/v4/accounts/#{account_id}/ai/run/#{model_name}"
63
+ end
64
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudflare
4
+ module AI
5
+ class ContextualLogger
6
+ MESSAGE_COLOR_OPTIONS = {
7
+ debug: {
8
+ color: :white
9
+ },
10
+ error: {
11
+ color: :red
12
+ },
13
+ fatal: {
14
+ color: :red,
15
+ background: :white,
16
+ mode: :bold
17
+ },
18
+ unknown: {
19
+ color: :white
20
+ },
21
+ info: {
22
+ color: :white
23
+ },
24
+ warn: {
25
+ color: :yellow,
26
+ mode: :bold
27
+ }
28
+ }
29
+
30
+ def initialize(logger)
31
+ @logger = logger
32
+ @levels = Logger::Severity.constants.map(&:downcase)
33
+ end
34
+
35
+ def respond_to_missing?(method, include_private = false)
36
+ @logger.respond_to?(method, include_private)
37
+ end
38
+
39
+ def method_missing(method, *args, **kwargs, &block)
40
+ return @logger.send(method, *args, **kwargs, &block) unless @levels.include?(method)
41
+
42
+ for_class = kwargs.delete(:for)
43
+ for_class_name = for_class&.name
44
+
45
+ log_line_parts = []
46
+ log_line_parts << "[cloudflare-ai]".colorize(color: :yellow)
47
+ log_line_parts << if for_class.respond_to?(:logger_options)
48
+ "[#{for_class_name}]".colorize(for_class.logger_options) + ":"
49
+ elsif for_class_name
50
+ "[#{for_class_name}]:"
51
+ end
52
+ log_line_parts << args.first.colorize(MESSAGE_COLOR_OPTIONS[method])
53
+ log_line = log_line_parts.compact.join(" ")
54
+
55
+ @logger.send(
56
+ method,
57
+ log_line
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ require "active_model"
2
+
3
+ class Cloudflare::AI::Message
4
+ include ActiveModel::Validations
5
+ include ActiveModel::Serializers::JSON
6
+
7
+ attr_accessor :role, :content
8
+
9
+ validates_inclusion_of :role, in: %w[system user assistant]
10
+ validates_presence_of :content
11
+
12
+ def initialize(role:, content:)
13
+ @role = role
14
+ @content = content
15
+ end
16
+
17
+ def attributes
18
+ {role: role, content: content}
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ require "active_support/core_ext/hash/indifferent_access"
2
+
3
+ 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
10
+
11
+ @result = @json["result"]
12
+ @success = @json["success"]
13
+ @errors = @json["errors"]
14
+ @messages = @json["messages"]
15
+ end
16
+
17
+ def to_json
18
+ @json.to_json
19
+ end
20
+
21
+ def failure?
22
+ !success?
23
+ end
24
+
25
+ def success?
26
+ success
27
+ end
28
+
29
+ def response
30
+ result.with_indifferent_access["response"]
31
+ end
32
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudflare
4
+ module AI
5
+ VERSION = "0.2.0"
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ai/version"
4
+ require "logger"
5
+ require "pathname"
6
+ require "zeitwerk"
7
+
8
+ loader = Zeitwerk::Loader.for_gem
9
+ loader.inflector.inflect(
10
+ "ai" => "AI"
11
+ )
12
+ loader.push_dir("#{__dir__}/", namespace: Cloudflare)
13
+ loader.setup
14
+
15
+ module Cloudflare
16
+ module AI
17
+ class << self
18
+ attr_reader :logger, :root
19
+
20
+ def logger=(logger)
21
+ @logger = ContextualLogger.new(logger)
22
+ end
23
+ end
24
+
25
+ self.logger ||= ::Logger.new($stdout, level: :debug)
26
+
27
+ @root = Pathname.new(__dir__)
28
+
29
+ class Error < StandardError; end
30
+ # Your code goes here...
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloudflare-ai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Ajay Krishnan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '7.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '7.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: event_stream_parser
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: zeitwerk
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.5'
83
+ description: This opinionated ruby client is intended to make it uncomfortably easy
84
+ to use Cloudflare's models.
85
+ email:
86
+ - rubygems@krishnan.ca
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - CHANGELOG.md
92
+ - LICENSE.txt
93
+ - README.md
94
+ - lib/cloudflare/ai.rb
95
+ - lib/cloudflare/ai/client.rb
96
+ - lib/cloudflare/ai/contextual_logger.rb
97
+ - lib/cloudflare/ai/message.rb
98
+ - lib/cloudflare/ai/result.rb
99
+ - lib/cloudflare/ai/version.rb
100
+ homepage: https://rubygems.org/gems/cloudflare-ai
101
+ licenses:
102
+ - MIT
103
+ metadata:
104
+ homepage_uri: https://rubygems.org/gems/cloudflare-ai
105
+ source_code_uri: https://github.com/ajaynomics/cloudflare-ai
106
+ changelog_uri: https://github.com/ajaynomics/cloudflare-ai/blob/main/CHANGELOG.md
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: 3.0.0
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubygems_version: 3.5.5
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: A client for the Cloudflare Workers AI API.
126
+ test_files: []