langchainrb 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +53 -25
- data/lib/langchain/assistants/assistant.rb +199 -0
- data/lib/langchain/assistants/message.rb +58 -0
- data/lib/langchain/assistants/thread.rb +34 -0
- data/lib/langchain/conversation/memory.rb +1 -6
- data/lib/langchain/conversation.rb +7 -18
- data/lib/langchain/llm/ai21.rb +1 -1
- data/lib/langchain/llm/azure.rb +10 -97
- data/lib/langchain/llm/base.rb +1 -0
- data/lib/langchain/llm/cohere.rb +4 -6
- data/lib/langchain/llm/google_palm.rb +2 -0
- data/lib/langchain/llm/google_vertex_ai.rb +12 -10
- data/lib/langchain/llm/openai.rb +104 -160
- data/lib/langchain/llm/replicate.rb +0 -6
- data/lib/langchain/llm/response/anthropic_response.rb +4 -0
- data/lib/langchain/llm/response/google_palm_response.rb +4 -0
- data/lib/langchain/llm/response/ollama_response.rb +4 -0
- data/lib/langchain/llm/response/openai_response.rb +8 -0
- data/lib/langchain/tool/base.rb +24 -0
- data/lib/langchain/tool/google_search.rb +1 -4
- data/lib/langchain/utils/token_length/ai21_validator.rb +2 -2
- data/lib/langchain/utils/token_length/cohere_validator.rb +2 -2
- data/lib/langchain/utils/token_length/google_palm_validator.rb +2 -2
- data/lib/langchain/utils/token_length/openai_validator.rb +2 -2
- data/lib/langchain/version.rb +1 -1
- data/lib/langchain.rb +2 -1
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb443fa9eb8f0f9ee32fcef7b413d6825a4c45779c14551e03e71878215560d9
|
4
|
+
data.tar.gz: ed70c0b23899598c04fc6c6178466f2bda354f2483f712e83eeb9797f55c38ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cf9baef16a801a1fd81ab6cb1ee89ab297fb8bc633a15641d125e44b1f4121208ec5e41f1c79ac49f93d27e60be899e030ec2bfb99359d6dc983b99398302ce
|
7
|
+
data.tar.gz: af0961e7ee973c0fd35f6e44206f66f7f598c0213a4ec71fb5a7608a58cf56336a6a1d700341c53ad6c0c65fb8eebd9bd382b54829821caa5219dda0089ca8f2
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.9.0]
|
4
|
+
- Introducing new `Langchain::Assistant` that will be replacing `Langchain::Conversation` and `Langchain::Agent`s.
|
5
|
+
- `Langchain::Conversation` is deprecated.
|
6
|
+
|
3
7
|
## [0.8.2]
|
4
8
|
- Introducing new `Langchain::Chunker::Markdown` chunker (thanks @spikex)
|
5
9
|
- Fixes
|
data/README.md
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
# Please fill out the [Ruby AI Survey 2023](https://docs.google.com/forms/d/1dH_0js1wpEyh1YqPTOxU3b5fXj76sb5lYp12lVoNNZE/edit).
|
2
|
-
Results will be anonymized and shared!
|
3
|
-
|
4
1
|
💎🔗 Langchain.rb
|
5
2
|
---
|
6
3
|
⚡ Building LLM-powered applications in Ruby ⚡
|
@@ -18,8 +15,7 @@ Available for paid consulting engagements! [Email me](mailto:andrei@sourcelabs.i
|
|
18
15
|
|
19
16
|
## Use Cases
|
20
17
|
* Retrieval Augmented Generation (RAG) and vector search
|
21
|
-
*
|
22
|
-
* [AI agents](https://github.com/andreibondarev/langchainrb/tree/main/lib/langchain/agent/agents.md)
|
18
|
+
* [Assistants](#assistants) (chat bots) & [AI Agents](https://github.com/andreibondarev/langchainrb/tree/main/lib/langchain/agent/agents.md)
|
23
19
|
|
24
20
|
## Table of Contents
|
25
21
|
|
@@ -29,7 +25,7 @@ Available for paid consulting engagements! [Email me](mailto:andrei@sourcelabs.i
|
|
29
25
|
- [Prompt Management](#prompt-management)
|
30
26
|
- [Output Parsers](#output-parsers)
|
31
27
|
- [Building RAG](#building-retrieval-augment-generation-rag-system)
|
32
|
-
- [
|
28
|
+
- [Assistants](#assistants)
|
33
29
|
- [Evaluations](#evaluations-evals)
|
34
30
|
- [Examples](#examples)
|
35
31
|
- [Logging](#logging)
|
@@ -64,7 +60,7 @@ Langchain.rb wraps all supported LLMs in a unified interface allowing you to eas
|
|
64
60
|
| [AWS Bedrock](https://aws.amazon.com/bedrock?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ❌ | ❌ | Provides AWS, Cohere, AI21, Antropic and Stability AI models |
|
65
61
|
| [Cohere](https://cohere.com/?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ✅ | ✅ | |
|
66
62
|
| [GooglePalm](https://ai.google/discover/palm2?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ✅ | ✅ | |
|
67
|
-
| [Google Vertex AI](https://cloud.google.com/vertex-ai?utm_source=langchainrb&utm_medium=github) | ✅ |
|
63
|
+
| [Google Vertex AI](https://cloud.google.com/vertex-ai?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ❌ | ✅ | |
|
68
64
|
| [HuggingFace](https://huggingface.co/?utm_source=langchainrb&utm_medium=github) | ✅ | ❌ | ❌ | ❌ | |
|
69
65
|
| [Ollama](https://ollama.ai/?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ❌ | ❌ | |
|
70
66
|
| [Replicate](https://replicate.com/?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ✅ | ✅ | |
|
@@ -73,7 +69,7 @@ Langchain.rb wraps all supported LLMs in a unified interface allowing you to eas
|
|
73
69
|
|
74
70
|
#### OpenAI
|
75
71
|
|
76
|
-
Add `gem "ruby-openai", "~> 6.
|
72
|
+
Add `gem "ruby-openai", "~> 6.3.0"` to your Gemfile.
|
77
73
|
|
78
74
|
```ruby
|
79
75
|
llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
|
@@ -405,43 +401,75 @@ client.ask(
|
|
405
401
|
)
|
406
402
|
```
|
407
403
|
|
408
|
-
##
|
404
|
+
## Evaluations (Evals)
|
405
|
+
The Evaluations module is a collection of tools that can be used to evaluate and track the performance of the output products by LLM and your RAG (Retrieval Augmented Generation) pipelines.
|
409
406
|
|
410
|
-
|
407
|
+
## Assistants
|
408
|
+
Assistants are Agent-like objects that leverage helpful instructions, LLMs, tools and knowledge to respond to user queries. Assistants can be configured with an LLM of your choice (currently only OpenAI), any vector search database and easily extended with additional tools.
|
411
409
|
|
412
|
-
|
410
|
+
### Creating an Assistant
|
411
|
+
1. Instantiate an LLM of your choice
|
413
412
|
```ruby
|
414
413
|
llm = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
|
415
414
|
```
|
416
|
-
Instantiate the
|
415
|
+
2. Instantiate a Thread. Threads keep track of the messages in the Assistant conversation.
|
416
|
+
```ruby
|
417
|
+
thread = Langchain::Thread.new
|
418
|
+
```
|
419
|
+
You can pass old message from previously using the Assistant:
|
420
|
+
```ruby
|
421
|
+
thread.messages = messages
|
422
|
+
```
|
423
|
+
Messages contain the conversation history and the whole message history is sent to the LLM every time. A Message belongs to 1 of the 4 roles:
|
424
|
+
* `Message(role: "system")` message usually contains the instructions.
|
425
|
+
* `Message(role: "user")` messages come from the user.
|
426
|
+
* `Message(role: "assistant")` messages are produced by the LLM.
|
427
|
+
* `Message(role: "tool")` messages are sent in response to tool calls with tool outputs.
|
428
|
+
|
429
|
+
3. Instantiate an Assistant
|
430
|
+
```ruby
|
431
|
+
assistant = Langchain::Assistant.new(
|
432
|
+
llm: llm,
|
433
|
+
thread: thread,
|
434
|
+
instructions: "You are a Meteorologist Assistant that is able to pull the weather for any location",
|
435
|
+
tools: [
|
436
|
+
Langchain::Tool::GoogleSearch.new(api_key: ENV["SERPAPI_API_KEY"])
|
437
|
+
]
|
438
|
+
)
|
439
|
+
```
|
440
|
+
### Using an Assistant
|
441
|
+
You can now add your message to an Assistant.
|
417
442
|
```ruby
|
418
|
-
|
443
|
+
assistant.add_message content: "What's the weather in New York City?"
|
419
444
|
```
|
420
445
|
|
421
|
-
|
446
|
+
Run the Assistant to generate a response.
|
422
447
|
```ruby
|
423
|
-
|
448
|
+
assistant.run
|
424
449
|
```
|
425
450
|
|
426
|
-
|
451
|
+
If a Tool is invoked you can manually submit an output.
|
427
452
|
```ruby
|
428
|
-
|
453
|
+
assistant.submit_tool_output tool_call_id: "...", output: "It's 70 degrees and sunny in New York City"
|
429
454
|
```
|
430
455
|
|
431
|
-
|
456
|
+
Or run the assistant with `auto_tool_execution: tool` to call Tools automatically.
|
432
457
|
```ruby
|
433
|
-
|
434
|
-
|
435
|
-
|
458
|
+
assistant.add_message content: "How about San Diego, CA?"
|
459
|
+
assistant.run(auto_tool_execution: true)
|
460
|
+
```
|
461
|
+
You can also combine the two by calling:
|
462
|
+
```ruby
|
463
|
+
assistant.add_message_and_run content: "What about Sacramento, CA?", auto_tool_execution: true
|
436
464
|
```
|
437
465
|
|
438
|
-
|
466
|
+
### Accessing Thread messages
|
467
|
+
You can access the messages in a Thread by calling `assistant.thread.messages`.
|
439
468
|
```ruby
|
440
|
-
|
469
|
+
assistant.thread.messages
|
441
470
|
```
|
442
471
|
|
443
|
-
|
444
|
-
The Evaluations module is a collection of tools that can be used to evaluate and track the performance of the output products by LLM and your RAG (Retrieval Augmented Generation) pipelines.
|
472
|
+
The Assistant checks the context window limits before every request to the LLM and remove oldest thread messages one by one if the context window is exceeded.
|
445
473
|
|
446
474
|
### RAGAS
|
447
475
|
Ragas helps you evaluate your Retrieval Augmented Generation (RAG) pipelines. The implementation is based on this [paper](https://arxiv.org/abs/2309.15217) and the original Python [repo](https://github.com/explodinggradients/ragas). Ragas tracks the following 3 metrics and assigns the 0.0 - 1.0 scores:
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain
|
4
|
+
class Assistant
|
5
|
+
attr_reader :llm, :thread, :instructions
|
6
|
+
attr_accessor :tools
|
7
|
+
|
8
|
+
# Create a new assistant
|
9
|
+
#
|
10
|
+
# @param llm [Langchain::LLM::Base] LLM instance that the assistant will use
|
11
|
+
# @param thread [Langchain::Thread] The thread that'll keep track of the conversation
|
12
|
+
# @param tools [Array<Langchain::Tool::Base>] Tools that the assistant has access to
|
13
|
+
# @param instructions [String] The system instructions to include in the thread
|
14
|
+
def initialize(
|
15
|
+
llm:,
|
16
|
+
thread:,
|
17
|
+
tools: [],
|
18
|
+
instructions: nil
|
19
|
+
)
|
20
|
+
raise ArgumentError, "Invalid LLM; currently only Langchain::LLM::OpenAI is supported" unless llm.instance_of?(Langchain::LLM::OpenAI)
|
21
|
+
raise ArgumentError, "Thread must be an instance of Langchain::Thread" unless thread.is_a?(Langchain::Thread)
|
22
|
+
raise ArgumentError, "Tools must be an array of Langchain::Tool::Base instance(s)" unless tools.is_a?(Array) && tools.all? { |tool| tool.is_a?(Langchain::Tool::Base) }
|
23
|
+
|
24
|
+
@llm = llm
|
25
|
+
@thread = thread
|
26
|
+
@tools = tools
|
27
|
+
@instructions = instructions
|
28
|
+
|
29
|
+
# The first message in the thread should be the system instructions
|
30
|
+
# TODO: What if the user added old messages and the system instructions are already in there? Should this overwrite the existing instructions?
|
31
|
+
add_message(role: "system", content: instructions) if instructions
|
32
|
+
end
|
33
|
+
|
34
|
+
# Add a user message to the thread
|
35
|
+
#
|
36
|
+
# @param content [String] The content of the message
|
37
|
+
# @param role [String] The role attribute of the message. Default: "user"
|
38
|
+
# @param tool_calls [Array<Hash>] The tool calls to include in the message
|
39
|
+
# @param tool_call_id [String] The ID of the tool call to include in the message
|
40
|
+
# @return [Array<Langchain::Message>] The messages in the thread
|
41
|
+
def add_message(content: nil, role: "user", tool_calls: [], tool_call_id: nil)
|
42
|
+
message = build_message(role: role, content: content, tool_calls: tool_calls, tool_call_id: tool_call_id)
|
43
|
+
thread.add_message(message)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Run the assistant
|
47
|
+
#
|
48
|
+
# @param auto_tool_execution [Boolean] Whether or not to automatically run tools
|
49
|
+
# @return [Array<Langchain::Message>] The messages in the thread
|
50
|
+
def run(auto_tool_execution: false)
|
51
|
+
running = true
|
52
|
+
|
53
|
+
while running
|
54
|
+
# TODO: I think we need to look at all messages and not just the last one.
|
55
|
+
case (last_message = thread.messages.last).role
|
56
|
+
when "system"
|
57
|
+
# Do nothing
|
58
|
+
running = false
|
59
|
+
when "assistant"
|
60
|
+
if last_message.tool_calls.any?
|
61
|
+
if auto_tool_execution
|
62
|
+
run_tools(last_message.tool_calls)
|
63
|
+
else
|
64
|
+
# Maybe log and tell the user that there's outstanding tool calls?
|
65
|
+
running = false
|
66
|
+
end
|
67
|
+
else
|
68
|
+
# Last message was from the assistant without any tools calls.
|
69
|
+
# Do nothing
|
70
|
+
running = false
|
71
|
+
end
|
72
|
+
when "user"
|
73
|
+
# Run it!
|
74
|
+
response = chat_with_llm
|
75
|
+
|
76
|
+
if response.tool_calls
|
77
|
+
# Re-run the while(running) loop to process the tool calls
|
78
|
+
running = true
|
79
|
+
add_message(role: response.role, tool_calls: response.tool_calls)
|
80
|
+
elsif response.chat_completion
|
81
|
+
# Stop the while(running) loop and add the assistant's response to the thread
|
82
|
+
running = false
|
83
|
+
add_message(role: response.role, content: response.chat_completion)
|
84
|
+
end
|
85
|
+
when "tool"
|
86
|
+
# Run it!
|
87
|
+
response = chat_with_llm
|
88
|
+
running = true
|
89
|
+
|
90
|
+
if response.tool_calls
|
91
|
+
add_message(role: response.role, tool_calls: response.tool_calls)
|
92
|
+
elsif response.chat_completion
|
93
|
+
add_message(role: response.role, content: response.chat_completion)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
thread.messages
|
99
|
+
end
|
100
|
+
|
101
|
+
# Add a user message to the thread and run the assistant
|
102
|
+
#
|
103
|
+
# @param content [String] The content of the message
|
104
|
+
# @param auto_tool_execution [Boolean] Whether or not to automatically run tools
|
105
|
+
# @return [Array<Langchain::Message>] The messages in the thread
|
106
|
+
def add_message_and_run(content:, auto_tool_execution: false)
|
107
|
+
add_message(content: content, role: "user")
|
108
|
+
run(auto_tool_execution: auto_tool_execution)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Submit tool output to the thread
|
112
|
+
#
|
113
|
+
# @param tool_call_id [String] The ID of the tool call to submit output for
|
114
|
+
# @param output [String] The output of the tool
|
115
|
+
# @return [Array<Langchain::Message>] The messages in the thread
|
116
|
+
def submit_tool_output(tool_call_id:, output:)
|
117
|
+
# TODO: Validate that `tool_call_id` is valid
|
118
|
+
add_message(role: "tool", content: output, tool_call_id: tool_call_id)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
# Call to the LLM#chat() method
|
124
|
+
#
|
125
|
+
# @return [Langchain::LLM::BaseResponse] The LLM response object
|
126
|
+
def chat_with_llm
|
127
|
+
params = {messages: thread.openai_messages}
|
128
|
+
|
129
|
+
if tools.any?
|
130
|
+
params[:tools] = tools.map(&:to_openai_tool)
|
131
|
+
# TODO: Not sure that tool_choice should always be "auto"; Maybe we can let the user toggle it.
|
132
|
+
params[:tool_choice] = "auto"
|
133
|
+
end
|
134
|
+
|
135
|
+
llm.chat(**params)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Run the tools automatically
|
139
|
+
#
|
140
|
+
# @param tool_calls [Array<Hash>] The tool calls to run
|
141
|
+
def run_tools(tool_calls)
|
142
|
+
# Iterate over each function invocation and submit tool output
|
143
|
+
tool_calls.each do |tool_call|
|
144
|
+
tool_call_id = tool_call.dig("id")
|
145
|
+
tool_name = tool_call.dig("function", "name")
|
146
|
+
tool_arguments = JSON.parse(tool_call.dig("function", "arguments"), symbolize_names: true)
|
147
|
+
|
148
|
+
tool_instance = tools.find do |t|
|
149
|
+
t.name == tool_name
|
150
|
+
end or raise ArgumentError, "Tool not found in assistant.tools"
|
151
|
+
|
152
|
+
output = tool_instance.execute(**tool_arguments)
|
153
|
+
|
154
|
+
submit_tool_output(tool_call_id: tool_call_id, output: output)
|
155
|
+
end
|
156
|
+
|
157
|
+
response = chat_with_llm
|
158
|
+
|
159
|
+
if response.tool_calls
|
160
|
+
add_message(role: response.role, tool_calls: response.tool_calls)
|
161
|
+
elsif response.chat_completion
|
162
|
+
add_message(role: response.role, content: response.chat_completion)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Build a message
|
167
|
+
#
|
168
|
+
# @param role [String] The role of the message
|
169
|
+
# @param content [String] The content of the message
|
170
|
+
# @param tool_calls [Array<Hash>] The tool calls to include in the message
|
171
|
+
# @param tool_call_id [String] The ID of the tool call to include in the message
|
172
|
+
# @return [Langchain::Message] The Message object
|
173
|
+
def build_message(role:, content: nil, tool_calls: [], tool_call_id: nil)
|
174
|
+
Message.new(role: role, content: content, tool_calls: tool_calls, tool_call_id: tool_call_id)
|
175
|
+
end
|
176
|
+
|
177
|
+
# # TODO: Fix the message truncation when context window is exceeded
|
178
|
+
# def build_assistant_prompt(instructions:, tools:)
|
179
|
+
# while begin
|
180
|
+
# # Check if the prompt exceeds the context window
|
181
|
+
# # Return false to exit the while loop
|
182
|
+
# !llm.class.const_get(:LENGTH_VALIDATOR).validate_max_tokens!(
|
183
|
+
# thread.messages,
|
184
|
+
# llm.defaults[:chat_completion_model_name],
|
185
|
+
# {llm: llm}
|
186
|
+
# )
|
187
|
+
# # Rescue error if context window is exceeded and return true to continue the while loop
|
188
|
+
# rescue Langchain::Utils::TokenLength::TokenLimitExceeded
|
189
|
+
# # Should be using `retry` instead of while()
|
190
|
+
# true
|
191
|
+
# end
|
192
|
+
# # Truncate the oldest messages when the context window is exceeded
|
193
|
+
# thread.messages.shift
|
194
|
+
# end
|
195
|
+
|
196
|
+
# prompt
|
197
|
+
# end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain
|
4
|
+
# Langchain::Message are the messages that are sent to LLM chat methods
|
5
|
+
class Message
|
6
|
+
attr_reader :role, :content, :tool_calls, :tool_call_id
|
7
|
+
|
8
|
+
ROLES = %w[
|
9
|
+
system
|
10
|
+
assistant
|
11
|
+
user
|
12
|
+
tool
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
# @param role [String] The role of the message
|
16
|
+
# @param content [String] The content of the message
|
17
|
+
# @param tool_calls [Array<Hash>] Tool calls to be made
|
18
|
+
# @param tool_call_id [String] The ID of the tool call to be made
|
19
|
+
def initialize(role:, content: nil, tool_calls: [], tool_call_id: nil) # TODO: Implement image_file: reference (https://platform.openai.com/docs/api-reference/messages/object#messages/object-content)
|
20
|
+
raise ArgumentError, "Role must be one of #{ROLES.join(", ")}" unless ROLES.include?(role)
|
21
|
+
raise ArgumentError, "Tool calls must be an array of hashes" unless tool_calls.is_a?(Array) && tool_calls.all? { |tool_call| tool_call.is_a?(Hash) }
|
22
|
+
|
23
|
+
@role = role
|
24
|
+
# Some Tools return content as a JSON hence `.to_s`
|
25
|
+
@content = content.to_s
|
26
|
+
@tool_calls = tool_calls
|
27
|
+
@tool_call_id = tool_call_id
|
28
|
+
end
|
29
|
+
|
30
|
+
# Convert the message to an OpenAI API-compatible hash
|
31
|
+
#
|
32
|
+
# @return [Hash] The message as an OpenAI API-compatible hash
|
33
|
+
def to_openai_format
|
34
|
+
{}.tap do |h|
|
35
|
+
h[:role] = role
|
36
|
+
h[:content] = content if content # Content is nil for tool calls
|
37
|
+
h[:tool_calls] = tool_calls if tool_calls.any?
|
38
|
+
h[:tool_call_id] = tool_call_id if tool_call_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def assistant?
|
43
|
+
role == "assistant"
|
44
|
+
end
|
45
|
+
|
46
|
+
def system?
|
47
|
+
role == "system"
|
48
|
+
end
|
49
|
+
|
50
|
+
def user?
|
51
|
+
role == "user"
|
52
|
+
end
|
53
|
+
|
54
|
+
def tool?
|
55
|
+
role == "tool"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Langchain
|
4
|
+
# Langchain::Thread keeps track of messages in a conversation
|
5
|
+
# Eventually we may want to add functionality to persist to the thread to disk, DB, storage, etc.
|
6
|
+
class Thread
|
7
|
+
attr_accessor :messages
|
8
|
+
|
9
|
+
# @param messages [Array<Langchain::Message>]
|
10
|
+
def initialize(messages: [])
|
11
|
+
raise ArgumentError, "messages array must only contain Langchain::Message instance(s)" unless messages.is_a?(Array) && messages.all? { |m| m.is_a?(Langchain::Message) }
|
12
|
+
|
13
|
+
@messages = messages
|
14
|
+
end
|
15
|
+
|
16
|
+
# Convert the thread to an OpenAI API-compatible array of hashes
|
17
|
+
#
|
18
|
+
# @return [Array<Hash>] The thread as an OpenAI API-compatible array of hashes
|
19
|
+
def openai_messages
|
20
|
+
messages.map(&:to_openai_format)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add a message to the thread
|
24
|
+
#
|
25
|
+
# @param message [Langchain::Message] The message to add
|
26
|
+
# @return [Array<Langchain::Message>] The updated messages array
|
27
|
+
def add_message(message)
|
28
|
+
raise ArgumentError, "message must be a Langchain::Message instance" unless message.is_a?(Langchain::Message)
|
29
|
+
|
30
|
+
# Prepend the message to the thread
|
31
|
+
messages << message
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Langchain
|
4
4
|
class Conversation
|
5
5
|
class Memory
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :messages
|
7
7
|
|
8
8
|
# The least number of tokens we want to be under the limit by
|
9
9
|
TOKEN_LEEWAY = 20
|
@@ -12,7 +12,6 @@ module Langchain
|
|
12
12
|
@llm = llm
|
13
13
|
@context = nil
|
14
14
|
@summary = nil
|
15
|
-
@examples = []
|
16
15
|
@messages = messages
|
17
16
|
@strategy = options.delete(:strategy) || :truncate
|
18
17
|
@options = options
|
@@ -22,10 +21,6 @@ module Langchain
|
|
22
21
|
@context = message
|
23
22
|
end
|
24
23
|
|
25
|
-
def add_examples(examples)
|
26
|
-
@examples.concat examples
|
27
|
-
end
|
28
|
-
|
29
24
|
def append_message(message)
|
30
25
|
@messages.append(message)
|
31
26
|
end
|
@@ -25,9 +25,10 @@ module Langchain
|
|
25
25
|
# @param options [Hash] Options to pass to the LLM, like temperature, top_k, etc.
|
26
26
|
# @return [Langchain::Conversation] The Langchain::Conversation instance
|
27
27
|
def initialize(llm:, **options, &block)
|
28
|
+
warn "[DEPRECATION] `Langchain::Conversation` is deprecated. Please use `Langchain::Assistant` instead."
|
29
|
+
|
28
30
|
@llm = llm
|
29
31
|
@context = nil
|
30
|
-
@examples = []
|
31
32
|
@memory = ::Langchain::Conversation::Memory.new(
|
32
33
|
llm: llm,
|
33
34
|
messages: options.delete(:messages) || [],
|
@@ -37,22 +38,12 @@ module Langchain
|
|
37
38
|
@block = block
|
38
39
|
end
|
39
40
|
|
40
|
-
def set_functions(functions)
|
41
|
-
@llm.functions = functions
|
42
|
-
end
|
43
|
-
|
44
41
|
# Set the context of the conversation. Usually used to set the model's persona.
|
45
42
|
# @param message [String] The context of the conversation
|
46
43
|
def set_context(message)
|
47
44
|
@memory.set_context ::Langchain::Conversation::Context.new(message)
|
48
45
|
end
|
49
46
|
|
50
|
-
# Add examples to the conversation. Used to give the model a sense of the conversation.
|
51
|
-
# @param examples [Array<Prompt|Response>] The examples to add to the conversation
|
52
|
-
def add_examples(examples)
|
53
|
-
@memory.add_examples examples
|
54
|
-
end
|
55
|
-
|
56
47
|
# Message the model with a prompt and return the response.
|
57
48
|
# @param message [String] The prompt to message the model with
|
58
49
|
# @return [Response] The response from the model
|
@@ -75,16 +66,14 @@ module Langchain
|
|
75
66
|
@memory.context
|
76
67
|
end
|
77
68
|
|
78
|
-
# Examples from conversation memory
|
79
|
-
# @return [Array<Prompt|Response>] Examples from the conversation memory
|
80
|
-
def examples
|
81
|
-
@memory.examples
|
82
|
-
end
|
83
|
-
|
84
69
|
private
|
85
70
|
|
86
71
|
def llm_response
|
87
|
-
|
72
|
+
message_history = messages.map(&:to_h)
|
73
|
+
# Prepend the system message as context as the first message
|
74
|
+
message_history.prepend({role: "system", content: @memory.context.to_s}) if @memory.context
|
75
|
+
|
76
|
+
@llm.chat(messages: message_history, **@options, &@block)
|
88
77
|
rescue Langchain::Utils::TokenLength::TokenLimitExceeded => exception
|
89
78
|
@memory.reduce_messages(exception)
|
90
79
|
retry
|
data/lib/langchain/llm/ai21.rb
CHANGED
@@ -35,7 +35,7 @@ module Langchain::LLM
|
|
35
35
|
def complete(prompt:, **params)
|
36
36
|
parameters = complete_parameters params
|
37
37
|
|
38
|
-
parameters[:maxTokens] = LENGTH_VALIDATOR.validate_max_tokens!(prompt, parameters[:model], client)
|
38
|
+
parameters[:maxTokens] = LENGTH_VALIDATOR.validate_max_tokens!(prompt, parameters[:model], {llm: client})
|
39
39
|
|
40
40
|
response = client.complete(prompt, parameters)
|
41
41
|
Langchain::LLM::AI21Response.new response, model: parameters[:model]
|
data/lib/langchain/llm/azure.rb
CHANGED
@@ -4,7 +4,7 @@ module Langchain::LLM
|
|
4
4
|
# LLM interface for Azure OpenAI Service APIs: https://learn.microsoft.com/en-us/azure/ai-services/openai/
|
5
5
|
#
|
6
6
|
# Gem requirements:
|
7
|
-
# gem "ruby-openai", "~> 6.
|
7
|
+
# gem "ruby-openai", "~> 6.3.0"
|
8
8
|
#
|
9
9
|
# Usage:
|
10
10
|
# openai = Langchain::LLM::Azure.new(api_key:, llm_options: {}, embedding_deployment_url: chat_deployment_url:)
|
@@ -34,106 +34,19 @@ module Langchain::LLM
|
|
34
34
|
@defaults = DEFAULTS.merge(default_options)
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# @param text [String] The text to generate an embedding for
|
41
|
-
# @param params extra parameters passed to OpenAI::Client#embeddings
|
42
|
-
# @return [Langchain::LLM::OpenAIResponse] Response object
|
43
|
-
#
|
44
|
-
def embed(text:, **params)
|
45
|
-
parameters = {model: @defaults[:embeddings_model_name], input: text}
|
46
|
-
|
47
|
-
validate_max_tokens(text, parameters[:model])
|
48
|
-
|
49
|
-
response = with_api_error_handling do
|
50
|
-
embed_client.embeddings(parameters: parameters.merge(params))
|
51
|
-
end
|
52
|
-
|
53
|
-
Langchain::LLM::OpenAIResponse.new(response)
|
37
|
+
def embed(...)
|
38
|
+
@client = @embed_client
|
39
|
+
super(...)
|
54
40
|
end
|
55
41
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# @param prompt [String] The prompt to generate a completion for
|
60
|
-
# @param params extra parameters passed to OpenAI::Client#complete
|
61
|
-
# @return [Langchain::LLM::Response::OpenaAI] Response object
|
62
|
-
#
|
63
|
-
def complete(prompt:, **params)
|
64
|
-
parameters = compose_parameters @defaults[:completion_model_name], params
|
65
|
-
|
66
|
-
parameters[:messages] = compose_chat_messages(prompt: prompt)
|
67
|
-
parameters[:max_tokens] = validate_max_tokens(parameters[:messages], parameters[:model])
|
68
|
-
|
69
|
-
response = with_api_error_handling do
|
70
|
-
chat_client.chat(parameters: parameters)
|
71
|
-
end
|
72
|
-
|
73
|
-
Langchain::LLM::OpenAIResponse.new(response)
|
42
|
+
def complete(...)
|
43
|
+
@client = @chat_client
|
44
|
+
super(...)
|
74
45
|
end
|
75
46
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# == Examples
|
80
|
-
#
|
81
|
-
# # simplest case, just give a prompt
|
82
|
-
# openai.chat prompt: "When was Ruby first released?"
|
83
|
-
#
|
84
|
-
# # prompt plus some context about how to respond
|
85
|
-
# openai.chat context: "You are RubyGPT, a helpful chat bot for helping people learn Ruby", prompt: "Does Ruby have a REPL like IPython?"
|
86
|
-
#
|
87
|
-
# # full control over messages that get sent, equivilent to the above
|
88
|
-
# openai.chat messages: [
|
89
|
-
# {
|
90
|
-
# role: "system",
|
91
|
-
# content: "You are RubyGPT, a helpful chat bot for helping people learn Ruby", prompt: "Does Ruby have a REPL like IPython?"
|
92
|
-
# },
|
93
|
-
# {
|
94
|
-
# role: "user",
|
95
|
-
# content: "When was Ruby first released?"
|
96
|
-
# }
|
97
|
-
# ]
|
98
|
-
#
|
99
|
-
# # few-short prompting with examples
|
100
|
-
# openai.chat prompt: "When was factory_bot released?",
|
101
|
-
# examples: [
|
102
|
-
# {
|
103
|
-
# role: "user",
|
104
|
-
# content: "When was Ruby on Rails released?"
|
105
|
-
# }
|
106
|
-
# {
|
107
|
-
# role: "assistant",
|
108
|
-
# content: "2004"
|
109
|
-
# },
|
110
|
-
# ]
|
111
|
-
#
|
112
|
-
# @param prompt [String] The prompt to generate a chat completion for
|
113
|
-
# @param messages [Array<Hash>] The messages that have been sent in the conversation
|
114
|
-
# @param context [String] An initial context to provide as a system message, ie "You are RubyGPT, a helpful chat bot for helping people learn Ruby"
|
115
|
-
# @param examples [Array<Hash>] Examples of messages to provide to the model. Useful for Few-Shot Prompting
|
116
|
-
# @param options [Hash] extra parameters passed to OpenAI::Client#chat
|
117
|
-
# @yield [Hash] Stream responses back one token at a time
|
118
|
-
# @return [Langchain::LLM::OpenAIResponse] Response object
|
119
|
-
#
|
120
|
-
def chat(prompt: "", messages: [], context: "", examples: [], **options, &block)
|
121
|
-
raise ArgumentError.new(":prompt or :messages argument is expected") if prompt.empty? && messages.empty?
|
122
|
-
|
123
|
-
parameters = compose_parameters @defaults[:chat_completion_model_name], options, &block
|
124
|
-
parameters[:messages] = compose_chat_messages(prompt: prompt, messages: messages, context: context, examples: examples)
|
125
|
-
|
126
|
-
if functions
|
127
|
-
parameters[:functions] = functions
|
128
|
-
else
|
129
|
-
parameters[:max_tokens] = validate_max_tokens(parameters[:messages], parameters[:model])
|
130
|
-
end
|
131
|
-
|
132
|
-
response = with_api_error_handling { chat_client.chat(parameters: parameters) }
|
133
|
-
|
134
|
-
return if block
|
135
|
-
|
136
|
-
Langchain::LLM::OpenAIResponse.new(response)
|
47
|
+
def chat(...)
|
48
|
+
@client = @chat_client
|
49
|
+
super(...)
|
137
50
|
end
|
138
51
|
end
|
139
52
|
end
|