ollama_chat 0.0.118 → 0.0.119

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: f5db2a60d43d3a57a30e3c33a16b9ba26e60807afd9ff6b74c0781af25fe2516
4
- data.tar.gz: e670d0cfcbbcfa815e4b9a7f6e0ccb6093dc4986b736035aea3dc38b4bc646ab
3
+ metadata.gz: a7b876b681130c1ff2ec672460bb04aa85ed095e9358dbdaa62d744f82d68720
4
+ data.tar.gz: 3f85175d15231880d45e8f192f9f9af47c861cb8f1475d2fe653309ca173ab1c
5
5
  SHA512:
6
- metadata.gz: 8f7cb31ed0e38db5baca42a29076b3a01e3bae64d5a80d55584bd0ecdaaa41fd15eb814fc8b34ac55d2e73e4d767c52f305167cd8d2a7b32af4e9668f7d43706
7
- data.tar.gz: a54ebe900c8b8a2c9e31ce3a4aded809f1b6e179679b2f2b4e3174f9e4ba5f6c976c8e7f1aaf3ae7efb8b8c8417f605b8c2522676eb0952f6d4b38165ac087af
6
+ metadata.gz: 9cf60761b6ce28cb22873b30808d3398ffcefc9e9991fcf781621b4db8a8fed5fd6cca21de046d8bf2851dd60a7529cfa162125ca4ee567e32c09042f4dc4e81
7
+ data.tar.gz: a2f1e6210141a8f00c4b9428453c49e4e1673e51521f788dcec694cd2c2c9963542bd491618ec8d1a4bb3ad5788588ca3d2c7f51ed3b67b977eaac1b4a59115a
data/CHANGES.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-09-14 v0.0.119
4
+
5
+ * **Word-boundary fuzzy matching in chooser**
6
+ * Pass `/\b/` as the word-boundary argument to `matcher.similar` in
7
+ `chooser.rb` to filter out partial-word hits, improving ranked results
8
+ for model names containing `/`, `-`, `:`, and `.` separators.
9
+
10
+ * **Parallel embedding via bounded thread pool**
11
+ * Add `concurrency: 3` under `embedding:` in `default_config.yml`.
12
+ * Add `bulk_embed_sources` to `source_fetching.rb` using a
13
+ `Tins::Limited` bounded thread pool with a `Mutex`-guarded results
14
+ array.
15
+ * Change `embed` to accept a `prompt:` kwarg (defaults to
16
+ `prompt(:embed)`) so worker threads share a single resolved template
17
+ instead of each hitting Sequel/SQLite.
18
+ * Replace `STDOUT.puts` with `infobar.puts` in `embed_source` to prevent
19
+ raw output from clobbering the progress bar in concurrent workers.
20
+ * Rewrite the `-p` pattern path in `commands.rb` embedding to build a
21
+ `{source => tags}` Hash and call `bulk_embed_sources`.
22
+ * Rewrite `update_collection` in `rag_handling.rb` to collect both
23
+ modified and new sources into a single Hash and issue one
24
+ `bulk_embed_sources` call instead of a sequential `embed` loop.
25
+ * Update `commands_spec.rb` to stub `bulk_embed_sources` returning an
26
+ array.
27
+
28
+ * **Speed up embedding generation**
29
+ * Implement batch processing in the ollama server.
30
+
31
+ * **Dependency updates**
32
+ * Update `documentrix` minimum version to **0.7.0** in `Rakefile` and
33
+ `ollama_chat.gemspec` to support improved embedding speed.
34
+
35
+ * **Improve session switching and duplication robustness**
36
+ * In `duplicate_session`, replaced defensive `session.lock?` check with a
37
+ direct `session.lock` call, since a freshly duplicated session is never
38
+ yet locked.
39
+ * In `change_session`, added `except_id: session.id` to `choose_session`
40
+ to prevent re-selecting the current session from the chooser.
41
+ * Track `previous_session` as a full object instead of just an ID,
42
+ enabling restoration on lock failure.
43
+ * On lock failure, restore `@session` to the previous session, re-acquire
44
+ its lock, and reset `name` to `??` so the next iteration opens the
45
+ interactive picker instead of retrying the same locked session.
46
+ * Log `previous_session_id` correctly in the "Session changed" info
47
+ message.
48
+
49
+ * **Formatting fix**
50
+ * Add space before `\`.
51
+
52
+ * **Fix `context_spook` returning `nil` instead of context**
53
+ * Reordered `STDOUT.puts` and `ctx.send("to_#{format.downcase}")` in
54
+ `context_spook` so the serialized context is the last expression in the
55
+ `if count > 0` branch, preventing `STDOUT.puts` from clobbering the
56
+ implicit return value.
57
+ * Added a regression spec in `input_content_spec.rb` that exercises the
58
+ `count > 0` path with a real file and `all: true`, asserting the return
59
+ value is a non-blank `String` containing the ingested file path.
60
+
3
61
  ## 2026-09-05 v0.0.118
4
62
 
5
63
  * Documented the upgrade workflow in the README, adding an "Upgrading"
data/Rakefile CHANGED
@@ -40,7 +40,7 @@ GemHadar do
40
40
 
41
41
  dependency 'excon', '~> 1.0'
42
42
  dependency 'ollama-ruby', '~> 1.23'
43
- dependency 'documentrix', '>= 0.6.0'
43
+ dependency 'documentrix', '>= 0.7.0'
44
44
  dependency 'unix_socks', '>= 0.4'
45
45
  dependency 'rss', '~> 0.3'
46
46
  dependency 'term-ansicolor', '~> 1.11'
@@ -883,7 +883,8 @@ module OllamaChat::Commands
883
883
  if opts[?p]
884
884
  all = opts.fetch(?a, false)
885
885
  patterns = extract_patterns(arg)
886
- next provide_file_set_content(patterns, all:, skip_blank: true) { embed(_1, tags:) } || :next
886
+ sources = file_set_each(patterns, all:).map(&:to_s).to_h { [_1, tags] }
887
+ next bulk_embed_sources(sources) || :next
887
888
  elsif arg
888
889
  next embed(arg, tags:) || :next
889
890
  else
@@ -106,8 +106,8 @@ module OllamaChat::InputContent
106
106
  end
107
107
  end
108
108
  if count > 0
109
- ctx.send("to_#{format.downcase}")
110
109
  STDOUT.puts "✅ Ingesting context now."
110
+ ctx.send("to_#{format.downcase}")
111
111
  else
112
112
  STDERR.puts "❌ No files in context. ⇨ Cancelled."
113
113
  end
@@ -478,10 +478,12 @@ embedding:
478
478
  name: snowflake-arctic-embed:137m
479
479
  embedding_length: 768
480
480
  context_length: 2048
481
- options: {}
481
+ options:
482
+ num_batch: 2048
482
483
  # Retrieval prompt template:
483
484
  prompt: 'Represent this sentence for searching relevant passages: %s'
484
- batch_size: 100
485
+ batch_size: 500
486
+ concurrency: 3
485
487
  database_filename: null # ':memory:'
486
488
  collection: <%= OC::OLLAMA::CHAT::COLLECTION %>
487
489
  found_texts_size: 16384
@@ -432,7 +432,7 @@ module OllamaChat::PromptManagement
432
432
  end
433
433
 
434
434
  if orphans.any?
435
- STDOUT.puts "\n#{orphans.count} orphaned prompt(s) "\
435
+ STDOUT.puts "\n#{orphans.count} orphaned prompt(s) " \
436
436
  "(no longer in default config):\n"
437
437
  orphans.each { |p| STDOUT.puts " • #{bold{p.name}} in #{italic{p.context}}" }
438
438
  STDOUT.puts
@@ -184,12 +184,13 @@ module OllamaChat::RAGHandling
184
184
  #
185
185
  # @return [String] a newline-separated string of embedding result messages.
186
186
  def update_collection(collection)
187
+ results = []
187
188
  switch_collection(collection) do
188
189
  unless col = database_collection?(collection)
189
190
  STDERR.puts "❌ Collection #{collection.inspect} not found in database."
190
- return
191
+ return ''
191
192
  end
192
- results = []
193
+ sources = {}
193
194
  seen = {}
194
195
  @documents.each_record do |record|
195
196
  source = @documents.normalize_source(record.source) or next
@@ -199,24 +200,20 @@ module OllamaChat::RAGHandling
199
200
  infobar.puts "Source #{source.to_s.inspect} is unmodified. => Skipping."
200
201
  next
201
202
  end
202
- tags = record.tags_set
203
+ sources[source] = record.tags_set
203
204
  @documents.source_remove(source)
204
- r = embed(source, tags:) or next
205
- results << r
206
205
  end
207
206
 
208
207
  if patterns = col.patterns.full?
209
- all_file_set(patterns).each do |file|
210
- seen[file.to_s] and next
211
- seen[file.to_s] = true
212
- r = embed(file.to_s, tags: []) or next
213
- results << r
214
- end
208
+ new_sources = all_file_set(patterns).map(&:to_s).reject { seen.key?(_1) }
209
+ new_sources.each { seen[_1] = true }
210
+ new_sources.each { sources[_1] = [] }
215
211
  end
216
212
 
213
+ results.concat(bulk_embed_sources(sources))
217
214
  log(:info, "Collection updated", data: { collection:, sources_updated: results.size })
218
- results * "\n"
219
215
  end
216
+ results * ?\n
220
217
  end
221
218
 
222
219
  # Extracts and normalizes file patterns from a shell-style string.
@@ -219,8 +219,7 @@ module OllamaChat::SessionManagement
219
219
  @session = session.duplicate
220
220
  set_previous_session_on_change(old_session.id)
221
221
  session.update(name:)
222
- session.lock? or raise OllamaChat::OllamaChatError,
223
- "Could not lock session #{session.id} #{session.errors.full?(:inspect)}"
222
+ session.lock
224
223
  confirm?(
225
224
  prompt: "🔔 Clear messages of duplicated session? (y/n) ",
226
225
  yes: /\Ay/i
@@ -537,9 +536,9 @@ module OllamaChat::SessionManagement
537
536
  # @param name [String] the name or ID of the session to switch to
538
537
  def change_session(name)
539
538
  name.full? or name = ??
540
- previous_session_id = nil
539
+ previous_session = nil
541
540
  loop do
542
- if chosen_session = choose_session(name, allow_new: true)
541
+ if chosen_session = choose_session(name, except_id: session.id, allow_new: true)
543
542
  if chosen_session.nil? || chosen_session == session
544
543
  confirm?(
545
544
  prompt: "\n⏎ Same session chosen, Press any key to continue (%s). ",
@@ -548,7 +547,7 @@ module OllamaChat::SessionManagement
548
547
  break
549
548
  end
550
549
  session_close
551
- previous_session_id = session.id
550
+ previous_session = session
552
551
  @session = chosen_session
553
552
  if session.lock?
554
553
  messages.read_conversation_jsonl(session.messages.to_s)
@@ -563,10 +562,13 @@ module OllamaChat::SessionManagement
563
562
  set_default_persona_name(session.default_persona_name.full? || :none)
564
563
  set_current_system_prompt(session.current_system_prompt.full? || 'default')
565
564
  session_apply
566
- log(:info, "Session changed", data: { session_id: session.id, name: session.name, previous_session_id: })
565
+ log(:info, "Session changed", data: { session_id: session.id, name: session.name, previous_session_id: previous_session&.id })
567
566
  info_session
568
567
  break
569
568
  else
569
+ @session = previous_session
570
+ @session.lock
571
+ name = ??
570
572
  confirm?(
571
573
  prompt: "\n⏎ Session locked: could not switch, Press any key to continue (%s). ",
572
574
  timeout: 3
@@ -579,7 +581,7 @@ module OllamaChat::SessionManagement
579
581
  end
580
582
  end
581
583
  ensure
582
- set_previous_session_on_change(previous_session_id)
584
+ set_previous_session_on_change(previous_session&.id)
583
585
  session_sync
584
586
  end
585
587
 
@@ -185,7 +185,7 @@ module OllamaChat::SourceFetching
185
185
  def embed_source(source_io, source, tags: [], count: nil)
186
186
  @embedding.on? or return parse_source(source_io)
187
187
  unless @documents.source_modified?(source)
188
- STDOUT.puts "Source #{source.to_s.inspect} already up-to-date. => Skipping."
188
+ infobar.puts "Source #{source.to_s.inspect} already up-to-date. => Skipping."
189
189
  log(:info, "Source up-to-date", data: { source: source.to_s })
190
190
  return
191
191
  end
@@ -225,9 +225,9 @@ module OllamaChat::SourceFetching
225
225
  chunks: inputs.size
226
226
  })
227
227
  if count
228
- STDOUT.puts '%u. %s' % [ count, m ]
228
+ infobar.puts '%u. %s' % [ count, m ]
229
229
  else
230
- STDOUT.puts m
230
+ infobar.puts m
231
231
  end
232
232
  source = source.to_s
233
233
  command = false
@@ -253,10 +253,14 @@ module OllamaChat::SourceFetching
253
253
  #
254
254
  # @param source [String] The source identifier which can be a command, URL,
255
255
  # or file path
256
+ # @param tags [Array<String>] Tags to apply to the embedded source
257
+ # @param prompt [#to_s] The prompt template to format the result with
258
+ # (defaults to `prompt(:embed)`)
256
259
  #
257
260
  # @return [String, nil] The formatted embedding result or summary message, or
258
261
  # nil if the operation fails
259
- def embed(source, tags: [])
262
+ def embed(source, tags: [], prompt: prompt(:embed))
263
+ prompt = prompt.to_s
260
264
  @embedding.on? or return
261
265
  fetch_source(source) do |source_io|
262
266
  content = parse_source(source_io)
@@ -264,7 +268,31 @@ module OllamaChat::SourceFetching
264
268
  source_io.rewind
265
269
  embed_source(source_io, source, tags:) or return
266
270
  end
267
- prompt(:embed).to_s % { source:, collection: }
271
+ prompt.to_s % { source:, collection: }
272
+ end
273
+
274
+ # Embeds multiple sources concurrently using a bounded thread pool.
275
+ #
276
+ # @param sources_tags [Hash{String => Array}] mapping of source identifier
277
+ # to the tags array to apply to that source
278
+ # @return [Array<String>] results from each successful embed call
279
+ def bulk_embed_sources(sources_tags)
280
+ return sources_tags.map { |s, tags| embed(s, tags:) }.compact if
281
+ sources_tags.size <= 1 or config.embedding.concurrency <= 1
282
+
283
+ results = []
284
+ mutex = Mutex.new
285
+ prompt = prompt(:embed)
286
+ Tins::Limited.new(config.embedding.concurrency, name: 'embed').process do |l|
287
+ sources_tags.each do |s, tags|
288
+ l.execute do
289
+ r = embed(s, prompt:, tags:)
290
+ mutex.synchronize { results << r } if r
291
+ end
292
+ end
293
+ l.stop
294
+ end
295
+ results
268
296
  end
269
297
 
270
298
  private
@@ -51,7 +51,7 @@ module OllamaChat::Utils::Chooser
51
51
  matches = entries.map { |n|
52
52
  [
53
53
  n,
54
- -matcher.similar(n.ask_and_send_or_self(:value).to_s.downcase)
54
+ -matcher.similar(n.ask_and_send_or_self(:value).to_s.downcase, /\b/)
55
55
  ]
56
56
  }.select { |_, s| s < 0 }.sort_by(&:last).map(&:first)
57
57
  matches.empty? and matches = entries
@@ -1,6 +1,6 @@
1
1
  module OllamaChat
2
2
  # OllamaChat version
3
- VERSION = '0.0.118'
3
+ VERSION = '0.0.119'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/ollama_chat.gemspec CHANGED
@@ -1,9 +1,9 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: ollama_chat 0.0.118 ruby lib
2
+ # stub: ollama_chat 0.0.119 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "ollama_chat".freeze
6
- s.version = "0.0.118".freeze
6
+ s.version = "0.0.119".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
@@ -35,7 +35,7 @@ Gem::Specification.new do |s|
35
35
  s.add_development_dependency(%q<utils>.freeze, [">= 0".freeze])
36
36
  s.add_runtime_dependency(%q<excon>.freeze, ["~> 1.0".freeze])
37
37
  s.add_runtime_dependency(%q<ollama-ruby>.freeze, ["~> 1.23".freeze])
38
- s.add_runtime_dependency(%q<documentrix>.freeze, [">= 0.6.0".freeze])
38
+ s.add_runtime_dependency(%q<documentrix>.freeze, [">= 0.7.0".freeze])
39
39
  s.add_runtime_dependency(%q<unix_socks>.freeze, [">= 0.4".freeze])
40
40
  s.add_runtime_dependency(%q<rss>.freeze, ["~> 0.3".freeze])
41
41
  s.add_runtime_dependency(%q<term-ansicolor>.freeze, ["~> 1.11".freeze])
@@ -411,11 +411,11 @@ describe OllamaChat::Commands, protect_env: true do
411
411
  end
412
412
 
413
413
  it 'returns "success" when input is "/input embedding -p (.+)"' do
414
- expect(chat).to receive(:embed).
415
- with(asset_pathname('example.rb'), tags: nil).
416
- and_return 'success'
417
- expect(chat.handle_input("/input embedding -a -p #{asset('*.rb')}")).
418
- to match(/success/)
414
+ expect(chat).to receive(:bulk_embed_sources).
415
+ with(anything).
416
+ and_return [ 'success' ]
417
+ expect(chat.handle_input("/input embedding -a -p #{asset('*.rb')}"))
418
+ .to eq [ 'success' ]
419
419
  end
420
420
 
421
421
  it 'returns :next when input is "/input embedding"' do
@@ -97,5 +97,15 @@ describe OllamaChat::InputContent do
97
97
  and_return(nil)
98
98
  expect(chat.context_spook(nil)).to be_nil
99
99
  end
100
+
101
+ it 'returns the serialized context instead of nil' do
102
+ patterns = ['spec/assets/example.rb']
103
+ expect(STDOUT).to receive(:puts).with(/Ingesting context now/)
104
+ result = chat.context_spook(patterns, all: true)
105
+ expect(result).to be_a(String)
106
+ json = JSON.parse(result)
107
+ expected = Pathname.new('spec/assets/example.rb').expand_path.to_path
108
+ expect(json['files'].keys).to include(expected)
109
+ end
100
110
  end
101
111
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ollama_chat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.118
4
+ version: 0.0.119
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
@@ -169,14 +169,14 @@ dependencies:
169
169
  requirements:
170
170
  - - ">="
171
171
  - !ruby/object:Gem::Version
172
- version: 0.6.0
172
+ version: 0.7.0
173
173
  type: :runtime
174
174
  prerelease: false
175
175
  version_requirements: !ruby/object:Gem::Requirement
176
176
  requirements:
177
177
  - - ">="
178
178
  - !ruby/object:Gem::Version
179
- version: 0.6.0
179
+ version: 0.7.0
180
180
  - !ruby/object:Gem::Dependency
181
181
  name: unix_socks
182
182
  requirement: !ruby/object:Gem::Requirement