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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13eec34cc529732ddfb8994956659bd4307a79ebfd76ff883fe3b6644d647c24
4
- data.tar.gz: ce04acfe42a6a8da5a5951734651dd0083f7d2efc43cf4b3367710c8221ee96a
3
+ metadata.gz: eb443fa9eb8f0f9ee32fcef7b413d6825a4c45779c14551e03e71878215560d9
4
+ data.tar.gz: ed70c0b23899598c04fc6c6178466f2bda354f2483f712e83eeb9797f55c38ef
5
5
  SHA512:
6
- metadata.gz: 2094d99610311a1583d890f8c6898605bcd3e76d2fb72deb1ccd4b250f2b98f7a883401faf2e161b97b82fb29f6e64ead8843d8af22f0bd3e8a4c872c150c134
7
- data.tar.gz: d7ce155cbb992e651aa8dc468ed1ee39bd96d1457f50faa11a32d7caac87086f5d8a381fc2b50aaba10ac934486ed415d5e609f47ee0426b4187540e2436b2e9
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
- * Chat bots
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
- - [Building chat bots](#building-chat-bots)
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.1.0"` to your Gemfile.
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
- ## Building chat bots
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
- ### Conversation class
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
- Choose and instantiate the LLM provider you'll be using:
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 Conversation class:
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
- chat = Langchain::Conversation.new(llm: llm)
443
+ assistant.add_message content: "What's the weather in New York City?"
419
444
  ```
420
445
 
421
- (Optional) Set the conversation context:
446
+ Run the Assistant to generate a response.
422
447
  ```ruby
423
- chat.set_context("You are a chatbot from the future")
448
+ assistant.run
424
449
  ```
425
450
 
426
- Exchange messages with the LLM
451
+ If a Tool is invoked you can manually submit an output.
427
452
  ```ruby
428
- chat.message("Tell me about future technologies")
453
+ assistant.submit_tool_output tool_call_id: "...", output: "It's 70 degrees and sunny in New York City"
429
454
  ```
430
455
 
431
- To stream the chat response:
456
+ Or run the assistant with `auto_tool_execution: tool` to call Tools automatically.
432
457
  ```ruby
433
- chat = Langchain::Conversation.new(llm: llm) do |chunk|
434
- print(chunk)
435
- end
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
- Open AI Functions support
466
+ ### Accessing Thread messages
467
+ You can access the messages in a Thread by calling `assistant.thread.messages`.
439
468
  ```ruby
440
- chat.set_functions(functions)
469
+ assistant.thread.messages
441
470
  ```
442
471
 
443
- ## Evaluations (Evals)
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 :examples, :messages
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
- @llm.chat(messages: @memory.messages.map(&:to_h), context: @memory.context&.to_s, examples: @memory.examples.map(&:to_h), **@options, &@block)
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
@@ -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]
@@ -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.1.0"
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
- # Generate an embedding for a given text
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
- # Generate a completion for a given prompt
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
- # Generate a chat completion for a given prompt or messages.
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