ollama-ruby 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +48 -0
- data/README.md +2 -0
- data/Rakefile +5 -3
- data/bin/ollama_chat +75 -44
- data/lib/ollama/documents/cache/common.rb +17 -0
- data/lib/ollama/documents/{memory_cache.rb → cache/memory_cache.rb} +8 -10
- data/lib/ollama/documents/cache/redis_backed_memory_cache.rb +44 -0
- data/lib/ollama/documents/{redis_cache.rb → cache/redis_cache.rb} +9 -8
- data/lib/ollama/documents.rb +19 -16
- data/lib/ollama/utils/chooser.rb +3 -1
- data/lib/ollama/utils/colorize_texts.rb +21 -1
- data/lib/ollama/utils/fetcher.rb +8 -6
- data/lib/ollama/utils/file_argument.rb +25 -7
- data/lib/ollama/utils/tags.rb +1 -0
- data/lib/ollama/version.rb +1 -1
- data/ollama-ruby.gemspec +8 -7
- data/spec/assets/prompt.txt +1 -0
- data/spec/ollama/documents/memory_cache_spec.rb +16 -16
- data/spec/ollama/documents/redis_backed_memory_cache_spec.rb +95 -0
- data/spec/ollama/documents/redis_cache_spec.rb +15 -15
- data/spec/ollama/utils/fetcher_spec.rb +2 -1
- data/spec/ollama/utils/file_argument_spec.rb +17 -0
- data/spec/ollama/utils/tags_spec.rb +3 -3
- metadata +31 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 29863f68627a05541de5eab810850b0509db94c1d91b2e9a5a9be9bd387e8aea
|
4
|
+
data.tar.gz: 07f3558153a0a724c08e27a86482e0951070348bf25641d2a9feed113e3e7046
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11c405277b59c6acc75c24220942dd883bb7aec99d97d19bc9baf8d61aa4e5dc59af905631dc37641b91babdd9e5ab7305720a8720451db7b4bb6ca067833913
|
7
|
+
data.tar.gz: 129f49e2d4b5cb65b7999f666f33798511b8a34b1be84bbc7bd4433872cd7c323812cd4dd3e8d2ae325cb013cfb6403db8181c8cbe641139469e5ea9821cc71d
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,53 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 2024-09-21 v0.4.0
|
4
|
+
|
5
|
+
### Change Log for **1.2.3**
|
6
|
+
|
7
|
+
#### New Features
|
8
|
+
|
9
|
+
* Added `-E` option to disable embeddings for this chat session.
|
10
|
+
* Added `-M` option to load document embeddings into (empty) MemoryCache for this chat session.
|
11
|
+
* Added CSV parsing support to `ollama_chat`.
|
12
|
+
* Improved error handling in `Ollama::Utils::Fetcher` methods.
|
13
|
+
|
14
|
+
#### Bug Fixes
|
15
|
+
|
16
|
+
* Handle case in `ollama_chat` where responses don't provide counts, display 0
|
17
|
+
rates instead.
|
18
|
+
|
19
|
+
#### Refactoring and Improvements
|
20
|
+
|
21
|
+
* Updated eval count and rate display in FollowChat class.
|
22
|
+
* Refactor system prompt display and chunk listing in chat output.
|
23
|
+
* Refactor cache implementation to use Ollama::Documents::Cache::Common module.
|
24
|
+
* Improved system prompt formatting in `ollama_chat` script.
|
25
|
+
* Renamed `tags` method to `tags_set` in `Ollama::Documents::Record` class.
|
26
|
+
|
27
|
+
#### Documentation
|
28
|
+
|
29
|
+
* Added comments to ColorizeTexts utility class.
|
30
|
+
|
31
|
+
## 2024-09-15 v0.3.2
|
32
|
+
|
33
|
+
* Add color support to chooser module:
|
34
|
+
+ Include `Term::ANSIColor` in `Ollama::Utils::Chooser` module
|
35
|
+
+ Use `blue`, `on_blue` ANSI color for selected item in query method
|
36
|
+
* Refactor summarize method to also import sources:
|
37
|
+
+ Added `content` variable to store result of `parse_source`
|
38
|
+
+ Replaced `or return` with explicit assignment and return
|
39
|
+
+ Added calls to `source_io.rewind` and `import_document`
|
40
|
+
* Add new test for `file_argument_spec.rb`
|
41
|
+
* Refactor tag list initialization and merging:
|
42
|
+
+ Use array literals for initializing tags lists
|
43
|
+
+ Use array literals for passing to merge method
|
44
|
+
* Update dependencies and dates in Rakefile and gemspec:
|
45
|
+
+ Removed '.utilsrc' from ignored files in Rakefile
|
46
|
+
+ Updated date in `ollama-ruby.gemspec` to "2024-09-13"
|
47
|
+
+ Removed 'utils' development dependency from `ollama-ruby.gemspec`
|
48
|
+
* Refactor `search_web` method to allow n parameter to be optional and default
|
49
|
+
to 1.
|
50
|
+
|
3
51
|
## 2024-09-12 v0.3.1
|
4
52
|
|
5
53
|
* Update dependencies and date in gemspec files:
|
data/README.md
CHANGED
@@ -43,6 +43,8 @@ ollama_chat [OPTIONS]
|
|
43
43
|
-c CHAT a saved chat conversation to load
|
44
44
|
-C COLLECTION name of the collection used in this conversation
|
45
45
|
-D DOCUMENT load document and add to collection (multiple)
|
46
|
+
-M use (empty) MemoryCache for this chat session
|
47
|
+
-E disable embeddings for this chat session
|
46
48
|
-v use voice output
|
47
49
|
-h this help
|
48
50
|
```
|
data/Rakefile
CHANGED
@@ -13,9 +13,10 @@ GemHadar do
|
|
13
13
|
description 'Library that allows interacting with the Ollama API'
|
14
14
|
test_dir 'spec'
|
15
15
|
ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', '.AppleDouble', '.bundle',
|
16
|
-
'.yardoc', 'tags', 'errors.lst', 'cscope.out', 'coverage', 'tmp',
|
16
|
+
'.yardoc', 'doc', 'tags', 'errors.lst', 'cscope.out', 'coverage', 'tmp',
|
17
|
+
'corpus'
|
17
18
|
package_ignore '.all_images.yml', '.tool-versions', '.gitignore', 'VERSION',
|
18
|
-
'.
|
19
|
+
'.rspec', *Dir.glob('.github/**/*', File::FNM_DOTMATCH)
|
19
20
|
readme 'README.md'
|
20
21
|
|
21
22
|
executables << 'ollama_console' << 'ollama_chat' <<
|
@@ -40,8 +41,9 @@ GemHadar do
|
|
40
41
|
dependency 'pdf-reader', '~> 2.0'
|
41
42
|
development_dependency 'all_images', '~> 0.4'
|
42
43
|
development_dependency 'rspec', '~> 3.2'
|
43
|
-
development_dependency 'utils'
|
44
44
|
development_dependency 'webmock'
|
45
|
+
development_dependency 'debug'
|
46
|
+
development_dependency 'simplecov'
|
45
47
|
|
46
48
|
licenses << 'MIT'
|
47
49
|
|
data/bin/ollama_chat
CHANGED
@@ -14,6 +14,7 @@ require 'uri'
|
|
14
14
|
require 'nokogiri'
|
15
15
|
require 'rss'
|
16
16
|
require 'pdf/reader'
|
17
|
+
require 'csv'
|
17
18
|
|
18
19
|
class OllamaChatConfig
|
19
20
|
include ComplexConfig
|
@@ -47,7 +48,7 @@ class OllamaChatConfig
|
|
47
48
|
splitter:
|
48
49
|
name: RecursiveCharacter
|
49
50
|
chunk_size: 1024
|
50
|
-
cache: Ollama::Documents::
|
51
|
+
cache: Ollama::Documents::Cache::RedisBackedMemoryCache
|
51
52
|
redis:
|
52
53
|
url: <%= ENV.fetch('REDIS_URL', 'null') %>
|
53
54
|
debug: <%= ENV['OLLAMA_CHAT_DEBUG'].to_i == 1 ? true : false %>
|
@@ -130,11 +131,11 @@ class FollowChat
|
|
130
131
|
prompt_eval_duration = response.prompt_eval_duration / 1e9
|
131
132
|
stats_text = {
|
132
133
|
eval_duration: Tins::Duration.new(eval_duration),
|
133
|
-
eval_count: response.eval_count,
|
134
|
-
eval_rate: bold { "%.2f c/s" % (response.eval_count / eval_duration) } + color(111),
|
134
|
+
eval_count: response.eval_count.to_i,
|
135
|
+
eval_rate: bold { "%.2f c/s" % (response.eval_count.to_i / eval_duration) } + color(111),
|
135
136
|
prompt_eval_duration: Tins::Duration.new(prompt_eval_duration),
|
136
|
-
prompt_eval_count: response.prompt_eval_count,
|
137
|
-
prompt_eval_rate: bold { "%.2f c/s" % (response.prompt_eval_count / prompt_eval_duration) } + color(111),
|
137
|
+
prompt_eval_count: response.prompt_eval_count.to_i,
|
138
|
+
prompt_eval_rate: bold { "%.2f c/s" % (response.prompt_eval_count.to_i / prompt_eval_duration) } + color(111),
|
138
139
|
total_duration: Tins::Duration.new(response.total_duration / 1e9),
|
139
140
|
load_duration: Tins::Duration.new(response.load_duration / 1e9),
|
140
141
|
}.map { _1 * '=' } * ' '
|
@@ -144,10 +145,12 @@ class FollowChat
|
|
144
145
|
end
|
145
146
|
end
|
146
147
|
|
147
|
-
def search_web(query, n =
|
148
|
+
def search_web(query, n = nil)
|
149
|
+
n = n.to_i
|
150
|
+
n < 1 and n = 1
|
148
151
|
query = URI.encode_uri_component(query)
|
149
152
|
url = "https://www.duckduckgo.com/html/?q=#{query}"
|
150
|
-
Ollama::Utils::Fetcher.new.get(url) do |tmp|
|
153
|
+
Ollama::Utils::Fetcher.new(debug: $config.debug).get(url) do |tmp|
|
151
154
|
result = []
|
152
155
|
doc = Nokogiri::HTML(tmp)
|
153
156
|
doc.css('.results_links').each do |link|
|
@@ -300,6 +303,16 @@ def parse_source(source_io)
|
|
300
303
|
end
|
301
304
|
source_io.rewind
|
302
305
|
source_io.read
|
306
|
+
when 'text/csv'
|
307
|
+
result = +''
|
308
|
+
CSV.table(File.new(source_io), col_sep: ?,).each do |row|
|
309
|
+
next if row.fields.select(&:present?).size == 0
|
310
|
+
result << row.map { |pair|
|
311
|
+
pair.compact.map { _1.to_s.strip } * ': ' if pair.last.present?
|
312
|
+
}.select(&:present?).map { _1.prepend(' ') } * ?\n
|
313
|
+
result << "\n\n"
|
314
|
+
end
|
315
|
+
result
|
303
316
|
when %r(\Atext/)
|
304
317
|
source_io.read
|
305
318
|
when 'application/rss+xml'
|
@@ -310,11 +323,7 @@ def parse_source(source_io)
|
|
310
323
|
source_io.read
|
311
324
|
when 'application/pdf'
|
312
325
|
reader = PDF::Reader.new(source_io)
|
313
|
-
result
|
314
|
-
reader.pages.each do |page|
|
315
|
-
result << page.text
|
316
|
-
end
|
317
|
-
result
|
326
|
+
reader.pages.inject(+'') { |result, page| result << page.text }
|
318
327
|
else
|
319
328
|
STDERR.puts "Cannot import #{source_io&.content_type} document."
|
320
329
|
return
|
@@ -322,10 +331,7 @@ def parse_source(source_io)
|
|
322
331
|
end
|
323
332
|
|
324
333
|
def import_document(source_io, source)
|
325
|
-
|
326
|
-
STDOUT.puts "Embedding disabled, I won't import any documents, try: /summarize"
|
327
|
-
return
|
328
|
-
end
|
334
|
+
embedding_enabled? or return parse_source(source_io)
|
329
335
|
puts "Importing #{italic { source_io&.content_type }} document #{source.to_s.inspect}."
|
330
336
|
text = parse_source(source_io) or return
|
331
337
|
text.downcase!
|
@@ -362,7 +368,7 @@ end
|
|
362
368
|
def fetch_source(source, &block)
|
363
369
|
case source
|
364
370
|
when %r(\Ahttps?://\S+)
|
365
|
-
Utils::Fetcher.get(source) do |tmp|
|
371
|
+
Utils::Fetcher.get(source, debug: $config.debug) do |tmp|
|
366
372
|
block.(tmp)
|
367
373
|
end
|
368
374
|
when %r(\Afile://(?:(?:[.-]|[[:alnum:]])*)(/\S*)|([~.]?/\S*))
|
@@ -382,7 +388,11 @@ def summarize(source)
|
|
382
388
|
puts "Now summarizing #{source.to_s.inspect}."
|
383
389
|
source_content =
|
384
390
|
fetch_source(source) do |source_io|
|
385
|
-
parse_source(source_io)
|
391
|
+
content = parse_source(source_io)
|
392
|
+
content.present? or return
|
393
|
+
source_io.rewind
|
394
|
+
import_document(source_io, source)
|
395
|
+
content
|
386
396
|
end
|
387
397
|
$config.prompts.summarize % source_content
|
388
398
|
end
|
@@ -428,7 +438,7 @@ ensure
|
|
428
438
|
end
|
429
439
|
|
430
440
|
def choose_collection(default_collection)
|
431
|
-
collections = [ default_collection ] + $documents.collections
|
441
|
+
collections = [ default_collection ] + $documents.collections.map(&:to_s)
|
432
442
|
collections = collections.uniq.sort
|
433
443
|
$documents.collection = collection =
|
434
444
|
Ollama::Utils::Chooser.choose(collections) || default_collection
|
@@ -447,7 +457,11 @@ def collection_stats
|
|
447
457
|
end
|
448
458
|
|
449
459
|
def configure_cache
|
450
|
-
|
460
|
+
if $opts[?M]
|
461
|
+
Ollama::Documents::MemoryCache
|
462
|
+
else
|
463
|
+
Object.const_get($config.cache)
|
464
|
+
end
|
451
465
|
rescue => e
|
452
466
|
STDERR.puts "Caught #{e.class}: #{e} => Falling back to MemoryCache."
|
453
467
|
Ollama::Documents::MemoryCache
|
@@ -463,6 +477,14 @@ def set_markdown(value)
|
|
463
477
|
end
|
464
478
|
end
|
465
479
|
|
480
|
+
def clear_messages(messages)
|
481
|
+
messages.delete_if { _1.role != 'system' }
|
482
|
+
end
|
483
|
+
|
484
|
+
def embedding_enabled?
|
485
|
+
$config.embedding.enabled && !$opts[?E]
|
486
|
+
end
|
487
|
+
|
466
488
|
def display_chat_help
|
467
489
|
puts <<~end
|
468
490
|
/paste to paste content
|
@@ -493,7 +515,9 @@ def usage
|
|
493
515
|
-s SYSTEM the system prompt to use as a file, OLLAMA_CHAT_SYSTEM
|
494
516
|
-c CHAT a saved chat conversation to load
|
495
517
|
-C COLLECTION name of the collection used in this conversation
|
496
|
-
-D DOCUMENT load document and add to collection (multiple)
|
518
|
+
-D DOCUMENT load document and add to embeddings collection (multiple)
|
519
|
+
-M use (empty) MemoryCache for this chat session
|
520
|
+
-E disable embeddings for this chat session
|
497
521
|
-v use voice output
|
498
522
|
-h this help
|
499
523
|
|
@@ -505,28 +529,28 @@ def ollama
|
|
505
529
|
$ollama
|
506
530
|
end
|
507
531
|
|
508
|
-
opts = go 'f:u:m:s:c:C:D:
|
532
|
+
$opts = go 'f:u:m:s:c:C:D:MEvh'
|
509
533
|
|
510
|
-
config = OllamaChatConfig.new(opts[?f])
|
534
|
+
config = OllamaChatConfig.new($opts[?f])
|
511
535
|
$config = config.config
|
512
536
|
|
513
|
-
opts[?h] and usage
|
537
|
+
$opts[?h] and usage
|
514
538
|
|
515
539
|
puts "Configuration read from #{config.filename.inspect} is:", $config
|
516
540
|
|
517
|
-
base_url = opts[?u] || $config.url
|
541
|
+
base_url = $opts[?u] || $config.url
|
518
542
|
$ollama = Client.new(base_url:, debug: $config.debug)
|
519
543
|
|
520
|
-
model = choose_model(opts[?m], $config.model.name)
|
544
|
+
model = choose_model($opts[?m], $config.model.name)
|
521
545
|
options = Options[$config.model.options]
|
522
546
|
model_system = pull_model_unless_present(model, options)
|
523
547
|
messages = []
|
524
548
|
|
525
|
-
if
|
549
|
+
if embedding_enabled?
|
526
550
|
embedding_model = $config.embedding.model.name
|
527
551
|
embedding_model_options = Options[$config.embedding.model.options]
|
528
552
|
pull_model_unless_present(embedding_model, embedding_model_options)
|
529
|
-
collection = opts[?C] || $config.embedding.collection
|
553
|
+
collection = $opts[?C] || $config.embedding.collection
|
530
554
|
$documents = Documents.new(
|
531
555
|
ollama:,
|
532
556
|
model: $config.embedding.model.name,
|
@@ -536,7 +560,7 @@ if $config.embedding.enabled
|
|
536
560
|
redis_url: $config.redis.url?,
|
537
561
|
)
|
538
562
|
|
539
|
-
document_list = opts[?D].to_a
|
563
|
+
document_list = $opts[?D].to_a
|
540
564
|
if document_list.any?(&:empty?)
|
541
565
|
puts "Clearing collection #{bold{collection}}."
|
542
566
|
$documents.clear
|
@@ -564,18 +588,21 @@ else
|
|
564
588
|
$documents = Documents.new(ollama:, model:)
|
565
589
|
end
|
566
590
|
|
567
|
-
if voice = ($config.voice if opts[?v])
|
591
|
+
if voice = ($config.voice if $opts[?v])
|
568
592
|
puts "Using voice #{bold{voice}} to speak."
|
569
593
|
end
|
570
594
|
markdown = set_markdown($config.markdown)
|
571
595
|
|
572
|
-
if opts[?c]
|
573
|
-
messages.concat load_conversation(opts[?c])
|
596
|
+
if $opts[?c]
|
597
|
+
messages.concat load_conversation($opts[?c])
|
574
598
|
else
|
575
599
|
if system = Ollama::Utils::FileArgument.
|
576
|
-
get_file_argument(opts[?s], default: $config.prompts.system? || model_system)
|
600
|
+
get_file_argument($opts[?s], default: $config.prompts.system? || model_system)
|
577
601
|
messages << Message.new(role: 'system', content: system)
|
578
|
-
puts
|
602
|
+
puts <<~end
|
603
|
+
Configured system prompt is:
|
604
|
+
#{italic{Ollama::Utils::Width.wrap(system, percentage: 90)}}
|
605
|
+
end
|
579
606
|
end
|
580
607
|
end
|
581
608
|
|
@@ -601,11 +628,11 @@ loop do
|
|
601
628
|
list_conversation(messages, markdown)
|
602
629
|
next
|
603
630
|
when %r(^/clear$)
|
604
|
-
messages
|
631
|
+
clear_messages(messages)
|
605
632
|
puts "Cleared messages."
|
606
633
|
next
|
607
634
|
when %r(^/clobber$)
|
608
|
-
messages
|
635
|
+
clear_messages(messages)
|
609
636
|
$documents.clear
|
610
637
|
puts "Cleared messages and collection."
|
611
638
|
next
|
@@ -648,6 +675,8 @@ loop do
|
|
648
675
|
puts "Not enough messages in this conversation."
|
649
676
|
redo
|
650
677
|
end
|
678
|
+
parse_content = false
|
679
|
+
content
|
651
680
|
when %r(^/summarize\s+(.+))
|
652
681
|
parse_content = false
|
653
682
|
content = summarize($1) or next
|
@@ -676,9 +705,12 @@ loop do
|
|
676
705
|
when %r(^/)
|
677
706
|
display_chat_help
|
678
707
|
next
|
679
|
-
when
|
708
|
+
when ''
|
680
709
|
puts "Type /quit to quit."
|
681
710
|
next
|
711
|
+
when nil
|
712
|
+
puts "Goodbye."
|
713
|
+
exit 0
|
682
714
|
end
|
683
715
|
|
684
716
|
content, tags = if parse_content
|
@@ -687,7 +719,7 @@ loop do
|
|
687
719
|
[ content, Utils::Tags.new ]
|
688
720
|
end
|
689
721
|
|
690
|
-
if
|
722
|
+
if embedding_enabled? && content
|
691
723
|
records = $documents.find_where(
|
692
724
|
content.downcase,
|
693
725
|
tags:,
|
@@ -695,10 +727,9 @@ loop do
|
|
695
727
|
text_size: $config.embedding.found_texts_size?,
|
696
728
|
text_count: $config.embedding.found_texts_count?,
|
697
729
|
)
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
"#{found_texts.join("\n\n---\n\n")}"
|
730
|
+
unless records.empty?
|
731
|
+
content += "\nConsider these chunks for your answer:\n\n"\
|
732
|
+
"#{records.map { [ _1.text, _1.tags_set ] * ?\n }.join("\n\n---\n\n")}"
|
702
733
|
end
|
703
734
|
end
|
704
735
|
|
@@ -706,8 +737,8 @@ loop do
|
|
706
737
|
handler = FollowChat.new(messages:, markdown:, voice:)
|
707
738
|
ollama.chat(model:, messages:, options:, stream: true, &handler)
|
708
739
|
|
709
|
-
if records
|
710
|
-
puts records.map { |record|
|
740
|
+
if embedding_enabled? && !records.empty?
|
741
|
+
puts "", records.map { |record|
|
711
742
|
link = if record.source =~ %r(\Ahttps?://)
|
712
743
|
record.source
|
713
744
|
else
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Ollama::Documents::Cache::Common
|
2
|
+
attr_writer :prefix
|
3
|
+
|
4
|
+
def collections(prefix)
|
5
|
+
unique = Set.new
|
6
|
+
full_each { |key, _| unique << key[/\A#{prefix}(.*)-/, 1] }
|
7
|
+
unique.map(&:to_sym)
|
8
|
+
end
|
9
|
+
|
10
|
+
def pre(key)
|
11
|
+
[ @prefix, key ].join
|
12
|
+
end
|
13
|
+
|
14
|
+
def unpre(key)
|
15
|
+
key.sub(/\A#@prefix/, '')
|
16
|
+
end
|
17
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
|
+
require 'ollama/documents/cache/common'
|
2
|
+
|
1
3
|
class Ollama::Documents::MemoryCache
|
4
|
+
include Ollama::Documents::Cache::Common
|
5
|
+
|
2
6
|
def initialize(prefix:)
|
3
7
|
@prefix = prefix
|
4
8
|
@data = {}
|
5
9
|
end
|
6
10
|
|
7
|
-
attr_writer :prefix
|
8
|
-
|
9
11
|
def [](key)
|
10
12
|
@data[pre(key)]
|
11
13
|
end
|
@@ -23,11 +25,11 @@ class Ollama::Documents::MemoryCache
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def size
|
26
|
-
|
28
|
+
count
|
27
29
|
end
|
28
30
|
|
29
31
|
def clear
|
30
|
-
@data.
|
32
|
+
@data.delete_if { |key, _| key.start_with?(@prefix) }
|
31
33
|
self
|
32
34
|
end
|
33
35
|
|
@@ -36,11 +38,7 @@ class Ollama::Documents::MemoryCache
|
|
36
38
|
end
|
37
39
|
include Enumerable
|
38
40
|
|
39
|
-
def
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
def unpre(key)
|
44
|
-
key.sub(/\A#@prefix/, '')
|
41
|
+
def full_each(&block)
|
42
|
+
@data.each(&block)
|
45
43
|
end
|
46
44
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
class Ollama::Documents
|
4
|
+
class RedisBackedMemoryCache < MemoryCache
|
5
|
+
def initialize(prefix:, url: ENV['REDIS_URL'])
|
6
|
+
super(prefix:)
|
7
|
+
url or raise ArgumentError, 'require redis url'
|
8
|
+
@prefix, @url = prefix, url
|
9
|
+
@redis_cache = Ollama::Documents::RedisCache.new(prefix:, url:)
|
10
|
+
@redis_cache.full_each do |key, value|
|
11
|
+
@data[key] = value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def redis
|
16
|
+
@redis_cache.redis
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(key, value)
|
20
|
+
super
|
21
|
+
redis.set(pre(key), JSON(value))
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
result = redis.del(pre(key))
|
26
|
+
super
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear
|
31
|
+
redis.scan_each(match: "#@prefix*") { |key| redis.del(key) }
|
32
|
+
super
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def pre(key)
|
37
|
+
[ @prefix, key ].join
|
38
|
+
end
|
39
|
+
|
40
|
+
def unpre(key)
|
41
|
+
key.sub(/\A#@prefix/, '')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,13 +1,14 @@
|
|
1
|
+
require 'ollama/documents/cache/common'
|
1
2
|
require 'redis'
|
2
3
|
|
3
4
|
class Ollama::Documents::RedisCache
|
5
|
+
include Ollama::Documents::Cache::Common
|
6
|
+
|
4
7
|
def initialize(prefix:, url: ENV['REDIS_URL'])
|
5
8
|
url or raise ArgumentError, 'require redis url'
|
6
9
|
@prefix, @url = prefix, url
|
7
10
|
end
|
8
11
|
|
9
|
-
attr_writer :prefix
|
10
|
-
|
11
12
|
def redis
|
12
13
|
@redis ||= Redis.new(url: @url)
|
13
14
|
end
|
@@ -48,11 +49,11 @@ class Ollama::Documents::RedisCache
|
|
48
49
|
end
|
49
50
|
include Enumerable
|
50
51
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
52
|
+
def full_each(&block)
|
53
|
+
redis.scan_each do |key|
|
54
|
+
value = redis.get(key) or next
|
55
|
+
value = JSON(value, object_class: Ollama::Documents::Record)
|
56
|
+
block.(key, value)
|
57
|
+
end
|
57
58
|
end
|
58
59
|
end
|
data/lib/ollama/documents.rb
CHANGED
@@ -3,8 +3,11 @@ require 'digest'
|
|
3
3
|
|
4
4
|
class Ollama::Documents
|
5
5
|
end
|
6
|
-
|
7
|
-
|
6
|
+
class Ollama::Documents::Cache
|
7
|
+
end
|
8
|
+
require 'ollama/documents/cache/memory_cache'
|
9
|
+
require 'ollama/documents/cache/redis_cache'
|
10
|
+
require 'ollama/documents/cache/redis_backed_memory_cache'
|
8
11
|
module Ollama::Documents::Splitters
|
9
12
|
end
|
10
13
|
require 'ollama/documents/splitters/character'
|
@@ -16,11 +19,15 @@ class Ollama::Documents
|
|
16
19
|
|
17
20
|
class Record < JSON::GenericObject
|
18
21
|
def to_s
|
19
|
-
my_tags =
|
22
|
+
my_tags = tags_set
|
20
23
|
my_tags.empty? or my_tags = " #{my_tags}"
|
21
24
|
"#<#{self.class} #{text.inspect}#{my_tags} #{similarity || 'n/a'}>"
|
22
25
|
end
|
23
26
|
|
27
|
+
def tags_set
|
28
|
+
Ollama::Utils::Tags.new(tags)
|
29
|
+
end
|
30
|
+
|
24
31
|
def ==(other)
|
25
32
|
text == other.text
|
26
33
|
end
|
@@ -28,15 +35,19 @@ class Ollama::Documents
|
|
28
35
|
alias inspect to_s
|
29
36
|
end
|
30
37
|
|
31
|
-
def initialize(ollama:, model:, model_options: nil, collection:
|
32
|
-
@ollama, @model, @model_options, @collection = ollama, model, model_options, collection
|
38
|
+
def initialize(ollama:, model:, model_options: nil, collection: default_collection, cache: MemoryCache, redis_url: nil)
|
39
|
+
@ollama, @model, @model_options, @collection = ollama, model, model_options, collection.to_sym
|
33
40
|
@cache, @redis_url = connect_cache(cache), redis_url
|
34
41
|
end
|
35
42
|
|
43
|
+
def default_collection
|
44
|
+
:default
|
45
|
+
end
|
46
|
+
|
36
47
|
attr_reader :ollama, :model, :collection
|
37
48
|
|
38
49
|
def collection=(new_collection)
|
39
|
-
@collection
|
50
|
+
@collection = new_collection.to_sym
|
40
51
|
@cache.prefix = prefix
|
41
52
|
end
|
42
53
|
|
@@ -137,15 +148,7 @@ class Ollama::Documents
|
|
137
148
|
end
|
138
149
|
|
139
150
|
def collections
|
140
|
-
|
141
|
-
when MemoryCache
|
142
|
-
[ @collection ]
|
143
|
-
when RedisCache
|
144
|
-
prefix = '%s-' % self.class
|
145
|
-
Documents::RedisCache.new(prefix:, url: @redis_url).map { _1[/#{prefix}(.*)-/, 1] }.uniq
|
146
|
-
else
|
147
|
-
[]
|
148
|
-
end
|
151
|
+
([ default_collection ] + @cache.collections('%s-' % self.class)).uniq
|
149
152
|
end
|
150
153
|
|
151
154
|
def tags
|
@@ -156,7 +159,7 @@ class Ollama::Documents
|
|
156
159
|
|
157
160
|
def connect_cache(cache_class)
|
158
161
|
cache = nil
|
159
|
-
if cache_class
|
162
|
+
if cache_class.instance_method(:redis)
|
160
163
|
begin
|
161
164
|
cache = cache_class.new(prefix:)
|
162
165
|
cache.size
|
data/lib/ollama/utils/chooser.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'amatch'
|
2
2
|
require 'search_ui'
|
3
|
+
require 'term/ansicolor'
|
3
4
|
|
4
5
|
module Ollama::Utils::Chooser
|
5
6
|
include SearchUI
|
7
|
+
include Term::ANSIColor
|
6
8
|
|
7
9
|
module_function
|
8
10
|
|
@@ -17,7 +19,7 @@ module Ollama::Utils::Chooser
|
|
17
19
|
},
|
18
20
|
query: -> _answer, matches, selector {
|
19
21
|
matches.each_with_index.map { |m, i|
|
20
|
-
i == selector ? "#{
|
22
|
+
i == selector ? "#{blue{?⮕}} #{on_blue{m}}" : " #{m}"
|
21
23
|
} * ?\n
|
22
24
|
},
|
23
25
|
found: -> _answer, matches, selector {
|
@@ -3,11 +3,20 @@ class Ollama::Utils::ColorizeTexts
|
|
3
3
|
include Term::ANSIColor
|
4
4
|
include Ollama::Utils::Width
|
5
5
|
|
6
|
+
# Initializes a new instance of Ollama::Utils::ColorizeTexts
|
7
|
+
#
|
8
|
+
# @param [Array<String>] texts the array of strings to be displayed with colors
|
9
|
+
#
|
10
|
+
# @return [Ollama::Utils::ColorizeTexts] an instance of Ollama::Utils::ColorizeTexts
|
6
11
|
def initialize(*texts)
|
7
|
-
texts
|
12
|
+
texts = texts.map(&:to_a)
|
8
13
|
@texts = Array(texts.flatten)
|
9
14
|
end
|
10
15
|
|
16
|
+
# Returns a string representation of the object, including all texts content,
|
17
|
+
# colored differently and their sizes.
|
18
|
+
#
|
19
|
+
# @return [String] The formatted string.
|
11
20
|
def to_s
|
12
21
|
result = +''
|
13
22
|
@texts.each_with_index do |t, i|
|
@@ -22,6 +31,14 @@ class Ollama::Utils::ColorizeTexts
|
|
22
31
|
|
23
32
|
private
|
24
33
|
|
34
|
+
# Returns the nearest RGB color to the given ANSI color
|
35
|
+
#
|
36
|
+
# @param [color] color The ANSI color attribute
|
37
|
+
#
|
38
|
+
# @return [Array<RGBTriple>] An array containing two RGB colors, one for black and
|
39
|
+
# one for white text, where the first is the closest match to the input color
|
40
|
+
# when printed on a black background, and the second is the closest match
|
41
|
+
# when printed on a white background.
|
25
42
|
def text_color(color)
|
26
43
|
color = Term::ANSIColor::Attribute[color]
|
27
44
|
[
|
@@ -30,6 +47,9 @@ class Ollama::Utils::ColorizeTexts
|
|
30
47
|
].max_by { |t| t.distance_to(color) }
|
31
48
|
end
|
32
49
|
|
50
|
+
# Returns an array of colors for each step in the gradient
|
51
|
+
#
|
52
|
+
# @return [Array<Array<Integer>>] An array of RGB color arrays
|
33
53
|
def colors
|
34
54
|
@colors ||= (0..255).map { |i|
|
35
55
|
[
|