langchainrb 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.env.example +2 -1
- data/.rubocop.yml +11 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +14 -1
- data/README.md +42 -7
- data/Rakefile +5 -0
- data/examples/pdf_store_and_query_with_chroma.rb +1 -2
- data/examples/store_and_query_with_pinecone.rb +1 -2
- data/examples/store_and_query_with_qdrant.rb +1 -2
- data/examples/store_and_query_with_weaviate.rb +1 -2
- data/lefthook.yml +5 -0
- data/lib/langchain/agent/chain_of_thought_agent/chain_of_thought_agent.rb +6 -10
- data/lib/langchain/agent/sql_query_agent/sql_query_agent.rb +78 -0
- data/lib/langchain/agent/sql_query_agent/sql_query_agent_answer_prompt.json +10 -0
- data/lib/langchain/agent/sql_query_agent/sql_query_agent_sql_prompt.json +10 -0
- data/lib/langchain/dependency_helper.rb +34 -0
- data/lib/langchain/llm/ai21.rb +45 -0
- data/lib/langchain/llm/base.rb +2 -19
- data/lib/langchain/llm/cohere.rb +9 -0
- data/lib/langchain/llm/google_palm.rb +7 -0
- data/lib/langchain/llm/hugging_face.rb +9 -0
- data/lib/langchain/llm/openai.rb +33 -41
- data/lib/langchain/llm/replicate.rb +5 -2
- data/lib/langchain/processors/base.rb +2 -0
- data/lib/langchain/processors/xlsx.rb +27 -0
- data/lib/langchain/prompt/base.rb +8 -4
- data/lib/langchain/prompt/loading.rb +6 -1
- data/lib/langchain/prompt/prompt_template.rb +1 -1
- data/lib/langchain/tool/base.rb +4 -1
- data/lib/langchain/tool/calculator.rb +9 -0
- data/lib/langchain/tool/database.rb +45 -0
- data/lib/langchain/tool/ruby_code_interpreter.rb +6 -0
- data/lib/langchain/tool/serp_api.rb +5 -1
- data/lib/langchain/tool/wikipedia.rb +4 -0
- data/lib/langchain/vectorsearch/base.rb +8 -14
- data/lib/langchain/vectorsearch/chroma.rb +15 -7
- data/lib/langchain/vectorsearch/milvus.rb +13 -4
- data/lib/langchain/vectorsearch/pgvector.rb +15 -8
- data/lib/langchain/vectorsearch/pinecone.rb +15 -7
- data/lib/langchain/vectorsearch/qdrant.rb +15 -7
- data/lib/langchain/vectorsearch/weaviate.rb +15 -7
- data/lib/{version.rb → langchain/version.rb} +1 -1
- data/lib/langchain.rb +6 -2
- metadata +82 -4
- data/lib/dependency_helper.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0a2fe8026e861c9d97465bce7da08a0b077492d6f7cf8fb42c45dbfdfe6749f
|
4
|
+
data.tar.gz: c04099c44a847bd9c05e8594859f92ca1f54d338c463ce59a375c2cb9731b1ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
langchainrb (0.
|
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: :
|
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
|
172
|
-
prompt.input_variables # [
|
173
|
-
prompt.format
|
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: :
|
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 `:
|
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: :
|
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: :
|
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: :
|
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: :
|
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
@@ -2,23 +2,19 @@
|
|
2
2
|
|
3
3
|
module Langchain::Agent
|
4
4
|
class ChainOfThoughtAgent < Base
|
5
|
-
attr_reader :llm, :
|
5
|
+
attr_reader :llm, :tools
|
6
6
|
|
7
7
|
# Initializes the Agent
|
8
8
|
#
|
9
|
-
# @param llm [
|
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:,
|
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
|
-
@
|
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 =
|
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
|
data/lib/langchain/llm/base.rb
CHANGED
@@ -2,17 +2,9 @@
|
|
2
2
|
|
3
3
|
module Langchain::LLM
|
4
4
|
class Base
|
5
|
-
|
5
|
+
include Langchain::DependencyHelper
|
6
6
|
|
7
|
-
|
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
|
data/lib/langchain/llm/cohere.rb
CHANGED
@@ -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 = {
|