├── .env.example
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .rspec
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── console
└── setup
├── cohere.gemspec
├── lib
├── cohere.rb
└── cohere
│ ├── client.rb
│ └── version.rb
├── sig
└── cohere.rbs
└── spec
├── cohere
└── client_spec.rb
├── cohere_spec.rb
├── fixtures
├── chat.json
├── classify.json
├── detect_language.json
├── detokenize.json
├── embed.json
├── generate.json
├── rerank.json
├── summarize.json
└── tokenize.json
└── spec_helper.rb
/.env.example:
--------------------------------------------------------------------------------
1 | COHERE_API_KEY=
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - "*"
7 | push:
8 | branches:
9 | - master
10 | jobs:
11 | tests:
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | ruby: ["2.7", "3.0", "3.1", "3.2"]
16 |
17 | steps:
18 | - uses: actions/checkout@master
19 |
20 | - name: Set up Ruby
21 | uses: ruby/setup-ruby@v1
22 | with:
23 | ruby-version: ${{ matrix.ruby }}
24 | bundler: default
25 | bundler-cache: true
26 |
27 | - name: StandardRb check
28 | run: bundle exec standardrb
29 |
30 | - name: Run tests
31 | run: |
32 | bundle exec rspec
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 |
10 | # rspec failure tracking
11 | .rspec_status
12 |
13 | .env
14 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [Unreleased]
2 |
3 | ## [1.0.1] - 2024-11-22
4 |
5 | ## [1.0.0] - 2024-11-22
6 | - Migrate to v2 APIs
7 |
8 | ## [0.9.11] - 2024-08-01
9 | - New `rerank()` method
10 |
11 | ## [0.9.10] - 2024-05-10
12 | - /chat endpoint does not require `message:` parameter anymore
13 |
14 | ## [0.9.9] - 2024-04-05
15 | - Adding missing parameters to endpoints
16 |
17 | ## [0.9.8] - 2024-02-10
18 | - Adding method for /chat endpoint
19 |
20 | ## [0.1.0] - 2023-04-23
21 |
22 | - Initial release
23 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | # Specify your gem's dependencies in cohere.gemspec
6 | gemspec
7 |
8 | gem "rake", "~> 13.0"
9 |
10 | gem "rspec", "~> 3.0"
11 | gem "standard", "~> 1.28.0"
12 | gem "dotenv", "~> 2.8.1"
13 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | cohere-ruby (1.0.1)
5 | faraday (>= 2.0.1, < 3.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | ast (2.4.2)
11 | diff-lcs (1.5.0)
12 | dotenv (2.8.1)
13 | faraday (2.7.10)
14 | faraday-net_http (>= 2.0, < 3.1)
15 | ruby2_keywords (>= 0.0.4)
16 | faraday-net_http (3.0.2)
17 | json (2.6.3)
18 | language_server-protocol (3.17.0.3)
19 | lint_roller (1.0.0)
20 | parallel (1.23.0)
21 | parser (3.2.2.1)
22 | ast (~> 2.4.1)
23 | rainbow (3.1.1)
24 | rake (13.0.6)
25 | regexp_parser (2.8.0)
26 | rexml (3.2.5)
27 | rspec (3.12.0)
28 | rspec-core (~> 3.12.0)
29 | rspec-expectations (~> 3.12.0)
30 | rspec-mocks (~> 3.12.0)
31 | rspec-core (3.12.1)
32 | rspec-support (~> 3.12.0)
33 | rspec-expectations (3.12.2)
34 | diff-lcs (>= 1.2.0, < 2.0)
35 | rspec-support (~> 3.12.0)
36 | rspec-mocks (3.12.5)
37 | diff-lcs (>= 1.2.0, < 2.0)
38 | rspec-support (~> 3.12.0)
39 | rspec-support (3.12.0)
40 | rubocop (1.50.2)
41 | json (~> 2.3)
42 | parallel (~> 1.10)
43 | parser (>= 3.2.0.0)
44 | rainbow (>= 2.2.2, < 4.0)
45 | regexp_parser (>= 1.8, < 3.0)
46 | rexml (>= 3.2.5, < 4.0)
47 | rubocop-ast (>= 1.28.0, < 2.0)
48 | ruby-progressbar (~> 1.7)
49 | unicode-display_width (>= 2.4.0, < 3.0)
50 | rubocop-ast (1.28.0)
51 | parser (>= 3.2.1.0)
52 | rubocop-performance (1.16.0)
53 | rubocop (>= 1.7.0, < 2.0)
54 | rubocop-ast (>= 0.4.0)
55 | ruby-progressbar (1.13.0)
56 | ruby2_keywords (0.0.5)
57 | standard (1.28.0)
58 | language_server-protocol (~> 3.17.0.2)
59 | lint_roller (~> 1.0)
60 | rubocop (~> 1.50.2)
61 | standard-custom (~> 1.0.0)
62 | standard-performance (~> 1.0.1)
63 | standard-custom (1.0.0)
64 | lint_roller (~> 1.0)
65 | standard-performance (1.0.1)
66 | lint_roller (~> 1.0)
67 | rubocop-performance (~> 1.16.0)
68 | unicode-display_width (2.4.2)
69 |
70 | PLATFORMS
71 | arm64-darwin-23
72 | x86_64-darwin-19
73 | x86_64-darwin-21
74 | x86_64-linux
75 |
76 | DEPENDENCIES
77 | cohere-ruby!
78 | dotenv (~> 2.8.1)
79 | rake (~> 13.0)
80 | rspec (~> 3.0)
81 | standard (~> 1.28.0)
82 |
83 | BUNDLED WITH
84 | 2.4.0
85 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 Andrei Bondarev
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cohere
2 |
3 |
4 |
5 | +
6 |
7 |
8 |
9 | Cohere API client for Ruby.
10 |
11 | Part of the [Langchain.rb](https://github.com/patterns-ai-core/langchainrb) stack.
12 |
13 | 
14 | [](https://badge.fury.io/rb/cohere-ruby)
15 | [](http://rubydoc.info/gems/cohere-ruby)
16 | [](https://github.com/patterns-ai-core/cohere-ruby/blob/main/LICENSE.txt)
17 | [](https://discord.gg/WDARp7J2n8)
18 |
19 | ## Installation
20 |
21 | Install the gem and add to the application's Gemfile by executing:
22 |
23 | $ bundle add cohere-ruby
24 |
25 | If bundler is not being used to manage dependencies, install the gem by executing:
26 |
27 | $ gem install cohere-ruby
28 |
29 | ## Usage
30 |
31 | ### Instantiating API client
32 |
33 | ```ruby
34 | require "cohere"
35 |
36 | client = Cohere::Client.new(
37 | api_key: ENV['COHERE_API_KEY']
38 | )
39 | ```
40 |
41 | ### Generate
42 |
43 | ```ruby
44 | client.generate(
45 | prompt: "Once upon a time in a magical land called"
46 | )
47 | ```
48 |
49 | ### Chat
50 |
51 | ```ruby
52 | client.chat(
53 | model: "command-r-plus-08-2024",
54 | messages: [{role:"user", content: "Hey! How are you?"}]
55 | )
56 | ```
57 |
58 | `chat` supports a streaming option. You can pass a block to the `chat` method and it will yield a new chunk as soon as it is received.
59 |
60 | ```ruby
61 | client.chat(
62 | model: "command-r-plus-08-2024",
63 | messages: [{role:"user", content: "Hey! How are you?"}]
64 | ) do |chunk, overall_received_bytes|
65 | puts "Received #{overall_received_bytes} bytes: #{chunk.force_encoding(Encoding::UTF_8)}"
66 | end
67 | ```
68 |
69 | `force_encoding` is preferred to avoid JSON parsing issue when Cohere returns emoticon.
70 |
71 | `chat` supports Tool use (function calling).
72 |
73 | ```ruby
74 | tools = [
75 | {
76 | name: "query_daily_sales_report",
77 | description: "Connects to a database to retrieve overall sales volumes and sales information for a given day.",
78 | parameter_definitions: {
79 | day: {
80 | description: "Retrieves sales data for this day, formatted as YYYY-MM-DD.",
81 | type: "str",
82 | required: true
83 | }
84 | }
85 | }
86 | ]
87 |
88 | message = "Can you provide a sales summary for 29th September 2023, and also give me some details about the products in the 'Electronics' category, for example their prices and stock levels?"
89 |
90 | client.chat(
91 | model: model,
92 | messages: [{ role:"user", content: message }],
93 | tools: tools
94 | )
95 | ```
96 |
97 | ### Embed
98 |
99 | ```ruby
100 | client.embed(
101 | model: "embed-english-v3.0",
102 | texts: ["hello", "goodbye"],
103 | input_type: "classification",
104 | embedding_types: ["float"]
105 | )
106 | ```
107 |
108 | ### Rerank
109 |
110 | ```ruby
111 | docs = [
112 | "Carson City is the capital city of the American state of Nevada.",
113 | "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan.",
114 | "Capitalization or capitalisation in English grammar is the use of a capital letter at the start of a word. English usage varies from capitalization in other languages.",
115 | "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district.",
116 | "Capital punishment (the death penalty) has existed in the United States since beforethe United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states.",
117 | ]
118 |
119 | client.rerank(
120 | model: "rerank-english-v3.0",
121 | query: "What is the capital of the United States?",
122 | documents: docs
123 | )
124 | ```
125 |
126 | ### Classify
127 |
128 | ```ruby
129 | examples = [
130 | { text: "Dermatologists don't like her!", label: "Spam" },
131 | { text: "Hello, open to this?", label: "Spam" },
132 | { text: "I need help please wire me $1000 right now", label: "Spam" },
133 | { text: "Nice to know you ;)", label: "Spam" },
134 | { text: "Please help me?", label: "Spam" },
135 | { text: "Your parcel will be delivered today", label: "Not spam" },
136 | { text: "Review changes to our Terms and Conditions", label: "Not spam" },
137 | { text: "Weekly sync notes", label: "Not spam" },
138 | { text: "Re: Follow up from today's meeting", label: "Not spam" },
139 | { text: "Pre-read for tomorrow", label: "Not spam" }
140 | ]
141 |
142 | inputs = [
143 | "Confirm your email address",
144 | "hey i need u to send some $",
145 | ]
146 |
147 | client.classify(
148 | model: "embed-multilingual-v2.0",
149 | inputs: inputs,
150 | examples: examples
151 | )
152 | ```
153 |
154 | ### Tokenize
155 |
156 | ```ruby
157 | client.tokenize(
158 | model: "command-r-plus-08-2024",
159 | text: "Hello, world!"
160 | )
161 | ```
162 |
163 | ### Detokenize
164 |
165 | ```ruby
166 | client.detokenize(
167 | model: "command-r-plus-08-2024",
168 | tokens: [33555, 1114, 34]
169 | )
170 | ```
171 |
172 | ### Detect language
173 |
174 | ```ruby
175 | client.detect_language(
176 | texts: ["Здравствуй, Мир"]
177 | )
178 | ```
179 |
180 | ### Summarize
181 |
182 | ```ruby
183 | client.summarize(
184 | text: "..."
185 | )
186 | ```
187 |
188 | ## Development
189 |
190 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec spec/` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
191 |
192 | To install this gem onto your local machine, run `bundle exec rake install`. 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).
193 |
194 | ## Contributing
195 |
196 | Bug reports and pull requests are welcome on GitHub at https://github.com/andreibondarev/cohere.
197 |
198 | ## License
199 |
200 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
201 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/gem_tasks"
4 | require "rspec/core/rake_task"
5 |
6 | RSpec::Core::RakeTask.new(:spec)
7 |
8 | task default: :spec
9 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "bundler/setup"
5 | require "cohere"
6 | require "dotenv/load"
7 |
8 | client = Cohere::Client.new(
9 | api_key: ENV['COHERE_API_KEY']
10 | )
11 |
12 | require "irb"
13 | IRB.start(__FILE__)
14 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/cohere.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "lib/cohere/version"
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = "cohere-ruby"
7 | spec.version = Cohere::VERSION
8 | spec.authors = ["Andrei Bondarev"]
9 | spec.email = ["andrei.bondarev13@gmail.com"]
10 |
11 | spec.summary = "Cohere API client for Ruby."
12 | spec.description = "Cohere API client for Ruby."
13 | spec.homepage = "https://github.com/andreibondarev/cohere-ruby"
14 | spec.license = "MIT"
15 | spec.required_ruby_version = ">= 2.6.0"
16 |
17 | spec.metadata["homepage_uri"] = spec.homepage
18 | spec.metadata["source_code_uri"] = "https://github.com/andreibondarev/cohere-ruby"
19 | spec.metadata["changelog_uri"] = "https://github.com/andreibondarev/cohere-ruby/CHANGELOG.md"
20 |
21 | # Specify which files should be added to the gem when it is released.
22 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23 | spec.files = Dir.chdir(__dir__) do
24 | `git ls-files -z`.split("\x0").reject do |f|
25 | (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
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 | spec.add_dependency "faraday", ">= 2.0.1", "< 3.0"
33 | end
34 |
--------------------------------------------------------------------------------
/lib/cohere.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "cohere/version"
4 |
5 | module Cohere
6 | class Error < StandardError; end
7 | # Your code goes here...
8 |
9 | autoload :Client, "cohere/client"
10 | end
11 |
--------------------------------------------------------------------------------
/lib/cohere/client.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "faraday"
4 |
5 | module Cohere
6 | class Client
7 | attr_reader :api_key, :connection
8 |
9 | def initialize(api_key:, timeout: nil)
10 | @api_key = api_key
11 | @timeout = timeout
12 | end
13 |
14 | # Generates a text response to a user message and streams it down, token by token
15 | def chat(
16 | model:,
17 | messages:,
18 | stream: false,
19 | tools: [],
20 | documents: [],
21 | citation_options: nil,
22 | response_format: nil,
23 | safety_mode: nil,
24 | max_tokens: nil,
25 | stop_sequences: nil,
26 | temperature: nil,
27 | seed: nil,
28 | frequency_penalty: nil,
29 | presence_penalty: nil,
30 | k: nil,
31 | p: nil,
32 | logprops: nil,
33 | &block
34 | )
35 | response = v2_connection.post("chat") do |req|
36 | req.body = {}
37 |
38 | req.body[:model] = model
39 | req.body[:messages] = messages if messages
40 | req.body[:tools] = tools if tools.any?
41 | req.body[:documents] = documents if documents.any?
42 | req.body[:citation_options] = citation_options if citation_options
43 | req.body[:response_format] = response_format if response_format
44 | req.body[:safety_mode] = safety_mode if safety_mode
45 | req.body[:max_tokens] = max_tokens if max_tokens
46 | req.body[:stop_sequences] = stop_sequences if stop_sequences
47 | req.body[:temperature] = temperature if temperature
48 | req.body[:seed] = seed if seed
49 | req.body[:frequency_penalty] = frequency_penalty if frequency_penalty
50 | req.body[:presence_penalty] = presence_penalty if presence_penalty
51 | req.body[:k] = k if k
52 | req.body[:p] = p if p
53 | req.body[:logprops] = logprops if logprops
54 |
55 | if stream || block
56 | req.body[:stream] = true
57 | req.options.on_data = block if block
58 | end
59 | end
60 | response.body
61 | end
62 |
63 | # This endpoint generates realistic text conditioned on a given input.
64 | def generate(
65 | prompt:,
66 | model: nil,
67 | num_generations: nil,
68 | max_tokens: nil,
69 | preset: nil,
70 | temperature: nil,
71 | k: nil,
72 | p: nil,
73 | frequency_penalty: nil,
74 | presence_penalty: nil,
75 | end_sequences: nil,
76 | stop_sequences: nil,
77 | return_likelihoods: nil,
78 | logit_bias: nil,
79 | truncate: nil
80 | )
81 | response = v1_connection.post("generate") do |req|
82 | req.body = {prompt: prompt}
83 | req.body[:model] = model if model
84 | req.body[:num_generations] = num_generations if num_generations
85 | req.body[:max_tokens] = max_tokens if max_tokens
86 | req.body[:preset] = preset if preset
87 | req.body[:temperature] = temperature if temperature
88 | req.body[:k] = k if k
89 | req.body[:p] = p if p
90 | req.body[:frequency_penalty] = frequency_penalty if frequency_penalty
91 | req.body[:presence_penalty] = presence_penalty if presence_penalty
92 | req.body[:end_sequences] = end_sequences if end_sequences
93 | req.body[:stop_sequences] = stop_sequences if stop_sequences
94 | req.body[:return_likelihoods] = return_likelihoods if return_likelihoods
95 | req.body[:logit_bias] = logit_bias if logit_bias
96 | req.body[:truncate] = truncate if truncate
97 | end
98 | response.body
99 | end
100 |
101 | # This endpoint returns text embeddings. An embedding is a list of floating point numbers that captures semantic information about the text that it represents.
102 | def embed(
103 | model:,
104 | input_type:,
105 | embedding_types:,
106 | texts: nil,
107 | images: nil,
108 | truncate: nil
109 | )
110 | response = v2_connection.post("embed") do |req|
111 | req.body = {
112 | model: model,
113 | input_type: input_type,
114 | embedding_types: embedding_types
115 | }
116 | req.body[:texts] = texts if texts
117 | req.body[:images] = images if images
118 | req.body[:truncate] = truncate if truncate
119 | end
120 | response.body
121 | end
122 |
123 | # This endpoint takes in a query and a list of texts and produces an ordered array with each text assigned a relevance score.
124 | def rerank(
125 | model:,
126 | query:,
127 | documents:,
128 | top_n: nil,
129 | rank_fields: nil,
130 | return_documents: nil,
131 | max_chunks_per_doc: nil
132 | )
133 | response = v2_connection.post("rerank") do |req|
134 | req.body = {
135 | model: model,
136 | query: query,
137 | documents: documents
138 | }
139 | req.body[:top_n] = top_n if top_n
140 | req.body[:rank_fields] = rank_fields if rank_fields
141 | req.body[:return_documents] = return_documents if return_documents
142 | req.body[:max_chunks_per_doc] = max_chunks_per_doc if max_chunks_per_doc
143 | end
144 | response.body
145 | end
146 |
147 | # This endpoint makes a prediction about which label fits the specified text inputs best.
148 | def classify(
149 | model:,
150 | inputs:,
151 | examples: nil,
152 | preset: nil,
153 | truncate: nil
154 | )
155 | response = v1_connection.post("classify") do |req|
156 | req.body = {
157 | model: model,
158 | inputs: inputs
159 | }
160 | req.body[:examples] = examples if examples
161 | req.body[:preset] = preset if preset
162 | req.body[:truncate] = truncate if truncate
163 | end
164 | response.body
165 | end
166 |
167 | # This endpoint splits input text into smaller units called tokens using byte-pair encoding (BPE).
168 | def tokenize(text:, model:)
169 | response = v1_connection.post("tokenize") do |req|
170 | req.body = {text: text, model: model}
171 | end
172 | response.body
173 | end
174 |
175 | # This endpoint takes tokens using byte-pair encoding and returns their text representation.
176 | def detokenize(tokens:, model:)
177 | response = v1_connection.post("detokenize") do |req|
178 | req.body = {tokens: tokens, model: model}
179 | end
180 | response.body
181 | end
182 |
183 | def detect_language(texts:)
184 | response = v1_connection.post("detect-language") do |req|
185 | req.body = {texts: texts}
186 | end
187 | response.body
188 | end
189 |
190 | def summarize(
191 | text:,
192 | length: nil,
193 | format: nil,
194 | model: nil,
195 | extractiveness: nil,
196 | temperature: nil,
197 | additional_command: nil
198 | )
199 | response = v1_connection.post("summarize") do |req|
200 | req.body = {text: text}
201 | req.body[:length] = length if length
202 | req.body[:format] = format if format
203 | req.body[:model] = model if model
204 | req.body[:extractiveness] = extractiveness if extractiveness
205 | req.body[:temperature] = temperature if temperature
206 | req.body[:additional_command] = additional_command if additional_command
207 | end
208 | response.body
209 | end
210 |
211 | private
212 |
213 | def v1_connection
214 | @connection ||= Faraday.new(url: "https://api.cohere.ai/v1", request: {timeout: @timeout}) do |faraday|
215 | faraday.request :authorization, :Bearer, api_key
216 | faraday.request :json
217 | faraday.response :json, content_type: /\bjson$/
218 | faraday.adapter Faraday.default_adapter
219 | end
220 | end
221 |
222 | def v2_connection
223 | @connection ||= Faraday.new(url: "https://api.cohere.com/v2", request: {timeout: @timeout}) do |faraday|
224 | faraday.request :authorization, :Bearer, api_key
225 | faraday.request :json
226 | faraday.response :json, content_type: /\bjson$/
227 | faraday.adapter Faraday.default_adapter
228 | end
229 | end
230 | end
231 | end
232 |
--------------------------------------------------------------------------------
/lib/cohere/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Cohere
4 | VERSION = "1.0.1"
5 | end
6 |
--------------------------------------------------------------------------------
/sig/cohere.rbs:
--------------------------------------------------------------------------------
1 | module Cohere
2 | VERSION: String
3 | # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4 | end
5 |
--------------------------------------------------------------------------------
/spec/cohere/client_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "spec_helper"
4 |
5 | RSpec.describe Cohere::Client do
6 | subject { described_class.new(api_key: "123") }
7 |
8 | describe "#generate" do
9 | let(:generate_result) { JSON.parse(File.read("spec/fixtures/generate.json")) }
10 | let(:response) { OpenStruct.new(body: generate_result) }
11 |
12 | before do
13 | allow_any_instance_of(Faraday::Connection).to receive(:post)
14 | .with("generate")
15 | .and_return(response)
16 | end
17 |
18 | it "returns a response" do
19 | expect(subject.generate(
20 | prompt: "Once upon a time in a magical land called"
21 | ).dig("generations").first.dig("text")).to eq(" The Past there was a Game called Warhammer Fantasy Battle.")
22 | end
23 | end
24 |
25 | describe "#chat" do
26 | let(:generate_result) { JSON.parse(File.read("spec/fixtures/chat.json")) }
27 | let(:response) { OpenStruct.new(body: generate_result) }
28 |
29 | before do
30 | allow_any_instance_of(Faraday::Connection).to receive(:post)
31 | .with("chat")
32 | .and_return(response)
33 | end
34 |
35 | it "returns a response" do
36 | expect(subject.chat(
37 | model: "command-r-plus-08-2024",
38 | messages: [{role: "user", content: "Hey! How are you?"}]
39 | ).dig("message", "content", 0, "text")).to eq("I'm doing well, thank you for asking! As an AI language model, I don't have emotions or feelings, but I'm designed to provide helpful and informative responses to assist you in the best way I can. Is there anything you'd like to know or discuss today?")
40 | end
41 | end
42 |
43 | describe "#embed" do
44 | let(:embed_result) { JSON.parse(File.read("spec/fixtures/embed.json")) }
45 | let(:response) { OpenStruct.new(body: embed_result) }
46 |
47 | before do
48 | allow_any_instance_of(Faraday::Connection).to receive(:post)
49 | .with("embed")
50 | .and_return(response)
51 | end
52 |
53 | it "returns a response" do
54 | expect(subject.embed(
55 | model: "embed-english-v3.0",
56 | texts: ["hello", "goodbye"],
57 | input_type: "classification",
58 | embedding_types: ["float"]
59 | ).dig("embeddings", "float")).to eq([[0.017578125, -0.009162903, -0.046325684]])
60 | end
61 | end
62 |
63 | describe "#rerank" do
64 | let(:embed_result) { JSON.parse(File.read("spec/fixtures/rerank.json")) }
65 | let(:response) { OpenStruct.new(body: embed_result) }
66 | let(:docs) {
67 | [
68 | "Carson City is the capital city of the American state of Nevada.",
69 | "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan.",
70 | "Capitalization or capitalisation in English grammar is the use of a capital letter at the start of a word. English usage varies from capitalization in other languages.",
71 | "Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States. It is a federal district.",
72 | "Capital punishment (the death penalty) has existed in the United States since beforethe United States was a country. As of 2017, capital punishment is legal in 30 of the 50 states."
73 | ]
74 | }
75 |
76 | before do
77 | allow_any_instance_of(Faraday::Connection).to receive(:post)
78 | .with("rerank")
79 | .and_return(response)
80 | end
81 |
82 | it "returns a response" do
83 | expect(
84 | subject
85 | .rerank(model: "rerank-english-v3.0", query: "What is the capital of the United States?", documents: docs)
86 | .dig("results")
87 | .map { |h| h["index"] }
88 | ).to eq([3, 4, 2, 0, 1])
89 | end
90 | end
91 |
92 | describe "#classify" do
93 | let(:classify_result) { JSON.parse(File.read("spec/fixtures/classify.json")) }
94 | let(:response) { OpenStruct.new(body: classify_result) }
95 |
96 | let(:examples) {
97 | [
98 | {text: "Dermatologists don't like her!", label: "Spam"},
99 | {text: "Hello, open to this?", label: "Spam"},
100 | {text: "Your parcel will be delivered today", label: "Not spam"},
101 | {text: "Review changes to our Terms and Conditions", label: "Not spam"}
102 | ]
103 | }
104 |
105 | let(:inputs) {
106 | [
107 | "Confirm your email address",
108 | "hey i need u to send some $"
109 | ]
110 | }
111 |
112 | before do
113 | allow_any_instance_of(Faraday::Connection).to receive(:post)
114 | .with("classify")
115 | .and_return(response)
116 | end
117 |
118 | it "returns a response" do
119 | res = subject.classify(
120 | model: "embed-multilingual-v2.0",
121 | inputs: inputs,
122 | examples: examples
123 | ).dig("classifications")
124 |
125 | expect(res.first.dig("prediction")).to eq("Not spam")
126 | expect(res.last.dig("prediction")).to eq("Spam")
127 | end
128 | end
129 |
130 | describe "#tokenize" do
131 | let(:tokenize_result) { JSON.parse(File.read("spec/fixtures/tokenize.json")) }
132 | let(:response) { OpenStruct.new(body: tokenize_result) }
133 |
134 | before do
135 | allow_any_instance_of(Faraday::Connection).to receive(:post)
136 | .with("tokenize")
137 | .and_return(response)
138 | end
139 |
140 | it "returns a response" do
141 | expect(subject.tokenize(
142 | model: "command-r-plus-08-2024",
143 | text: "Hello, world!"
144 | ).dig("tokens")).to eq([33555, 1114, 34])
145 | end
146 | end
147 |
148 | describe "#detokenize" do
149 | let(:detokenize_result) { JSON.parse(File.read("spec/fixtures/detokenize.json")) }
150 | let(:response) { OpenStruct.new(body: detokenize_result) }
151 |
152 | before do
153 | allow_any_instance_of(Faraday::Connection).to receive(:post)
154 | .with("detokenize")
155 | .and_return(response)
156 | end
157 |
158 | it "returns a response" do
159 | expect(subject.detokenize(
160 | model: "command-r-plus-08-2024",
161 | tokens: [33555, 1114, 34]
162 | ).dig("text")).to eq("hello world!")
163 | end
164 | end
165 |
166 | describe "#detect_language" do
167 | let(:detect_language_result) { JSON.parse(File.read("spec/fixtures/detect_language.json")) }
168 | let(:response) { OpenStruct.new(body: detect_language_result) }
169 |
170 | before do
171 | allow_any_instance_of(Faraday::Connection).to receive(:post)
172 | .with("detect-language")
173 | .and_return(response)
174 | end
175 |
176 | it "returns a response" do
177 | expect(subject.detect_language(
178 | texts: ["Здравствуй, Мир"]
179 | ).dig("results").first.dig("language_code")).to eq("ru")
180 | end
181 | end
182 |
183 | describe "#summarize" do
184 | let(:summarize_result) { JSON.parse(File.read("spec/fixtures/summarize.json")) }
185 | let(:response) { OpenStruct.new(body: summarize_result) }
186 |
187 | before do
188 | allow_any_instance_of(Faraday::Connection).to receive(:post)
189 | .with("summarize")
190 | .and_return(response)
191 | end
192 |
193 | it "returns a response" do
194 | expect(subject.summarize(
195 | text: "Ice cream is a sweetened frozen food typically eaten as a snack or dessert. " \
196 | "It may be made from milk or cream and is flavoured with a sweetener, " \
197 | "either sugar or an alternative, and a spice, such as cocoa or vanilla, " \
198 | "or with fruit such as strawberries or peaches. " \
199 | "It can also be made by whisking a flavored cream base and liquid nitrogen together. " \
200 | "Food coloring is sometimes added, in addition to stabilizers. " \
201 | "The mixture is cooled below the freezing point of water and stirred to incorporate air spaces " \
202 | "and to prevent detectable ice crystals from forming. The result is a smooth, " \
203 | "semi-solid foam that is solid at very low temperatures (below 2 °C or 35 °F). " \
204 | "It becomes more malleable as its temperature increases.\n\n" \
205 | "The meaning of the name \"ice cream\" varies from one country to another. " \
206 | "In some countries, such as the United States, \"ice cream\" applies only to a specific variety, " \
207 | "and most governments regulate the commercial use of the various terms according to the " \
208 | "relative quantities of the main ingredients, notably the amount of cream. " \
209 | "Products that do not meet the criteria to be called ice cream are sometimes labelled " \
210 | "\"frozen dairy dessert\" instead. In other countries, such as Italy and Argentina, " \
211 | "one word is used fo\r all variants. Analogues made from dairy alternatives, " \
212 | "such as goat's or sheep's milk, or milk substitutes " \
213 | "(e.g., soy, cashew, coconut, almond milk or tofu), are available for those who are " \
214 | "lactose intolerant, allergic to dairy protein or vegan."
215 | ).dig("summary")).to eq("Ice cream is a frozen dessert made from dairy products or non-dairy substitutes. It is flavoured with a sweetener and a spice or with fruit. It is smooth and semi-solid at low temperatures.")
216 | end
217 | end
218 | end
219 |
--------------------------------------------------------------------------------
/spec/cohere_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe Cohere do
4 | it "has a version number" do
5 | expect(Cohere::VERSION).not_to be nil
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/fixtures/chat.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "731bae88-f610-45a4-8f75-2b44a7856388",
3 | "message": {
4 | "role": "assistant",
5 | "content": [
6 | {
7 | "type": "text",
8 | "text": "I'm doing well, thank you for asking! As an AI language model, I don't have emotions or feelings, but I'm designed to provide helpful and informative responses to assist you in the best way I can. Is there anything you'd like to know or discuss today?"
9 | }
10 | ]
11 | },
12 | "finish_reason": "COMPLETE",
13 | "usage": {
14 | "billed_units": {
15 | "input_tokens": 6,
16 | "output_tokens": 56
17 | },
18 | "tokens": {
19 | "input_tokens": 207,
20 | "output_tokens": 56
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/spec/fixtures/classify.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "dd6484b4-952a-45d1-8e2a-1fbcadf1a3bc",
3 | "classifications": [
4 | {
5 | "classification_type": "single-label",
6 | "confidence": 0.66766936,
7 | "confidences": [0.66766936],
8 | "id": "5abf9fea-dcb1-42cb-8bc7-e797c80f2293",
9 | "input": "Confirm your email address",
10 | "labels": {
11 | "Not spam": { "confidence": 0.66766936 },
12 | "Spam": { "confidence": 0.33233067 }
13 | },
14 | "prediction": "Not spam",
15 | "predictions": ["Not spam"]
16 | },
17 | {
18 | "classification_type": "single-label",
19 | "confidence": 0.5345887,
20 | "confidences": [0.5345887],
21 | "id": "75ddba7e-466d-408a-aca2-78165e6f1dfa",
22 | "input": "hey i need u to send some $",
23 | "labels": {
24 | "Not spam": { "confidence": 0.46541128 },
25 | "Spam": { "confidence": 0.5345887 }
26 | },
27 | "prediction": "Spam",
28 | "predictions": ["Spam"]
29 | }
30 | ],
31 | "meta": {
32 | "api_version": { "version": "2" },
33 | "billed_units": { "classifications": 2 }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/spec/fixtures/detect_language.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "a3fda54a-303f-4bfe-9649-d677fedf8d0d",
3 | "results": [
4 | {
5 | "language_code": "ru",
6 | "language_name": "Russian"
7 | }
8 | ],
9 | "meta": {
10 | "api_version": {
11 | "version": "1"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/spec/fixtures/detokenize.json:
--------------------------------------------------------------------------------
1 | {
2 | "text": "hello world!",
3 | "meta": {
4 | "api_version": {
5 | "version": "1"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/spec/fixtures/embed.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "420351e8-81e6-490d-8ca1-f320911c5fde",
3 | "texts": ["hello", "goodbye"],
4 | "embeddings": {
5 | "float": [
6 | [
7 | 0.017578125,
8 | -0.009162903,
9 | -0.046325684
10 | ]
11 | ]
12 | },
13 | "meta": {
14 | "api_version": {
15 | "version": "2"
16 | },
17 | "billed_units": {
18 | "input_tokens": 2
19 | }
20 | },
21 | "response_type": "embeddings_by_type"
22 | }
--------------------------------------------------------------------------------
/spec/fixtures/generate.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "a6bdaf27-5050-4ec1-862e-cdc171245692",
3 | "generations": [
4 | {
5 | "id": "2dcf03a3-e375-4aa5-b442-ab95bdc5f989",
6 | "text": " The Past there was a Game called Warhammer Fantasy Battle."
7 | }
8 | ],
9 | "prompt": "Once upon a time in a magical land called",
10 | "meta": {
11 | "api_version": {
12 | "version": "1"
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/spec/fixtures/rerank.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "fd2f37a7-78e5-4d43-9230-ca0804f8cab5",
3 | "results": [
4 | {
5 | "index": 3,
6 | "relevance_score": 0.97997653
7 | },
8 | {
9 | "index": 4,
10 | "relevance_score": 0.27963173
11 | },
12 | {
13 | "index": 2,
14 | "relevance_score": 0.10502681
15 | },
16 | {
17 | "index": 0,
18 | "relevance_score": 0.10212547
19 | },
20 | {
21 | "index": 1,
22 | "relevance_score": 0.0721122
23 | }
24 | ],
25 | "meta": {
26 | "api_version": {
27 | "version": "1"
28 | },
29 | "billed_units": {
30 | "search_units": 1
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/spec/fixtures/summarize.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "14c24036-149e-439a-889d-c1eef43b72cb",
3 | "summary": "Ice cream is a frozen dessert made from dairy products or non-dairy substitutes. It is flavoured with a sweetener and a spice or with fruit. It is smooth and semi-solid at low temperatures.",
4 | "meta": {
5 | "api_version": {
6 | "version": "1"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/spec/fixtures/tokenize.json:
--------------------------------------------------------------------------------
1 | {
2 | "tokens": [
3 | 33555,
4 | 1114,
5 | 34
6 | ],
7 | "token_strings": [
8 | "hello",
9 | " world",
10 | "!"
11 | ],
12 | "meta": {
13 | "api_version": {
14 | "version": "1"
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "json"
4 | require "ostruct"
5 | require "faraday"
6 | require "cohere"
7 |
8 | RSpec.configure do |config|
9 | # Enable flags like --only-failures and --next-failure
10 | config.example_status_persistence_file_path = ".rspec_status"
11 |
12 | # Disable RSpec exposing methods globally on `Module` and `main`
13 | config.disable_monkey_patching!
14 |
15 | config.expect_with :rspec do |c|
16 | c.syntax = :expect
17 | end
18 | end
19 |
--------------------------------------------------------------------------------