langchainrb 0.3.2 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +57 -10
- data/README.md +26 -5
- data/Rakefile +5 -0
- data/examples/create_and_manage_few_shot_prompt_templates.rb +3 -3
- data/examples/store_and_query_with_pinecone.rb +5 -2
- data/examples/store_and_query_with_qdrant.rb +4 -2
- data/examples/store_and_query_with_weaviate.rb +4 -1
- data/lib/agent/chain_of_thought_agent/chain_of_thought_agent.rb +13 -14
- data/lib/dependency_helper.rb +18 -0
- data/lib/langchain.rb +3 -0
- data/lib/llm/base.rb +3 -3
- data/lib/llm/cohere.rb +5 -5
- data/lib/llm/hugging_face.rb +32 -0
- data/lib/llm/openai.rb +4 -4
- data/lib/logging.rb +13 -0
- data/lib/prompt/base.rb +3 -4
- data/lib/prompt/loading.rb +3 -3
- data/lib/tool/base.rb +20 -8
- data/lib/tool/calculator.rb +11 -5
- data/lib/tool/serp_api.rb +25 -13
- data/lib/tool/wikipedia.rb +15 -8
- data/lib/vectorsearch/base.rb +5 -3
- data/lib/vectorsearch/milvus.rb +9 -24
- data/lib/vectorsearch/pinecone.rb +10 -20
- data/lib/vectorsearch/qdrant.rb +8 -15
- data/lib/vectorsearch/weaviate.rb +11 -26
- data/lib/version.rb +1 -1
- metadata +46 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5a782dd2282ab5dd4aed3f1d0e421a4f9b227fa4c5450ed27f2f98a86af74f4
|
4
|
+
data.tar.gz: b47bb5d6789d7abb81f56ee1beb0b52323184f578475aec3e92fcc19b4a1314a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e918b49b2b04a0e7009db732a5b24ab080a0d8d6b4c3be4084aa3a1492ddd6c1627d467b8bff4b34e1e5160b71622a75772ef92658dce2589b9542b8b0a8137
|
7
|
+
data.tar.gz: 3b465f1a05e614d64d416582aeaf7d998bf51422705331b220e08b00774d4beed4dcbc4e2a71b81bad87cddfe8c8f4c8db421d650e7161f16b26a1259922f71c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.4] - 2023-05-16
|
4
|
+
- LLMs
|
5
|
+
- Introducing support for HuggingFace
|
6
|
+
|
7
|
+
## [0.3.3] - 2023-05-16
|
8
|
+
- Dependencies are now optionally loaded and required at runtime
|
9
|
+
- Start using `standardrb` for linting
|
10
|
+
- Use the Ruby logger
|
11
|
+
|
3
12
|
## [0.3.2] - 2023-05-15
|
4
13
|
- Agents
|
5
14
|
- Fix Chain of Thought prompt loader
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,16 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
langchainrb (0.3.
|
5
|
-
cohere-ruby (~> 0.9.3)
|
6
|
-
eqn (~> 1.6.5)
|
7
|
-
google_search_results (~> 2.0.0)
|
8
|
-
milvus (~> 0.9.0)
|
9
|
-
pinecone (~> 0.1.6)
|
10
|
-
qdrant-ruby (~> 0.9.0)
|
11
|
-
ruby-openai (~> 4.0.0)
|
12
|
-
weaviate-ruby (~> 0.8.0)
|
13
|
-
wikipedia-client (~> 1.17.0)
|
4
|
+
langchainrb (0.3.4)
|
14
5
|
|
15
6
|
GEM
|
16
7
|
remote: https://rubygems.org/
|
@@ -35,6 +26,7 @@ GEM
|
|
35
26
|
tzinfo (~> 2.0)
|
36
27
|
addressable (2.8.4)
|
37
28
|
public_suffix (>= 2.0.2, < 6.0)
|
29
|
+
ast (2.4.2)
|
38
30
|
builder (3.2.4)
|
39
31
|
byebug (11.1.3)
|
40
32
|
coderay (1.1.3)
|
@@ -125,9 +117,14 @@ GEM
|
|
125
117
|
httparty (0.21.0)
|
126
118
|
mini_mime (>= 1.0.0)
|
127
119
|
multi_xml (>= 0.5.2)
|
120
|
+
hugging-face (0.3.2)
|
121
|
+
faraday (~> 1.0)
|
128
122
|
i18n (1.13.0)
|
129
123
|
concurrent-ruby (~> 1.0)
|
130
124
|
ice_nine (0.11.2)
|
125
|
+
json (2.6.3)
|
126
|
+
language_server-protocol (3.17.0.3)
|
127
|
+
lint_roller (1.0.0)
|
131
128
|
loofah (2.21.1)
|
132
129
|
crass (~> 1.0.2)
|
133
130
|
nokogiri (>= 1.5.9)
|
@@ -138,10 +135,15 @@ GEM
|
|
138
135
|
minitest (5.18.0)
|
139
136
|
multi_xml (0.6.0)
|
140
137
|
multipart-post (2.3.0)
|
138
|
+
nokogiri (1.14.3-arm64-darwin)
|
139
|
+
racc (~> 1.4)
|
141
140
|
nokogiri (1.14.3-x86_64-darwin)
|
142
141
|
racc (~> 1.4)
|
143
142
|
nokogiri (1.14.3-x86_64-linux)
|
144
143
|
racc (~> 1.4)
|
144
|
+
parallel (1.23.0)
|
145
|
+
parser (3.2.2.1)
|
146
|
+
ast (~> 2.4.1)
|
145
147
|
pinecone (0.1.71)
|
146
148
|
dry-struct (~> 1.6.0)
|
147
149
|
dry-validation (~> 1.10.0)
|
@@ -173,7 +175,10 @@ GEM
|
|
173
175
|
rake (>= 12.2)
|
174
176
|
thor (~> 1.0)
|
175
177
|
zeitwerk (~> 2.5)
|
178
|
+
rainbow (3.1.1)
|
176
179
|
rake (13.0.6)
|
180
|
+
regexp_parser (2.8.0)
|
181
|
+
rexml (3.2.5)
|
177
182
|
rspec (3.12.0)
|
178
183
|
rspec-core (~> 3.12.0)
|
179
184
|
rspec-expectations (~> 3.12.0)
|
@@ -187,15 +192,45 @@ GEM
|
|
187
192
|
diff-lcs (>= 1.2.0, < 2.0)
|
188
193
|
rspec-support (~> 3.12.0)
|
189
194
|
rspec-support (3.12.0)
|
195
|
+
rubocop (1.50.2)
|
196
|
+
json (~> 2.3)
|
197
|
+
parallel (~> 1.10)
|
198
|
+
parser (>= 3.2.0.0)
|
199
|
+
rainbow (>= 2.2.2, < 4.0)
|
200
|
+
regexp_parser (>= 1.8, < 3.0)
|
201
|
+
rexml (>= 3.2.5, < 4.0)
|
202
|
+
rubocop-ast (>= 1.28.0, < 2.0)
|
203
|
+
ruby-progressbar (~> 1.7)
|
204
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
205
|
+
rubocop-ast (1.28.1)
|
206
|
+
parser (>= 3.2.1.0)
|
207
|
+
rubocop-performance (1.16.0)
|
208
|
+
rubocop (>= 1.7.0, < 2.0)
|
209
|
+
rubocop-ast (>= 0.4.0)
|
190
210
|
ruby-openai (4.0.0)
|
191
211
|
faraday (>= 1)
|
192
212
|
faraday-multipart (>= 1)
|
213
|
+
ruby-progressbar (1.13.0)
|
193
214
|
ruby2_keywords (0.0.5)
|
215
|
+
standard (1.28.2)
|
216
|
+
language_server-protocol (~> 3.17.0.2)
|
217
|
+
lint_roller (~> 1.0)
|
218
|
+
rubocop (~> 1.50.2)
|
219
|
+
standard-custom (~> 1.0.0)
|
220
|
+
standard-performance (~> 1.0.1)
|
221
|
+
standard-custom (1.0.0)
|
222
|
+
lint_roller (~> 1.0)
|
223
|
+
standard-performance (1.0.1)
|
224
|
+
lint_roller (~> 1.0)
|
225
|
+
rubocop-performance (~> 1.16.0)
|
226
|
+
standardrb (1.0.1)
|
227
|
+
standard
|
194
228
|
thor (1.2.1)
|
195
229
|
treetop (1.6.12)
|
196
230
|
polyglot (~> 0.3)
|
197
231
|
tzinfo (2.0.6)
|
198
232
|
concurrent-ruby (~> 1.0)
|
233
|
+
unicode-display_width (2.4.2)
|
199
234
|
weaviate-ruby (0.8.1)
|
200
235
|
faraday (~> 1)
|
201
236
|
faraday_middleware (~> 1)
|
@@ -205,15 +240,27 @@ GEM
|
|
205
240
|
zeitwerk (2.6.8)
|
206
241
|
|
207
242
|
PLATFORMS
|
243
|
+
arm64-darwin-22
|
208
244
|
x86_64-darwin-19
|
209
245
|
x86_64-linux
|
210
246
|
|
211
247
|
DEPENDENCIES
|
248
|
+
cohere-ruby (~> 0.9.3)
|
212
249
|
dotenv-rails (~> 2.7.6)
|
250
|
+
eqn (~> 1.6.5)
|
251
|
+
google_search_results (~> 2.0.0)
|
252
|
+
hugging-face (~> 0.3.2)
|
213
253
|
langchainrb!
|
254
|
+
milvus (~> 0.9.0)
|
255
|
+
pinecone (~> 0.1.6)
|
214
256
|
pry-byebug (~> 3.10.0)
|
257
|
+
qdrant-ruby (~> 0.9.0)
|
215
258
|
rake (~> 13.0)
|
216
259
|
rspec (~> 3.0)
|
260
|
+
ruby-openai (~> 4.0.0)
|
261
|
+
standardrb
|
262
|
+
weaviate-ruby (~> 0.8.0)
|
263
|
+
wikipedia-client (~> 1.17.0)
|
217
264
|
|
218
265
|
BUNDLED WITH
|
219
266
|
2.4.0
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
🦜️🔗 LangChain.rb
|
2
|
-
---
|
2
|
+
---
|
3
3
|
⚡ Building applications with LLMs through composability ⚡
|
4
4
|
|
5
5
|
👨💻👩💻 CURRENTLY SEEKING PEOPLE TO FORM THE CORE GROUP OF MAINTAINERS WITH
|
@@ -39,6 +39,8 @@ require "langchain"
|
|
39
39
|
|
40
40
|
Choose the LLM provider you'll be using (OpenAI or Cohere) and retrieve the API key.
|
41
41
|
|
42
|
+
Add `gem "weaviate-ruby", "~> 0.8.0"` to your Gemfile.
|
43
|
+
|
42
44
|
Pick the vector search database you'll be using and instantiate the client:
|
43
45
|
```ruby
|
44
46
|
client = Vectorsearch::Weaviate.new(
|
@@ -49,9 +51,9 @@ client = Vectorsearch::Weaviate.new(
|
|
49
51
|
)
|
50
52
|
|
51
53
|
# You can instantiate any other supported vector search database:
|
52
|
-
client = Vectorsearch::Milvus.new(...)
|
53
|
-
client = Vectorsearch::Qdrant.new(...)
|
54
|
-
client = Vectorsearch::Pinecone.new(...)
|
54
|
+
client = Vectorsearch::Milvus.new(...) # `gem "milvus", "~> 0.9.0"`
|
55
|
+
client = Vectorsearch::Qdrant.new(...) # `gem"qdrant-ruby", "~> 0.9.0"`
|
56
|
+
client = Vectorsearch::Pinecone.new(...) # `gem "pinecone", "~> 0.1.6"`
|
55
57
|
```
|
56
58
|
|
57
59
|
```ruby
|
@@ -92,6 +94,8 @@ client.ask(
|
|
92
94
|
|
93
95
|
### Using Standalone LLMs 🗣️
|
94
96
|
|
97
|
+
Add `gem "ruby-openai", "~> 4.0.0"` to your Gemfile.
|
98
|
+
|
95
99
|
#### OpenAI
|
96
100
|
```ruby
|
97
101
|
openai = LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
|
@@ -104,6 +108,8 @@ openai.complete(prompt: "What is the meaning of life?")
|
|
104
108
|
```
|
105
109
|
|
106
110
|
#### Cohere
|
111
|
+
Add `gem "cohere-ruby", "~> 0.9.3"` to your Gemfile.
|
112
|
+
|
107
113
|
```ruby
|
108
114
|
cohere = LLM::Cohere.new(api_key: ENV["COHERE_API_KEY"])
|
109
115
|
```
|
@@ -114,6 +120,9 @@ cohere.embed(text: "foo bar")
|
|
114
120
|
cohere.complete(prompt: "What is the meaning of life?")
|
115
121
|
```
|
116
122
|
|
123
|
+
#### HuggingFace
|
124
|
+
Add `gem "hugging-face", "~> 0.3.2"` to your Gemfile.
|
125
|
+
|
117
126
|
### Using Prompts 📋
|
118
127
|
|
119
128
|
#### Prompt Templates
|
@@ -204,6 +213,8 @@ Agents are semi-autonomous bots that can respond to user questions and use avail
|
|
204
213
|
|
205
214
|
#### Chain-of-Thought Agent
|
206
215
|
|
216
|
+
Add `gem "openai-ruby"`, `gem "eqn"`, and `gem "google_search_results"` to your Gemfile
|
217
|
+
|
207
218
|
```ruby
|
208
219
|
agent = Agent::ChainOfThoughtAgent.new(llm: :openai, llm_api_key: ENV["OPENAI_API_KEY"], tools: ['search', 'calculator'])
|
209
220
|
|
@@ -211,7 +222,7 @@ agent.tools
|
|
211
222
|
# => ["search", "calculator"]
|
212
223
|
```
|
213
224
|
```ruby
|
214
|
-
agent.run(question: "How many full soccer fields would be needed to cover the distance between NYC and DC in a straight line?"
|
225
|
+
agent.run(question: "How many full soccer fields would be needed to cover the distance between NYC and DC in a straight line?")
|
215
226
|
#=> "Approximately 2,945 soccer fields would be needed to cover the distance between NYC and DC in a straight line."
|
216
227
|
```
|
217
228
|
|
@@ -228,6 +239,16 @@ agent.run(question: "How many full soccer fields would be needed to cover the di
|
|
228
239
|
| "search" | A wrapper around Google Search | `ENV["SERPAPI_API_KEY"]` (https://serpapi.com/manage-api-key)
|
229
240
|
| "wikipedia" | Calls Wikipedia API to retrieve the summary | |
|
230
241
|
|
242
|
+
|
243
|
+
## Logging
|
244
|
+
|
245
|
+
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.
|
246
|
+
To show all log messages:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
Langchain.logger.level = :info
|
250
|
+
```
|
251
|
+
|
231
252
|
## Development
|
232
253
|
|
233
254
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
@@ -9,10 +9,10 @@ prompt = Prompt::FewShotPromptTemplate.new(
|
|
9
9
|
template: "Input: {input}\nOutput: {output}"
|
10
10
|
),
|
11
11
|
examples: [
|
12
|
-
{
|
13
|
-
{
|
12
|
+
{input: "happy", output: "sad"},
|
13
|
+
{input: "tall", output: "short"}
|
14
14
|
],
|
15
|
-
|
15
|
+
input_variables: ["adjective"]
|
16
16
|
)
|
17
17
|
|
18
18
|
prompt.format(adjective: "good")
|
@@ -1,5 +1,8 @@
|
|
1
1
|
require "langchain"
|
2
2
|
|
3
|
+
# gem install pinecone
|
4
|
+
# or add `gem "pinecone"` to your Gemfile
|
5
|
+
|
3
6
|
# Instantiate the Qdrant client
|
4
7
|
pinecone = Vectorsearch::Pinecone.new(
|
5
8
|
environment: ENV["PINECONE_ENVIRONMENT"],
|
@@ -35,9 +38,9 @@ pinecone.ask(
|
|
35
38
|
)
|
36
39
|
|
37
40
|
# Generate your an embedding and search by it
|
38
|
-
openai = LLM::OpenAI.new(api_key: ENV[
|
41
|
+
openai = LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
|
39
42
|
embedding = openai.embed(text: "veggie")
|
40
43
|
|
41
44
|
pinecone.similarity_search_by_vector(
|
42
45
|
embedding: embedding
|
43
|
-
)
|
46
|
+
)
|
@@ -1,5 +1,8 @@
|
|
1
1
|
require "langchain"
|
2
2
|
|
3
|
+
# gem install qdrant-ruby
|
4
|
+
# or add `gem "qdrant-ruby"` to your Gemfile
|
5
|
+
|
3
6
|
# Instantiate the Qdrant client
|
4
7
|
qdrant = Vectorsearch::Qdrant.new(
|
5
8
|
url: ENV["QDRANT_URL"],
|
@@ -9,7 +12,6 @@ qdrant = Vectorsearch::Qdrant.new(
|
|
9
12
|
llm_api_key: ENV["COHERE_API_KEY"]
|
10
13
|
)
|
11
14
|
|
12
|
-
|
13
15
|
# Create the default schema.
|
14
16
|
qdrant.create_default_schema
|
15
17
|
|
@@ -33,4 +35,4 @@ qdrant.similarity_search(
|
|
33
35
|
# Interact with your index through Q&A
|
34
36
|
qdrant.ask(
|
35
37
|
question: "What is the best recipe for chicken?"
|
36
|
-
)
|
38
|
+
)
|
@@ -1,5 +1,8 @@
|
|
1
1
|
require "langchain"
|
2
2
|
|
3
|
+
# gem install weaviate-ruby
|
4
|
+
# or add `gem "weaviate-ruby"` to your Gemfile
|
5
|
+
|
3
6
|
# Instantiate the Weaviate client
|
4
7
|
weaviate = Vectorsearch::Weaviate.new(
|
5
8
|
url: ENV["WEAVIATE_URL"],
|
@@ -27,4 +30,4 @@ weaviate.add_texts(
|
|
27
30
|
weaviate.similarity_search(
|
28
31
|
query: "chicken",
|
29
32
|
k: 1
|
30
|
-
)
|
33
|
+
)
|
@@ -5,7 +5,7 @@ module Agent
|
|
5
5
|
attr_reader :llm, :llm_api_key, :llm_client, :tools
|
6
6
|
|
7
7
|
# Initializes the Agent
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# @param llm [Symbol] The LLM to use
|
10
10
|
# @param llm_api_key [String] The API key for the LLM
|
11
11
|
# @param tools [Array] The tools to use
|
@@ -22,7 +22,7 @@ module Agent
|
|
22
22
|
end
|
23
23
|
|
24
24
|
# Validate tools when they're re-assigned
|
25
|
-
#
|
25
|
+
#
|
26
26
|
# @param value [Array] The tools to use
|
27
27
|
# @return [Array] The tools that will be used
|
28
28
|
def tools=(value)
|
@@ -31,11 +31,10 @@ module Agent
|
|
31
31
|
end
|
32
32
|
|
33
33
|
# Run the Agent!
|
34
|
-
#
|
34
|
+
#
|
35
35
|
# @param question [String] The question to ask
|
36
|
-
# @param logging [Boolean] Whether or not to log the Agent's actions
|
37
36
|
# @return [String] The answer to the question
|
38
|
-
def run(question
|
37
|
+
def run(question:)
|
39
38
|
question = question.strip
|
40
39
|
prompt = create_prompt(
|
41
40
|
question: question,
|
@@ -43,7 +42,7 @@ module Agent
|
|
43
42
|
)
|
44
43
|
|
45
44
|
loop do
|
46
|
-
|
45
|
+
Langchain.logger.info("Agent: Passing the prompt to the #{llm} LLM")
|
47
46
|
response = llm_client.generate_completion(
|
48
47
|
prompt: prompt,
|
49
48
|
stop_sequences: ["Observation:"],
|
@@ -51,16 +50,16 @@ module Agent
|
|
51
50
|
)
|
52
51
|
|
53
52
|
# Append the response to the prompt
|
54
|
-
prompt += response
|
55
|
-
|
53
|
+
prompt += response
|
54
|
+
|
56
55
|
# Find the requested action in the "Action: search" format
|
57
56
|
action = response.match(/Action: (.*)/)&.send(:[], -1)
|
58
|
-
|
57
|
+
|
59
58
|
if action
|
60
59
|
# Find the input to the action in the "Action Input: [action_input]" format
|
61
60
|
action_input = response.match(/Action Input: "?(.*)"?/)&.send(:[], -1)
|
62
61
|
|
63
|
-
|
62
|
+
Langchain.logger.info("Agent: Using the \"#{action}\" Tool with \"#{action_input}\"")
|
64
63
|
|
65
64
|
# Retrieve the Tool::[ToolName] class and call `execute`` with action_input as the input
|
66
65
|
result = Tool
|
@@ -68,10 +67,10 @@ module Agent
|
|
68
67
|
.execute(input: action_input)
|
69
68
|
|
70
69
|
# Append the Observation to the prompt
|
71
|
-
if prompt.end_with?("Observation:")
|
72
|
-
|
70
|
+
prompt += if prompt.end_with?("Observation:")
|
71
|
+
" #{result}\nThought:"
|
73
72
|
else
|
74
|
-
|
73
|
+
"\nObservation: #{result}\nThought:"
|
75
74
|
end
|
76
75
|
else
|
77
76
|
# Return the final answer
|
@@ -92,7 +91,7 @@ module Agent
|
|
92
91
|
question: question,
|
93
92
|
tool_names: "[#{tools.join(", ")}]",
|
94
93
|
tools: tools.map do |tool|
|
95
|
-
"#{tool}: #{Tool.const_get(Tool::Base::TOOLS[tool]).const_get(
|
94
|
+
"#{tool}: #{Tool.const_get(Tool::Base::TOOLS[tool]).const_get(:DESCRIPTION)}"
|
96
95
|
end.join("\n")
|
97
96
|
)
|
98
97
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
def depends_on(gem_name)
|
4
|
+
gem(gem_name) # require the gem
|
5
|
+
|
6
|
+
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
|
7
|
+
|
8
|
+
gem_version = Gem.loaded_specs[gem_name].version
|
9
|
+
gem_requirement = Bundler.load.dependencies.find { |g| g.name == gem_name }.requirement
|
10
|
+
|
11
|
+
if !gem_requirement.satisfied_by?(gem_version)
|
12
|
+
raise "The #{gem_name} gem is installed, but version #{gem_requirement} is required. You have #{gem_version}."
|
13
|
+
end
|
14
|
+
|
15
|
+
true
|
16
|
+
rescue LoadError
|
17
|
+
raise LoadError, "Could not load #{gem_name}. Please ensure that the #{gem_name} gem is installed."
|
18
|
+
end
|
data/lib/langchain.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "./version"
|
4
|
+
require_relative "./dependency_helper"
|
5
|
+
require_relative "./logging"
|
4
6
|
|
5
7
|
module Agent
|
6
8
|
autoload :Base, "agent/base"
|
@@ -18,6 +20,7 @@ end
|
|
18
20
|
module LLM
|
19
21
|
autoload :Base, "llm/base"
|
20
22
|
autoload :Cohere, "llm/cohere"
|
23
|
+
autoload :HuggingFace, "llm/hugging_face"
|
21
24
|
autoload :OpenAI, "llm/openai"
|
22
25
|
end
|
23
26
|
|
data/lib/llm/base.rb
CHANGED
@@ -12,16 +12,16 @@ module LLM
|
|
12
12
|
}.freeze
|
13
13
|
|
14
14
|
def default_dimension
|
15
|
-
self.class.const_get(
|
15
|
+
self.class.const_get(:DEFAULTS).dig(:dimension)
|
16
16
|
end
|
17
17
|
|
18
18
|
# Ensure that the LLM value passed in is supported
|
19
19
|
# @param llm [Symbol] The LLM to use
|
20
20
|
def self.validate_llm!(llm:)
|
21
21
|
# TODO: Fix so this works when `llm` value is a string instead of a symbol
|
22
|
-
unless LLM::Base::LLMS.
|
22
|
+
unless LLM::Base::LLMS.key?(llm)
|
23
23
|
raise ArgumentError, "LLM must be one of #{LLM::Base::LLMS.keys}"
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
27
|
-
end
|
27
|
+
end
|
data/lib/llm/cohere.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "cohere"
|
4
|
-
|
5
3
|
module LLM
|
6
4
|
class Cohere < Base
|
7
|
-
|
8
5
|
DEFAULTS = {
|
9
6
|
temperature: 0.0,
|
10
7
|
completion_model_name: "base",
|
@@ -13,6 +10,9 @@ module LLM
|
|
13
10
|
}.freeze
|
14
11
|
|
15
12
|
def initialize(api_key:)
|
13
|
+
depends_on "cohere-ruby"
|
14
|
+
require "cohere"
|
15
|
+
|
16
16
|
@client = ::Cohere::Client.new(api_key: api_key)
|
17
17
|
end
|
18
18
|
|
@@ -22,7 +22,7 @@ module LLM
|
|
22
22
|
def embed(text:)
|
23
23
|
response = client.embed(
|
24
24
|
texts: [text],
|
25
|
-
model: DEFAULTS[:embeddings_model_name]
|
25
|
+
model: DEFAULTS[:embeddings_model_name]
|
26
26
|
)
|
27
27
|
response.dig("embeddings").first
|
28
28
|
end
|
@@ -50,4 +50,4 @@ module LLM
|
|
50
50
|
alias_method :generate_completion, :complete
|
51
51
|
alias_method :generate_embedding, :embed
|
52
52
|
end
|
53
|
-
end
|
53
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM
|
4
|
+
class HuggingFace < Base
|
5
|
+
# The gem does not currently accept other models:
|
6
|
+
# https://github.com/alchaplinsky/hugging-face/blob/main/lib/hugging_face/inference_api.rb#L32-L34
|
7
|
+
DEFAULTS = {
|
8
|
+
embeddings_model_name: "sentence-transformers/all-MiniLM-L6-v2"
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
#
|
12
|
+
# Intialize the HuggingFace LLM
|
13
|
+
# @param api_key [String] The API key to use
|
14
|
+
#
|
15
|
+
def initialize(api_key:)
|
16
|
+
depends_on "hugging-face"
|
17
|
+
require "hugging_face"
|
18
|
+
|
19
|
+
@client = ::HuggingFace::InferenceApi.new(api_token: api_key)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Generate an embedding for a given text
|
23
|
+
# @param text [String] The text to embed
|
24
|
+
# @return [Array] The embedding
|
25
|
+
def embed(text:)
|
26
|
+
response = client.embedding(
|
27
|
+
input: text,
|
28
|
+
model: DEFAULTS[:embeddings_model_name]
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/llm/openai.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "openai"
|
4
|
-
|
5
3
|
module LLM
|
6
4
|
class OpenAI < Base
|
7
|
-
|
8
5
|
DEFAULTS = {
|
9
6
|
temperature: 0.0,
|
10
7
|
completion_model_name: "text-davinci-003",
|
@@ -13,6 +10,9 @@ module LLM
|
|
13
10
|
}.freeze
|
14
11
|
|
15
12
|
def initialize(api_key:)
|
13
|
+
depends_on "ruby-openai"
|
14
|
+
require "openai"
|
15
|
+
|
16
16
|
# TODO: Add support to pass `organization_id:`
|
17
17
|
@client = ::OpenAI::Client.new(access_token: api_key)
|
18
18
|
end
|
@@ -53,4 +53,4 @@ module LLM
|
|
53
53
|
alias_method :generate_completion, :complete
|
54
54
|
alias_method :generate_embedding, :embed
|
55
55
|
end
|
56
|
-
end
|
56
|
+
end
|
data/lib/logging.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
|
5
|
+
module Langchain
|
6
|
+
def self.logger
|
7
|
+
@@logger ||= Logger.new($stdout, level: :warn, formatter: ->(severity, datetime, progname, msg) { "[LangChain.rb] #{msg}\n" })
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.logger=(instance)
|
11
|
+
@@logger = instance
|
12
|
+
end
|
13
|
+
end
|
data/lib/prompt/base.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "strscan"
|
4
|
+
require "json"
|
4
5
|
|
5
6
|
module Prompt
|
6
7
|
class Base
|
@@ -52,14 +53,12 @@ module Prompt
|
|
52
53
|
FileUtils.mkdir_p(directory_path) unless directory_path.directory?
|
53
54
|
|
54
55
|
if save_path.extname == ".json"
|
55
|
-
File.
|
56
|
+
File.write(file_path, to_h.to_json)
|
56
57
|
else
|
57
58
|
raise ArgumentError, "#{file_path} must be json"
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
61
|
-
private
|
62
|
-
|
63
62
|
#
|
64
63
|
# Extracts variables from a template string.
|
65
64
|
#
|
data/lib/prompt/loading.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "strscan"
|
4
|
+
require "pathname"
|
5
5
|
|
6
6
|
module Prompt
|
7
7
|
TYPE_TO_LOADER = {
|
@@ -70,7 +70,7 @@ module Prompt
|
|
70
70
|
def load_from_config(config)
|
71
71
|
# If `_type` key is not present in the configuration hash, add it with a default value of `prompt`
|
72
72
|
unless config.key?("_type")
|
73
|
-
|
73
|
+
Langchain.logger.warn "No `_type` key found, defaulting to `prompt`"
|
74
74
|
config["_type"] = "prompt"
|
75
75
|
end
|
76
76
|
|