├── .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 | Cohere logo 5 | +   6 | Ruby logo 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 | ![Tests status](https://github.com/patterns-ai-core/cohere-ruby/actions/workflows/ci.yml/badge.svg) 14 | [![Gem Version](https://badge.fury.io/rb/cohere-ruby.svg)](https://badge.fury.io/rb/cohere-ruby) 15 | [![Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/gems/cohere-ruby) 16 | [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/patterns-ai-core/cohere-ruby/blob/main/LICENSE.txt) 17 | [![](https://dcbadge.vercel.app/api/server/WDARp7J2n8?compact=true&style=flat)](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 | --------------------------------------------------------------------------------