langchainrb 0.5.2 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d36de4206b792714ba9b6773c03272e9638b14caf7140e0bc00c3e767aa5fdef
4
- data.tar.gz: 819fab9de55a34e4e6dc865febc19bb9979df55fa8fc6a753774cf1961c40103
3
+ metadata.gz: 87647b8a7e2dc49359f3f6d655eda501dcac26ebdd14247ad6c583be8dc1a71c
4
+ data.tar.gz: fb7b4321caa4ff026439158f5ccfc2ae9e7b515a69c35cba87385f2cb367fa85
5
5
  SHA512:
6
- metadata.gz: 6e180b41bbca96bd5523c276923f223bbebe470314086c6a909df440890793bcc70dbd66ecf59bf5d0fd52426650cc5d2684c56cc8fc643209cc1679527cbef4
7
- data.tar.gz: af5db76c2b22b5c7bdc1170de437921e8464a16566f46a5cad465d69e6da47c97a82f7331a5ea5747840e58acc71463aa8456b03e9bc8851efda7b734e5d23cc
6
+ metadata.gz: 4f80677c43c00e6d50e0494aa79cb7648b9f3878ed8d2a5f4f2dc90e308a3639589f8457a4615821b70b44c5a43ae4f26fcf00d7548684740e4c05dbcc165bf8
7
+ data.tar.gz: 4722233dbed83d21f2dadff19a9b79a30d8fd208d6e30bd057f018060c602b7f00f0526ee0364823597806388e2a8da48e883a5e6fa77b31490199685644b4d2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.4] - 2023-06-10
4
+ - 🔍 Vectorsearch
5
+ - Introducing support for HNSWlib
6
+ - Improved and new `Langchain::Chat` interface that persists chat history in memory
7
+
8
+ ## [0.5.3] - 2023-06-09
9
+ - 🗣️ LLMs
10
+ - Chat message history support for Langchain::LLM::GooglePalm and Langchain::LLM::OpenAI
11
+
3
12
  ## [0.5.2] - 2023-06-07
4
13
  - 🗣️ LLMs
5
14
  - Auto-calculate the max_tokens: setting to be passed on to OpenAI
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- langchainrb (0.5.2)
4
+ langchainrb (0.5.4)
5
5
  colorize (~> 0.8.1)
6
6
  tiktoken_ruby (~> 0.0.5)
7
7
 
@@ -122,7 +122,7 @@ GEM
122
122
  faraday-retry (1.0.3)
123
123
  faraday_middleware (1.2.0)
124
124
  faraday (~> 1.0)
125
- google_palm_api (0.1.0)
125
+ google_palm_api (0.1.1)
126
126
  faraday (>= 1.0.0)
127
127
  faraday_middleware (>= 1.0.0)
128
128
  google_search_results (2.0.1)
@@ -135,6 +135,7 @@ GEM
135
135
  activesupport (>= 3.0)
136
136
  graphql
137
137
  hashery (2.1.2)
138
+ hnswlib (0.8.1)
138
139
  httparty (0.21.0)
139
140
  mini_mime (>= 1.0.0)
140
141
  multi_xml (>= 0.5.2)
@@ -310,8 +311,9 @@ DEPENDENCIES
310
311
  docx (~> 0.8.0)
311
312
  dotenv-rails (~> 2.7.6)
312
313
  eqn (~> 1.6.5)
313
- google_palm_api (~> 0.1.0)
314
+ google_palm_api (~> 0.1.1)
314
315
  google_search_results (~> 2.0.0)
316
+ hnswlib (~> 0.8.1)
315
317
  hugging-face (~> 0.3.4)
316
318
  langchainrb!
317
319
  milvus (~> 0.9.0)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- 🦜️🔗 LangChain.rb
1
+ 💎🔗 LangChain.rb
2
2
  ---
3
3
  ⚡ Building applications with LLMs through composability ⚡
4
4
 
@@ -6,7 +6,10 @@
6
6
 
7
7
  :warning: UNDER ACTIVE AND RAPID DEVELOPMENT (MAY BE BUGGY AND UNTESTED)
8
8
 
9
- ![Tests status](https://github.com/andreibondarev/langchainrb/actions/workflows/ci.yml/badge.svg) [![Gem Version](https://badge.fury.io/rb/langchainrb.svg)](https://badge.fury.io/rb/langchainrb)
9
+ ![Tests status](https://github.com/andreibondarev/langchainrb/actions/workflows/ci.yml/badge.svg)
10
+ [![Gem Version](https://badge.fury.io/rb/langchainrb.svg)](https://badge.fury.io/rb/langchainrb)
11
+ [![Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/gems/langchainrb)
12
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/andreibondarev/langchainrb/blob/main/LICENSE.txt)
10
13
 
11
14
  Langchain.rb is a library that's an abstraction layer on top many emergent AI, ML and other DS tools. The goal is to abstract complexity and difficult concepts to make building AI/ML-supercharged applications approachable for traditional software engineers.
12
15
 
@@ -31,8 +34,10 @@ require "langchain"
31
34
  | Database | Querying | Storage | Schema Management | Backups | Rails Integration |
32
35
  | -------- |:------------------:| -------:| -----------------:| -------:| -----------------:|
33
36
  | [Chroma](https://trychroma.com/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | WIP | WIP |
37
+ | [Hnswlib](https://github.com/nmslib/hnswlib/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | WIP | WIP |
34
38
  | [Milvus](https://milvus.io/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | WIP | WIP |
35
39
  | [Pinecone](https://www.pinecone.io/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | WIP | WIP |
40
+ | [Pgvector](https://github.com/pgvector/pgvector) | :white_check_mark: | :white_check_mark: | :white_check_mark: | WIP | WIP |
36
41
  | [Qdrant](https://qdrant.tech/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | WIP | WIP |
37
42
  | [Weaviate](https://weaviate.io/) | :white_check_mark: | :white_check_mark: | :white_check_mark: | WIP | WIP |
38
43
 
@@ -47,14 +52,17 @@ Pick the vector search database you'll be using and instantiate the client:
47
52
  client = Langchain::Vectorsearch::Weaviate.new(
48
53
  url: ENV["WEAVIATE_URL"],
49
54
  api_key: ENV["WEAVIATE_API_KEY"],
55
+ index: "",
50
56
  llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
51
57
  )
52
58
 
53
59
  # You can instantiate any other supported vector search database:
60
+ client = Langchain::Vectorsearch::Chroma.new(...) # `gem "chroma-db", "~> 0.3.0"`
61
+ client = Langchain::Vectorsearch::Hnswlib.new(...) # `gem "hnswlib", "~> 0.8.1"`
54
62
  client = Langchain::Vectorsearch::Milvus.new(...) # `gem "milvus", "~> 0.9.0"`
55
- client = Langchain::Vectorsearch::Qdrant.new(...) # `gem"qdrant-ruby", "~> 0.9.0"`
56
63
  client = Langchain::Vectorsearch::Pinecone.new(...) # `gem "pinecone", "~> 0.1.6"`
57
- client = Langchain::Vectorsearch::Chroma.new(...) # `gem "chroma-db", "~> 0.3.0"`
64
+ client = Langchain::Vectorsearch::Pgvector.new(...) # `gem "pgvector", "~> 0.2"`
65
+ client = Langchain::Vectorsearch::Qdrant.new(...) # `gem"qdrant-ruby", "~> 0.9.0"`
58
66
  ```
59
67
 
60
68
  ```ruby
@@ -135,17 +143,17 @@ cohere.complete(prompt: "What is the meaning of life?")
135
143
  #### HuggingFace
136
144
  Add `gem "hugging-face", "~> 0.3.2"` to your Gemfile.
137
145
  ```ruby
138
- cohere = Langchain::LLM::HuggingFace.new(api_key: ENV["HUGGING_FACE_API_KEY"])
146
+ hugging_face = Langchain::LLM::HuggingFace.new(api_key: ENV["HUGGING_FACE_API_KEY"])
139
147
  ```
140
148
 
141
149
  #### Replicate
142
150
  Add `gem "replicate-ruby", "~> 0.2.2"` to your Gemfile.
143
151
  ```ruby
144
- cohere = Langchain::LLM::Replicate.new(api_key: ENV["REPLICATE_API_KEY"])
152
+ replicate = Langchain::LLM::Replicate.new(api_key: ENV["REPLICATE_API_KEY"])
145
153
  ```
146
154
 
147
155
  #### Google PaLM (Pathways Language Model)
148
- Add `"google_palm_api", "~> 0.1.0"` to your Gemfile.
156
+ Add `"google_palm_api", "~> 0.1.1"` to your Gemfile.
149
157
  ```ruby
150
158
  google_palm = Langchain::LLM::GooglePalm.new(api_key: ENV["GOOGLE_PALM_API_KEY"])
151
159
  ```
data/Rakefile CHANGED
@@ -14,5 +14,4 @@ Rake::Task["spec"].enhance do
14
14
  end
15
15
 
16
16
  YARD::Rake::YardocTask.new do |t|
17
- t.options = ["--fail-on-warning"]
18
17
  end
@@ -1,6 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain::Agent
4
+ # = Agents
5
+ #
6
+ # Agents are semi-autonomous bots that can respond to user questions and use available to them Tools to provide informed replies. They break down problems into series of steps and define Actions (and Action Inputs) along the way that are executed and fed back to them as additional information. Once an Agent decides that it has the Final Answer it responds with it.
7
+ #
8
+ # Available:
9
+ # - {Langchain::Agent::ChainOfThoughtAgent}
10
+ #
11
+ # @abstract
4
12
  class Base
5
13
  end
6
14
  end
@@ -1,6 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain::Agent
4
+ # = Chain of Thought Agent
5
+ #
6
+ # llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"]) # or your choice of Langchain::LLM::Base implementation
7
+ #
8
+ # agent = Langchain::Agent::ChainOfThoughtAgent.new(
9
+ # llm: llm,
10
+ # tools: ["search", "calculator", "wikipedia"]
11
+ # )
12
+ #
13
+ # agent.tools
14
+ # # => ["search", "calculator", "wikipedia"]
15
+ #
16
+ # agent.run(question: "How many full soccer fields would be needed to cover the distance between NYC and DC in a straight line?")
17
+ # #=> "Approximately 2,945 soccer fields would be needed to cover the distance between NYC and DC in a straight line."
4
18
  class ChainOfThoughtAgent < Base
5
19
  attr_reader :llm, :tools
6
20
 
@@ -87,7 +101,7 @@ module Langchain::Agent
87
101
  tool_names: "[#{tool_list.join(", ")}]",
88
102
  tools: tools.map do |tool|
89
103
  tool_name = tool.tool_name
90
- tool_description = tool.class.const_get(:DESCRIPTION)
104
+ tool_description = tool.tool_description
91
105
  "#{tool_name}: #{tool_description}"
92
106
  end.join("\n")
93
107
  )
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langchain
4
+ class Chat
5
+ attr_reader :context
6
+
7
+ def initialize(llm:, **options)
8
+ @llm = llm
9
+ @context = nil
10
+ @examples = []
11
+ @messages = []
12
+ end
13
+
14
+ # Set the context of the conversation. Usually used to set the model's persona.
15
+ # @param message [String] The context of the conversation
16
+ def set_context(message)
17
+ @context = message
18
+ end
19
+
20
+ # Add examples to the conversation. Used to give the model a sense of the conversation.
21
+ # @param examples [Array<Hash>] The examples to add to the conversation
22
+ def add_examples(examples)
23
+ @examples.concat examples
24
+ end
25
+
26
+ # Message the model with a prompt and return the response.
27
+ # @param message [String] The prompt to message the model with
28
+ # @return [String] The response from the model
29
+ def message(message)
30
+ append_user_message(message)
31
+ response = llm_response(message)
32
+ append_ai_message(response)
33
+ response
34
+ end
35
+
36
+ private
37
+
38
+ def llm_response(prompt)
39
+ @llm.chat(messages: @messages, context: @context, examples: @examples)
40
+ end
41
+
42
+ def append_ai_message(message)
43
+ @messages << {role: "ai", content: message}
44
+ end
45
+
46
+ def append_user_message(message)
47
+ @messages << {role: "user", content: message}
48
+ end
49
+ end
50
+ end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain::LLM
4
+ #
5
+ # Wrapper around AI21 Studio APIs.
6
+ #
7
+ # Gem requirements:
8
+ # gem "ai21", "~> 0.2.0"
9
+ #
10
+ # Usage:
11
+ # ai21 = Langchain::LLM::AI21.new(api_key:)
12
+ #
4
13
  class AI21 < Base
5
- #
6
- # Wrapper around AI21 Studio APIs.
7
- #
8
- # Gem requirements: gem "ai21", "~> 0.2.0"
9
- #
10
- # Usage:
11
- # ai21 = Langchain::LLM::AI21.new(api_key:)
12
- #
13
-
14
14
  def initialize(api_key:)
15
15
  depends_on "ai21"
16
16
  require "ai21"
@@ -1,31 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain::LLM
4
+ # A LLM is a language model consisting of a neural network with many parameters (typically billions of weights or more), trained on large quantities of unlabeled text using self-supervised learning or semi-supervised learning.
5
+ #
6
+ # Langchain.rb provides a common interface to interact with all supported LLMs:
7
+ #
8
+ # - {Langchain::LLM::AI21}
9
+ # - {Langchain::LLM::Cohere}
10
+ # - {Langchain::LLM::GooglePalm}
11
+ # - {Langchain::LLM::HuggingFace}
12
+ # - {Langchain::LLM::OpenAI}
13
+ # - {Langchain::LLM::Replicate}
14
+ #
15
+ # @abstract
4
16
  class Base
5
17
  include Langchain::DependencyHelper
6
18
 
19
+ # A client for communicating with the LLM
7
20
  attr_reader :client
8
21
 
9
22
  def default_dimension
10
23
  self.class.const_get(:DEFAULTS).dig(:dimension)
11
24
  end
12
25
 
13
- # Method supported by an LLM that generates a response for a given chat-style prompt
26
+ #
27
+ # Generate a chat completion for a given prompt. Parameters will depend on the LLM
28
+ #
29
+ # @raise NotImplementedError if not supported by the LLM
14
30
  def chat(...)
15
31
  raise NotImplementedError, "#{self.class.name} does not support chat"
16
32
  end
17
33
 
18
- # Method supported by an LLM that completes a given prompt
34
+ #
35
+ # Generate a completion for a given prompt. Parameters will depend on the LLM.
36
+ #
37
+ # @raise NotImplementedError if not supported by the LLM
19
38
  def complete(...)
20
39
  raise NotImplementedError, "#{self.class.name} does not support completion"
21
40
  end
22
41
 
23
- # Method supported by an LLM that generates an embedding for a given text or array of texts
42
+ #
43
+ # Generate an embedding for a given text. Parameters depends on the LLM.
44
+ #
45
+ # @raise NotImplementedError if not supported by the LLM
46
+ #
24
47
  def embed(...)
25
48
  raise NotImplementedError, "#{self.class.name} does not support generating embeddings"
26
49
  end
27
50
 
28
- # Method supported by an LLM that summarizes a given text
51
+ #
52
+ # Generate a summary for a given text. Parameters depends on the LLM.
53
+ #
54
+ # @raise NotImplementedError if not supported by the LLM
55
+ #
29
56
  def summarize(...)
30
57
  raise NotImplementedError, "#{self.class.name} does not support summarization"
31
58
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain::LLM
4
+ #
5
+ # Wrapper around the Cohere API.
6
+ #
7
+ # Gem requirements:
8
+ # gem "cohere-ruby", "~> 0.9.4"
9
+ #
10
+ # Usage:
11
+ # cohere = Langchain::LLM::Cohere.new(api_key: "YOUR_API_KEY")
12
+ #
4
13
  class Cohere < Base
5
- #
6
- # Wrapper around the Cohere API.
7
- #
8
- # Gem requirements: gem "cohere-ruby", "~> 0.9.4"
9
- #
10
- # Usage:
11
- # cohere = Langchain::LLM::Cohere.new(api_key: "YOUR_API_KEY")
12
- #
13
-
14
14
  DEFAULTS = {
15
15
  temperature: 0.0,
16
16
  completion_model_name: "base",
@@ -43,6 +43,7 @@ module Langchain::LLM
43
43
  # Generate a completion for a given prompt
44
44
  #
45
45
  # @param prompt [String] The prompt to generate a completion for
46
+ # @param params[:stop_sequences]
46
47
  # @return [Hash] The completion
47
48
  #
48
49
  def complete(prompt:, **params)
@@ -1,11 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain::LLM
4
+ #
5
+ # Wrapper around the Google PaLM (Pathways Language Model) APIs: https://ai.google/build/machine-learning/
6
+ #
7
+ # Gem requirements:
8
+ # gem "google_palm_api", "~> 0.1.0"
9
+ #
10
+ # Usage:
11
+ # google_palm = Langchain::LLM::GooglePalm.new(api_key: "YOUR_API_KEY")
12
+ #
4
13
  class GooglePalm < Base
5
14
  #
6
15
  # Wrapper around the Google PaLM (Pathways Language Model) APIs.
7
16
  #
8
- # Gem requirements: gem "google_palm_api", "~> 0.1.0"
17
+ # Gem requirements: gem "google_palm_api", "~> 0.1.1"
9
18
  #
10
19
  # Usage:
11
20
  # google_palm = Langchain::LLM::GooglePalm.new(api_key: "YOUR_API_KEY")
@@ -40,6 +49,7 @@ module Langchain::LLM
40
49
  # Generate a completion for a given prompt
41
50
  #
42
51
  # @param prompt [String] The prompt to generate a completion for
52
+ # @param params extra parameters passed to GooglePalmAPI::Client#generate_text
43
53
  # @return [String] The completion
44
54
  #
45
55
  def complete(prompt:, **params)
@@ -66,26 +76,35 @@ module Langchain::LLM
66
76
  # Generate a chat completion for a given prompt
67
77
  #
68
78
  # @param prompt [String] The prompt to generate a chat completion for
79
+ # @param messages [Array] The messages that have been sent in the conversation
80
+ # @param params extra parameters passed to GooglePalmAPI::Client#generate_chat_message
69
81
  # @return [String] The chat completion
70
82
  #
71
- def chat(prompt:, **params)
72
- # TODO: Figure out how to introduce persisted conversations
83
+ def chat(prompt: "", messages: [], context: "", examples: [], **options)
84
+ raise ArgumentError.new(":prompt or :messages argument is expected") if prompt.empty? && messages.empty?
85
+
73
86
  default_params = {
74
- prompt: prompt,
75
- temperature: DEFAULTS[:temperature]
87
+ temperature: DEFAULTS[:temperature],
88
+ context: context,
89
+ messages: compose_chat_messages(prompt: prompt, messages: messages),
90
+ examples: compose_examples(examples)
76
91
  }
77
92
 
78
- if params[:stop_sequences]
79
- default_params[:stop] = params.delete(:stop_sequences)
93
+ Langchain::Utils::TokenLength::GooglePalmValidator.validate_max_tokens!(self, default_params[:messages], "chat-bison-001")
94
+
95
+ if options[:stop_sequences]
96
+ default_params[:stop] = options.delete(:stop_sequences)
80
97
  end
81
98
 
82
- if params[:max_tokens]
83
- default_params[:max_output_tokens] = params.delete(:max_tokens)
99
+ if options[:max_tokens]
100
+ default_params[:max_output_tokens] = options.delete(:max_tokens)
84
101
  end
85
102
 
86
- default_params.merge!(params)
103
+ default_params.merge!(options)
87
104
 
88
105
  response = client.generate_chat_message(**default_params)
106
+ raise "GooglePalm API returned an error: #{response}" if response.dig("error")
107
+
89
108
  response.dig("candidates", 0, "content")
90
109
  end
91
110
 
@@ -108,5 +127,39 @@ module Langchain::LLM
108
127
  max_tokens: 2048
109
128
  )
110
129
  end
130
+
131
+ private
132
+
133
+ def compose_chat_messages(prompt:, messages:)
134
+ history = []
135
+ history.concat transform_messages(messages) unless messages.empty?
136
+
137
+ unless prompt.empty?
138
+ if history.last && history.last[:role] == "user"
139
+ history.last[:content] += "\n#{prompt}"
140
+ else
141
+ history.append({author: "user", content: prompt})
142
+ end
143
+ end
144
+ history
145
+ end
146
+
147
+ def compose_examples(examples)
148
+ examples.each_slice(2).map do |example|
149
+ {
150
+ input: {content: example.first[:content]},
151
+ output: {content: example.last[:content]}
152
+ }
153
+ end
154
+ end
155
+
156
+ def transform_messages(messages)
157
+ messages.map do |message|
158
+ {
159
+ author: message[:role],
160
+ content: message[:content]
161
+ }
162
+ end
163
+ end
111
164
  end
112
165
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain::LLM
4
+ #
5
+ # Wrapper around the HuggingFace Inference API: https://huggingface.co/inference-api
6
+ #
7
+ # Gem requirements:
8
+ # gem "hugging-face", "~> 0.3.4"
9
+ #
10
+ # Usage:
11
+ # hf = Langchain::LLM::HuggingFace.new(api_key: "YOUR_API_KEY")
12
+ #
4
13
  class HuggingFace < Base
5
- #
6
- # Wrapper around the HuggingFace Inference API.
7
- #
8
- # Gem requirements: gem "hugging-face", "~> 0.3.4"
9
- #
10
- # Usage:
11
- # hf = Langchain::LLM::HuggingFace.new(api_key: "YOUR_API_KEY")
12
- #
13
-
14
14
  # The gem does not currently accept other models:
15
15
  # https://github.com/alchaplinsky/hugging-face/blob/main/lib/hugging_face/inference_api.rb#L32-L34
16
16
  DEFAULTS = {
@@ -1,16 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain::LLM
4
+ # LLM interface for OpenAI APIs: https://platform.openai.com/overview
5
+ #
6
+ # Gem requirements:
7
+ # gem "ruby-openai", "~> 4.0.0"
8
+ #
9
+ # Usage:
10
+ # openai = Langchain::LLM::OpenAI.new(api_key:, llm_options: {})
11
+ #
4
12
  class OpenAI < Base
5
- #
6
- # Wrapper around OpenAI APIs.
7
- #
8
- # Gem requirements: gem "ruby-openai", "~> 4.0.0"
9
- #
10
- # Usage:
11
- # openai = Langchain::LLM::OpenAI.new(api_key:, llm_options: {})
12
- #
13
-
14
13
  DEFAULTS = {
15
14
  temperature: 0.0,
16
15
  completion_model_name: "text-davinci-003",
@@ -30,12 +29,13 @@ module Langchain::LLM
30
29
  # Generate an embedding for a given text
31
30
  #
32
31
  # @param text [String] The text to generate an embedding for
32
+ # @param params extra parameters passed to OpenAI::Client#embeddings
33
33
  # @return [Array] The embedding
34
34
  #
35
35
  def embed(text:, **params)
36
36
  parameters = {model: DEFAULTS[:embeddings_model_name], input: text}
37
37
 
38
- Langchain::Utils::TokenLengthValidator.validate_max_tokens!(text, parameters[:model])
38
+ Langchain::Utils::TokenLength::OpenAIValidator.validate_max_tokens!(text, parameters[:model])
39
39
 
40
40
  response = client.embeddings(parameters: parameters.merge(params))
41
41
  response.dig("data").first.dig("embedding")
@@ -45,13 +45,14 @@ module Langchain::LLM
45
45
  # Generate a completion for a given prompt
46
46
  #
47
47
  # @param prompt [String] The prompt to generate a completion for
48
+ # @param params extra parameters passed to OpenAI::Client#complete
48
49
  # @return [String] The completion
49
50
  #
50
51
  def complete(prompt:, **params)
51
52
  parameters = compose_parameters DEFAULTS[:completion_model_name], params
52
53
 
53
54
  parameters[:prompt] = prompt
54
- parameters[:max_tokens] = Langchain::Utils::TokenLengthValidator.validate_max_tokens!(prompt, parameters[:model])
55
+ parameters[:max_tokens] = Langchain::Utils::TokenLength::OpenAIValidator.validate_max_tokens!(prompt, parameters[:model])
55
56
 
56
57
  response = client.completions(parameters: parameters)
57
58
  response.dig("choices", 0, "text")
@@ -61,15 +62,23 @@ module Langchain::LLM
61
62
  # Generate a chat completion for a given prompt
62
63
  #
63
64
  # @param prompt [String] The prompt to generate a chat completion for
65
+ # @param messages [Array] The messages that have been sent in the conversation
66
+ # @param context [String] The context of the conversation
67
+ # @param examples [Array] Examples of messages provide model with
68
+ # @param options extra parameters passed to OpenAI::Client#chat
64
69
  # @return [String] The chat completion
65
70
  #
66
- def chat(prompt:, **params)
67
- parameters = compose_parameters DEFAULTS[:chat_completion_model_name], params
71
+ def chat(prompt: "", messages: [], context: "", examples: [], **options)
72
+ raise ArgumentError.new(":prompt or :messages argument is expected") if prompt.empty? && messages.empty?
68
73
 
69
- parameters[:messages] = [{role: "user", content: prompt}]
70
- parameters[:max_tokens] = Langchain::Utils::TokenLengthValidator.validate_max_tokens!(prompt, parameters[:model])
74
+ parameters = compose_parameters DEFAULTS[:chat_completion_model_name], options
75
+ parameters[:messages] = compose_chat_messages(prompt: prompt, messages: messages, context: context, examples: examples)
76
+ parameters[:max_tokens] = validate_max_tokens(parameters[:messages], parameters[:model])
71
77
 
72
78
  response = client.chat(parameters: parameters)
79
+
80
+ raise "Chat completion failed: #{response}" if response.dig("error")
81
+
73
82
  response.dig("choices", 0, "message", "content")
74
83
  end
75
84
 
@@ -97,5 +106,41 @@ module Langchain::LLM
97
106
 
98
107
  default_params.merge(params)
99
108
  end
109
+
110
+ def compose_chat_messages(prompt:, messages:, context:, examples:)
111
+ history = []
112
+
113
+ history.concat transform_messages(examples) unless examples.empty?
114
+
115
+ history.concat transform_messages(messages) unless messages.empty?
116
+
117
+ unless context.nil? || context.empty?
118
+ history.reject! { |message| message[:role] == "system" }
119
+ history.prepend({role: "system", content: context})
120
+ end
121
+
122
+ unless prompt.empty?
123
+ if history.last && history.last[:role] == "user"
124
+ history.last[:content] += "\n#{prompt}"
125
+ else
126
+ history.append({role: "user", content: prompt})
127
+ end
128
+ end
129
+
130
+ history
131
+ end
132
+
133
+ def transform_messages(messages)
134
+ messages.map do |message|
135
+ {
136
+ content: message[:content],
137
+ role: (message[:role] == "ai") ? "assistant" : message[:role]
138
+ }
139
+ end
140
+ end
141
+
142
+ def validate_max_tokens(messages, model)
143
+ Langchain::Utils::TokenLength::OpenAIValidator.validate_max_tokens!(messages, model)
144
+ end
100
145
  end
101
146
  end
@@ -1,22 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain::LLM
4
+ #
5
+ # Wrapper around Replicate.com LLM provider
6
+ #
7
+ # Gem requirements:
8
+ # gem "replicate-ruby", "~> 0.2.2"
9
+ #
10
+ # Use it directly:
11
+ # replicate = Langchain::LLM::Replicate.new(api_key: ENV["REPLICATE_API_KEY"])
12
+ #
13
+ # Or pass it to be used by a vector search DB:
14
+ # chroma = Langchain::Vectorsearch::Chroma.new(
15
+ # url: ENV["CHROMA_URL"],
16
+ # index_name: "...",
17
+ # llm: replicate
18
+ # )
19
+ #
4
20
  class Replicate < Base
5
- #
6
- # Wrapper around Replicate.com LLM provider
7
- #
8
- # Gem requirements: gem "replicate-ruby", "~> 0.2.2"
9
- #
10
- # Use it directly:
11
- # replicate = LLM::Replicate.new(api_key: ENV["REPLICATE_API_KEY"])
12
- #
13
- # Or pass it to be instantiated by a vector search DB:
14
- # chroma = Vectorsearch::Chroma.new(
15
- # url: ENV["CHROMA_URL"],
16
- # index_name: "...",
17
- # llm: Langchain::LLM::Replicate(api_key: ENV["REPLICATE_API_KEY"])
18
- # )
19
-
20
21
  DEFAULTS = {
21
22
  # TODO: Figure out how to send the temperature to the API
22
23
  temperature: 0.01, # Minimum accepted value
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Langchain
4
4
  module Processors
5
+ # Processors load and parse/process various data types such as CSVs, PDFs, Word documents, HTML pages, and others.
5
6
  class Base
6
7
  include Langchain::DependencyHelper
7
8