langchainrb 0.13.3 → 0.13.5

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: d39fd55aeaa36e9d2a0009dac4808743d97941bd139bc9373eafb153cfb7854e
4
- data.tar.gz: ddf8757341169f2e38bb076a9dfb5f5328a0de4e01ce58ec5b3263fdd3c47105
3
+ metadata.gz: d7eac7a6ba7767f6a3f84ee808fa4810eaa1843776695ab0225ddd6b77cf7a73
4
+ data.tar.gz: e9f7c0170fc2a8dbf443f1bac24874878ee0fbba7e0495bf65a8df969d3d86e6
5
5
  SHA512:
6
- metadata.gz: 243d774a05c2d5bfa27c8f98f51adac3ff13252cb108de085d8ac21b65977b8e8c7f9b78f4a58872b2a299c1e615b9fa6852cf27b564b567f6fa3d15dea2cf26
7
- data.tar.gz: 6665f726069148c0adc24947d570b67d2f56f00ad5ffeeb6653fe745401f14768b324c67711cdaf023e782a3fa61c2787050ed2087db6ca34410a65a5e02a8ec
6
+ metadata.gz: e4d14ac64e54e5c7245a9586dfb4899154793ea466f9564a510eb3dfe17a3a7229cf61e408445b38fec37500065b5e1ee725afa634284bea5538abac0766237f
7
+ data.tar.gz: e8fe3e1639a3f2ed087436610dd1653e775703c1c6cc83f7f52eb7d3fb46db554e7be790bc6bc2ddf18ec4e3c26dddbe1ec72e8f25603db1192e5a111d0f9543
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.13.5] - 2024-07-01
4
+ - Add Milvus#remove_texts() method
5
+ - Langchain::Assistant has a `state` now
6
+ - Misc fixes and improvements
7
+
8
+ ## [0.13.4] - 2024-06-16
9
+ - Fix Chroma#remove_texts() method
10
+ - Fix NewsRetriever Tool returning non UTF-8 characters
11
+ - Misc fixes and improvements
12
+
3
13
  ## [0.13.3] - 2024-06-03
4
14
  - New 🛠️ `Langchain::Tool::Tavily` to execute search (better than the GoogleSearch tool)
5
15
  - Remove `activesupport` dependency
data/README.md CHANGED
@@ -57,7 +57,7 @@ Langchain.rb wraps supported LLMs in a unified interface allowing you to easily
57
57
  #### Supported LLMs and features:
58
58
  | LLM providers | `embed()` | `complete()` | `chat()` | `summarize()` | Notes |
59
59
  | -------- |:------------------:| :-------: | :-----------------: | :-------: | :----------------- |
60
- | [OpenAI](https://openai.com/?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ✅ | | Including Azure OpenAI |
60
+ | [OpenAI](https://openai.com/?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ✅ | | Including Azure OpenAI |
61
61
  | [AI21](https://ai21.com/?utm_source=langchainrb&utm_medium=github) | ❌ | ✅ | ❌ | ✅ | |
62
62
  | [Anthropic](https://anthropic.com/?utm_source=langchainrb&utm_medium=github) | ❌ | ✅ | ✅ | ❌ | |
63
63
  | [AwsBedrock](https://aws.amazon.com/bedrock?utm_source=langchainrb&utm_medium=github) | ✅ | ✅ | ✅ | ❌ | Provides AWS, Cohere, AI21, Antropic and Stability AI models |
@@ -343,7 +343,7 @@ You can instantiate any other supported vector search database:
343
343
  client = Langchain::Vectorsearch::Chroma.new(...) # `gem "chroma-db", "~> 0.6.0"`
344
344
  client = Langchain::Vectorsearch::Epsilla.new(...) # `gem "epsilla-ruby", "~> 0.0.3"`
345
345
  client = Langchain::Vectorsearch::Hnswlib.new(...) # `gem "hnswlib", "~> 0.8.1"`
346
- client = Langchain::Vectorsearch::Milvus.new(...) # `gem "milvus", "~> 0.9.2"`
346
+ client = Langchain::Vectorsearch::Milvus.new(...) # `gem "milvus", "~> 0.9.3"`
347
347
  client = Langchain::Vectorsearch::Pinecone.new(...) # `gem "pinecone", "~> 0.1.6"`
348
348
  client = Langchain::Vectorsearch::Pgvector.new(...) # `gem "pgvector", "~> 0.2"`
349
349
  client = Langchain::Vectorsearch::Qdrant.new(...) # `gem "qdrant-ruby", "~> 0.9.3"`
@@ -15,7 +15,7 @@ module Langchain
15
15
  extend Forwardable
16
16
  def_delegators :thread, :messages, :messages=
17
17
 
18
- attr_reader :llm, :thread, :instructions
18
+ attr_reader :llm, :thread, :instructions, :state
19
19
  attr_accessor :tools
20
20
 
21
21
  SUPPORTED_LLMS = [
@@ -46,6 +46,7 @@ module Langchain
46
46
  @thread = thread || Langchain::Thread.new
47
47
  @tools = tools
48
48
  @instructions = instructions
49
+ @state = :ready
49
50
 
50
51
  raise ArgumentError, "Thread must be an instance of Langchain::Thread" unless @thread.is_a?(Langchain::Thread)
51
52
 
@@ -66,7 +67,10 @@ module Langchain
66
67
  # @return [Array<Langchain::Message>] The messages in the thread
67
68
  def add_message(content: nil, role: "user", tool_calls: [], tool_call_id: nil)
68
69
  message = build_message(role: role, content: content, tool_calls: tool_calls, tool_call_id: tool_call_id)
69
- thread.add_message(message)
70
+ messages = thread.add_message(message)
71
+ @state = :ready
72
+
73
+ messages
70
74
  end
71
75
 
72
76
  # Run the assistant
@@ -76,56 +80,12 @@ module Langchain
76
80
  def run(auto_tool_execution: false)
77
81
  if thread.messages.empty?
78
82
  Langchain.logger.warn("No messages in the thread")
83
+ @state = :completed
79
84
  return
80
85
  end
81
86
 
82
- running = true
83
-
84
- while running
85
- # TODO: I think we need to look at all messages and not just the last one.
86
- last_message = thread.messages.last
87
-
88
- if last_message.system?
89
- # Do nothing
90
- running = false
91
- elsif last_message.llm?
92
- if last_message.tool_calls.any?
93
- if auto_tool_execution
94
- run_tools(last_message.tool_calls)
95
- else
96
- # Maybe log and tell the user that there's outstanding tool calls?
97
- running = false
98
- end
99
- else
100
- # Last message was from the assistant without any tools calls.
101
- # Do nothing
102
- running = false
103
- end
104
- elsif last_message.user?
105
- # Run it!
106
- response = chat_with_llm
107
-
108
- if response.tool_calls.any?
109
- # Re-run the while(running) loop to process the tool calls
110
- running = true
111
- add_message(role: response.role, tool_calls: response.tool_calls)
112
- elsif response.chat_completion
113
- # Stop the while(running) loop and add the assistant's response to the thread
114
- running = false
115
- add_message(role: response.role, content: response.chat_completion)
116
- end
117
- elsif last_message.tool?
118
- # Run it!
119
- response = chat_with_llm
120
- running = true
121
-
122
- if response.tool_calls.any?
123
- add_message(role: response.role, tool_calls: response.tool_calls)
124
- elsif response.chat_completion
125
- add_message(role: response.role, content: response.chat_completion)
126
- end
127
- end
128
- end
87
+ @state = :in_progress
88
+ @state = handle_state until run_finished?(auto_tool_execution)
129
89
 
130
90
  thread.messages
131
91
  end
@@ -146,13 +106,7 @@ module Langchain
146
106
  # @param output [String] The output of the tool
147
107
  # @return [Array<Langchain::Message>] The messages in the thread
148
108
  def submit_tool_output(tool_call_id:, output:)
149
- tool_role = if llm.is_a?(Langchain::LLM::OpenAI)
150
- Langchain::Messages::OpenAIMessage::TOOL_ROLE
151
- elsif [Langchain::LLM::GoogleGemini, Langchain::LLM::GoogleVertexAI].include?(llm.class)
152
- Langchain::Messages::GoogleGeminiMessage::TOOL_ROLE
153
- elsif llm.is_a?(Langchain::LLM::Anthropic)
154
- Langchain::Messages::AnthropicMessage::TOOL_ROLE
155
- end
109
+ tool_role = determine_tool_role
156
110
 
157
111
  # TODO: Validate that `tool_call_id` is valid by scanning messages and checking if this tool call ID was invoked
158
112
  add_message(role: tool_role, content: output, tool_call_id: tool_call_id)
@@ -183,6 +137,114 @@ module Langchain
183
137
 
184
138
  private
185
139
 
140
+ # Check if the run is finished
141
+ #
142
+ # @param auto_tool_execution [Boolean] Whether or not to automatically run tools
143
+ # @return [Boolean] Whether the run is finished
144
+ def run_finished?(auto_tool_execution)
145
+ finished_states = [:completed, :failed]
146
+
147
+ requires_manual_action = (@state == :requires_action) && !auto_tool_execution
148
+ finished_states.include?(@state) || requires_manual_action
149
+ end
150
+
151
+ # Handle the current state and transition to the next state
152
+ #
153
+ # @param state [Symbol] The current state
154
+ # @return [Symbol] The next state
155
+ def handle_state
156
+ case @state
157
+ when :in_progress
158
+ process_latest_message
159
+ when :requires_action
160
+ execute_tools
161
+ end
162
+ end
163
+
164
+ # Process the latest message in the thread
165
+ #
166
+ # @return [Symbol] The next state
167
+ def process_latest_message
168
+ last_message = thread.messages.last
169
+
170
+ case last_message.standard_role
171
+ when :system
172
+ handle_system_message
173
+ when :llm
174
+ handle_llm_message
175
+ when :user, :tool
176
+ handle_user_or_tool_message
177
+ else
178
+ handle_unexpected_message
179
+ end
180
+ end
181
+
182
+ # Handle system message scenario
183
+ #
184
+ # @return [Symbol] The completed state
185
+ def handle_system_message
186
+ Langchain.logger.warn("At least one user message is required after a system message")
187
+ :completed
188
+ end
189
+
190
+ # Handle LLM message scenario
191
+ #
192
+ # @param auto_tool_execution [Boolean] Flag to indicate if tools should be executed automatically
193
+ # @return [Symbol] The next state
194
+ def handle_llm_message
195
+ thread.messages.last.tool_calls.any? ? :requires_action : :completed
196
+ end
197
+
198
+ # Handle unexpected message scenario
199
+ #
200
+ # @return [Symbol] The failed state
201
+ def handle_unexpected_message
202
+ Langchain.logger.error("Unexpected message role encountered: #{thread.messages.last.standard_role}")
203
+ :failed
204
+ end
205
+
206
+ # Handle user or tool message scenario by processing the LLM response
207
+ #
208
+ # @return [Symbol] The next state
209
+ def handle_user_or_tool_message
210
+ response = chat_with_llm
211
+ add_message(role: response.role, content: response.chat_completion, tool_calls: response.tool_calls)
212
+
213
+ if response.tool_calls.any?
214
+ :in_progress
215
+ elsif response.chat_completion
216
+ :completed
217
+ else
218
+ Langchain.logger.error("LLM response does not contain tool calls or chat completion")
219
+ :failed
220
+ end
221
+ end
222
+
223
+ # Execute the tools based on the tool calls in the last message
224
+ #
225
+ # @return [Symbol] The next state
226
+ def execute_tools
227
+ run_tools(thread.messages.last.tool_calls)
228
+ :in_progress
229
+ rescue => e
230
+ Langchain.logger.error("Error running tools: #{e.message}")
231
+ :failed
232
+ end
233
+
234
+ # Determine the tool role based on the LLM type
235
+ #
236
+ # @return [String] The tool role
237
+ def determine_tool_role
238
+ case llm
239
+ when Langchain::LLM::OpenAI
240
+ Langchain::Messages::OpenAIMessage::TOOL_ROLE
241
+ when Langchain::LLM::GoogleGemini, Langchain::LLM::GoogleVertexAI
242
+ Langchain::Messages::GoogleGeminiMessage::TOOL_ROLE
243
+ when Langchain::LLM::Anthropic
244
+ Langchain::Messages::AnthropicMessage::TOOL_ROLE
245
+ end
246
+ end
247
+
186
248
  # Call to the LLM#chat() method
187
249
  #
188
250
  # @return [Langchain::LLM::BaseResponse] The LLM response object
@@ -232,14 +294,6 @@ module Langchain
232
294
 
233
295
  submit_tool_output(tool_call_id: tool_call_id, output: output)
234
296
  end
235
-
236
- response = chat_with_llm
237
-
238
- if response.tool_calls.any?
239
- add_message(role: response.role, tool_calls: response.tool_calls)
240
- elsif response.chat_completion
241
- add_message(role: response.role, content: response.chat_completion)
242
- end
243
297
  end
244
298
 
245
299
  # Extract the tool call information from the OpenAI tool call hash
@@ -7,10 +7,44 @@ module Langchain
7
7
 
8
8
  # Check if the message came from a user
9
9
  #
10
- # @param [Boolean] true/false whether the message came from a user
10
+ # @return [Boolean] true/false whether the message came from a user
11
11
  def user?
12
12
  role == "user"
13
13
  end
14
+
15
+ # Check if the message came from an LLM
16
+ #
17
+ # @raise NotImplementedError if the subclass does not implement this method
18
+ def llm?
19
+ raise NotImplementedError, "Class #{self.class.name} must implement the method 'llm?'"
20
+ end
21
+
22
+ # Check if the message is a tool call
23
+ #
24
+ # @raise NotImplementedError if the subclass does not implement this method
25
+ def tool?
26
+ raise NotImplementedError, "Class #{self.class.name} must implement the method 'tool?'"
27
+ end
28
+
29
+ # Check if the message is a system prompt
30
+ #
31
+ # @raise NotImplementedError if the subclass does not implement this method
32
+ def system?
33
+ raise NotImplementedError, "Class #{self.class.name} must implement the method 'system?'"
34
+ end
35
+
36
+ # Returns the standardized role symbol based on the specific role methods
37
+ #
38
+ # @return [Symbol] the standardized role symbol (:system, :llm, :tool, :user, or :unknown)
39
+ def standard_role
40
+ return :user if user?
41
+ return :llm if llm?
42
+ return :tool if tool?
43
+ return :system if system?
44
+
45
+ # TODO: Should we return :unknown or raise an error?
46
+ :unknown
47
+ end
14
48
  end
15
49
  end
16
50
  end
@@ -35,8 +35,8 @@ module Langchain
35
35
  @logger.respond_to?(method, include_private)
36
36
  end
37
37
 
38
- def method_missing(method, *args, **kwargs, &block)
39
- return @logger.send(method, *args, **kwargs, &block) unless @levels.include?(method)
38
+ def method_missing(method, *args, **kwargs, &)
39
+ return @logger.send(method, *args, **kwargs, &) unless @levels.include?(method)
40
40
 
41
41
  for_class = kwargs.delete(:for)
42
42
  for_class_name = for_class&.name
@@ -18,7 +18,9 @@ module Langchain::LLM
18
18
 
19
19
  chat_parameters.update(
20
20
  model: {default: @defaults[:chat_completion_model_name]},
21
- temperature: {default: @defaults[:temperature]}
21
+ temperature: {default: @defaults[:temperature]},
22
+ generation_config: {default: nil},
23
+ safety_settings: {default: nil}
22
24
  )
23
25
  chat_parameters.remap(
24
26
  messages: :contents,
@@ -42,13 +44,25 @@ module Langchain::LLM
42
44
  raise ArgumentError.new("messages argument is required") if Array(params[:messages]).empty?
43
45
 
44
46
  parameters = chat_parameters.to_params(params)
45
- parameters[:generation_config] = {temperature: parameters.delete(:temperature)} if parameters[:temperature]
47
+ parameters[:generation_config] ||= {}
48
+ parameters[:generation_config][:temperature] ||= parameters[:temperature] if parameters[:temperature]
49
+ parameters.delete(:temperature)
50
+ parameters[:generation_config][:top_p] ||= parameters[:top_p] if parameters[:top_p]
51
+ parameters.delete(:top_p)
52
+ parameters[:generation_config][:top_k] ||= parameters[:top_k] if parameters[:top_k]
53
+ parameters.delete(:top_k)
54
+ parameters[:generation_config][:max_output_tokens] ||= parameters[:max_tokens] if parameters[:max_tokens]
55
+ parameters.delete(:max_tokens)
56
+ parameters[:generation_config][:response_mime_type] ||= parameters[:response_format] if parameters[:response_format]
57
+ parameters.delete(:response_format)
58
+ parameters[:generation_config][:stop_sequences] ||= parameters[:stop] if parameters[:stop]
59
+ parameters.delete(:stop)
46
60
 
47
61
  uri = URI("https://generativelanguage.googleapis.com/v1beta/models/#{parameters[:model]}:generateContent?key=#{api_key}")
48
62
 
49
63
  request = Net::HTTP::Post.new(uri)
50
64
  request.content_type = "application/json"
51
- request.body = parameters.to_json
65
+ request.body = Langchain::Utils::HashTransformer.deep_transform_keys(parameters) { |key| Langchain::Utils::HashTransformer.camelize_lower(key.to_s).to_sym }.to_json
52
66
 
53
67
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
54
68
  http.request(request)
@@ -64,7 +64,7 @@ module Langchain::LLM
64
64
  # Generate a completion for a given prompt
65
65
  #
66
66
  # @param prompt [String] The prompt to generate a completion for
67
- # @return [Langchain::LLM::ReplicateResponse] Reponse object
67
+ # @return [Langchain::LLM::ReplicateResponse] Response object
68
68
  #
69
69
  def complete(prompt:, **params)
70
70
  response = completion_model.predict(prompt: prompt)
@@ -90,7 +90,9 @@ module Langchain
90
90
  private
91
91
 
92
92
  def load_from_url
93
- URI.parse(@path).open
93
+ unescaped_url = URI.decode_www_form_component(@path)
94
+ escaped_url = URI::DEFAULT_PARSER.escape(unescaped_url)
95
+ URI.parse(escaped_url).open
94
96
  end
95
97
 
96
98
  def load_from_path
@@ -105,7 +107,7 @@ module Langchain
105
107
  # Only load and add to result files with supported extensions
106
108
  Langchain::Loader.new(file, @options).load(&block)
107
109
  rescue
108
- UnknownFormatError nil
110
+ UnknownFormatError.new("Unknown format: #{source_type}")
109
111
  end.flatten.compact
110
112
  end
111
113
  # rubocop:enable Style/ArgumentsForwarding
@@ -123,7 +125,7 @@ module Langchain
123
125
  end
124
126
 
125
127
  def processor_klass
126
- raise UnknownFormatError unless (kind = find_processor)
128
+ raise UnknownFormatError.new("Unknown format: #{source_type}") unless (kind = find_processor)
127
129
 
128
130
  Langchain::Processors.const_get(kind)
129
131
  end
@@ -126,7 +126,10 @@ module Langchain::Tool
126
126
  request["Content-Type"] = "application/json"
127
127
 
128
128
  response = http.request(request)
129
- response.body
129
+ response
130
+ .body
131
+ # Remove non-UTF-8 characters
132
+ .force_encoding(Encoding::UTF_8)
130
133
  end
131
134
  end
132
135
  end
@@ -0,0 +1,25 @@
1
+ module Langchain
2
+ module Utils
3
+ class HashTransformer
4
+ # Converts a string to camelCase
5
+ def self.camelize_lower(str)
6
+ str.split("_").inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
7
+ end
8
+
9
+ # Recursively transforms the keys of a hash to camel case
10
+ def self.deep_transform_keys(hash, &block)
11
+ case hash
12
+ when Hash
13
+ hash.each_with_object({}) do |(key, value), result|
14
+ new_key = block.call(key)
15
+ result[new_key] = deep_transform_keys(value, &block)
16
+ end
17
+ when Array
18
+ hash.map { |item| deep_transform_keys(item, &block) }
19
+ else
20
+ hash
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -64,7 +64,9 @@ module Langchain::Vectorsearch
64
64
  # @param ids [Array<String>] The list of ids to remove
65
65
  # @return [Hash] The response from the server
66
66
  def remove_texts(ids:)
67
- collection.delete(ids)
67
+ collection.delete(
68
+ ids: ids.map(&:to_s)
69
+ )
68
70
  end
69
71
 
70
72
  # Create the collection with the default schema
@@ -122,7 +124,7 @@ module Langchain::Vectorsearch
122
124
  # @param k [Integer] The number of results to have in context
123
125
  # @yield [String] Stream responses back one String at a time
124
126
  # @return [String] The answer to the question
125
- def ask(question:, k: 4, &block)
127
+ def ask(question:, k: 4, &)
126
128
  search_results = similarity_search(query: question, k: k)
127
129
 
128
130
  context = search_results.map do |result|
@@ -134,7 +136,7 @@ module Langchain::Vectorsearch
134
136
  prompt = generate_rag_prompt(question: question, context: context)
135
137
 
136
138
  messages = [{role: "user", content: prompt}]
137
- response = llm.chat(messages: messages, &block)
139
+ response = llm.chat(messages: messages, &)
138
140
 
139
141
  response.context = context
140
142
  response
@@ -143,7 +143,7 @@ module Langchain::Vectorsearch
143
143
  # @param k [Integer] The number of results to have in context
144
144
  # @yield [String] Stream responses back one String at a time
145
145
  # @return [String] The answer to the question
146
- def ask(question:, k: 4, &block)
146
+ def ask(question:, k: 4, &)
147
147
  search_results = similarity_search(query: question, k: k)
148
148
 
149
149
  context = search_results.map do |result|
@@ -153,7 +153,7 @@ module Langchain::Vectorsearch
153
153
  prompt = generate_rag_prompt(question: question, context: context)
154
154
 
155
155
  messages = [{role: "user", content: prompt}]
156
- response = llm.chat(messages: messages, &block)
156
+ response = llm.chat(messages: messages, &)
157
157
 
158
158
  response.context = context
159
159
  response
@@ -129,7 +129,7 @@ module Langchain::Vectorsearch
129
129
  # @param k [Integer] The number of results to have in context
130
130
  # @yield [String] Stream responses back one String at a time
131
131
  # @return [String] The answer to the question
132
- def ask(question:, k: 4, &block)
132
+ def ask(question:, k: 4, &)
133
133
  search_results = similarity_search(query: question, k: k)
134
134
 
135
135
  context = search_results.map do |result|
@@ -140,7 +140,7 @@ module Langchain::Vectorsearch
140
140
  prompt = generate_rag_prompt(question: question, context: context)
141
141
 
142
142
  messages = [{role: "user", content: prompt}]
143
- response = llm.chat(messages: messages, &block)
143
+ response = llm.chat(messages: messages, &)
144
144
 
145
145
  response.context = context
146
146
  response
@@ -58,7 +58,7 @@ module Langchain::Vectorsearch
58
58
  #
59
59
  # @param query [String] The text to search for
60
60
  # @param k [Integer] The number of results to return
61
- # @return [Array] Results in the format `[[id1, distance3], [id2, distance2]]`
61
+ # @return [Array] Results in the format `[[id1, id2], [distance1, distance2]]`
62
62
  #
63
63
  def similarity_search(
64
64
  query:,
@@ -77,7 +77,7 @@ module Langchain::Vectorsearch
77
77
  #
78
78
  # @param embedding [Array<Float>] The embedding to search for
79
79
  # @param k [Integer] The number of results to return
80
- # @return [Array] Results in the format `[[id1, distance3], [id2, distance2]]`
80
+ # @return [Array] Results in the format `[[id1, id2], [distance1, distance2]]`
81
81
  #
82
82
  def similarity_search_by_vector(
83
83
  embedding:,
@@ -6,7 +6,7 @@ module Langchain::Vectorsearch
6
6
  # Wrapper around Milvus REST APIs.
7
7
  #
8
8
  # Gem requirements:
9
- # gem "milvus", "~> 0.9.2"
9
+ # gem "milvus", "~> 0.9.3"
10
10
  #
11
11
  # Usage:
12
12
  # milvus = Langchain::Vectorsearch::Milvus.new(url:, index_name:, llm:, api_key:)
@@ -39,6 +39,21 @@ module Langchain::Vectorsearch
39
39
  )
40
40
  end
41
41
 
42
+ # Deletes a list of texts in the index
43
+ #
44
+ # @param ids [Array<Integer>] The ids of texts to delete
45
+ # @return [Boolean] The response from the server
46
+ def remove_texts(ids:)
47
+ raise ArgumentError, "ids must be an array" unless ids.is_a?(Array)
48
+ # Convert ids to integers if strings are passed
49
+ ids = ids.map(&:to_i)
50
+
51
+ client.entities.delete(
52
+ collection_name: index_name,
53
+ expression: "id in #{ids}"
54
+ )
55
+ end
56
+
42
57
  # TODO: Add update_texts method
43
58
 
44
59
  # Create default schema
@@ -83,7 +98,7 @@ module Langchain::Vectorsearch
83
98
  # @return [Boolean] The response from the server
84
99
  def create_default_index
85
100
  client.indices.create(
86
- collection_name: "Documents",
101
+ collection_name: index_name,
87
102
  field_name: "vectors",
88
103
  extra_params: [
89
104
  {key: "metric_type", value: "L2"},
@@ -141,7 +156,7 @@ module Langchain::Vectorsearch
141
156
  # @param k [Integer] The number of results to have in context
142
157
  # @yield [String] Stream responses back one String at a time
143
158
  # @return [String] The answer to the question
144
- def ask(question:, k: 4, &block)
159
+ def ask(question:, k: 4, &)
145
160
  search_results = similarity_search(query: question, k: k)
146
161
 
147
162
  content_field = search_results.dig("results", "fields_data").select { |field| field.dig("field_name") == "content" }
@@ -152,7 +167,7 @@ module Langchain::Vectorsearch
152
167
  prompt = generate_rag_prompt(question: question, context: context)
153
168
 
154
169
  messages = [{role: "user", content: prompt}]
155
- response = llm.chat(messages: messages, &block)
170
+ response = llm.chat(messages: messages, &)
156
171
 
157
172
  response.context = context
158
173
  response
@@ -146,7 +146,7 @@ module Langchain::Vectorsearch
146
146
  # @param k [Integer] The number of results to have in context
147
147
  # @yield [String] Stream responses back one String at a time
148
148
  # @return [String] The answer to the question
149
- def ask(question:, k: 4, &block)
149
+ def ask(question:, k: 4, &)
150
150
  search_results = similarity_search(query: question, k: k)
151
151
 
152
152
  context = search_results.map do |result|
@@ -157,7 +157,7 @@ module Langchain::Vectorsearch
157
157
  prompt = generate_rag_prompt(question: question, context: context)
158
158
 
159
159
  messages = [{role: "user", content: prompt}]
160
- response = llm.chat(messages: messages, &block)
160
+ response = llm.chat(messages: messages, &)
161
161
 
162
162
  response.context = context
163
163
  response
@@ -171,7 +171,7 @@ module Langchain::Vectorsearch
171
171
  # @param filter [String] The filter to use
172
172
  # @yield [String] Stream responses back one String at a time
173
173
  # @return [String] The answer to the question
174
- def ask(question:, namespace: "", filter: nil, k: 4, &block)
174
+ def ask(question:, namespace: "", filter: nil, k: 4, &)
175
175
  search_results = similarity_search(query: question, namespace: namespace, filter: filter, k: k)
176
176
 
177
177
  context = search_results.map do |result|
@@ -182,7 +182,7 @@ module Langchain::Vectorsearch
182
182
  prompt = generate_rag_prompt(question: question, context: context)
183
183
 
184
184
  messages = [{role: "user", content: prompt}]
185
- response = llm.chat(messages: messages, &block)
185
+ response = llm.chat(messages: messages, &)
186
186
 
187
187
  response.context = context
188
188
  response
@@ -137,7 +137,7 @@ module Langchain::Vectorsearch
137
137
  # @param k [Integer] The number of results to have in context
138
138
  # @yield [String] Stream responses back one String at a time
139
139
  # @return [String] The answer to the question
140
- def ask(question:, k: 4, &block)
140
+ def ask(question:, k: 4, &)
141
141
  search_results = similarity_search(query: question, k: k)
142
142
 
143
143
  context = search_results.map do |result|
@@ -148,7 +148,7 @@ module Langchain::Vectorsearch
148
148
  prompt = generate_rag_prompt(question: question, context: context)
149
149
 
150
150
  messages = [{role: "user", content: prompt}]
151
- response = llm.chat(messages: messages, &block)
151
+ response = llm.chat(messages: messages, &)
152
152
 
153
153
  response.context = context
154
154
  response
@@ -143,7 +143,7 @@ module Langchain::Vectorsearch
143
143
  # @param k [Integer] The number of results to have in context
144
144
  # @yield [String] Stream responses back one String at a time
145
145
  # @return [Hash] The answer
146
- def ask(question:, k: 4, &block)
146
+ def ask(question:, k: 4, &)
147
147
  search_results = similarity_search(query: question, k: k)
148
148
 
149
149
  context = search_results.map do |result|
@@ -154,7 +154,7 @@ module Langchain::Vectorsearch
154
154
  prompt = generate_rag_prompt(question: question, context: context)
155
155
 
156
156
  messages = [{role: "user", content: prompt}]
157
- response = llm.chat(messages: messages, &block)
157
+ response = llm.chat(messages: messages, &)
158
158
 
159
159
  response.context = context
160
160
  response
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langchain
4
- VERSION = "0.13.3"
4
+ VERSION = "0.13.5"
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.13.3
4
+ version: 0.13.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: 2024-06-03 00:00:00.000000000 Z
11
+ date: 2024-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: baran
@@ -408,14 +408,14 @@ dependencies:
408
408
  requirements:
409
409
  - - "~>"
410
410
  - !ruby/object:Gem::Version
411
- version: 0.9.2
411
+ version: 0.9.3
412
412
  type: :development
413
413
  prerelease: false
414
414
  version_requirements: !ruby/object:Gem::Requirement
415
415
  requirements:
416
416
  - - "~>"
417
417
  - !ruby/object:Gem::Version
418
- version: 0.9.2
418
+ version: 0.9.3
419
419
  - !ruby/object:Gem::Dependency
420
420
  name: llama_cpp
421
421
  requirement: !ruby/object:Gem::Requirement
@@ -809,6 +809,7 @@ files:
809
809
  - lib/langchain/tool/wikipedia/wikipedia.json
810
810
  - lib/langchain/tool/wikipedia/wikipedia.rb
811
811
  - lib/langchain/utils/cosine_similarity.rb
812
+ - lib/langchain/utils/hash_transformer.rb
812
813
  - lib/langchain/utils/token_length/ai21_validator.rb
813
814
  - lib/langchain/utils/token_length/base_validator.rb
814
815
  - lib/langchain/utils/token_length/cohere_validator.rb
@@ -834,8 +835,8 @@ licenses:
834
835
  - MIT
835
836
  metadata:
836
837
  homepage_uri: https://rubygems.org/gems/langchainrb
837
- source_code_uri: https://github.com/andreibondarev/langchainrb
838
- changelog_uri: https://github.com/andreibondarev/langchainrb/CHANGELOG.md
838
+ source_code_uri: https://github.com/patterns-ai-core/langchainrb
839
+ changelog_uri: https://github.com/patterns-ai-core/langchainrb/blob/main/CHANGELOG.md
839
840
  documentation_uri: https://rubydoc.info/gems/langchainrb
840
841
  post_install_message:
841
842
  rdoc_options: []
@@ -852,7 +853,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
852
853
  - !ruby/object:Gem::Version
853
854
  version: '0'
854
855
  requirements: []
855
- rubygems_version: 3.5.11
856
+ rubygems_version: 3.5.14
856
857
  signing_key:
857
858
  specification_version: 4
858
859
  summary: Build LLM-backed Ruby applications with Ruby's Langchain.rb