langchainrb 0.6.3 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.env.example +4 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +3 -1
- data/README.md +100 -0
- data/examples/conversation_with_openai.rb +52 -0
- data/examples/create_and_manage_prompt_templates_using_structured_output_parser.rb +13 -1
- data/examples/llama_cpp.rb +24 -0
- data/lib/langchain/conversation.rb +28 -36
- data/lib/langchain/conversation_memory.rb +88 -0
- data/lib/langchain/llm/base.rb +3 -0
- data/lib/langchain/llm/cohere.rb +1 -1
- data/lib/langchain/llm/llama_cpp.rb +102 -0
- data/lib/langchain/llm/openai.rb +1 -1
- data/lib/langchain/loader.rb +31 -8
- data/lib/langchain/output_parsers/fix.rb +84 -0
- data/lib/langchain/output_parsers/prompts/naive_fix_prompt.yaml +22 -0
- data/lib/langchain/utils/token_length/google_palm_validator.rb +3 -0
- data/lib/langchain/vectorsearch/base.rb +5 -0
- data/lib/langchain/vectorsearch/chroma.rb +11 -4
- data/lib/langchain/vectorsearch/hnswlib.rb +1 -2
- data/lib/langchain/vectorsearch/milvus.rb +11 -1
- data/lib/langchain/vectorsearch/pinecone.rb +6 -0
- data/lib/langchain/vectorsearch/qdrant.rb +10 -3
- data/lib/langchain/vectorsearch/weaviate.rb +6 -0
- data/lib/langchain/version.rb +1 -1
- data/lib/langchain.rb +3 -0
- metadata +22 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3404535e036c3efe68fd12706d2ebb269caed87b562fc38434122b1be01a356d
|
4
|
+
data.tar.gz: e3be77b32cf754235e8895fb1af60edca54cb5acb84278bfa2e39b6ed7c2abbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3fae04c73176c758c2d2d32c3ac538f3e094eb10f378b9a8befbbdcc62b60e55941a1bfefcb61eac7daca43ef91d0e57306dbc26bd59afbdad6ab4efff2ba89
|
7
|
+
data.tar.gz: 626bb4a226112ee6fe709077a6d49ba91c0483fee657848153e9cff61693183709aede5844237c24cc02c561f59be82ea1fd296fe1c3f4ee4d971494ee4dcd75
|
data/.env.example
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
AI21_API_KEY=
|
2
2
|
CHROMA_URL=
|
3
3
|
COHERE_API_KEY=
|
4
|
+
GOOGLE_PALM_API_KEY=
|
4
5
|
HUGGING_FACE_API_KEY=
|
6
|
+
LLAMACPP_MODEL_PATH=
|
7
|
+
LLAMACPP_N_THREADS=
|
8
|
+
LLAMACPP_N_GPU_LAYERS=
|
5
9
|
MILVUS_URL=
|
6
10
|
OPENAI_API_KEY=
|
7
|
-
GOOGLE_PALM_API_KEY=
|
8
11
|
OPEN_WEATHER_API_KEY=
|
9
12
|
PINECONE_API_KEY=
|
10
13
|
PINECONE_ENVIRONMENT=
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.6.4] - 2023-07-01
|
4
|
+
- Fix `Langchain::Vectorsearch::Qdrant#add_texts()`
|
5
|
+
- Introduce `ConversationMemory`
|
6
|
+
- Allow loading multiple files from a directory
|
7
|
+
- Add `get_default_schema()`, `create_default_schema()`, `destroy_default_schema()` missing methods to `Langchain::Vectorsearch::*` classes
|
8
|
+
|
3
9
|
## [0.6.3] - 2023-06-25
|
4
10
|
- Add #destroy_default_schema() to Langchain::Vectorsearch::* classes
|
5
11
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
langchainrb (0.6.
|
4
|
+
langchainrb (0.6.5)
|
5
5
|
baran (~> 0.1.6)
|
6
6
|
colorize (~> 0.8.1)
|
7
7
|
json-schema (~> 4.0.0)
|
@@ -153,6 +153,7 @@ GEM
|
|
153
153
|
addressable (>= 2.8)
|
154
154
|
language_server-protocol (3.17.0.3)
|
155
155
|
lint_roller (1.0.0)
|
156
|
+
llama_cpp (0.3.0)
|
156
157
|
loofah (2.21.1)
|
157
158
|
crass (~> 1.0.2)
|
158
159
|
nokogiri (>= 1.5.9)
|
@@ -327,6 +328,7 @@ DEPENDENCIES
|
|
327
328
|
hnswlib (~> 0.8.1)
|
328
329
|
hugging-face (~> 0.3.4)
|
329
330
|
langchainrb!
|
331
|
+
llama_cpp
|
330
332
|
milvus (~> 0.9.0)
|
331
333
|
nokogiri (~> 1.13)
|
332
334
|
open-weather-ruby-client (~> 0.3.0)
|
data/README.md
CHANGED
@@ -274,6 +274,106 @@ prompt = Langchain::Prompt.load_from_path(file_path: "spec/fixtures/prompt/promp
|
|
274
274
|
prompt.input_variables #=> ["adjective", "content"]
|
275
275
|
```
|
276
276
|
|
277
|
+
### Using Output Parsers
|
278
|
+
|
279
|
+
Parse LLM text responses into structured output, such as JSON.
|
280
|
+
|
281
|
+
#### Structured Output Parser
|
282
|
+
|
283
|
+
You can use the `StructuredOutputParser` to generate a prompt that instructs the LLM to provide a JSON response adhering to a specific JSON schema:
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
json_schema = {
|
287
|
+
type: "object",
|
288
|
+
properties: {
|
289
|
+
name: {
|
290
|
+
type: "string",
|
291
|
+
description: "Persons name"
|
292
|
+
},
|
293
|
+
age: {
|
294
|
+
type: "number",
|
295
|
+
description: "Persons age"
|
296
|
+
},
|
297
|
+
interests: {
|
298
|
+
type: "array",
|
299
|
+
items: {
|
300
|
+
type: "object",
|
301
|
+
properties: {
|
302
|
+
interest: {
|
303
|
+
type: "string",
|
304
|
+
description: "A topic of interest"
|
305
|
+
},
|
306
|
+
levelOfInterest: {
|
307
|
+
type: "number",
|
308
|
+
description: "A value between 0 and 100 of how interested the person is in this interest"
|
309
|
+
}
|
310
|
+
},
|
311
|
+
required: ["interest", "levelOfInterest"],
|
312
|
+
additionalProperties: false
|
313
|
+
},
|
314
|
+
minItems: 1,
|
315
|
+
maxItems: 3,
|
316
|
+
description: "A list of the person's interests"
|
317
|
+
}
|
318
|
+
},
|
319
|
+
required: ["name", "age", "interests"],
|
320
|
+
additionalProperties: false
|
321
|
+
}
|
322
|
+
parser = Langchain::OutputParsers::StructuredOutputParser.from_json_schema(json_schema)
|
323
|
+
prompt = Langchain::Prompt::PromptTemplate.new(template: "Generate details of a fictional character.\n{format_instructions}\nCharacter description: {description}", input_variables: ["description", "format_instructions"])
|
324
|
+
prompt_text = prompt.format(description: "Korean chemistry student", format_instructions: parser.get_format_instructions)
|
325
|
+
# Generate details of a fictional character.
|
326
|
+
# You must format your output as a JSON value that adheres to a given "JSON Schema" instance.
|
327
|
+
# ...
|
328
|
+
```
|
329
|
+
|
330
|
+
Then parse the llm response:
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
|
334
|
+
llm_response = llm.chat(prompt: prompt_text)
|
335
|
+
parser.parse(llm_response)
|
336
|
+
# {
|
337
|
+
# "name" => "Kim Ji-hyun",
|
338
|
+
# "age" => 22,
|
339
|
+
# "interests" => [
|
340
|
+
# {
|
341
|
+
# "interest" => "Organic Chemistry",
|
342
|
+
# "levelOfInterest" => 85
|
343
|
+
# },
|
344
|
+
# ...
|
345
|
+
# ]
|
346
|
+
# }
|
347
|
+
```
|
348
|
+
|
349
|
+
If the parser fails to parse the LLM response, you can use the `OutputFixingParser`. It sends an error message, prior output, and the original prompt text to the LLM, asking for a "fixed" response:
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
begin
|
353
|
+
parser.parse(llm_response)
|
354
|
+
rescue Langchain::OutputParsers::OutputParserException => e
|
355
|
+
fix_parser = Langchain::OutputParsers::OutputFixingParser.from_llm(
|
356
|
+
llm: llm,
|
357
|
+
parser: parser
|
358
|
+
)
|
359
|
+
fix_parser.parse(llm_response)
|
360
|
+
end
|
361
|
+
```
|
362
|
+
|
363
|
+
Alternatively, if you don't need to handle the `OutputParserException`, you can simplify the code:
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
# we already have the `OutputFixingParser`:
|
367
|
+
# parser = Langchain::OutputParsers::StructuredOutputParser.from_json_schema(json_schema)
|
368
|
+
fix_parser = Langchain::OutputParsers::OutputFixingParser.from_llm(
|
369
|
+
llm: llm,
|
370
|
+
parser: parser
|
371
|
+
)
|
372
|
+
fix_parser.parse(llm_response)
|
373
|
+
```
|
374
|
+
|
375
|
+
See [here](https://github.com/andreibondarev/langchainrb/tree/main/examples/create_and_manage_prompt_templates_using_structured_output_parser.rb) for a concrete example
|
376
|
+
|
277
377
|
### Using Agents 🤖
|
278
378
|
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.
|
279
379
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "langchain"
|
2
|
+
require "reline"
|
3
|
+
|
4
|
+
# gem install reline
|
5
|
+
# or add `gem "reline"` to your Gemfile
|
6
|
+
|
7
|
+
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
|
8
|
+
|
9
|
+
chat = Langchain::Conversation.new(llm: openai)
|
10
|
+
chat.set_context("You are a chatbot from the future")
|
11
|
+
|
12
|
+
DONE = %w[done end eof exit].freeze
|
13
|
+
|
14
|
+
puts "Welcome to the chatbot from the future!"
|
15
|
+
|
16
|
+
def prompt_for_message
|
17
|
+
puts "(multiline input; type 'end' on its own line when done. or exit to exit)"
|
18
|
+
|
19
|
+
user_message = Reline.readmultiline("Question: ", true) do |multiline_input|
|
20
|
+
last = multiline_input.split.last
|
21
|
+
DONE.include?(last)
|
22
|
+
end
|
23
|
+
|
24
|
+
return :noop unless user_message
|
25
|
+
|
26
|
+
lines = user_message.split("\n")
|
27
|
+
if lines.size > 1 && DONE.include?(lines.last)
|
28
|
+
# remove the "done" from the message
|
29
|
+
user_message = lines[0..-2].join("\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
return :exit if DONE.include?(user_message.downcase)
|
33
|
+
|
34
|
+
user_message
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
loop do
|
39
|
+
user_message = prompt_for_message
|
40
|
+
|
41
|
+
case user_message
|
42
|
+
when :noop
|
43
|
+
next
|
44
|
+
when :exit
|
45
|
+
break
|
46
|
+
end
|
47
|
+
|
48
|
+
puts chat.message(user_message)
|
49
|
+
end
|
50
|
+
rescue Interrupt
|
51
|
+
exit 0
|
52
|
+
end
|
@@ -58,6 +58,11 @@ prompt.format(description: "Korean chemistry student", format_instructions: pars
|
|
58
58
|
|
59
59
|
# Character description: Korean chemistry student
|
60
60
|
|
61
|
+
llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
|
62
|
+
# llm_response = llm.chat(
|
63
|
+
# prompt: prompt.format(description: "Korean chemistry student", format_instructions: parser.get_format_instructions)
|
64
|
+
# )
|
65
|
+
|
61
66
|
# LLM example response:
|
62
67
|
llm_example_response = <<~RESPONSE
|
63
68
|
Here is your character:
|
@@ -83,7 +88,14 @@ llm_example_response = <<~RESPONSE
|
|
83
88
|
```
|
84
89
|
RESPONSE
|
85
90
|
|
86
|
-
|
91
|
+
fix_parser = Langchain::OutputParsers::OutputFixingParser.from_llm(
|
92
|
+
llm: llm,
|
93
|
+
parser: parser
|
94
|
+
)
|
95
|
+
# The OutputFixingParser wraps the StructuredOutputParser such that if initial
|
96
|
+
# LLM response does not conform to the schema, will call out the LLM to fix
|
97
|
+
# the error
|
98
|
+
fix_parser.parse(llm_example_response)
|
87
99
|
# {
|
88
100
|
# "name" => "Kim Ji-hyun",
|
89
101
|
# "age" => 22,
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "langchain"
|
2
|
+
|
3
|
+
llm = Langchain::LLM::LlamaCpp.new(
|
4
|
+
model_path: ENV["LLAMACPP_MODEL_PATH"],
|
5
|
+
n_gpu_layers: Integer(ENV["LLAMACPP_N_GPU_LAYERS"]),
|
6
|
+
n_threads: Integer(ENV["LLAMACPP_N_THREADS"])
|
7
|
+
)
|
8
|
+
|
9
|
+
instructions = [
|
10
|
+
"Tell me about the creator of Ruby",
|
11
|
+
"Write a story about a pony who goes to the store to buy some apples."
|
12
|
+
]
|
13
|
+
|
14
|
+
template = Langchain::Prompt::PromptTemplate.new(
|
15
|
+
template: "{instruction}\n\n### Response:",
|
16
|
+
input_variables: %w[instruction]
|
17
|
+
)
|
18
|
+
|
19
|
+
instructions.each do |instruction|
|
20
|
+
puts "USER: #{instruction}"
|
21
|
+
prompt = template.format(instruction: instruction)
|
22
|
+
response = llm.complete prompt: prompt, n_predict: 1024
|
23
|
+
puts "ASSISTANT: #{response}"
|
24
|
+
end
|
@@ -17,10 +17,7 @@ module Langchain
|
|
17
17
|
# end
|
18
18
|
#
|
19
19
|
class Conversation
|
20
|
-
attr_reader :
|
21
|
-
|
22
|
-
# The least number of tokens we want to be under the limit by
|
23
|
-
TOKEN_LEEWAY = 20
|
20
|
+
attr_reader :options
|
24
21
|
|
25
22
|
# Intialize Conversation with a LLM
|
26
23
|
#
|
@@ -31,7 +28,11 @@ module Langchain
|
|
31
28
|
@llm = llm
|
32
29
|
@context = nil
|
33
30
|
@examples = []
|
34
|
-
@
|
31
|
+
@memory = ConversationMemory.new(
|
32
|
+
llm: llm,
|
33
|
+
messages: options.delete(:messages) || [],
|
34
|
+
strategy: options.delete(:memory_strategy)
|
35
|
+
)
|
35
36
|
@options = options
|
36
37
|
@block = block
|
37
38
|
end
|
@@ -39,59 +40,50 @@ module Langchain
|
|
39
40
|
# Set the context of the conversation. Usually used to set the model's persona.
|
40
41
|
# @param message [String] The context of the conversation
|
41
42
|
def set_context(message)
|
42
|
-
@
|
43
|
+
@memory.set_context message
|
43
44
|
end
|
44
45
|
|
45
46
|
# Add examples to the conversation. Used to give the model a sense of the conversation.
|
46
47
|
# @param examples [Array<Hash>] The examples to add to the conversation
|
47
48
|
def add_examples(examples)
|
48
|
-
@
|
49
|
+
@memory.add_examples examples
|
49
50
|
end
|
50
51
|
|
51
52
|
# Message the model with a prompt and return the response.
|
52
53
|
# @param message [String] The prompt to message the model with
|
53
54
|
# @return [String] The response from the model
|
54
55
|
def message(message)
|
55
|
-
append_user_message(message)
|
56
|
+
@memory.append_user_message(message)
|
56
57
|
response = llm_response(message)
|
57
|
-
append_ai_message(response)
|
58
|
+
@memory.append_ai_message(response)
|
58
59
|
response
|
59
60
|
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
def
|
64
|
-
@
|
65
|
-
rescue Langchain::Utils::TokenLength::TokenLimitExceeded => exception
|
66
|
-
raise exception if @messages.size == 1
|
67
|
-
|
68
|
-
reduce_messages(exception.token_overflow)
|
69
|
-
retry
|
62
|
+
# Messages from conversation memory
|
63
|
+
# @return [Array<Hash>] The messages from the conversation memory
|
64
|
+
def messages
|
65
|
+
@memory.messages
|
70
66
|
end
|
71
67
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
proceed
|
78
|
-
end
|
68
|
+
# Context from conversation memory
|
69
|
+
# @return [String] Context from conversation memory
|
70
|
+
def context
|
71
|
+
@memory.context
|
79
72
|
end
|
80
73
|
|
81
|
-
|
82
|
-
|
74
|
+
# Examples from conversation memory
|
75
|
+
# @return [Array<Hash>] Examples from the conversation memory
|
76
|
+
def examples
|
77
|
+
@memory.examples
|
83
78
|
end
|
84
79
|
|
85
|
-
|
86
|
-
@messages << {role: "user", content: message}
|
87
|
-
end
|
88
|
-
|
89
|
-
def model_name
|
90
|
-
@options[:model] || @llm.class::DEFAULTS[:chat_completion_model_name]
|
91
|
-
end
|
80
|
+
private
|
92
81
|
|
93
|
-
def
|
94
|
-
@llm.
|
82
|
+
def llm_response(prompt)
|
83
|
+
@llm.chat(messages: @memory.messages, context: @memory.context, examples: @memory.examples, **@options, &@block)
|
84
|
+
rescue Langchain::Utils::TokenLength::TokenLimitExceeded => exception
|
85
|
+
@memory.reduce_messages(exception)
|
86
|
+
retry
|
95
87
|
end
|
96
88
|
end
|
97
89
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain
|
4
|
+
class ConversationMemory
|
5
|
+
attr_reader :examples, :messages
|
6
|
+
|
7
|
+
# The least number of tokens we want to be under the limit by
|
8
|
+
TOKEN_LEEWAY = 20
|
9
|
+
|
10
|
+
def initialize(llm:, messages: [], **options)
|
11
|
+
@llm = llm
|
12
|
+
@context = nil
|
13
|
+
@summary = nil
|
14
|
+
@examples = []
|
15
|
+
@messages = messages
|
16
|
+
@strategy = options.delete(:strategy) || :truncate
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_context(message)
|
21
|
+
@context = message
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_examples(examples)
|
25
|
+
@examples.concat examples
|
26
|
+
end
|
27
|
+
|
28
|
+
def append_ai_message(message)
|
29
|
+
@messages << {role: "ai", content: message}
|
30
|
+
end
|
31
|
+
|
32
|
+
def append_user_message(message)
|
33
|
+
@messages << {role: "user", content: message}
|
34
|
+
end
|
35
|
+
|
36
|
+
def reduce_messages(exception)
|
37
|
+
case @strategy
|
38
|
+
when :truncate
|
39
|
+
truncate_messages(exception)
|
40
|
+
when :summarize
|
41
|
+
summarize_messages
|
42
|
+
else
|
43
|
+
raise "Unknown strategy: #{@options[:strategy]}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def context
|
48
|
+
return if @context.nil? && @summary.nil?
|
49
|
+
|
50
|
+
[@context, @summary].compact.join("\n")
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def truncate_messages(exception)
|
56
|
+
raise exception if @messages.size == 1
|
57
|
+
|
58
|
+
token_overflow = exception.token_overflow
|
59
|
+
|
60
|
+
@messages = @messages.drop_while do |message|
|
61
|
+
proceed = token_overflow > -TOKEN_LEEWAY
|
62
|
+
token_overflow -= token_length(message.to_json, model_name, llm: @llm)
|
63
|
+
|
64
|
+
proceed
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def summarize_messages
|
69
|
+
history = [@summary, @messages.to_json].compact.join("\n")
|
70
|
+
partitions = [history[0, history.size / 2], history[history.size / 2, history.size]]
|
71
|
+
|
72
|
+
@summary = partitions.map { |messages| @llm.summarize(text: messages.to_json) }.join("\n")
|
73
|
+
|
74
|
+
@messages = [@messages.last]
|
75
|
+
end
|
76
|
+
|
77
|
+
def partition_messages
|
78
|
+
end
|
79
|
+
|
80
|
+
def model_name
|
81
|
+
@llm.class::DEFAULTS[:chat_completion_model_name]
|
82
|
+
end
|
83
|
+
|
84
|
+
def token_length(content, model_name, options)
|
85
|
+
@llm.class::LENGTH_VALIDATOR.token_length(content, model_name, options)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/langchain/llm/base.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Langchain::LLM
|
4
|
+
class ApiError < StandardError; end
|
5
|
+
|
4
6
|
# 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
7
|
#
|
6
8
|
# Langchain.rb provides a common interface to interact with all supported LLMs:
|
@@ -9,6 +11,7 @@ module Langchain::LLM
|
|
9
11
|
# - {Langchain::LLM::Cohere}
|
10
12
|
# - {Langchain::LLM::GooglePalm}
|
11
13
|
# - {Langchain::LLM::HuggingFace}
|
14
|
+
# - {Langchain::LLM::LlamaCpp}
|
12
15
|
# - {Langchain::LLM::OpenAI}
|
13
16
|
# - {Langchain::LLM::Replicate}
|
14
17
|
#
|
data/lib/langchain/llm/cohere.rb
CHANGED
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain::LLM
|
4
|
+
# A wrapper around the LlamaCpp.rb library
|
5
|
+
#
|
6
|
+
# Gem requirements:
|
7
|
+
# gem "llama_cpp"
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
# llama = Langchain::LLM::LlamaCpp.new(
|
11
|
+
# model_path: ENV["LLAMACPP_MODEL_PATH"],
|
12
|
+
# n_gpu_layers: Integer(ENV["LLAMACPP_N_GPU_LAYERS"]),
|
13
|
+
# n_threads: Integer(ENV["LLAMACPP_N_THREADS"])
|
14
|
+
# )
|
15
|
+
#
|
16
|
+
class LlamaCpp < Base
|
17
|
+
attr_accessor :model_path, :n_gpu_layers, :n_ctx, :seed
|
18
|
+
attr_writer :n_threads
|
19
|
+
|
20
|
+
# @param model_path [String] The path to the model to use
|
21
|
+
# @param n_gpu_layers [Integer] The number of GPU layers to use
|
22
|
+
# @param n_ctx [Integer] The number of context tokens to use
|
23
|
+
# @param n_threads [Integer] The CPU number of threads to use
|
24
|
+
# @param seed [Integer] The seed to use
|
25
|
+
def initialize(model_path:, n_gpu_layers: 1, n_ctx: 2048, n_threads: 1, seed: -1)
|
26
|
+
depends_on "llama_cpp"
|
27
|
+
require "llama_cpp"
|
28
|
+
|
29
|
+
@model_path = model_path
|
30
|
+
@n_gpu_layers = n_gpu_layers
|
31
|
+
@n_ctx = n_ctx
|
32
|
+
@n_threads = n_threads
|
33
|
+
@seed = seed
|
34
|
+
end
|
35
|
+
|
36
|
+
# @params text [String] The text to embed
|
37
|
+
# @params n_threads [Integer] The number of CPU threads to use
|
38
|
+
# @return [Array] The embedding
|
39
|
+
def embed(text:, n_threads: nil)
|
40
|
+
# contexts are kinda stateful when it comes to embeddings, so allocate one each time
|
41
|
+
context = embedding_context
|
42
|
+
|
43
|
+
embedding_input = context.tokenize(text: text, add_bos: true)
|
44
|
+
return unless embedding_input.size.positive?
|
45
|
+
|
46
|
+
n_threads ||= self.n_threads
|
47
|
+
|
48
|
+
context.eval(tokens: embedding_input, n_past: 0, n_threads: n_threads)
|
49
|
+
context.embeddings
|
50
|
+
end
|
51
|
+
|
52
|
+
# @params prompt [String] The prompt to complete
|
53
|
+
# @params n_predict [Integer] The number of tokens to predict
|
54
|
+
# @params n_threads [Integer] The number of CPU threads to use
|
55
|
+
# @return [String] The completed prompt
|
56
|
+
def complete(prompt:, n_predict: 128, n_threads: nil)
|
57
|
+
n_threads ||= self.n_threads
|
58
|
+
# contexts do not appear to be stateful when it comes to completion, so re-use the same one
|
59
|
+
context = completion_context
|
60
|
+
::LLaMACpp.generate(context, prompt, n_threads: n_threads, n_predict: n_predict)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def n_threads
|
66
|
+
# Use the maximum number of CPU threads available, if not configured
|
67
|
+
@n_threads ||= `sysctl -n hw.ncpu`.strip.to_i
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_context_params(embeddings: false)
|
71
|
+
context_params = ::LLaMACpp::ContextParams.new
|
72
|
+
|
73
|
+
context_params.seed = seed
|
74
|
+
context_params.n_ctx = n_ctx
|
75
|
+
context_params.n_gpu_layers = n_gpu_layers
|
76
|
+
context_params.embedding = embeddings
|
77
|
+
|
78
|
+
context_params
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_model(embeddings: false)
|
82
|
+
return @model if defined?(@model)
|
83
|
+
@model = ::LLaMACpp::Model.new(model_path: model_path, params: build_context_params(embeddings: embeddings))
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_completion_context
|
87
|
+
::LLaMACpp::Context.new(model: build_model)
|
88
|
+
end
|
89
|
+
|
90
|
+
def build_embedding_context
|
91
|
+
::LLaMACpp::Context.new(model: build_model(embeddings: true))
|
92
|
+
end
|
93
|
+
|
94
|
+
def completion_context
|
95
|
+
@completion_context ||= build_completion_context
|
96
|
+
end
|
97
|
+
|
98
|
+
def embedding_context
|
99
|
+
@embedding_context ||= build_embedding_context
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/langchain/llm/openai.rb
CHANGED
@@ -125,7 +125,7 @@ module Langchain::LLM
|
|
125
125
|
|
126
126
|
response = client.chat(parameters: parameters)
|
127
127
|
|
128
|
-
raise "Chat completion failed: #{response}" if !response.empty? && response.dig("error")
|
128
|
+
raise Langchain::LLM::ApiError.new "Chat completion failed: #{response.dig("error", "message")}" if !response.empty? && response.dig("error")
|
129
129
|
|
130
130
|
unless streaming
|
131
131
|
response.dig("choices", 0, "message", "content")
|
data/lib/langchain/loader.rb
CHANGED
@@ -51,6 +51,13 @@ module Langchain
|
|
51
51
|
!!(@path =~ URI_REGEX)
|
52
52
|
end
|
53
53
|
|
54
|
+
# Is the path a directory
|
55
|
+
#
|
56
|
+
# @return [Boolean] true if path is a directory
|
57
|
+
def directory?
|
58
|
+
File.directory?(@path)
|
59
|
+
end
|
60
|
+
|
54
61
|
# Load data from a file or URL
|
55
62
|
#
|
56
63
|
# loader = Langchain::Loader.new("README.md")
|
@@ -69,15 +76,10 @@ module Langchain
|
|
69
76
|
#
|
70
77
|
# @return [Data] data that was loaded
|
71
78
|
def load(&block)
|
72
|
-
|
79
|
+
return process_data(load_from_url, &block) if url?
|
80
|
+
return load_from_directory(&block) if directory?
|
73
81
|
|
74
|
-
|
75
|
-
yield @raw_data.read, @options
|
76
|
-
else
|
77
|
-
processor_klass.new(@options).parse(@raw_data)
|
78
|
-
end
|
79
|
-
|
80
|
-
Langchain::Data.new(data, source: @path)
|
82
|
+
process_data(load_from_path, &block)
|
81
83
|
end
|
82
84
|
|
83
85
|
private
|
@@ -92,6 +94,27 @@ module Langchain
|
|
92
94
|
File.open(@path)
|
93
95
|
end
|
94
96
|
|
97
|
+
def load_from_directory(&block)
|
98
|
+
Dir.glob(File.join(@path, "**/*")).map do |file|
|
99
|
+
# Only load and add to result files with supported extensions
|
100
|
+
Langchain::Loader.new(file, @options).load(&block)
|
101
|
+
rescue
|
102
|
+
UnknownFormatError nil
|
103
|
+
end.flatten.compact
|
104
|
+
end
|
105
|
+
|
106
|
+
def process_data(data, &block)
|
107
|
+
@raw_data = data
|
108
|
+
|
109
|
+
result = if block
|
110
|
+
yield @raw_data.read, @options
|
111
|
+
else
|
112
|
+
processor_klass.new(@options).parse(@raw_data)
|
113
|
+
end
|
114
|
+
|
115
|
+
Langchain::Data.new(result)
|
116
|
+
end
|
117
|
+
|
95
118
|
def processor_klass
|
96
119
|
raise UnknownFormatError unless (kind = find_processor)
|
97
120
|
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain::OutputParsers
|
4
|
+
# = Output Fixing Parser
|
5
|
+
#
|
6
|
+
class OutputFixingParser < Base
|
7
|
+
attr_reader :llm, :parser, :prompt
|
8
|
+
|
9
|
+
#
|
10
|
+
# Initializes a new instance of the class.
|
11
|
+
#
|
12
|
+
# @param llm [Langchain::LLM] The LLM used in the fixing process
|
13
|
+
# @param parser [Langchain::OutputParsers] The parser originally used which resulted in parsing error
|
14
|
+
# @param prompt [Langchain::Prompt::PromptTemplate]
|
15
|
+
#
|
16
|
+
def initialize(llm:, parser:, prompt:)
|
17
|
+
raise ArgumentError.new("llm must be an instance of Langchain::LLM got: #{llm.class}") unless llm.is_a?(Langchain::LLM::Base)
|
18
|
+
raise ArgumentError.new("parser must be an instance of Langchain::OutputParsers got #{parser.class}") unless parser.is_a?(Langchain::OutputParsers::Base)
|
19
|
+
raise ArgumentError.new("prompt must be an instance of Langchain::Prompt::PromptTemplate got #{prompt.class}") unless prompt.is_a?(Langchain::Prompt::PromptTemplate)
|
20
|
+
@llm = llm
|
21
|
+
@parser = parser
|
22
|
+
@prompt = prompt
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
{
|
27
|
+
_type: "OutputFixingParser",
|
28
|
+
parser: parser.to_h,
|
29
|
+
prompt: prompt.to_h
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# calls get_format_instructions on the @parser
|
35
|
+
#
|
36
|
+
# @return [String] Instructions for how the output of a language model should be formatted
|
37
|
+
# according to the @schema.
|
38
|
+
#
|
39
|
+
def get_format_instructions
|
40
|
+
parser.get_format_instructions
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Parse the output of an LLM call, if fails with OutputParserException
|
45
|
+
# then call the LLM with a fix prompt in an attempt to get the correctly
|
46
|
+
# formatted response
|
47
|
+
#
|
48
|
+
# @param completion [String] Text output from the LLM call
|
49
|
+
#
|
50
|
+
# @return [Object] object that is succesfully parsed by @parser.parse
|
51
|
+
#
|
52
|
+
def parse(completion)
|
53
|
+
parser.parse(completion)
|
54
|
+
rescue OutputParserException => e
|
55
|
+
new_completion = llm.chat(
|
56
|
+
prompt: prompt.format(
|
57
|
+
instructions: parser.get_format_instructions,
|
58
|
+
completion: completion,
|
59
|
+
error: e
|
60
|
+
)
|
61
|
+
)
|
62
|
+
parser.parse(new_completion)
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Creates a new instance of the class using the given JSON::Schema.
|
67
|
+
#
|
68
|
+
# @param schema [JSON::Schema] The JSON::Schema to use
|
69
|
+
#
|
70
|
+
# @return [Object] A new instance of the class
|
71
|
+
#
|
72
|
+
def self.from_llm(llm:, parser:, prompt: nil)
|
73
|
+
new(llm: llm, parser: parser, prompt: prompt || naive_fix_prompt)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
private_class_method def self.naive_fix_prompt
|
79
|
+
Langchain::Prompt.load_from_path(
|
80
|
+
file_path: Langchain.root.join("langchain/output_parsers/prompts/naive_fix_prompt.yaml")
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
_type: prompt
|
2
|
+
input_variables:
|
3
|
+
- instructions
|
4
|
+
- completion
|
5
|
+
- error
|
6
|
+
template: |
|
7
|
+
Instructions:
|
8
|
+
--------------
|
9
|
+
{instructions}
|
10
|
+
--------------
|
11
|
+
Completion:
|
12
|
+
--------------
|
13
|
+
{completion}
|
14
|
+
--------------
|
15
|
+
|
16
|
+
Above, the Completion did not satisfy the constraints given in the Instructions.
|
17
|
+
Error:
|
18
|
+
--------------
|
19
|
+
{error}
|
20
|
+
--------------
|
21
|
+
|
22
|
+
Please try again. Please only respond with an answer that satisfies the constraints laid out in the Instructions:
|
@@ -37,6 +37,9 @@ module Langchain
|
|
37
37
|
#
|
38
38
|
def self.token_length(text, model_name = "chat-bison-001", options)
|
39
39
|
response = options[:llm].client.count_message_tokens(model: model_name, prompt: text)
|
40
|
+
|
41
|
+
raise Langchain::LLM::ApiError.new(response["error"]["message"]) unless response["error"].nil?
|
42
|
+
|
40
43
|
response.dig("tokenCount")
|
41
44
|
end
|
42
45
|
|
@@ -98,6 +98,11 @@ module Langchain::Vectorsearch
|
|
98
98
|
@llm = llm
|
99
99
|
end
|
100
100
|
|
101
|
+
# Method supported by Vectorsearch DB to retrieve a default schema
|
102
|
+
def get_default_schema
|
103
|
+
raise NotImplementedError, "#{self.class.name} does not support retrieving a default schema"
|
104
|
+
end
|
105
|
+
|
101
106
|
# Method supported by Vectorsearch DB to create a default schema
|
102
107
|
def create_default_schema
|
103
108
|
raise NotImplementedError, "#{self.class.name} does not support creating a default schema"
|
@@ -67,10 +67,17 @@ module Langchain::Vectorsearch
|
|
67
67
|
::Chroma::Resources::Collection.create(index_name)
|
68
68
|
end
|
69
69
|
|
70
|
-
#
|
71
|
-
#
|
72
|
-
|
73
|
-
|
70
|
+
# Get the default schema
|
71
|
+
# @return [Hash] The response from the server
|
72
|
+
def get_default_schema
|
73
|
+
::Chroma::Resources::Collection.get(index_name)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Delete the default schema
|
77
|
+
# @return [Hash] The response from the server
|
78
|
+
def destroy_default_schema
|
79
|
+
::Chroma::Resources::Collection.delete(index_name)
|
80
|
+
end
|
74
81
|
|
75
82
|
# Search for similar texts
|
76
83
|
# @param query [String] The text to search for
|
@@ -10,8 +10,7 @@ module Langchain::Vectorsearch
|
|
10
10
|
# gem "hnswlib", "~> 0.8.1"
|
11
11
|
#
|
12
12
|
# Usage:
|
13
|
-
# hnsw = Langchain::Vectorsearch::Hnswlib.new(llm:,
|
14
|
-
#
|
13
|
+
# hnsw = Langchain::Vectorsearch::Hnswlib.new(llm:, path_to_index:)
|
15
14
|
|
16
15
|
attr_reader :client, :path_to_index
|
17
16
|
|
@@ -79,7 +79,17 @@ module Langchain::Vectorsearch
|
|
79
79
|
)
|
80
80
|
end
|
81
81
|
|
82
|
-
#
|
82
|
+
# Get the default schema
|
83
|
+
# @return [Hash] The response from the server
|
84
|
+
def get_default_schema
|
85
|
+
client.collections.get(collection_name: index_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Delete default schema
|
89
|
+
# @return [Hash] The response from the server
|
90
|
+
def destroy_default_schema
|
91
|
+
client.collections.delete(collection_name: index_name)
|
92
|
+
end
|
83
93
|
|
84
94
|
def similarity_search(query:, k: 4)
|
85
95
|
embedding = llm.embed(text: query)
|
@@ -85,6 +85,12 @@ module Langchain::Vectorsearch
|
|
85
85
|
client.delete_index(index_name)
|
86
86
|
end
|
87
87
|
|
88
|
+
# Get the default schema
|
89
|
+
# @return [Pinecone::Vector] The default schema
|
90
|
+
def get_default_schema
|
91
|
+
index
|
92
|
+
end
|
93
|
+
|
88
94
|
# Search for similar texts
|
89
95
|
# @param query [String] The text to search for
|
90
96
|
# @param k [Integer] The number of results to return
|
@@ -32,11 +32,12 @@ module Langchain::Vectorsearch
|
|
32
32
|
# Add a list of texts to the index
|
33
33
|
# @param texts [Array] The list of texts to add
|
34
34
|
# @return [Hash] The response from the server
|
35
|
-
def add_texts(texts:, ids:)
|
35
|
+
def add_texts(texts:, ids: [])
|
36
36
|
batch = {ids: [], vectors: [], payloads: []}
|
37
37
|
|
38
38
|
Array(texts).each_with_index do |text, i|
|
39
|
-
|
39
|
+
id = ids[i] || SecureRandom.uuid
|
40
|
+
batch[:ids].push(id)
|
40
41
|
batch[:vectors].push(llm.embed(text: text))
|
41
42
|
batch[:payloads].push({content: text})
|
42
43
|
end
|
@@ -51,6 +52,12 @@ module Langchain::Vectorsearch
|
|
51
52
|
add_texts(texts: texts, ids: ids)
|
52
53
|
end
|
53
54
|
|
55
|
+
# Get the default schema
|
56
|
+
# @return [Hash] The response from the server
|
57
|
+
def get_default_schema
|
58
|
+
client.collections.get(collection_name: index_name)
|
59
|
+
end
|
60
|
+
|
54
61
|
# Deletes the default schema
|
55
62
|
# @return [Hash] The response from the server
|
56
63
|
def destroy_default_schema
|
@@ -109,7 +116,7 @@ module Langchain::Vectorsearch
|
|
109
116
|
def ask(question:)
|
110
117
|
search_results = similarity_search(query: question)
|
111
118
|
|
112
|
-
context = search_results.
|
119
|
+
context = search_results.map do |result|
|
113
120
|
result.dig("payload").to_s
|
114
121
|
end
|
115
122
|
context = context.join("\n---\n")
|
@@ -85,6 +85,12 @@ module Langchain::Vectorsearch
|
|
85
85
|
)
|
86
86
|
end
|
87
87
|
|
88
|
+
# Get default schema
|
89
|
+
# @return [Hash] The response from the server
|
90
|
+
def get_default_schema
|
91
|
+
client.schema.get(class_name: index_name)
|
92
|
+
end
|
93
|
+
|
88
94
|
# Delete the index
|
89
95
|
# @return [Boolean] Whether the index was deleted
|
90
96
|
def destroy_default_schema
|
data/lib/langchain/version.rb
CHANGED
data/lib/langchain.rb
CHANGED
@@ -51,6 +51,7 @@ module Langchain
|
|
51
51
|
autoload :Loader, "langchain/loader"
|
52
52
|
autoload :Data, "langchain/data"
|
53
53
|
autoload :Conversation, "langchain/conversation"
|
54
|
+
autoload :ConversationMemory, "langchain/conversation_memory"
|
54
55
|
autoload :DependencyHelper, "langchain/dependency_helper"
|
55
56
|
autoload :ContextualLogger, "langchain/contextual_logger"
|
56
57
|
|
@@ -133,6 +134,7 @@ module Langchain
|
|
133
134
|
autoload :Cohere, "langchain/llm/cohere"
|
134
135
|
autoload :GooglePalm, "langchain/llm/google_palm"
|
135
136
|
autoload :HuggingFace, "langchain/llm/hugging_face"
|
137
|
+
autoload :LlamaCpp, "langchain/llm/llama_cpp"
|
136
138
|
autoload :OpenAI, "langchain/llm/openai"
|
137
139
|
autoload :Replicate, "langchain/llm/replicate"
|
138
140
|
end
|
@@ -152,6 +154,7 @@ module Langchain
|
|
152
154
|
module OutputParsers
|
153
155
|
autoload :Base, "langchain/output_parsers/base"
|
154
156
|
autoload :StructuredOutputParser, "langchain/output_parsers/structured"
|
157
|
+
autoload :OutputFixingParser, "langchain/output_parsers/fix"
|
155
158
|
end
|
156
159
|
|
157
160
|
module Errors
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: langchainrb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrei Bondarev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06
|
11
|
+
date: 2023-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: baran
|
@@ -262,6 +262,20 @@ dependencies:
|
|
262
262
|
- - "~>"
|
263
263
|
- !ruby/object:Gem::Version
|
264
264
|
version: 0.9.0
|
265
|
+
- !ruby/object:Gem::Dependency
|
266
|
+
name: llama_cpp
|
267
|
+
requirement: !ruby/object:Gem::Requirement
|
268
|
+
requirements:
|
269
|
+
- - ">="
|
270
|
+
- !ruby/object:Gem::Version
|
271
|
+
version: '0'
|
272
|
+
type: :development
|
273
|
+
prerelease: false
|
274
|
+
version_requirements: !ruby/object:Gem::Requirement
|
275
|
+
requirements:
|
276
|
+
- - ">="
|
277
|
+
- !ruby/object:Gem::Version
|
278
|
+
version: '0'
|
265
279
|
- !ruby/object:Gem::Dependency
|
266
280
|
name: nokogiri
|
267
281
|
requirement: !ruby/object:Gem::Requirement
|
@@ -474,9 +488,11 @@ files:
|
|
474
488
|
- LICENSE.txt
|
475
489
|
- README.md
|
476
490
|
- Rakefile
|
491
|
+
- examples/conversation_with_openai.rb
|
477
492
|
- examples/create_and_manage_few_shot_prompt_templates.rb
|
478
493
|
- examples/create_and_manage_prompt_templates.rb
|
479
494
|
- examples/create_and_manage_prompt_templates_using_structured_output_parser.rb
|
495
|
+
- examples/llama_cpp.rb
|
480
496
|
- examples/pdf_store_and_query_with_chroma.rb
|
481
497
|
- examples/store_and_query_with_pinecone.rb
|
482
498
|
- examples/store_and_query_with_qdrant.rb
|
@@ -494,6 +510,7 @@ files:
|
|
494
510
|
- lib/langchain/chunker/text.rb
|
495
511
|
- lib/langchain/contextual_logger.rb
|
496
512
|
- lib/langchain/conversation.rb
|
513
|
+
- lib/langchain/conversation_memory.rb
|
497
514
|
- lib/langchain/data.rb
|
498
515
|
- lib/langchain/dependency_helper.rb
|
499
516
|
- lib/langchain/llm/ai21.rb
|
@@ -501,11 +518,14 @@ files:
|
|
501
518
|
- lib/langchain/llm/cohere.rb
|
502
519
|
- lib/langchain/llm/google_palm.rb
|
503
520
|
- lib/langchain/llm/hugging_face.rb
|
521
|
+
- lib/langchain/llm/llama_cpp.rb
|
504
522
|
- lib/langchain/llm/openai.rb
|
505
523
|
- lib/langchain/llm/prompts/summarize_template.yaml
|
506
524
|
- lib/langchain/llm/replicate.rb
|
507
525
|
- lib/langchain/loader.rb
|
508
526
|
- lib/langchain/output_parsers/base.rb
|
527
|
+
- lib/langchain/output_parsers/fix.rb
|
528
|
+
- lib/langchain/output_parsers/prompts/naive_fix_prompt.yaml
|
509
529
|
- lib/langchain/output_parsers/structured.rb
|
510
530
|
- lib/langchain/processors/base.rb
|
511
531
|
- lib/langchain/processors/csv.rb
|