langchainrb 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +2 -1
  3. data/.rubocop.yml +11 -0
  4. data/CHANGELOG.md +13 -0
  5. data/Gemfile +2 -0
  6. data/Gemfile.lock +14 -1
  7. data/README.md +42 -7
  8. data/Rakefile +5 -0
  9. data/examples/pdf_store_and_query_with_chroma.rb +1 -2
  10. data/examples/store_and_query_with_pinecone.rb +1 -2
  11. data/examples/store_and_query_with_qdrant.rb +1 -2
  12. data/examples/store_and_query_with_weaviate.rb +1 -2
  13. data/lefthook.yml +5 -0
  14. data/lib/langchain/agent/chain_of_thought_agent/chain_of_thought_agent.rb +6 -10
  15. data/lib/langchain/agent/sql_query_agent/sql_query_agent.rb +78 -0
  16. data/lib/langchain/agent/sql_query_agent/sql_query_agent_answer_prompt.json +10 -0
  17. data/lib/langchain/agent/sql_query_agent/sql_query_agent_sql_prompt.json +10 -0
  18. data/lib/langchain/dependency_helper.rb +34 -0
  19. data/lib/langchain/llm/ai21.rb +45 -0
  20. data/lib/langchain/llm/base.rb +2 -19
  21. data/lib/langchain/llm/cohere.rb +9 -0
  22. data/lib/langchain/llm/google_palm.rb +7 -0
  23. data/lib/langchain/llm/hugging_face.rb +9 -0
  24. data/lib/langchain/llm/openai.rb +33 -41
  25. data/lib/langchain/llm/replicate.rb +5 -2
  26. data/lib/langchain/processors/base.rb +2 -0
  27. data/lib/langchain/processors/xlsx.rb +27 -0
  28. data/lib/langchain/prompt/base.rb +8 -4
  29. data/lib/langchain/prompt/loading.rb +6 -1
  30. data/lib/langchain/prompt/prompt_template.rb +1 -1
  31. data/lib/langchain/tool/base.rb +4 -1
  32. data/lib/langchain/tool/calculator.rb +9 -0
  33. data/lib/langchain/tool/database.rb +45 -0
  34. data/lib/langchain/tool/ruby_code_interpreter.rb +6 -0
  35. data/lib/langchain/tool/serp_api.rb +5 -1
  36. data/lib/langchain/tool/wikipedia.rb +4 -0
  37. data/lib/langchain/vectorsearch/base.rb +8 -14
  38. data/lib/langchain/vectorsearch/chroma.rb +15 -7
  39. data/lib/langchain/vectorsearch/milvus.rb +13 -4
  40. data/lib/langchain/vectorsearch/pgvector.rb +15 -8
  41. data/lib/langchain/vectorsearch/pinecone.rb +15 -7
  42. data/lib/langchain/vectorsearch/qdrant.rb +15 -7
  43. data/lib/langchain/vectorsearch/weaviate.rb +15 -7
  44. data/lib/{version.rb → langchain/version.rb} +1 -1
  45. data/lib/langchain.rb +6 -2
  46. metadata +82 -4
  47. data/lib/dependency_helper.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 100c9989f1a6787168064f6d810f009c6ded8129949f0040a7c1ed05ebff9d20
4
- data.tar.gz: 0cf01f053841e9a45353d72b62df9a29c0daf7268fb69aff794aae830f7db448
3
+ metadata.gz: b0a2fe8026e861c9d97465bce7da08a0b077492d6f7cf8fb42c45dbfdfe6749f
4
+ data.tar.gz: c04099c44a847bd9c05e8594859f92ca1f54d338c463ce59a375c2cb9731b1ad
5
5
  SHA512:
6
- metadata.gz: becea5f089e7455e8dd9437cb3647c921eebb3155a9685697190e0e757d31ec8d9f4572faa3e299911b44b371c097bd9b5edf3da35772562027d1b12cd743b9e
7
- data.tar.gz: 44cfe6f8cb2056d6a0a5c619ded7828a9cdad0e5f4e8876242adad2f13251ea1c3dbb64c9feffea25b61e91f163245954c1f272fadb9f4f3a2a06cadd64bd9a0
6
+ metadata.gz: dec375b2b7cae377cf31f3f8ed0a6ac9d79215c945e7c0da78ed1fbad3c502ecfcc5ce5318c55a9a634db88fea1f5fbbeed7a0f7dc6ab8096c909e0a3ff02154
7
+ data.tar.gz: 558a0f6ddf90ad044f9e2cc7c6ca678958472748d2e430a3cbb4308290898b9b22a204a94a5b5943f06137f7da5238d038a65b8c79d96f8a3705499d95cfb597
data/.env.example CHANGED
@@ -1,3 +1,4 @@
1
+ AI21_API_KEY=
1
2
  CHROMA_URL=
2
3
  COHERE_API_KEY=
3
4
  HUGGING_FACE_API_KEY=
@@ -6,10 +7,10 @@ OPENAI_API_KEY=
6
7
  GOOGLE_PALM_API_KEY=
7
8
  PINECONE_API_KEY=
8
9
  PINECONE_ENVIRONMENT=
10
+ POSTGRES_URL=
9
11
  REPLICATE_API_KEY=
10
12
  QDRANT_API_KEY=
11
13
  QDRANT_URL=
12
14
  SERPAPI_API_KEY=
13
15
  WEAVIATE_API_KEY=
14
16
  WEAVIATE_URL=
15
- POSTGRES_URL=
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ AllCops:
2
+ SuggestExtensions: false
3
+
4
+ require:
5
+ - standard
6
+ - rubocop-performance
7
+
8
+ inherit_gem:
9
+ standard: config/base.yml
10
+ standard-performance: config/base.yml
11
+ standard-custom: config/base.yml
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2023-06-05
4
+ - [BREAKING] LLMs are now passed as objects to Vectorsearch classes instead of `llm: :name, llm_api_key:` previously
5
+ - 📋 Prompts
6
+ - YAML prompt templates are now supported
7
+ - 🚚 Loaders
8
+ - Introduce `Langchain::Processors::Xlsx` to parse .xlsx files
9
+
10
+ ## [0.4.2] - 2023-06-03
11
+ - 🗣️ LLMs
12
+ - Introducing support for AI21
13
+ - Better docs generation
14
+ - Refactors
15
+
3
16
  ## [0.4.1] - 2023-06-02
4
17
  - Beautiful colored log messages
5
18
  - 🛠️ Tools
data/Gemfile CHANGED
@@ -10,3 +10,5 @@ gem "rake", "~> 13.0"
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
12
  gem "standardrb"
13
+ # Lets add rubocop explicitly here, we are using only standardrb rules in .rubocop.yml
14
+ gem "rubocop"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- langchainrb (0.4.1)
4
+ langchainrb (0.5.0)
5
5
  colorize (~> 0.8.1)
6
6
  tiktoken_ruby (~> 0.0.5)
7
7
 
@@ -30,6 +30,7 @@ GEM
30
30
  addressable (2.8.4)
31
31
  public_suffix (>= 2.0.2, < 6.0)
32
32
  afm (0.2.2)
33
+ ai21 (0.2.0)
33
34
  ast (2.4.2)
34
35
  builder (3.2.4)
35
36
  byebug (11.1.3)
@@ -210,6 +211,7 @@ GEM
210
211
  rainbow (3.1.1)
211
212
  rake (13.0.6)
212
213
  rb_sys (0.9.78)
214
+ rdiscount (2.2.7)
213
215
  regexp_parser (2.8.0)
214
216
  replicate-ruby (0.2.2)
215
217
  addressable
@@ -217,6 +219,9 @@ GEM
217
219
  faraday-multipart
218
220
  faraday-retry
219
221
  rexml (3.2.5)
222
+ roo (2.10.0)
223
+ nokogiri (~> 1)
224
+ rubyzip (>= 1.3.0, < 3.0.0)
220
225
  rspec (3.12.0)
221
226
  rspec-core (~> 3.12.0)
222
227
  rspec-expectations (~> 3.12.0)
@@ -255,6 +260,7 @@ GEM
255
260
  rubyzip (2.3.2)
256
261
  safe_ruby (1.0.4)
257
262
  childprocess (>= 0.3.9)
263
+ sequel (5.68.0)
258
264
  standard (1.28.2)
259
265
  language_server-protocol (~> 3.17.0.2)
260
266
  lint_roller (~> 1.0)
@@ -286,6 +292,7 @@ GEM
286
292
  graphlient (~> 0.6.0)
287
293
  wikipedia-client (1.17.0)
288
294
  addressable (~> 2.7)
295
+ yard (0.9.34)
289
296
  zeitwerk (2.6.8)
290
297
 
291
298
  PLATFORMS
@@ -297,6 +304,7 @@ PLATFORMS
297
304
  x86_64-linux
298
305
 
299
306
  DEPENDENCIES
307
+ ai21 (~> 0.2.0)
300
308
  chroma-db (~> 0.3.0)
301
309
  cohere-ruby (~> 0.9.4)
302
310
  docx (~> 0.8.0)
@@ -315,13 +323,18 @@ DEPENDENCIES
315
323
  pry-byebug (~> 3.10.0)
316
324
  qdrant-ruby (~> 0.9.0)
317
325
  rake (~> 13.0)
326
+ rdiscount
318
327
  replicate-ruby (~> 0.2.2)
328
+ roo (~> 2.10.0)
319
329
  rspec (~> 3.0)
330
+ rubocop
320
331
  ruby-openai (~> 4.0.0)
321
332
  safe_ruby (~> 1.0.4)
333
+ sequel (~> 5.68.0)
322
334
  standardrb
323
335
  weaviate-ruby (~> 0.8.0)
324
336
  wikipedia-client (~> 1.17.0)
337
+ yard
325
338
 
326
339
  BUNDLED WITH
327
340
  2.4.0
data/README.md CHANGED
@@ -47,8 +47,7 @@ Pick the vector search database you'll be using and instantiate the client:
47
47
  client = Langchain::Vectorsearch::Weaviate.new(
48
48
  url: ENV["WEAVIATE_URL"],
49
49
  api_key: ENV["WEAVIATE_API_KEY"],
50
- llm: :openai, # or :cohere
51
- llm_api_key: ENV["OPENAI_API_KEY"]
50
+ llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
52
51
  )
53
52
 
54
53
  # You can instantiate any other supported vector search database:
@@ -109,6 +108,10 @@ Add `gem "ruby-openai", "~> 4.0.0"` to your Gemfile.
109
108
  ```ruby
110
109
  openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
111
110
  ```
111
+ You can pass additional parameters to the constructor, it will be passed to the OpenAI client:
112
+ ```ruby
113
+ openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"], llm_options: {uri_base: "http://localhost:1234"}) )
114
+ ```
112
115
  ```ruby
113
116
  openai.embed(text: "foo bar")
114
117
  ```
@@ -147,6 +150,12 @@ Add `"google_palm_api", "~> 0.1.0"` to your Gemfile.
147
150
  google_palm = Langchain::LLM::GooglePalm.new(api_key: ENV["GOOGLE_PALM_API_KEY"])
148
151
  ```
149
152
 
153
+ #### AI21
154
+ Add `gem "ai21", "~> 0.2.0"` to your Gemfile.
155
+ ```ruby
156
+ ai21 = Langchain::LLM::AI21.new(api_key: ENV["AI21_API_KEY"])
157
+ ```
158
+
150
159
  ### Using Prompts 📋
151
160
 
152
161
  #### Prompt Templates
@@ -168,9 +177,9 @@ prompt.format(adjective: "funny", content: "chickens") # "Tell me a funny joke a
168
177
  Creating a PromptTemplate using just a prompt and no input_variables:
169
178
 
170
179
  ```ruby
171
- prompt = Langchain::Prompt::PromptTemplate.from_template("Tell me a {adjective} joke about {content}.")
172
- prompt.input_variables # ["adjective", "content"]
173
- prompt.format(adjective: "funny", content: "chickens") # "Tell me a funny joke about chickens."
180
+ prompt = Langchain::Prompt::PromptTemplate.from_template("Tell me a funny joke about chickens.")
181
+ prompt.input_variables # []
182
+ prompt.format # "Tell me a funny joke about chickens."
174
183
  ```
175
184
 
176
185
  Save prompt template to JSON file:
@@ -232,6 +241,13 @@ prompt = Langchain::Prompt.load_from_path(file_path: "spec/fixtures/prompt/few_s
232
241
  prompt.prefix # "Write antonyms for the following words."
233
242
  ```
234
243
 
244
+ Loading a new prompt template using a YAML file:
245
+
246
+ ```ruby
247
+ prompt = Langchain::Prompt.load_from_path(file_path: "spec/fixtures/prompt/prompt_template.yaml")
248
+ prompt.input_variables #=> ["adjective", "content"]
249
+ ```
250
+
235
251
  ### Using Agents 🤖
236
252
  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.
237
253
 
@@ -240,7 +256,7 @@ Agents are semi-autonomous bots that can respond to user questions and use avail
240
256
  Add `gem "ruby-openai"`, `gem "eqn"`, and `gem "google_search_results"` to your Gemfile
241
257
 
242
258
  ```ruby
243
- agent = Langchain::Agent::ChainOfThoughtAgent.new(llm: :openai, llm_api_key: ENV["OPENAI_API_KEY"], tools: ['search', 'calculator'])
259
+ agent = Langchain::Agent::ChainOfThoughtAgent.new(llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"]), tools: ['search', 'calculator'])
244
260
 
245
261
  agent.tools
246
262
  # => ["search", "calculator"]
@@ -250,6 +266,19 @@ agent.run(question: "How many full soccer fields would be needed to cover the di
250
266
  #=> "Approximately 2,945 soccer fields would be needed to cover the distance between NYC and DC in a straight line."
251
267
  ```
252
268
 
269
+ #### SQL-Query Agent
270
+
271
+ Add `gem "sequel"` to your Gemfile
272
+
273
+ ```ruby
274
+ agent = Langchain::Agent::SQLQueryAgent.new(llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"]), db_connection_string: "postgres://user:password@localhost:5432/db_name")
275
+
276
+ ```
277
+ ```ruby
278
+ agent.ask(question: "How many users have a name with length greater than 5 in the users table?")
279
+ #=> "14 users have a name with length greater than 5 in the users table."
280
+ ```
281
+
253
282
  #### Demo
254
283
  ![May-12-2023 13-09-13](https://github.com/andreibondarev/langchainrb/assets/541665/6bad4cd9-976c-420f-9cf9-b85bf84f7eaf)
255
284
 
@@ -260,6 +289,7 @@ agent.run(question: "How many full soccer fields would be needed to cover the di
260
289
  | Name | Description | ENV Requirements | Gem Requirements |
261
290
  | ------------ | :------------------------------------------------: | :-----------------------------------------------------------: | :---------------------------------------: |
262
291
  | "calculator" | Useful for getting the result of a math expression | | `gem "eqn", "~> 1.6.5"` |
292
+ | "database" | Useful for querying a SQL database | | `gem "sequel", "~> 5.68.0"` |
263
293
  | "ruby_code_interpreter" | Interprets Ruby expressions | | `gem "safe_ruby", "~> 1.0.4"` |
264
294
  | "search" | A wrapper around Google Search | `ENV["SERPAPI_API_KEY"]` (https://serpapi.com/manage-api-key) | `gem "google_search_results", "~> 2.0.0"` |
265
295
  | "wikipedia" | Calls Wikipedia API to retrieve the summary | | `gem "wikipedia-client", "~> 1.17.0"` |
@@ -294,13 +324,14 @@ Langchain::Loader.load('https://www.example.com/file.pdf')
294
324
  | JSON | Langchain::Processors::JSON | |
295
325
  | JSONL | Langchain::Processors::JSONL | |
296
326
  | csv | Langchain::Processors::CSV | |
327
+ | xlsx | Langchain::Processors::Xlsx | `gem "roo", "~> 2.10.0"` |
297
328
 
298
329
  ## Examples
299
330
  Additional examples available: [/examples](https://github.com/andreibondarev/langchainrb/tree/main/examples)
300
331
 
301
332
  ## Logging
302
333
 
303
- LangChain.rb uses standard logging mechanisms and defaults to `:debug` level. Most messages are at info level, but we will add debug or warn statements as needed.
334
+ LangChain.rb uses standard logging mechanisms and defaults to `:warn` level. Most messages are at info level, but we will add debug or warn statements as needed.
304
335
  To show all log messages:
305
336
 
306
337
  ```ruby
@@ -313,6 +344,10 @@ Langchain.logger.level = :info
313
344
  2. `cp .env.example .env`, then fill out the environment variables in `.env`
314
345
  3. `bundle exec rake` to ensure that the tests pass and to run standardrb
315
346
  4. `bin/console` to load the gem in a REPL session. Feel free to add your own instances of LLMs, Tools, Agents, etc. and experiment with them.
347
+ 5. Optionally, install lefthook git hooks for pre-commit to auto lint: `gem install lefthook && lefthook install -f`
348
+
349
+ ## Community
350
+ Join us in the [Ruby AI Builders](https://discord.gg/SBmjAnKT) Discord community in #langchainrb
316
351
 
317
352
  ## Core Contributors
318
353
  [<img style="border-radius:50%" alt="Andrei Bondarev" src="https://avatars.githubusercontent.com/u/541665?v=4" width="80" height="80" class="avatar">](https://github.com/andreibondarev)
data/Rakefile CHANGED
@@ -3,6 +3,7 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
5
  require "standard/rake"
6
+ require "yard"
6
7
 
7
8
  RSpec::Core::RakeTask.new(:spec)
8
9
 
@@ -11,3 +12,7 @@ task default: :spec
11
12
  Rake::Task["spec"].enhance do
12
13
  Rake::Task["standard:fix"].invoke
13
14
  end
15
+
16
+ YARD::Rake::YardocTask.new do |t|
17
+ t.options = ["--fail-on-warning"]
18
+ end
@@ -7,8 +7,7 @@ require "langchain"
7
7
  chroma = Vectorsearch::Chroma.new(
8
8
  url: ENV["CHROMA_URL"],
9
9
  index_name: "documents",
10
- llm: :openai,
11
- llm_api_key: ENV["OPENAI_API_KEY"]
10
+ llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
12
11
  )
13
12
 
14
13
  # Create the default schema.
@@ -8,8 +8,7 @@ pinecone = Vectorsearch::Pinecone.new(
8
8
  environment: ENV["PINECONE_ENVIRONMENT"],
9
9
  api_key: ENV["PINECONE_API_KEY"],
10
10
  index_name: "recipes",
11
- llm: :openai,
12
- llm_api_key: ENV["OPENAI_API_KEY"]
11
+ llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
13
12
  )
14
13
 
15
14
  # Create the default schema.
@@ -8,8 +8,7 @@ qdrant = Vectorsearch::Qdrant.new(
8
8
  url: ENV["QDRANT_URL"],
9
9
  api_key: ENV["QDRANT_API_KEY"],
10
10
  index_name: "recipes",
11
- llm: :cohere,
12
- llm_api_key: ENV["COHERE_API_KEY"]
11
+ llm: Langchain::LLM::Cohere.new(api_key: ENV["COHERE_API_KEY"])
13
12
  )
14
13
 
15
14
  # Create the default schema.
@@ -8,8 +8,7 @@ weaviate = Vectorsearch::Weaviate.new(
8
8
  url: ENV["WEAVIATE_URL"],
9
9
  api_key: ENV["WEAVIATE_API_KEY"],
10
10
  index_name: "Recipes",
11
- llm: :openai,
12
- llm_api_key: ENV["OPENAI_API_KEY"]
11
+ llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
13
12
  )
14
13
 
15
14
  # Create the default schema. A text field `content` will be used.
data/lefthook.yml ADDED
@@ -0,0 +1,5 @@
1
+ pre-commit:
2
+ commands:
3
+ lint:
4
+ run: standardrb --fix
5
+ stage_fixed: true
@@ -2,23 +2,19 @@
2
2
 
3
3
  module Langchain::Agent
4
4
  class ChainOfThoughtAgent < Base
5
- attr_reader :llm, :llm_api_key, :llm_client, :tools
5
+ attr_reader :llm, :tools
6
6
 
7
7
  # Initializes the Agent
8
8
  #
9
- # @param llm [Symbol] The LLM to use
10
- # @param llm_api_key [String] The API key for the LLM
9
+ # @param llm [Object] The LLM client to use
11
10
  # @param tools [Array] The tools to use
12
11
  # @return [ChainOfThoughtAgent] The Agent::ChainOfThoughtAgent instance
13
- def initialize(llm:, llm_api_key:, tools: [])
14
- Langchain::LLM::Base.validate_llm!(llm: llm)
12
+ def initialize(llm:, tools: [])
15
13
  Langchain::Tool::Base.validate_tools!(tools: tools)
16
14
 
17
- @llm = llm
18
- @llm_api_key = llm_api_key
19
15
  @tools = tools
20
16
 
21
- @llm_client = Langchain::LLM.const_get(Langchain::LLM::Base::LLMS.fetch(llm)).new(api_key: llm_api_key)
17
+ @llm = llm
22
18
  end
23
19
 
24
20
  # Validate tools when they're re-assigned
@@ -42,8 +38,8 @@ module Langchain::Agent
42
38
  )
43
39
 
44
40
  loop do
45
- Langchain.logger.info("[#{self.class.name}]".red + ": Sending the prompt to the #{llm} LLM")
46
- response = llm_client.complete(
41
+ Langchain.logger.info("[#{self.class.name}]".red + ": Sending the prompt to the #{llm.class} LLM")
42
+ response = llm.complete(
47
43
  prompt: prompt,
48
44
  stop_sequences: ["Observation:"],
49
45
  max_tokens: 500
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langchain::Agent
4
+ class SQLQueryAgent < Base
5
+ attr_reader :llm, :db, :schema
6
+
7
+ # Initializes the Agent
8
+ #
9
+ # @param llm [Object] The LLM client to use
10
+ # @param db_connection_string [String] Database connection info
11
+ def initialize(llm:, db_connection_string:)
12
+ @llm = llm
13
+ @db = Langchain::Tool::Database.new(db_connection_string)
14
+ @schema = @db.schema
15
+ end
16
+
17
+ # Ask a question and get an answer
18
+ #
19
+ # @param question [String] Question to ask the LLM/Database
20
+ # @return [String] Answer to the question
21
+ def ask(question:)
22
+ prompt = create_prompt_for_sql(question: question)
23
+
24
+ # Get the SQL string to execute
25
+ Langchain.logger.info("[#{self.class.name}]".red + ": Passing the inital prompt to the #{llm.class} LLM")
26
+ sql_string = llm.complete(prompt: prompt, max_tokens: 500)
27
+
28
+ # Execute the SQL string and collect the results
29
+ Langchain.logger.info("[#{self.class.name}]".red + ": Passing the SQL to the Database: #{sql_string}")
30
+ results = db.execute(input: sql_string)
31
+
32
+ # Pass the results and get the LLM to synthesize the answer to the question
33
+ Langchain.logger.info("[#{self.class.name}]".red + ": Passing the synthesize prompt to the #{llm.class} LLM with results: #{results}")
34
+ prompt2 = create_prompt_for_answer(question: question, sql_query: sql_string, results: results)
35
+ llm.complete(prompt: prompt2, max_tokens: 500)
36
+ end
37
+
38
+ private
39
+
40
+ # Create the initial prompt to pass to the LLM
41
+ # @param question[String] Question to ask
42
+ # @return [String] Prompt
43
+ def create_prompt_for_sql(question:)
44
+ prompt_template_sql.format(
45
+ dialect: "standard SQL",
46
+ schema: schema,
47
+ question: question
48
+ )
49
+ end
50
+
51
+ # Load the PromptTemplate from the JSON file
52
+ # @return [PromptTemplate] PromptTemplate instance
53
+ def prompt_template_sql
54
+ Langchain::Prompt.load_from_path(
55
+ file_path: Langchain.root.join("langchain/agent/sql_query_agent/sql_query_agent_sql_prompt.json")
56
+ )
57
+ end
58
+
59
+ # Create the second prompt to pass to the LLM
60
+ # @param question [String] Question to ask
61
+ # @return [String] Prompt
62
+ def create_prompt_for_answer(question:, sql_query:, results:)
63
+ prompt_template_answer.format(
64
+ question: question,
65
+ sql_query: sql_query,
66
+ results: results
67
+ )
68
+ end
69
+
70
+ # Load the PromptTemplate from the JSON file
71
+ # @return [PromptTemplate] PromptTemplate instance
72
+ def prompt_template_answer
73
+ Langchain::Prompt.load_from_path(
74
+ file_path: Langchain.root.join("langchain/agent/sql_query_agent/sql_query_agent_answer_prompt.json")
75
+ )
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,10 @@
1
+ {
2
+ "_type": "prompt",
3
+ "template":
4
+ "Given an input question and results of a SQL query, look at the results and return the answer. Use the following format:\nQuestion: {question}\nThe SQL query: {sql_query}\nResult of the SQLQuery: {results}\nFinal answer: Final answer here",
5
+ "input_variables": [
6
+ "question",
7
+ "sql_query",
8
+ "results"
9
+ ]
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "_type": "prompt",
3
+ "template":
4
+ "Given an input question, create a syntactically correct {dialect} query to run, then return the query in valid SQL.\nNever query for all the columns from a specific table, only ask for a the few relevant columns given the question.\nPay attention to use only the column names that you can see in the schema description. Be careful to not query for columns that do not exist. Pay attention to which column is in which table. Also, qualify column names with the table name when needed.\nOnly use the tables listed below.\n{schema}\nUse the following format:\nQuestion: {question}\nSQLQuery:",
5
+ "input_variables": [
6
+ "dialect",
7
+ "schema",
8
+ "question"
9
+ ]
10
+ }
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langchain
4
+ module DependencyHelper
5
+ class VersionError < ScriptError; end
6
+
7
+ # This method requires and loads the given gem, and then checks to see if the version of the gem meets the requirements listed in `langchain.gemspec`
8
+ # This solution was built to avoid auto-loading every single gem in the Gemfile when the developer will mostly likely be only using a few of them.
9
+ #
10
+ # @param gem_name [String] The name of the gem to load
11
+ # @return [Boolean] Whether or not the gem was loaded successfully
12
+ # @raise [LoadError] If the gem is not installed
13
+ # @raise [VersionError] If the gem is installed, but the version does not meet the requirements
14
+ #
15
+ def depends_on(gem_name)
16
+ gem(gem_name) # require the gem
17
+
18
+ return(true) unless defined?(Bundler) # If we're in a non-bundler environment, we're no longer able to determine if we'll meet requirements
19
+
20
+ gem_version = Gem.loaded_specs[gem_name].version
21
+ gem_requirement = Bundler.load.dependencies.find { |g| g.name == gem_name }&.requirement
22
+
23
+ raise LoadError unless gem_requirement
24
+
25
+ unless gem_requirement.satisfied_by?(gem_version)
26
+ raise VersionError, "The #{gem_name} gem is installed, but version #{gem_requirement} is required. You have #{gem_version}."
27
+ end
28
+
29
+ true
30
+ rescue LoadError
31
+ raise LoadError, "Could not load #{gem_name}. Please ensure that the #{gem_name} gem is installed."
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langchain::LLM
4
+ 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
+ def initialize(api_key:)
15
+ depends_on "ai21"
16
+ require "ai21"
17
+
18
+ @client = ::AI21::Client.new(api_key)
19
+ end
20
+
21
+ #
22
+ # Generate a completion for a given prompt
23
+ #
24
+ # @param prompt [String] The prompt to generate a completion for
25
+ # @param params [Hash] The parameters to pass to the API
26
+ # @return [String] The completion
27
+ #
28
+ def complete(prompt:, **params)
29
+ response = client.complete(prompt, params)
30
+ response.dig(:completions, 0, :data, :text)
31
+ end
32
+
33
+ #
34
+ # Generate a summary for a given text
35
+ #
36
+ # @param text [String] The text to generate a summary for
37
+ # @param params [Hash] The parameters to pass to the API
38
+ # @return [String] The summary
39
+ #
40
+ def summarize(text:, **params)
41
+ response = client.summarize(text, "TEXT", params)
42
+ response.dig(:summary)
43
+ end
44
+ end
45
+ end
@@ -2,17 +2,9 @@
2
2
 
3
3
  module Langchain::LLM
4
4
  class Base
5
- attr_reader :client
5
+ include Langchain::DependencyHelper
6
6
 
7
- # Currently supported LLMs
8
- # TODO: Add support for HuggingFace and other LLMs
9
- LLMS = {
10
- cohere: "Cohere",
11
- google_palm: "GooglePalm",
12
- huggingface: "HuggingFace",
13
- openai: "OpenAI",
14
- replicate: "Replicate"
15
- }.freeze
7
+ attr_reader :client
16
8
 
17
9
  def default_dimension
18
10
  self.class.const_get(:DEFAULTS).dig(:dimension)
@@ -37,14 +29,5 @@ module Langchain::LLM
37
29
  def summarize(...)
38
30
  raise NotImplementedError, "#{self.class.name} does not support summarization"
39
31
  end
40
-
41
- # Ensure that the LLM value passed in is supported
42
- # @param llm [Symbol] The LLM to use
43
- def self.validate_llm!(llm:)
44
- # TODO: Fix so this works when `llm` value is a string instead of a symbol
45
- unless Langchain::LLM::Base::LLMS.key?(llm)
46
- raise ArgumentError, "LLM must be one of #{Langchain::LLM::Base::LLMS.keys}"
47
- end
48
- end
49
32
  end
50
33
  end
@@ -2,6 +2,15 @@
2
2
 
3
3
  module Langchain::LLM
4
4
  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
+
5
14
  DEFAULTS = {
6
15
  temperature: 0.0,
7
16
  completion_model_name: "base",
@@ -2,7 +2,14 @@
2
2
 
3
3
  module Langchain::LLM
4
4
  class GooglePalm < Base
5
+ #
5
6
  # Wrapper around the Google PaLM (Pathways Language Model) APIs.
7
+ #
8
+ # Gem requirements: gem "google_palm_api", "~> 0.1.0"
9
+ #
10
+ # Usage:
11
+ # google_palm = Langchain::LLM::GooglePalm.new(api_key: "YOUR_API_KEY")
12
+ #
6
13
 
7
14
  DEFAULTS = {
8
15
  temperature: 0.0,
@@ -2,6 +2,15 @@
2
2
 
3
3
  module Langchain::LLM
4
4
  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
+
5
14
  # The gem does not currently accept other models:
6
15
  # https://github.com/alchaplinsky/hugging-face/blob/main/lib/hugging_face/inference_api.rb#L32-L34
7
16
  DEFAULTS = {