langchainrb 0.15.6 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5ee7b8ae1bd67027846f794a9d070ce4dcedc6e3c6c36eb169a42ba8a55f124
4
- data.tar.gz: c5889ddebb21147551c33fe8be1df6ce1659018d6fb489579e497567fd744bf0
3
+ metadata.gz: 3f685910b0f3f1816c3822debc2ec470d72d85203b2695ab8ab780b5d0f1cb09
4
+ data.tar.gz: 7ec406ad7980e12739aa70e9710b21e1f7df0a1e46f66820d7003026e3bbc877
5
5
  SHA512:
6
- metadata.gz: ef5d74c4e9f2a0e5f0f13b000dd5b82bbfa3ed9481d0bd1defce49d03da7071b31e02a5ec0237cce43b8ea8d76607541e59fe400d9db8803cf6961bb02b2f5bc
7
- data.tar.gz: 01a816d66a313b387ce2b37789501636e7b7bede507bd61253bcea7cac3cd641fe74ab18498fe7ddff1bc5b8e38ec39bc0385912ae9e9f310cf94f4d5ec31e9c
6
+ metadata.gz: 1145ffbab814f09acb539df3662f0c4c5536ded25252a4db3e640d29cc550930b11ac9310b11436d591e1ec13449af59f125bdab3fe463f9a59683ea9d8f38ed
7
+ data.tar.gz: d9e76d70f24f964b3f17addda08ace46695149af48c7bd4aff1936a10888640f9cb222bef15bdb205ad7997028f88c81142dcb3ef49d96fcd37682c56409f4c6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.16.0] - 2024-09-19
4
+ - Remove `Langchain::Thread` class as it was not needed.
5
+ - Support `cohere` provider for `Langchain::LLM::AwsBedrock#embed`
6
+
3
7
  ## [0.15.6] - 2024-09-16
4
8
  - Throw an error when `Langchain::Assistant#add_message_callback` is not a callable proc.
5
9
  - Resetting instructions on Langchain::Assistant with Google Gemini no longer throws an error.
@@ -12,27 +12,23 @@ module Langchain
12
12
  # tools: [Langchain::Tool::NewsRetriever.new(api_key: ENV["NEWS_API_KEY"])]
13
13
  # )
14
14
  class Assistant
15
- extend Forwardable
16
- def_delegators :thread, :messages
17
-
18
- attr_reader :llm, :thread, :instructions, :state, :llm_adapter, :tool_choice
19
- attr_reader :total_prompt_tokens, :total_completion_tokens, :total_tokens
15
+ attr_reader :llm, :instructions, :state, :llm_adapter, :tool_choice
16
+ attr_reader :total_prompt_tokens, :total_completion_tokens, :total_tokens, :messages
20
17
  attr_accessor :tools, :add_message_callback
21
18
 
22
19
  # Create a new assistant
23
20
  #
24
21
  # @param llm [Langchain::LLM::Base] LLM instance that the assistant will use
25
- # @param thread [Langchain::Thread] The thread that'll keep track of the conversation
26
22
  # @param tools [Array<Langchain::Tool::Base>] Tools that the assistant has access to
27
- # @param instructions [String] The system instructions to include in the thread
23
+ # @param instructions [String] The system instructions
28
24
  # @param tool_choice [String] Specify how tools should be selected. Options: "auto", "any", "none", or <specific function name>
29
25
  # @params add_message_callback [Proc] A callback function (Proc or lambda) that is called when any message is added to the conversation
30
26
  def initialize(
31
27
  llm:,
32
- thread: nil,
33
28
  tools: [],
34
29
  instructions: nil,
35
30
  tool_choice: "auto",
31
+ messages: [],
36
32
  add_message_callback: nil
37
33
  )
38
34
  unless tools.is_a?(Array) && tools.all? { |tool| tool.class.singleton_class.included_modules.include?(Langchain::ToolDefinition) }
@@ -42,14 +38,13 @@ module Langchain
42
38
  @llm = llm
43
39
  @llm_adapter = LLM::Adapter.build(llm)
44
40
 
45
- @thread = thread || Langchain::Thread.new
46
-
47
41
  # TODO: Validate that it is, indeed, a Proc or lambda
48
42
  if !add_message_callback.nil? && !add_message_callback.respond_to?(:call)
49
43
  raise ArgumentError, "add_message_callback must be a callable object, like Proc or lambda"
50
44
  end
51
- @thread.add_message_callback = add_message_callback
45
+ @add_message_callback = add_message_callback
52
46
 
47
+ self.messages = messages
53
48
  @tools = tools
54
49
  self.tool_choice = tool_choice
55
50
  @instructions = instructions
@@ -59,41 +54,60 @@ module Langchain
59
54
  @total_completion_tokens = 0
60
55
  @total_tokens = 0
61
56
 
62
- raise ArgumentError, "Thread must be an instance of Langchain::Thread" unless @thread.is_a?(Langchain::Thread)
63
-
64
- # The first message in the thread should be the system instructions
57
+ # The first message in the messages array should be the system instructions
65
58
  # For Google Gemini, and Anthropic system instructions are added to the `system:` param in the `chat` method
66
59
  initialize_instructions
67
60
  end
68
61
 
69
- # Add a user message to the thread
62
+ # Add a user message to the messages array
70
63
  #
71
64
  # @param content [String] The content of the message
72
65
  # @param role [String] The role attribute of the message. Default: "user"
73
66
  # @param tool_calls [Array<Hash>] The tool calls to include in the message
74
67
  # @param tool_call_id [String] The ID of the tool call to include in the message
75
- # @return [Array<Langchain::Message>] The messages in the thread
68
+ # @return [Array<Langchain::Message>] The messages
76
69
  def add_message(content: nil, role: "user", tool_calls: [], tool_call_id: nil)
77
70
  message = build_message(role: role, content: content, tool_calls: tool_calls, tool_call_id: tool_call_id)
78
- messages = thread.add_message(message)
71
+
72
+ # Call the callback with the message
73
+ add_message_callback.call(message) if add_message_callback # rubocop:disable Style/SafeNavigation
74
+
75
+ # Prepend the message to the messages array
76
+ messages << message
77
+
79
78
  @state = :ready
80
79
 
81
80
  messages
82
81
  end
83
82
 
84
- # Set multiple messages to the thread
83
+ # Convert messages to an LLM APIs-compatible array of hashes
85
84
  #
86
- # @param messages [Array<Hash>] The messages to set
87
- # @return [Array<Langchain::Message>] The messages in the thread
85
+ # @return [Array<Hash>] Messages as an OpenAI API-compatible array of hashes
86
+ def array_of_message_hashes
87
+ messages
88
+ .map(&:to_hash)
89
+ .compact
90
+ end
91
+
92
+ # Only used by the Assistant when it calls the LLM#complete() method
93
+ def prompt_of_concatenated_messages
94
+ messages.map(&:to_s).join
95
+ end
96
+
97
+ # Set multiple messages
98
+ #
99
+ # @param messages [Array<Langchain::Message>] The messages to set
100
+ # @return [Array<Langchain::Message>] The messages
88
101
  def messages=(messages)
89
- clear_thread!
90
- add_messages(messages: messages)
102
+ raise ArgumentError, "messages array must only contain Langchain::Message instance(s)" unless messages.is_a?(Array) && messages.all? { |m| m.is_a?(Langchain::Messages::Base) }
103
+
104
+ @messages = messages
91
105
  end
92
106
 
93
- # Add multiple messages to the thread
107
+ # Add multiple messages
94
108
  #
95
109
  # @param messages [Array<Hash>] The messages to add
96
- # @return [Array<Langchain::Message>] The messages in the thread
110
+ # @return [Array<Langchain::Message>] The messages
97
111
  def add_messages(messages:)
98
112
  messages.each do |message_hash|
99
113
  add_message(**message_hash.slice(:content, :role, :tool_calls, :tool_call_id))
@@ -103,10 +117,10 @@ module Langchain
103
117
  # Run the assistant
104
118
  #
105
119
  # @param auto_tool_execution [Boolean] Whether or not to automatically run tools
106
- # @return [Array<Langchain::Message>] The messages in the thread
120
+ # @return [Array<Langchain::Message>] The messages
107
121
  def run(auto_tool_execution: false)
108
- if thread.messages.empty?
109
- Langchain.logger.warn("No messages in the thread")
122
+ if messages.empty?
123
+ Langchain.logger.warn("No messages to process")
110
124
  @state = :completed
111
125
  return
112
126
  end
@@ -114,39 +128,39 @@ module Langchain
114
128
  @state = :in_progress
115
129
  @state = handle_state until run_finished?(auto_tool_execution)
116
130
 
117
- thread.messages
131
+ messages
118
132
  end
119
133
 
120
134
  # Run the assistant with automatic tool execution
121
135
  #
122
- # @return [Array<Langchain::Message>] The messages in the thread
136
+ # @return [Array<Langchain::Message>] The messages
123
137
  def run!
124
138
  run(auto_tool_execution: true)
125
139
  end
126
140
 
127
- # Add a user message to the thread and run the assistant
141
+ # Add a user message and run the assistant
128
142
  #
129
143
  # @param content [String] The content of the message
130
144
  # @param auto_tool_execution [Boolean] Whether or not to automatically run tools
131
- # @return [Array<Langchain::Message>] The messages in the thread
145
+ # @return [Array<Langchain::Message>] The messages
132
146
  def add_message_and_run(content:, auto_tool_execution: false)
133
147
  add_message(content: content, role: "user")
134
148
  run(auto_tool_execution: auto_tool_execution)
135
149
  end
136
150
 
137
- # Add a user message to the thread and run the assistant with automatic tool execution
151
+ # Add a user message and run the assistant with automatic tool execution
138
152
  #
139
153
  # @param content [String] The content of the message
140
- # @return [Array<Langchain::Message>] The messages in the thread
154
+ # @return [Array<Langchain::Message>] The messages
141
155
  def add_message_and_run!(content:)
142
156
  add_message_and_run(content: content, auto_tool_execution: true)
143
157
  end
144
158
 
145
- # Submit tool output to the thread
159
+ # Submit tool output
146
160
  #
147
161
  # @param tool_call_id [String] The ID of the tool call to submit output for
148
162
  # @param output [String] The output of the tool
149
- # @return [Array<Langchain::Message>] The messages in the thread
163
+ # @return [Array<Langchain::Message>] The messages
150
164
  def submit_tool_output(tool_call_id:, output:)
151
165
  tool_role = determine_tool_role
152
166
 
@@ -154,18 +168,21 @@ module Langchain
154
168
  add_message(role: tool_role, content: output, tool_call_id: tool_call_id)
155
169
  end
156
170
 
157
- # Delete all messages in the thread
171
+ # Delete all messages
158
172
  #
159
173
  # @return [Array] Empty messages array
160
- def clear_thread!
174
+ def clear_messages!
161
175
  # TODO: If this a bug? Should we keep the "system" message?
162
- thread.messages = []
176
+ @messages = []
163
177
  end
164
178
 
179
+ # TODO: Remove in the next major release
180
+ alias_method :clear_thread!, :clear_messages!
181
+
165
182
  # Set new instructions
166
183
  #
167
184
  # @param new_instructions [String] New instructions that will be set as a system message
168
- # @return [Array<Langchain::Message>] The messages in the thread
185
+ # @return [Array<Langchain::Message>] The messages
169
186
  def instructions=(new_instructions)
170
187
  @instructions = new_instructions
171
188
 
@@ -173,7 +190,7 @@ module Langchain
173
190
  if !llm.is_a?(Langchain::LLM::GoogleGemini) &&
174
191
  !llm.is_a?(Langchain::LLM::GoogleVertexAI) &&
175
192
  !llm.is_a?(Langchain::LLM::Anthropic)
176
- # Find message with role: "system" in thread.messages and delete it from the thread.messages array
193
+ # Find message with role: "system" in messages and delete it from the messages array
177
194
  replace_system_message!(content: new_instructions)
178
195
  end
179
196
  end
@@ -192,12 +209,12 @@ module Langchain
192
209
  # Replace old system message with new one
193
210
  #
194
211
  # @param content [String] New system message content
195
- # @return [Array<Langchain::Message>] The messages in the thread
212
+ # @return [Array<Langchain::Message>] The messages
196
213
  def replace_system_message!(content:)
197
- thread.messages.delete_if(&:system?)
214
+ messages.delete_if(&:system?)
198
215
 
199
216
  message = build_message(role: "system", content: content)
200
- thread.messages.unshift(message)
217
+ messages.unshift(message)
201
218
  end
202
219
 
203
220
  # TODO: If tool_choice = "tool_function_name" and then tool is removed from the assistant, should we set tool_choice back to "auto"?
@@ -231,11 +248,11 @@ module Langchain
231
248
  end
232
249
  end
233
250
 
234
- # Process the latest message in the thread
251
+ # Process the latest message
235
252
  #
236
253
  # @return [Symbol] The next state
237
254
  def process_latest_message
238
- last_message = thread.messages.last
255
+ last_message = messages.last
239
256
 
240
257
  case last_message.standard_role
241
258
  when :system
@@ -261,14 +278,14 @@ module Langchain
261
278
  #
262
279
  # @return [Symbol] The next state
263
280
  def handle_llm_message
264
- thread.messages.last.tool_calls.any? ? :requires_action : :completed
281
+ messages.last.tool_calls.any? ? :requires_action : :completed
265
282
  end
266
283
 
267
284
  # Handle unexpected message scenario
268
285
  #
269
286
  # @return [Symbol] The failed state
270
287
  def handle_unexpected_message
271
- Langchain.logger.error("Unexpected message role encountered: #{thread.messages.last.standard_role}")
288
+ Langchain.logger.error("Unexpected message role encountered: #{messages.last.standard_role}")
272
289
  :failed
273
290
  end
274
291
 
@@ -301,7 +318,7 @@ module Langchain
301
318
  #
302
319
  # @return [Symbol] The next state
303
320
  def execute_tools
304
- run_tools(thread.messages.last.tool_calls)
321
+ run_tools(messages.last.tool_calls)
305
322
  :in_progress
306
323
  rescue => e
307
324
  Langchain.logger.error("Error running tools: #{e.message}; #{e.backtrace.join('\n')}")
@@ -340,7 +357,7 @@ module Langchain
340
357
 
341
358
  params = @llm_adapter.build_chat_params(
342
359
  instructions: @instructions,
343
- messages: thread.array_of_message_hashes,
360
+ messages: array_of_message_hashes,
344
361
  tools: @tools,
345
362
  tool_choice: tool_choice
346
363
  )
@@ -49,6 +49,8 @@ module Langchain
49
49
  "[#{for_class_name}]:"
50
50
  end
51
51
  log_line_parts << colorize(args.first, MESSAGE_COLOR_OPTIONS[method])
52
+ log_line_parts << kwargs if !!kwargs && kwargs.any?
53
+ log_line_parts << block.call if block
52
54
  log_line = log_line_parts.compact.join(" ")
53
55
 
54
56
  @logger.send(
@@ -50,7 +50,7 @@ module Langchain::LLM
50
50
 
51
51
  SUPPORTED_COMPLETION_PROVIDERS = %i[anthropic ai21 cohere meta].freeze
52
52
  SUPPORTED_CHAT_COMPLETION_PROVIDERS = %i[anthropic].freeze
53
- SUPPORTED_EMBEDDING_PROVIDERS = %i[amazon].freeze
53
+ SUPPORTED_EMBEDDING_PROVIDERS = %i[amazon cohere].freeze
54
54
 
55
55
  def initialize(completion_model: DEFAULTS[:completion_model_name], embedding_model: DEFAULTS[:embedding_model_name], aws_client_options: {}, default_options: {})
56
56
  depends_on "aws-sdk-bedrockruntime", req: "aws-sdk-bedrockruntime"
@@ -82,8 +82,7 @@ module Langchain::LLM
82
82
  def embed(text:, **params)
83
83
  raise "Completion provider #{embedding_provider} is not supported." unless SUPPORTED_EMBEDDING_PROVIDERS.include?(embedding_provider)
84
84
 
85
- parameters = {inputText: text}
86
- parameters = parameters.merge(params)
85
+ parameters = compose_embedding_parameters params.merge(text:)
87
86
 
88
87
  response = client.invoke_model({
89
88
  model_id: @defaults[:embedding_model_name],
@@ -92,7 +91,7 @@ module Langchain::LLM
92
91
  accept: "application/json"
93
92
  })
94
93
 
95
- Langchain::LLM::AwsTitanResponse.new(JSON.parse(response.body.string))
94
+ parse_embedding_response response
96
95
  end
97
96
 
98
97
  #
@@ -214,6 +213,14 @@ module Langchain::LLM
214
213
  end
215
214
  end
216
215
 
216
+ def compose_embedding_parameters(params)
217
+ if embedding_provider == :amazon
218
+ compose_embedding_parameters_amazon params
219
+ elsif embedding_provider == :cohere
220
+ compose_embedding_parameters_cohere params
221
+ end
222
+ end
223
+
217
224
  def parse_response(response)
218
225
  if completion_provider == :anthropic
219
226
  Langchain::LLM::AnthropicResponse.new(JSON.parse(response.body.string))
@@ -226,6 +233,37 @@ module Langchain::LLM
226
233
  end
227
234
  end
228
235
 
236
+ def parse_embedding_response(response)
237
+ json_response = JSON.parse(response.body.string)
238
+
239
+ if embedding_provider == :amazon
240
+ Langchain::LLM::AwsTitanResponse.new(json_response)
241
+ elsif embedding_provider == :cohere
242
+ Langchain::LLM::CohereResponse.new(json_response)
243
+ end
244
+ end
245
+
246
+ def compose_embedding_parameters_amazon(params)
247
+ default_params = @defaults.merge(params)
248
+
249
+ {
250
+ inputText: default_params[:text],
251
+ dimensions: default_params[:dimensions],
252
+ normalize: default_params[:normalize]
253
+ }.compact
254
+ end
255
+
256
+ def compose_embedding_parameters_cohere(params)
257
+ default_params = @defaults.merge(params)
258
+
259
+ {
260
+ texts: [default_params[:text]],
261
+ truncate: default_params[:truncate],
262
+ input_type: default_params[:input_type],
263
+ embedding_types: default_params[:embedding_types]
264
+ }.compact
265
+ end
266
+
229
267
  def compose_parameters_cohere(params)
230
268
  default_params = @defaults.merge(params)
231
269
 
@@ -74,7 +74,7 @@ module Langchain::LLM
74
74
  if wrapped_response.chat_completion || Array(wrapped_response.tool_calls).any?
75
75
  wrapped_response
76
76
  else
77
- raise StandardError.new(response)
77
+ raise StandardError.new(parsed_response)
78
78
  end
79
79
  end
80
80
 
@@ -112,7 +112,7 @@ module Langchain::LLM
112
112
  if wrapped_response.chat_completion || Array(wrapped_response.tool_calls).any?
113
113
  wrapped_response
114
114
  else
115
- raise StandardError.new(response)
115
+ raise StandardError.new(parsed_response)
116
116
  end
117
117
  end
118
118
  end
@@ -270,7 +270,7 @@ module Langchain::LLM
270
270
  conn.request :json
271
271
  conn.response :json
272
272
  conn.response :raise_error
273
- conn.response :logger, nil, {headers: true, bodies: true, errors: true}
273
+ conn.response :logger, Langchain.logger, {headers: true, bodies: true, errors: true}
274
274
  end
275
275
  end
276
276
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain
4
- VERSION = "0.15.6"
4
+ VERSION = "0.16.0"
5
5
  end
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.15.6
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Bondarev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-16 00:00:00.000000000 Z
11
+ date: 2024-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: baran
@@ -672,7 +672,6 @@ files:
672
672
  - lib/langchain/assistants/messages/mistral_ai_message.rb
673
673
  - lib/langchain/assistants/messages/ollama_message.rb
674
674
  - lib/langchain/assistants/messages/openai_message.rb
675
- - lib/langchain/assistants/thread.rb
676
675
  - lib/langchain/chunk.rb
677
676
  - lib/langchain/chunker/base.rb
678
677
  - lib/langchain/chunker/markdown.rb
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Langchain
4
- # Langchain::Thread keeps track of messages in a conversation.
5
- # TODO: Add functionality to persist to the thread to disk, DB, storage, etc.
6
- class Thread
7
- attr_accessor :messages, :add_message_callback
8
-
9
- # @param messages [Array<Langchain::Message>]
10
- # @param add_message_callback [Proc] A callback to call when a message is added to the thread
11
- def initialize(messages: [], add_message_callback: nil)
12
- raise ArgumentError, "messages array must only contain Langchain::Message instance(s)" unless messages.is_a?(Array) && messages.all? { |m| m.is_a?(Langchain::Messages::Base) }
13
-
14
- @add_message_callback = add_message_callback
15
- @messages = messages
16
- end
17
-
18
- # Convert the thread to an LLM APIs-compatible array of hashes
19
- #
20
- # @return [Array<Hash>] The thread as an OpenAI API-compatible array of hashes
21
- def array_of_message_hashes
22
- messages
23
- .map(&:to_hash)
24
- .compact
25
- end
26
-
27
- # Only used by the Assistant when it calls the LLM#complete() method
28
- def prompt_of_concatenated_messages
29
- messages.map(&:to_s).join
30
- end
31
-
32
- # Add a message to the thread
33
- #
34
- # @param message [Langchain::Message] The message to add
35
- # @return [Array<Langchain::Message>] The updated messages array
36
- def add_message(message)
37
- raise ArgumentError, "message must be a Langchain::Message instance" unless message.is_a?(Langchain::Messages::Base)
38
-
39
- # Call the callback with the message
40
- add_message_callback.call(message) if add_message_callback # rubocop:disable Style/SafeNavigation
41
-
42
- # Prepend the message to the thread
43
- messages << message
44
- end
45
- end
46
- end