ollama-ruby 0.6.0 → 0.7.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: bb7858cf04c638fa3369fe7d96d96eed12e5553fd17abe9ed48c1eaf25113ffb
4
- data.tar.gz: ccb56d4b85a6e74256feb52ac5642d50850f7b980f9060dbb9fdf933985be746
3
+ metadata.gz: 5bdf85f296b631c52b44984eee6a5e6d98c82a4cfc4a5764529f4d46b3bdaba7
4
+ data.tar.gz: 05e391e492f85f8c6fb1106d49ff4118c26a0922c5978156e2f1364aad82299d
5
5
  SHA512:
6
- metadata.gz: f36aacde399c0be934425f12669fa6cc55cdfad938ea8157c5195db8c2367243cc6470dff24ec5bf83800079732bfa271450a4779246335c45c2bedbb0e3a6cf
7
- data.tar.gz: 56e465f056934210a6cc45677d52518d8adb76e8702561248b6db4cd1e4b129cfd22eedbdcc0c935f69bf85eeb9bc324aef6876f39969942af4529978a277756
6
+ metadata.gz: 7a3f77296a526154ed46860a675515b864baa8c991933e76ceeb178b00037df844f20201ad200cb9df8d4fa105066fb40bb0248db3bb65c5bad81012ce3b7288
7
+ data.tar.gz: 1d25c9420fba86d992b77f29e4f4f57734dcbdaba864650f626af1c828262a9470f9c981da602335f8d3a03ef3cfc9773ad33d90d3e88a70a4d86be0518d762a
data/CHANGES.md CHANGED
@@ -1,5 +1,89 @@
1
1
  # Changes
2
2
 
3
+ ## 2024-10-03 v0.7.0
4
+
5
+ * **Refactor command line interface**
6
+ + Moved case order
7
+ + Renamed `/collection clear [tag]|change` to `/collection (clear|change)`
8
+ + Improved help message, added /info
9
+ * **Update README.md**
10
+ + Update README.md to reflect changed/added commands
11
+ * **Add support for reading PostScript**
12
+ + Extracted `pdf_read` method to read PDF files using `PDF::Reader`
13
+ + Added `ps_read` method to read PostScript files by converting them to PDF with Ghostscript and using `pdf_read`.
14
+ + Updated `parse_source` method to handle PostScript files
15
+ * **Update read buffer size for tempfile writes**
16
+ + Updated `tmp.write` to use a larger buffer size (**16KB**) in IO.popen block.
17
+ * **Refactor Collection Chooser and usages**
18
+ + Added confirmation prompt before clearing collection
19
+ + Improved collection chooser with `[EXIT]` and `[ALL]` options
20
+ + Added `ask?` method for user input
21
+ * **Add prompt to choose method**
22
+ + Added `prompt` parameter to `choose` method in `Ollama::Utils::Chooser`
23
+ + Modified output formatting for selected entry in `choose` method
24
+ + Updated `choose` method to handle cases better where no entry was chosen
25
+ * **Fix Redis cache expiration logic**
26
+ + Update `set` method to delete key expiration time is less than 1 second.
27
+ * **Update dependencies and add source tracking**
28
+ - Remove `sorted_set` dependency from Rakefile
29
+ - Modify `Ollama::Documents` class to track source of tags
30
+ - Update `Ollama::Utils::Tags` class to include source in tag output and add methods for tracking source
31
+ - Update tests for `Ollama::Utils::Tags` class
32
+ * **Refactor width calculation and add tests for wrap and truncate methods.**
33
+ + Extend `Term::ANSIColor` in `Ollama::Utils::Width`
34
+ + Update `width` method to use ellipsis length when truncating text
35
+ + Add tests for `wrap` and `truncate` methods with percentage and length arguments
36
+ * **Add attr_reader for data and update equality check**
37
+ + Added `attr_reader :data` to Ollama::Image class
38
+ + Updated `==` method in Ollama::Image class to use `other.data`
39
+ + Added test case in `image_spec.rb` to verify equality of images
40
+
41
+ ## 2024-09-30 v0.6.0
42
+
43
+ ### Significant Changes
44
+
45
+ * **Added voice toggle and change functionality**:
46
+ + Removed `-v` command line switch
47
+ + Added new Switch class for voice output
48
+ + Added new method `change_voice` to toggle or change voice output
49
+ + Updated `info` method to display current voice output if enabled
50
+ + Updated `display_chat_help` method to include /voice command
51
+ * **Added expiring cache support**:
52
+ + Added `Ollama::Utils::CacheFetcher` class for caching HTTP responses
53
+ + Modified `Ollama::Utils::Fetcher` to use the new cache class
54
+ + Updated `ollama_chat` script to use the cache when fetching sources
55
+ + Added specs for the new cache fetcher class
56
+ * **Added change system prompt feature**:
57
+ + Added `/system` command to change system prompt
58
+ + Implemented `set_system_prompt` and `change_system_prompt` methods in `bin/ollama_chat`
59
+ + Updated help messages in `README.md`
60
+
61
+ ### Other Changes
62
+
63
+ * **Updated dependencies**:
64
+ + Updated version of `xdg` gem to **7.0**
65
+ + Added `xdg` dependency to Rakefile
66
+ * **Refactored error handling**:
67
+ + Warn message updated to include more context about the error
68
+ + `warn` statement now mentions "while pulling model"
69
+ * **Updated chat commands and added clipboard functionality**:
70
+ + Added `/copy` command to copy last response to clipboard
71
+ + Implemented `copy_to_clipboard` method in `ollama_chat`
72
+ + Updated chat help display to include new `/copy` command
73
+ * **Refactored Ollama::Utils::Fetcher**:
74
+ + Made instance methods private and only exposed class methods
75
+ + Added `expose` method to `Ollama::Utils::FetcherSpec` for testing
76
+ * **Added version command to ollama chat binary**:
77
+ + Added `version` method to print Ollama version and exit
78
+ + Updated `$opts` string in `ollama` script to include `-V` option for version command
79
+ + Added call to `version` method when `-V` option is used
80
+ * **Updated system prompt display**:
81
+ + Changed `Ollama::Utils::Width.wrap` to `Ollama::Utils::ANSIMarkdown.parse` in `show_system_prompt` method
82
+ * **Added system prompt configuration via search_ui for ? argument value**:
83
+ + Added `show_system_prompt` method to print configured system prompt
84
+ + Modified `info` method to include system prompt in output
85
+ + Implemented option `-s ?` to choose or specify system prompt
86
+
3
87
  ## 2024-09-26 v0.5.0
4
88
 
5
89
  ### New Features
data/README.md CHANGED
@@ -34,7 +34,7 @@ This a chat client, that can be used to connect to an ollama server and enter a
34
34
  chat converstation with a LLM. It can be called with the following arguments:
35
35
 
36
36
  ```
37
- ollama_chat [OPTIONS]
37
+ Usage: ollama_chat [OPTIONS]
38
38
 
39
39
  -f CONFIG config file to read
40
40
  -u URL the ollama base url, OLLAMA_URL
@@ -42,9 +42,10 @@ ollama_chat [OPTIONS]
42
42
  -s SYSTEM the system prompt to use as a file, OLLAMA_CHAT_SYSTEM
43
43
  -c CHAT a saved chat conversation to load
44
44
  -C COLLECTION name of the collection used in this conversation
45
- -D DOCUMENT load document and add to collection (multiple)
45
+ -D DOCUMENT load document and add to embeddings collection (multiple)
46
46
  -M use (empty) MemoryCache for this chat session
47
47
  -E disable embeddings for this chat session
48
+ -V display the current version number and quit
48
49
  -h this help
49
50
  ```
50
51
 
@@ -157,6 +158,7 @@ The following commands can be given inside the chat, if prefixed by a `/`:
157
158
  /paste to paste content
158
159
  /markdown toggle markdown output
159
160
  /stream toggle stream output
161
+ /location toggle location submission
160
162
  /voice( change) toggle voice output or change the voice
161
163
  /list [n] list the last n / all conversation exchanges
162
164
  /clear clear the whole conversation
@@ -165,7 +167,8 @@ The following commands can be given inside the chat, if prefixed by a `/`:
165
167
  /model change the model
166
168
  /system change system prompt (clears conversation)
167
169
  /regenerate the last answer message
168
- /collection clear [tag]|change clear or show stats of current collection
170
+ /collection( clear|change) change (default) collection or clear
171
+ /info show information for current session
169
172
  /import source import the source's content
170
173
  /summarize [n] source summarize the source's content in n words
171
174
  /embedding toggle embedding paused or not
data/Rakefile CHANGED
@@ -32,7 +32,6 @@ GemHadar do
32
32
  dependency 'redis', '~> 5.0'
33
33
  dependency 'numo-narray', '~> 0.9'
34
34
  dependency 'more_math', '~> 1.1'
35
- dependency 'sorted_set', '~> 1.0'
36
35
  dependency 'mime-types', '~> 3.0'
37
36
  dependency 'reverse_markdown', '~> 2.0'
38
37
  dependency 'complex_config', '~> 0.22'
@@ -42,6 +41,7 @@ GemHadar do
42
41
  dependency 'logger', '~> 1.0'
43
42
  dependency 'json', '~> 2.0'
44
43
  dependency 'xdg', '~> 7.0'
44
+ dependency 'tins', '~> 1.34'
45
45
  development_dependency 'all_images', '~> 0.4'
46
46
  development_dependency 'rspec', '~> 3.2'
47
47
  development_dependency 'webmock'
data/bin/ollama_chat CHANGED
@@ -4,9 +4,6 @@ require 'ollama'
4
4
  include Ollama
5
5
  require 'term/ansicolor'
6
6
  include Term::ANSIColor
7
- require 'tins'
8
- require 'tins/xt/full'
9
- require 'tins/xt/hash_union'
10
7
  include Tins::GO
11
8
  require 'reline'
12
9
  require 'reverse_markdown'
@@ -31,6 +28,11 @@ class OllamaChatConfig
31
28
  name: <%= ENV.fetch('OLLAMA_CHAT_MODEL', 'llama3.1') %>
32
29
  options:
33
30
  num_ctx: 8192
31
+ location:
32
+ enabled: false
33
+ name: Berlin
34
+ decimal_degrees: [ 52.514127, 13.475211 ]
35
+ units: SI (International System of Units) # or USCS (United States Customary System)
34
36
  prompts:
35
37
  embed: "This source was now embedded: %{source}"
36
38
  summarize: |
@@ -83,7 +85,7 @@ class OllamaChatConfig
83
85
  if @filename == default_path && !retried
84
86
  retried = true
85
87
  mkdir_p File.dirname(default_path)
86
- File.secure_write(default_path.to_s, DEFAULT_CONFIG)
88
+ File.secure_write(default_path, DEFAULT_CONFIG)
87
89
  retry
88
90
  else
89
91
  raise
@@ -178,7 +180,7 @@ end
178
180
 
179
181
  class Switch
180
182
  def initialize(name, msg:, config: $config)
181
- @value = !!config.send("#{name}?")
183
+ @value = [ false, true ].include?(config) ? config : !!config.send("#{name}?")
182
184
  @msg = msg
183
185
  end
184
186
 
@@ -259,9 +261,21 @@ def setup_switches
259
261
  false => "Embedding is currently not performed.",
260
262
  }
261
263
  )
264
+
265
+ $location = Switch.new(
266
+ :location,
267
+ msg: {
268
+ true => "Location and localtime enabled.",
269
+ false => "Location and localtime disabled.",
270
+ },
271
+ config: $config.location.enabled
272
+ )
262
273
  end
263
274
 
264
275
  def search_web(query, n = nil)
276
+ if l = at_location
277
+ query += " #{at_location}"
278
+ end
265
279
  n = n.to_i
266
280
  n < 1 and n = 1
267
281
  query = URI.encode_uri_component(query)
@@ -401,6 +415,32 @@ def parse_atom(source_io)
401
415
  end
402
416
  end
403
417
 
418
+ def pdf_read(io)
419
+ reader = PDF::Reader.new(io)
420
+ reader.pages.inject(+'') { |result, page| result << page.text }
421
+ end
422
+
423
+ def ps_read(io)
424
+ gs = `which gs`.chomp
425
+ if gs.present?
426
+ Tempfile.create do |tmp|
427
+ IO.popen("#{gs} -q -sDEVICE=pdfwrite -sOutputFile=#{tmp.path} -", 'wb') do |gs_io|
428
+ until io.eof?
429
+ buffer = io.read(1 << 17)
430
+ IO.select(nil, [ gs_io ], nil)
431
+ gs_io.write buffer
432
+ end
433
+ gs_io.close
434
+ File.open(tmp.path, 'rb') do |pdf|
435
+ pdf_read(pdf)
436
+ end
437
+ end
438
+ end
439
+ else
440
+ STDERR.puts "Cannot convert #{io&.content_type} whith ghostscript, gs not in path."
441
+ end
442
+ end
443
+
404
444
  def parse_source(source_io)
405
445
  case source_io&.content_type
406
446
  when 'text/html'
@@ -426,12 +466,11 @@ def parse_source(source_io)
426
466
  parse_rss(source_io)
427
467
  when 'application/atom+xml'
428
468
  parse_atom(source_io)
429
- when 'application/json'
430
- source_io.read
469
+ when 'application/postscript'
470
+ ps_read(source_io)
431
471
  when 'application/pdf'
432
- reader = PDF::Reader.new(source_io)
433
- reader.pages.inject(+'') { |result, page| result << page.text }
434
- when %r(\Atext/), nil
472
+ pdf_read(source_io)
473
+ when %r(\Aapplication/(json|ld\+json|x-ruby|x-perl|x-gawk|x-python|x-javascript|x-c?sh|x-dosexec|x-shellscript|x-tex|x-latex|x-lyx|x-bibtex)), %r(\Atext/), nil
435
474
  source_io.read
436
475
  else
437
476
  STDERR.puts "Cannot embed #{source_io&.content_type} document."
@@ -478,7 +517,7 @@ def embed_source(source_io, source)
478
517
  length: 10
479
518
  )
480
519
  end
481
- $documents.add(inputs, source: source)
520
+ $documents.add(inputs, source:)
482
521
  end
483
522
 
484
523
  def add_image(images, source_io, source)
@@ -576,7 +615,7 @@ def parse_content(content, images)
576
615
  content.scan(%r([.~]?/\S+|https?://\S+|#\S+)).each do |source|
577
616
  case source
578
617
  when /\A#(\S+)/
579
- tags << $1
618
+ tags.add($1, source:)
580
619
  else
581
620
  source = source.sub(/(["')]|\*+)\z/, '')
582
621
  fetch_source(source) do |source_io|
@@ -598,29 +637,37 @@ def parse_content(content, images)
598
637
  return content, (tags unless tags.empty?)
599
638
  end
600
639
 
601
- def choose_model(cli_model, default_model)
640
+ def choose_model(cli_model, current_model)
602
641
  models = ollama.tags.models.map(&:name).sort
603
642
  model = if cli_model == ''
604
- Ollama::Utils::Chooser.choose(models) || default_model
643
+ Ollama::Utils::Chooser.choose(models) || current_model
605
644
  else
606
- cli_model || default_model
645
+ cli_model || current_model
607
646
  end
608
647
  ensure
609
648
  puts green { "Connecting to #{model}@#{ollama.base_url} now…" }
610
649
  end
611
650
 
612
- def choose_collection(default_collection)
613
- collections = [ default_collection ] + $documents.collections
651
+ def ask?(prompt:)
652
+ print prompt
653
+ STDIN.gets.chomp
654
+ end
655
+
656
+ def choose_collection(current_collection)
657
+ collections = [ current_collection ] + $documents.collections
614
658
  collections = collections.compact.map(&:to_s).uniq.sort
615
- collections.unshift('[NEW]')
616
- collection = Ollama::Utils::Chooser.choose(collections) || default_collection
617
- if collection == '[NEW]'
618
- print "Enter name of the new collection: "
619
- collection = STDIN.gets.chomp
659
+ collections.unshift('[EXIT]').unshift('[NEW]')
660
+ collection = Ollama::Utils::Chooser.choose(collections) || current_collection
661
+ case collection
662
+ when '[NEW]'
663
+ $documents.collection = ask?(prompt: "Enter name of the new collection: ")
664
+ when nil, '[EXIT]'
665
+ puts "Exiting chooser."
666
+ when /./
667
+ $documents.collection = collection
620
668
  end
621
- $documents.collection = collection
622
669
  ensure
623
- puts "Changing to collection #{bold{collection}}."
670
+ puts "Using collection #{bold{$documents.collection}}."
624
671
  collection_stats
625
672
  end
626
673
 
@@ -652,13 +699,25 @@ def show_system_prompt
652
699
  EOT
653
700
  end
654
701
 
702
+ def at_location
703
+ if $location.on?
704
+ location_name = $config.location.name
705
+ location_decimal_degrees = $config.location.decimal_degrees * ', '
706
+ localtime = Time.now.iso8601
707
+ units = $config.location.units
708
+ $config.prompts.location % {
709
+ location_name:, location_decimal_degrees:, localtime:, units:,
710
+ }
711
+ end.to_s
712
+ end
713
+
655
714
  def set_system_prompt(messages, system)
656
715
  $system = system
657
716
  messages.clear
658
717
  messages << Message.new(role: 'system', content: system)
659
718
  end
660
719
 
661
- def change_system_prompt(messages)
720
+ def change_system_prompt(messages, default)
662
721
  prompts = $config.system_prompts.attribute_names.compact
663
722
  chosen = Ollama::Utils::Chooser.choose(prompts)
664
723
  system = if chosen
@@ -684,6 +743,7 @@ def info
684
743
  puts "Documents database cache is #{$documents.nil? ? 'n/a' : bold{$documents.cache.class}}"
685
744
  $markdown.show
686
745
  $stream.show
746
+ $location.show
687
747
  if $voice.on?
688
748
  puts "Using voice #{bold{$current_voice}} to speak."
689
749
  end
@@ -716,6 +776,7 @@ def display_chat_help
716
776
  /paste to paste content
717
777
  /markdown toggle markdown output
718
778
  /stream toggle stream output
779
+ /location toggle location submission
719
780
  /voice( change) toggle voice output or change the voice
720
781
  /list [n] list the last n / all conversation exchanges
721
782
  /clear clear the whole conversation
@@ -724,7 +785,8 @@ def display_chat_help
724
785
  /model change the model
725
786
  /system change system prompt (clears conversation)
726
787
  /regenerate the last answer message
727
- /collection clear [tag]|change clear or show stats of current collection
788
+ /collection( clear|change) change (default) collection or clear
789
+ /info show information for current session
728
790
  /import source import the source's content
729
791
  /summarize [n] source summarize the source's content in n words
730
792
  /embedding toggle embedding paused or not
@@ -739,7 +801,7 @@ end
739
801
 
740
802
  def usage
741
803
  puts <<~EOT
742
- #{File.basename($0)} [OPTIONS]
804
+ Usage: #{File.basename($0)} [OPTIONS]
743
805
 
744
806
  -f CONFIG config file to read
745
807
  -u URL the ollama base url, OLLAMA_URL
@@ -790,7 +852,7 @@ if $opts[?c]
790
852
  else
791
853
  default = $config.system_prompts.default? || model_system
792
854
  if $opts[?s] == ??
793
- change_system_prompt(messages)
855
+ change_system_prompt(messages, default)
794
856
  else
795
857
  system = Ollama::Utils::FileArgument.get_file_argument($opts[?s], default:)
796
858
  system.present? and set_system_prompt(messages, system)
@@ -860,18 +922,21 @@ loop do
860
922
  content = Reline.readline(input_prompt, true)&.chomp
861
923
 
862
924
  case content
863
- when %r(^/paste$)
864
- puts bold { "Paste your content and then press C-d!" }
865
- content = STDIN.read
866
925
  when %r(^/copy$)
867
926
  copy_to_clipboard(messages)
868
927
  next
928
+ when %r(^/paste$)
929
+ puts bold { "Paste your content and then press C-d!" }
930
+ content = STDIN.read
869
931
  when %r(^/markdown$)
870
932
  $markdown.toggle
871
933
  next
872
934
  when %r(^/stream$)
873
935
  $stream.toggle
874
936
  next
937
+ when %r(^/location$)
938
+ $location.toggle
939
+ next
875
940
  when %r(^/voice(?:\s+(change))?$)
876
941
  if $1 == 'change'
877
942
  change_voice
@@ -890,33 +955,14 @@ loop do
890
955
  puts "Cleared messages."
891
956
  next
892
957
  when %r(^/clobber$)
893
- clear_messages(messages)
894
- $documents.clear
895
- puts "Cleared messages and collection."
896
- next
897
- when %r(^/collection\s+(clear|change)(?:\s+(.+))?$)
898
- command, arg = $1, $2
899
- case command
900
- when 'clear'
901
- tags = arg.present? ? arg.sub(/\A#*/, '') : nil
902
- if tags
903
- $documents.clear(tags:)
904
- puts "Cleared tag ##{tags} from collection #{bold{collection}}."
905
- else
906
- $documents.clear
907
- puts "Cleared collection #{bold{collection}}."
908
- end
909
- when 'change'
910
- choose_collection(collection)
958
+ if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
959
+ clear_messages(messages)
960
+ $documents.clear
961
+ puts "Cleared messages and collection #{bold{$documents.collection}}."
962
+ else
963
+ puts 'Cancelled.'
911
964
  end
912
965
  next
913
- when %r(^/system$)
914
- change_system_prompt(messages)
915
- info
916
- next
917
- when %r(/info)
918
- info
919
- next
920
966
  when %r(^/pop(?:\s+(\d*))?$)
921
967
  if messages.size > 1
922
968
  n = $1.to_i.clamp(1, Float::INFINITY)
@@ -931,6 +977,10 @@ loop do
931
977
  when %r(^/model$)
932
978
  $model = choose_model('', $model)
933
979
  next
980
+ when %r(^/system$)
981
+ change_system_prompt(messages, $system)
982
+ info
983
+ next
934
984
  when %r(^/regenerate$)
935
985
  if content = messages[-2]&.content
936
986
  content.gsub!(/\nConsider these chunks for your answer.*\z/, '')
@@ -941,6 +991,38 @@ loop do
941
991
  end
942
992
  parse_content = false
943
993
  content
994
+ when %r(^/collection(?:\s+(clear|change))?$)
995
+ case $1 || 'change'
996
+ when 'clear'
997
+ loop do
998
+ tags = $documents.tags.add('[EXIT]').add('[ALL]')
999
+ tag = Ollama::Utils::Chooser.choose(tags, prompt: 'Clear? %s')
1000
+ case tag
1001
+ when nil, '[EXIT]'
1002
+ puts "Exiting chooser."
1003
+ break
1004
+ when '[ALL]'
1005
+ if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
1006
+ $documents.clear
1007
+ puts "Cleared collection #{bold{$documents.collection}}."
1008
+ break
1009
+ else
1010
+ puts 'Cancelled.'
1011
+ sleep 3
1012
+ end
1013
+ when /./
1014
+ $documents.clear(tags: [ tag ])
1015
+ puts "Cleared tag #{tag} from collection #{bold{$documents.collection}}."
1016
+ sleep 3
1017
+ end
1018
+ end
1019
+ when 'change'
1020
+ choose_collection($documents.collection)
1021
+ end
1022
+ next
1023
+ when %r(/info)
1024
+ info
1025
+ next
944
1026
  when %r(^/import\s+(.+))
945
1027
  parse_content = false
946
1028
  content = import($1) or next
@@ -1007,6 +1089,10 @@ loop do
1007
1089
  end
1008
1090
  end
1009
1091
 
1092
+ if location = at_location.full?
1093
+ content += " [#{location} – do not comment on this information, just consider it for eventual queries]"
1094
+ end
1095
+
1010
1096
  messages << Message.new(role: 'user', content:, images: images.dup)
1011
1097
  images.clear
1012
1098
  handler = FollowChat.new(messages:, markdown: $markdown.on?, voice: ($current_voice if $voice.on?))
data/bin/ollama_cli CHANGED
@@ -8,8 +8,8 @@ include Tins::GO
8
8
  require 'json'
9
9
 
10
10
  def usage
11
- puts <<~end
12
- #{File.basename($0)} [OPTIONS]
11
+ puts <<~EOT
12
+ Usage: #{File.basename($0)} [OPTIONS]
13
13
 
14
14
  -u URL the ollama base url, OLLAMA_URL
15
15
  -m MODEL the ollama model to chat with, OLLAMA_MODEL
@@ -22,7 +22,7 @@ def usage
22
22
  -S use streaming for generation
23
23
  -h this help
24
24
 
25
- end
25
+ EOT
26
26
  exit 0
27
27
  end
28
28
 
data/bin/ollama_update CHANGED
@@ -14,4 +14,6 @@ ollama.tags.models.each do |model|
14
14
  "Updating model #{bold {name}} (last modified at #{modified_at.iso8601}):"
15
15
  )
16
16
  ollama.pull(name:)
17
+ rescue Ollama::Errors::Error => e
18
+ infobar.puts "Caught #{e.class} for model #{bold { model.name }}: #{e} => Continuing."
17
19
  end
data/lib/ollama/client.rb CHANGED
@@ -1,6 +1,3 @@
1
- require 'tins/xt/string_camelize'
2
- require 'tins/annotate'
3
-
4
1
  class Ollama::Client
5
2
  end
6
3
  require 'ollama/client/doc'
@@ -23,7 +23,17 @@ class Ollama::Documents::RedisCache
23
23
  end
24
24
 
25
25
  def []=(key, value)
26
- redis.set(pre(key), JSON.generate(value), ex: @ex)
26
+ set(key, value)
27
+ end
28
+
29
+ def set(key, value, ex: nil)
30
+ ex ||= @ex
31
+ if !ex.nil? && ex < 1
32
+ redis.del(pre(key))
33
+ else
34
+ redis.set(pre(key), JSON.generate(value), ex:)
35
+ end
36
+ value
27
37
  end
28
38
 
29
39
  def ttl(key)
@@ -25,7 +25,7 @@ class Ollama::Documents
25
25
  end
26
26
 
27
27
  def tags_set
28
- Ollama::Utils::Tags.new(tags)
28
+ Ollama::Utils::Tags.new(tags, source:)
29
29
  end
30
30
 
31
31
  def ==(other)
@@ -57,8 +57,10 @@ class Ollama::Documents
57
57
 
58
58
  def add(inputs, batch_size: 10, source: nil, tags: [])
59
59
  inputs = Array(inputs)
60
- tags = Ollama::Utils::Tags.new(tags)
61
- source and tags.add File.basename(source).gsub(/\?.*/, '')
60
+ tags = Ollama::Utils::Tags.new(tags, source:)
61
+ if source
62
+ tags.add(File.basename(source).gsub(/\?.*/, ''), source:)
63
+ end
62
64
  inputs.map! { |i|
63
65
  text = i.respond_to?(:read) ? i.read : i.to_s
64
66
  text
@@ -70,7 +72,7 @@ class Ollama::Documents
70
72
  end
71
73
  batches = inputs.each_slice(batch_size).
72
74
  with_infobar(
73
- label: "Add #{truncate(tags.to_s, percentage: 25)}",
75
+ label: "Add #{truncate(tags.to_s(link: false), percentage: 25)}",
74
76
  total: inputs.size
75
77
  )
76
78
  batches.each do |batch|
@@ -159,7 +161,11 @@ class Ollama::Documents
159
161
  end
160
162
 
161
163
  def tags
162
- @cache.inject(Ollama::Utils::Tags.new) { |t, (_, record)| t.merge(record.tags) }
164
+ @cache.each_with_object(Ollama::Utils::Tags.new) do |(_, record), t|
165
+ record.tags.each do |tag|
166
+ t.add(tag, source: record.source)
167
+ end
168
+ end
163
169
  end
164
170
 
165
171
  private
data/lib/ollama/image.rb CHANGED
@@ -7,6 +7,8 @@ class Ollama::Image
7
7
 
8
8
  attr_accessor :path
9
9
 
10
+ attr_reader :data
11
+
10
12
  class << self
11
13
  def for_base64(data, path: nil)
12
14
  obj = new(data)
@@ -31,7 +33,7 @@ class Ollama::Image
31
33
  end
32
34
 
33
35
  def ==(other)
34
- @data == other..data
36
+ @data == other.data
35
37
  end
36
38
 
37
39
  def to_s
@@ -13,7 +13,7 @@ class Ollama::Utils::CacheFetcher
13
13
  if body && content_type
14
14
  io = StringIO.new(body)
15
15
  io.rewind
16
- io.extend(Ollama::Utils::Fetcher::ContentType)
16
+ io.extend(Ollama::Utils::Fetcher::HeaderExtension)
17
17
  io.content_type = content_type
18
18
  block.(io)
19
19
  end
@@ -25,8 +25,8 @@ class Ollama::Utils::CacheFetcher
25
25
  body.empty? and return
26
26
  content_type = io.content_type
27
27
  content_type.nil? and return
28
- @cache[key(:body, url)] = body
29
- @cache[key(:content_type, url)] = content_type.to_s
28
+ @cache.set(key(:body, url), body, ex: io.ex)
29
+ @cache.set(key(:content_type, url), content_type.to_s, ex: io.ex)
30
30
  self
31
31
  end
32
32
 
@@ -8,8 +8,9 @@ module Ollama::Utils::Chooser
8
8
 
9
9
  module_function
10
10
 
11
- def choose(entries)
11
+ def choose(entries, prompt: 'Search? %s')
12
12
  entry = Search.new(
13
+ prompt:,
13
14
  match: -> answer {
14
15
  matcher = Amatch::PairDistance.new(answer.downcase)
15
16
  matches = entries.map { |n| [ n, -matcher.similar(n.to_s.downcase) ] }.
@@ -19,7 +20,7 @@ module Ollama::Utils::Chooser
19
20
  },
20
21
  query: -> _answer, matches, selector {
21
22
  matches.each_with_index.map { |m, i|
22
- i == selector ? "#{blue{?⮕}} #{on_blue{m}}" : " #{m}"
23
+ i == selector ? "#{blue{?⮕}} #{on_blue{m}}" : " #{m.to_s}"
23
24
  } * ?\n
24
25
  },
25
26
  found: -> _answer, matches, selector {
@@ -27,6 +28,11 @@ module Ollama::Utils::Chooser
27
28
  },
28
29
  output: STDOUT
29
30
  ).start
30
- return entry if entry
31
+ if entry
32
+ entry
33
+ else
34
+ print clear_screen, move_home
35
+ nil
36
+ end
31
37
  end
32
38
  end
@@ -6,9 +6,11 @@ require 'stringio'
6
6
  require 'ollama/utils/cache_fetcher'
7
7
 
8
8
  class Ollama::Utils::Fetcher
9
- module ContentType
9
+ module HeaderExtension
10
10
  attr_accessor :content_type
11
11
 
12
+ attr_accessor :ex
13
+
12
14
  def self.failed
13
15
  object = StringIO.new.extend(self)
14
16
  object.content_type = MIME::Types['text/plain'].first
@@ -22,7 +24,7 @@ class Ollama::Utils::Fetcher
22
24
  cache = options.delete(:cache) and
23
25
  cache = Ollama::Utils::CacheFetcher.new(cache)
24
26
  if result = cache&.get(url, &block)
25
- infobar.puts "Getting #{url.inspect} from cache."
27
+ infobar.puts "Getting #{url.to_s.inspect} from cache."
26
28
  return result
27
29
  else
28
30
  new(**options).send(:get, url) do |tmp|
@@ -39,7 +41,7 @@ class Ollama::Utils::Fetcher
39
41
  def self.read(filename, &block)
40
42
  if File.exist?(filename)
41
43
  File.open(filename) do |file|
42
- file.extend(Ollama::Utils::Fetcher::ContentType)
44
+ file.extend(Ollama::Utils::Fetcher::HeaderExtension)
43
45
  file.content_type = MIME::Types.type_for(filename).first
44
46
  block.(file)
45
47
  end
@@ -50,10 +52,10 @@ class Ollama::Utils::Fetcher
50
52
  Tempfile.open do |tmp|
51
53
  IO.popen(command) do |command|
52
54
  until command.eof?
53
- tmp.write command.read(4096)
55
+ tmp.write command.read(1 << 14)
54
56
  end
55
57
  tmp.rewind
56
- tmp.extend(Ollama::Utils::Fetcher::ContentType)
58
+ tmp.extend(Ollama::Utils::Fetcher::HeaderExtension)
57
59
  tmp.content_type = MIME::Types['text/plain'].first
58
60
  block.(tmp)
59
61
  end
@@ -63,7 +65,7 @@ class Ollama::Utils::Fetcher
63
65
  if @debug && !e.is_a?(RuntimeError)
64
66
  STDERR.puts "#{e.backtrace * ?\n}"
65
67
  end
66
- yield ContentType.failed
68
+ yield HeaderExtension.failed
67
69
  end
68
70
 
69
71
  def initialize(debug: false, http_options: {})
@@ -110,7 +112,7 @@ class Ollama::Utils::Fetcher
110
112
  if @debug && !e.is_a?(RuntimeError)
111
113
  STDERR.puts "#{e.backtrace * ?\n}"
112
114
  end
113
- yield ContentType.failed
115
+ yield HeaderExtension.failed
114
116
  end
115
117
 
116
118
  def headers
@@ -123,12 +125,20 @@ class Ollama::Utils::Fetcher
123
125
  (Excon.defaults[:middlewares] + [ Excon::Middleware::RedirectFollower ]).uniq
124
126
  end
125
127
 
128
+ private
129
+
126
130
  def decorate_io(tmp, response)
127
131
  tmp.rewind
128
- tmp.extend(ContentType)
132
+ tmp.extend(HeaderExtension)
129
133
  if content_type = MIME::Types[response.headers['content-type']].first
130
134
  tmp.content_type = content_type
131
135
  end
136
+ if cache_control = response.headers['cache-control'] and
137
+ cache_control !~ /no-store|no-cache/ and
138
+ ex = cache_control[/s-maxage\s*=\s*(\d+)/, 1] || cache_control[/max-age\s*=\s*(\d+)/, 1]
139
+ then
140
+ tmp.ex = ex.to_i
141
+ end
132
142
  end
133
143
 
134
144
  def callback(tmp)
@@ -1,12 +1,66 @@
1
- require 'sorted_set'
1
+ class Ollama::Utils::Tags
2
+ class Tag < String
3
+ include Term::ANSIColor
2
4
 
5
+ def initialize(tag, source: nil)
6
+ super(tag.to_s)
7
+ self.source = source
8
+ end
3
9
 
4
- class Ollama::Utils::Tags < SortedSet
5
- def to_a
6
- super.map(&:to_s)
10
+ attr_accessor :source
11
+
12
+ alias_method :internal, :to_s
13
+
14
+ def to_s(link: true)
15
+ tag_string = start_with?(?#) ? super() : ?# + super()
16
+ my_source = source
17
+ if link && my_source
18
+ unless my_source =~ %r(\A(https?|file)://)
19
+ my_source = 'file://%s' % File.expand_path(my_source)
20
+ end
21
+ hyperlink(my_source) { tag_string }
22
+ else
23
+ tag_string
24
+ end
25
+ end
26
+ end
27
+
28
+ def initialize(tags = [], source: nil)
29
+ @set = []
30
+ tags.each { |tag| add(tag, source:) }
31
+ end
32
+
33
+ def add(tag, source: nil)
34
+ unless tag.is_a?(Tag)
35
+ tag = Tag.new(tag, source:)
36
+ end
37
+ index = @set.bsearch_index { _1 >= tag }
38
+ if index == nil
39
+ @set.push(tag)
40
+ elsif @set.at(index) != tag
41
+ @set.insert(index, tag)
42
+ end
43
+ self
44
+ end
45
+
46
+ def empty?
47
+ @set.empty?
48
+ end
49
+
50
+ def size
51
+ @set.size
52
+ end
53
+
54
+ def clear
55
+ @set.clear
56
+ end
57
+
58
+ def each(&block)
59
+ @set.each(&block)
7
60
  end
61
+ include Enumerable
8
62
 
9
- def to_s
10
- map { |t| '#%s' % t } * ' '
63
+ def to_s(link: true)
64
+ @set.map { |tag| tag.to_s(link:) } * ' '
11
65
  end
12
66
  end
@@ -2,6 +2,7 @@ require 'tins/terminal'
2
2
 
3
3
  module Ollama::Utils::Width
4
4
  include Term::ANSIColor
5
+ extend Term::ANSIColor
5
6
 
6
7
  module_function
7
8
 
@@ -26,10 +27,11 @@ module Ollama::Utils::Width
26
27
  percentage.nil? ^ length.nil? or
27
28
  raise ArgumentError, "either pass percentage or length argument"
28
29
  percentage and length ||= width(percentage:)
29
- if length < 1
30
+ ellipsis_length = ellipsis.size
31
+ if length < ellipsis_length
30
32
  +''
31
- elsif text.size > length
32
- text[0, length - 1] + ?…
33
+ elsif text.size >= length + ellipsis_length
34
+ text[0, length - ellipsis_length] + ellipsis
33
35
  else
34
36
  text
35
37
  end
@@ -1,6 +1,6 @@
1
1
  module Ollama
2
2
  # Ollama version
3
- VERSION = '0.6.0'
3
+ VERSION = '0.7.0'
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/lib/ollama.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  require 'json'
2
2
  require 'logger'
3
3
  require 'excon'
4
+ require 'tins'
5
+ require 'tins/xt/full'
6
+ require 'tins/xt/hash_union'
7
+ require 'tins/xt/string_camelize'
4
8
 
5
9
  module Ollama
6
10
  end
data/ollama-ruby.gemspec CHANGED
@@ -1,26 +1,26 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: ollama-ruby 0.6.0 ruby lib
2
+ # stub: ollama-ruby 0.7.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "ollama-ruby".freeze
6
- s.version = "0.6.0".freeze
6
+ s.version = "0.7.0".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]
10
10
  s.authors = ["Florian Frank".freeze]
11
- s.date = "2024-09-30"
11
+ s.date = "2024-10-02"
12
12
  s.description = "Library that allows interacting with the Ollama API".freeze
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.executables = ["ollama_console".freeze, "ollama_chat".freeze, "ollama_update".freeze, "ollama_cli".freeze]
15
15
  s.extra_rdoc_files = ["README.md".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/documents.rb".freeze, "lib/ollama/documents/cache/common.rb".freeze, "lib/ollama/documents/cache/memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_backed_memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_cache.rb".freeze, "lib/ollama/documents/splitters/character.rb".freeze, "lib/ollama/documents/splitters/semantic.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/utils/ansi_markdown.rb".freeze, "lib/ollama/utils/cache_fetcher.rb".freeze, "lib/ollama/utils/chooser.rb".freeze, "lib/ollama/utils/colorize_texts.rb".freeze, "lib/ollama/utils/fetcher.rb".freeze, "lib/ollama/utils/file_argument.rb".freeze, "lib/ollama/utils/math.rb".freeze, "lib/ollama/utils/tags.rb".freeze, "lib/ollama/utils/width.rb".freeze, "lib/ollama/version.rb".freeze]
16
- s.files = [".envrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/ollama_chat".freeze, "bin/ollama_cli".freeze, "bin/ollama_console".freeze, "bin/ollama_update".freeze, "config/redis.conf".freeze, "docker-compose.yml".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/documents.rb".freeze, "lib/ollama/documents/cache/common.rb".freeze, "lib/ollama/documents/cache/memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_backed_memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_cache.rb".freeze, "lib/ollama/documents/splitters/character.rb".freeze, "lib/ollama/documents/splitters/semantic.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/utils/ansi_markdown.rb".freeze, "lib/ollama/utils/cache_fetcher.rb".freeze, "lib/ollama/utils/chooser.rb".freeze, "lib/ollama/utils/colorize_texts.rb".freeze, "lib/ollama/utils/fetcher.rb".freeze, "lib/ollama/utils/file_argument.rb".freeze, "lib/ollama/utils/math.rb".freeze, "lib/ollama/utils/tags.rb".freeze, "lib/ollama/utils/width.rb".freeze, "lib/ollama/version.rb".freeze, "ollama-ruby.gemspec".freeze, "spec/assets/embeddings.json".freeze, "spec/assets/kitten.jpg".freeze, "spec/assets/prompt.txt".freeze, "spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/documents/memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_backed_memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_cache_spec.rb".freeze, "spec/ollama/documents/splitters/character_spec.rb".freeze, "spec/ollama/documents/splitters/semantic_spec.rb".freeze, "spec/ollama/documents_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/ollama/utils/ansi_markdown_spec.rb".freeze, "spec/ollama/utils/cache_fetcher_spec.rb".freeze, "spec/ollama/utils/fetcher_spec.rb".freeze, "spec/ollama/utils/file_argument_spec.rb".freeze, "spec/ollama/utils/tags_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tmp/.keep".freeze]
16
+ s.files = [".envrc".freeze, "CHANGES.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "bin/ollama_chat".freeze, "bin/ollama_cli".freeze, "bin/ollama_console".freeze, "bin/ollama_update".freeze, "config/redis.conf".freeze, "docker-compose.yml".freeze, "lib/ollama.rb".freeze, "lib/ollama/client.rb".freeze, "lib/ollama/client/command.rb".freeze, "lib/ollama/client/doc.rb".freeze, "lib/ollama/commands/chat.rb".freeze, "lib/ollama/commands/copy.rb".freeze, "lib/ollama/commands/create.rb".freeze, "lib/ollama/commands/delete.rb".freeze, "lib/ollama/commands/embed.rb".freeze, "lib/ollama/commands/embeddings.rb".freeze, "lib/ollama/commands/generate.rb".freeze, "lib/ollama/commands/ps.rb".freeze, "lib/ollama/commands/pull.rb".freeze, "lib/ollama/commands/push.rb".freeze, "lib/ollama/commands/show.rb".freeze, "lib/ollama/commands/tags.rb".freeze, "lib/ollama/documents.rb".freeze, "lib/ollama/documents/cache/common.rb".freeze, "lib/ollama/documents/cache/memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_backed_memory_cache.rb".freeze, "lib/ollama/documents/cache/redis_cache.rb".freeze, "lib/ollama/documents/splitters/character.rb".freeze, "lib/ollama/documents/splitters/semantic.rb".freeze, "lib/ollama/dto.rb".freeze, "lib/ollama/errors.rb".freeze, "lib/ollama/handlers.rb".freeze, "lib/ollama/handlers/collector.rb".freeze, "lib/ollama/handlers/concern.rb".freeze, "lib/ollama/handlers/dump_json.rb".freeze, "lib/ollama/handlers/dump_yaml.rb".freeze, "lib/ollama/handlers/markdown.rb".freeze, "lib/ollama/handlers/nop.rb".freeze, "lib/ollama/handlers/print.rb".freeze, "lib/ollama/handlers/progress.rb".freeze, "lib/ollama/handlers/say.rb".freeze, "lib/ollama/handlers/single.rb".freeze, "lib/ollama/image.rb".freeze, "lib/ollama/message.rb".freeze, "lib/ollama/options.rb".freeze, "lib/ollama/response.rb".freeze, "lib/ollama/tool.rb".freeze, "lib/ollama/tool/function.rb".freeze, "lib/ollama/tool/function/parameters.rb".freeze, "lib/ollama/tool/function/parameters/property.rb".freeze, "lib/ollama/utils/ansi_markdown.rb".freeze, "lib/ollama/utils/cache_fetcher.rb".freeze, "lib/ollama/utils/chooser.rb".freeze, "lib/ollama/utils/colorize_texts.rb".freeze, "lib/ollama/utils/fetcher.rb".freeze, "lib/ollama/utils/file_argument.rb".freeze, "lib/ollama/utils/math.rb".freeze, "lib/ollama/utils/tags.rb".freeze, "lib/ollama/utils/width.rb".freeze, "lib/ollama/version.rb".freeze, "ollama-ruby.gemspec".freeze, "spec/assets/embeddings.json".freeze, "spec/assets/kitten.jpg".freeze, "spec/assets/prompt.txt".freeze, "spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/documents/memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_backed_memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_cache_spec.rb".freeze, "spec/ollama/documents/splitters/character_spec.rb".freeze, "spec/ollama/documents/splitters/semantic_spec.rb".freeze, "spec/ollama/documents_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/ollama/utils/ansi_markdown_spec.rb".freeze, "spec/ollama/utils/cache_fetcher_spec.rb".freeze, "spec/ollama/utils/fetcher_spec.rb".freeze, "spec/ollama/utils/file_argument_spec.rb".freeze, "spec/ollama/utils/tags_spec.rb".freeze, "spec/ollama/utils/width_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tmp/.keep".freeze]
17
17
  s.homepage = "https://github.com/flori/ollama-ruby".freeze
18
18
  s.licenses = ["MIT".freeze]
19
19
  s.rdoc_options = ["--title".freeze, "Ollama-ruby - Interacting with the Ollama API".freeze, "--main".freeze, "README.md".freeze]
20
20
  s.required_ruby_version = Gem::Requirement.new("~> 3.1".freeze)
21
21
  s.rubygems_version = "3.5.18".freeze
22
22
  s.summary = "Interacting with the Ollama API".freeze
23
- s.test_files = ["spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/documents/memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_backed_memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_cache_spec.rb".freeze, "spec/ollama/documents/splitters/character_spec.rb".freeze, "spec/ollama/documents/splitters/semantic_spec.rb".freeze, "spec/ollama/documents_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/ollama/utils/ansi_markdown_spec.rb".freeze, "spec/ollama/utils/cache_fetcher_spec.rb".freeze, "spec/ollama/utils/fetcher_spec.rb".freeze, "spec/ollama/utils/file_argument_spec.rb".freeze, "spec/ollama/utils/tags_spec.rb".freeze, "spec/spec_helper.rb".freeze]
23
+ s.test_files = ["spec/ollama/client/doc_spec.rb".freeze, "spec/ollama/client_spec.rb".freeze, "spec/ollama/commands/chat_spec.rb".freeze, "spec/ollama/commands/copy_spec.rb".freeze, "spec/ollama/commands/create_spec.rb".freeze, "spec/ollama/commands/delete_spec.rb".freeze, "spec/ollama/commands/embed_spec.rb".freeze, "spec/ollama/commands/embeddings_spec.rb".freeze, "spec/ollama/commands/generate_spec.rb".freeze, "spec/ollama/commands/ps_spec.rb".freeze, "spec/ollama/commands/pull_spec.rb".freeze, "spec/ollama/commands/push_spec.rb".freeze, "spec/ollama/commands/show_spec.rb".freeze, "spec/ollama/commands/tags_spec.rb".freeze, "spec/ollama/documents/memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_backed_memory_cache_spec.rb".freeze, "spec/ollama/documents/redis_cache_spec.rb".freeze, "spec/ollama/documents/splitters/character_spec.rb".freeze, "spec/ollama/documents/splitters/semantic_spec.rb".freeze, "spec/ollama/documents_spec.rb".freeze, "spec/ollama/handlers/collector_spec.rb".freeze, "spec/ollama/handlers/dump_json_spec.rb".freeze, "spec/ollama/handlers/dump_yaml_spec.rb".freeze, "spec/ollama/handlers/markdown_spec.rb".freeze, "spec/ollama/handlers/nop_spec.rb".freeze, "spec/ollama/handlers/print_spec.rb".freeze, "spec/ollama/handlers/progress_spec.rb".freeze, "spec/ollama/handlers/say_spec.rb".freeze, "spec/ollama/handlers/single_spec.rb".freeze, "spec/ollama/image_spec.rb".freeze, "spec/ollama/message_spec.rb".freeze, "spec/ollama/options_spec.rb".freeze, "spec/ollama/tool_spec.rb".freeze, "spec/ollama/utils/ansi_markdown_spec.rb".freeze, "spec/ollama/utils/cache_fetcher_spec.rb".freeze, "spec/ollama/utils/fetcher_spec.rb".freeze, "spec/ollama/utils/file_argument_spec.rb".freeze, "spec/ollama/utils/tags_spec.rb".freeze, "spec/ollama/utils/width_spec.rb".freeze, "spec/spec_helper.rb".freeze]
24
24
 
25
25
  s.specification_version = 4
26
26
 
@@ -38,7 +38,6 @@ Gem::Specification.new do |s|
38
38
  s.add_runtime_dependency(%q<redis>.freeze, ["~> 5.0".freeze])
39
39
  s.add_runtime_dependency(%q<numo-narray>.freeze, ["~> 0.9".freeze])
40
40
  s.add_runtime_dependency(%q<more_math>.freeze, ["~> 1.1".freeze])
41
- s.add_runtime_dependency(%q<sorted_set>.freeze, ["~> 1.0".freeze])
42
41
  s.add_runtime_dependency(%q<mime-types>.freeze, ["~> 3.0".freeze])
43
42
  s.add_runtime_dependency(%q<reverse_markdown>.freeze, ["~> 2.0".freeze])
44
43
  s.add_runtime_dependency(%q<complex_config>.freeze, ["~> 0.22".freeze])
@@ -48,4 +47,5 @@ Gem::Specification.new do |s|
48
47
  s.add_runtime_dependency(%q<logger>.freeze, ["~> 1.0".freeze])
49
48
  s.add_runtime_dependency(%q<json>.freeze, ["~> 2.0".freeze])
50
49
  s.add_runtime_dependency(%q<xdg>.freeze, ["~> 7.0".freeze])
50
+ s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.34".freeze])
51
51
  end
@@ -9,6 +9,11 @@ RSpec.describe Ollama::Image do
9
9
  expect(image).to be_a described_class
10
10
  end
11
11
 
12
+ it 'can be equal or not' do
13
+ expect(image).not_to eq described_class.for_string('')
14
+ expect(image).to eq described_class.for_filename(asset('kitten.jpg'))
15
+ end
16
+
12
17
  it 'cannot be created via .new' do
13
18
  expect {
14
19
  described_class.new('nix')
@@ -33,10 +33,11 @@ RSpec.describe Ollama::Utils::CacheFetcher do
33
33
 
34
34
  it 'has #put' do
35
35
  io = StringIO.new('world')
36
- io.extend(Ollama::Utils::Fetcher::ContentType)
36
+ io.extend(Ollama::Utils::Fetcher::HeaderExtension)
37
37
  io.content_type = MIME::Types['text/plain'].first
38
- expect(cache).to receive(:[]=).with('body-69ce405ab83f42dffa9fd22bbd47783f', 'world')
39
- expect(cache).to receive(:[]=).with('content_type-69ce405ab83f42dffa9fd22bbd47783f', 'text/plain')
38
+ io.ex = 666
39
+ expect(cache).to receive(:set).with('body-69ce405ab83f42dffa9fd22bbd47783f', 'world', ex: 666)
40
+ expect(cache).to receive(:set).with('content_type-69ce405ab83f42dffa9fd22bbd47783f', 'text/plain', ex: 666)
40
41
  fetcher.put(url, io)
41
42
  end
42
43
  end
@@ -14,11 +14,35 @@ RSpec.describe Ollama::Utils::Tags do
14
14
  tags = described_class.new([ 'foo' ])
15
15
  tags.add 'bar'
16
16
  expect(tags.to_a).to eq %w[ bar foo ]
17
- tags.merge %w[ baz baz2 ]
18
- expect(tags.to_a).to eq %w[ bar baz baz2 foo ]
17
+ end
18
+
19
+ it 'can increase in size' do
20
+ tags = described_class.new
21
+ expect { tags.add 'foo' }.to change { tags.size }.from(0).to(1)
22
+ expect { tags.add 'bar' }.to change { tags.size }.from(1).to(2)
23
+ end
24
+
25
+ it 'can be cleared' do
26
+ tags = described_class.new([ 'foo', 'bar' ])
27
+ expect { tags.clear }.to change { tags.size }.from(2).to(0)
28
+ end
29
+
30
+ it 'tags can be empt' do
31
+ tags = described_class.new([ 'foo' ])
32
+ expect { tags.clear }.to change { tags.empty? }.from(false).to(true)
19
33
  end
20
34
 
21
35
  it 'can be output nicely' do
22
36
  expect(described_class.new(%w[ foo bar ]).to_s).to eq '#bar #foo'
23
37
  end
38
+
39
+ it 'can be output nicely with links to source' do
40
+ tags = described_class.new([ 'foo' ], source: 'https://foo.example.com')
41
+ tags.add 'bar', source: '/path/to/bar.html'
42
+ expect(tags.to_a).to eq %w[ bar foo ]
43
+ tags.all? { expect(_1).to be_a(Ollama::Utils::Tags::Tag) }
44
+ expect(tags.to_s).to eq(
45
+ "\e]8;;file:///path/to/bar.html\e\\#bar\e]8;;\e\\ \e]8;;https://foo.example.com\e\\#foo\e]8;;\e\\"
46
+ )
47
+ end
24
48
  end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Ollama::Utils::Width do
4
+ before do
5
+ allow(Tins::Terminal).to receive(:columns).and_return 80
6
+ end
7
+
8
+ describe '.width' do
9
+ it 'defaults to 100%' do
10
+ expect(described_class.width).to eq 80
11
+ end
12
+
13
+ it 'can be to 80%' do
14
+ expect(described_class.width(percentage: 80)).to eq 64
15
+ end
16
+ end
17
+
18
+ describe '.wrap' do
19
+ it 'can wrap with percentage' do
20
+ wrapped = described_class.wrap([ ?A * 10 ] * 10 * ' ', percentage: 80)
21
+ expect(wrapped).to eq(
22
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA\n"\
23
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA"
24
+ )
25
+ expect(wrapped.size).to eq 109
26
+ end
27
+
28
+ it 'can wrap with length' do
29
+ wrapped = described_class.wrap([ ?A * 10 ] * 10 * ' ', length: 64)
30
+ expect(wrapped).to eq(
31
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA\n"\
32
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA"
33
+ )
34
+ expect(wrapped.size).to eq 109
35
+ end
36
+
37
+ it "doesn't wrap with length 0" do
38
+ wrapped = described_class.wrap([ ?A * 10 ] * 10 * ' ', length: 0)
39
+ expect(wrapped).to eq(
40
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA "\
41
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA"
42
+ )
43
+ end
44
+ end
45
+
46
+ describe '.truncate' do
47
+ it 'can truncate with percentage' do
48
+ truncated = described_class.truncate([ ?A * 10 ] * 10 * ' ', percentage: 80)
49
+ expect(truncated).to eq(
50
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAA…"
51
+ )
52
+ expect(truncated.size).to eq 64
53
+ end
54
+
55
+ it 'can truncate with length' do
56
+ truncated = described_class.truncate([ ?A * 10 ] * 10 * ' ', length: 64)
57
+ expect(truncated).to eq(
58
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAA…"
59
+ )
60
+ expect(truncated.size).to eq 64
61
+ end
62
+
63
+ it 'cannot truncate if not necessary' do
64
+ text = [ ?A * 10 ] * 5 * ' '
65
+ truncated = described_class.truncate(text, length: 54)
66
+ expect(truncated).to eq text
67
+ end
68
+
69
+ it 'can truncate with length 0' do
70
+ truncated = described_class.truncate([ ?A * 10 ] * 10 * ' ', length: 0)
71
+ expect(truncated).to be_empty
72
+ end
73
+
74
+ it 'can truncate with ...' do
75
+ truncated = described_class.truncate([ ?A * 10 ] * 10 * ' ', length: 64, ellipsis: '...')
76
+ expect(truncated).to eq(
77
+ "AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAAAAAA AAAAAA..."
78
+ )
79
+ expect(truncated.size).to eq 64
80
+ end
81
+ end
82
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ollama-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-30 00:00:00.000000000 Z
11
+ date: 2024-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gem_hadar
@@ -206,20 +206,6 @@ dependencies:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
208
  version: '1.1'
209
- - !ruby/object:Gem::Dependency
210
- name: sorted_set
211
- requirement: !ruby/object:Gem::Requirement
212
- requirements:
213
- - - "~>"
214
- - !ruby/object:Gem::Version
215
- version: '1.0'
216
- type: :runtime
217
- prerelease: false
218
- version_requirements: !ruby/object:Gem::Requirement
219
- requirements:
220
- - - "~>"
221
- - !ruby/object:Gem::Version
222
- version: '1.0'
223
209
  - !ruby/object:Gem::Dependency
224
210
  name: mime-types
225
211
  requirement: !ruby/object:Gem::Requirement
@@ -346,6 +332,20 @@ dependencies:
346
332
  - - "~>"
347
333
  - !ruby/object:Gem::Version
348
334
  version: '7.0'
335
+ - !ruby/object:Gem::Dependency
336
+ name: tins
337
+ requirement: !ruby/object:Gem::Requirement
338
+ requirements:
339
+ - - "~>"
340
+ - !ruby/object:Gem::Version
341
+ version: '1.34'
342
+ type: :runtime
343
+ prerelease: false
344
+ version_requirements: !ruby/object:Gem::Requirement
345
+ requirements:
346
+ - - "~>"
347
+ - !ruby/object:Gem::Version
348
+ version: '1.34'
349
349
  description: Library that allows interacting with the Ollama API
350
350
  email: flori@ping.de
351
351
  executables:
@@ -519,6 +519,7 @@ files:
519
519
  - spec/ollama/utils/fetcher_spec.rb
520
520
  - spec/ollama/utils/file_argument_spec.rb
521
521
  - spec/ollama/utils/tags_spec.rb
522
+ - spec/ollama/utils/width_spec.rb
522
523
  - spec/spec_helper.rb
523
524
  - tmp/.keep
524
525
  homepage: https://github.com/flori/ollama-ruby
@@ -587,4 +588,5 @@ test_files:
587
588
  - spec/ollama/utils/fetcher_spec.rb
588
589
  - spec/ollama/utils/file_argument_spec.rb
589
590
  - spec/ollama/utils/tags_spec.rb
591
+ - spec/ollama/utils/width_spec.rb
590
592
  - spec/spec_helper.rb