ollama-ruby 0.11.0 → 0.12.1
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 +4 -4
- data/CHANGES.md +44 -1
- data/README.md +3 -2
- data/Rakefile +3 -1
- data/bin/ollama_chat +79 -29
- data/lib/ollama/client.rb +5 -5
- data/lib/ollama/documents/cache/common.rb +5 -1
- data/lib/ollama/documents/cache/memory_cache.rb +1 -1
- data/lib/ollama/documents/cache/records.rb +87 -0
- data/lib/ollama/documents/cache/redis_backed_memory_cache.rb +2 -1
- data/lib/ollama/documents/cache/redis_cache.rb +3 -10
- data/lib/ollama/documents/cache/sqlite_cache.rb +215 -0
- data/lib/ollama/documents/splitters/semantic.rb +1 -0
- data/lib/ollama/documents.rb +35 -62
- data/lib/ollama/utils/chooser.rb +15 -1
- data/lib/ollama/utils/tags.rb +2 -1
- data/lib/ollama/version.rb +1 -1
- data/ollama-ruby.gemspec +9 -7
- data/spec/ollama/documents/{memory_cache_spec.rb → cache/memory_cache_spec.rb} +37 -3
- data/spec/ollama/documents/{redis_backed_memory_cache_spec.rb → cache/redis_backed_memory_cache_spec.rb} +19 -7
- data/spec/ollama/documents/{redis_cache_spec.rb → cache/redis_cache_spec.rb} +34 -19
- data/spec/ollama/documents/cache/sqlite_cache_spec.rb +141 -0
- data/spec/ollama/utils/tags_spec.rb +7 -2
- metadata +50 -10
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b1a5d95ed76e20288199c655d7a48751f403db0228999a2703c50288a9f4816e
         | 
| 4 | 
            +
              data.tar.gz: '0727293192245719c82619e8cd71423f7e477dfef194504585d7a5f756c96cca'
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: c6c9f7ba3da2ba8f87182ee7acc53536a8b30fcc227a467b7d1cae8dee14d8b7258ad3e97b7960d9a079cfa2d28b5a1dea2a5776a27a1119a7515f515d0f5c3e
         | 
| 7 | 
            +
              data.tar.gz: 753a7812dbf850e21f042f8cbccec96163f21c7766a6a54d552cc0de98ba0581133140b5be9f11ae55a5faf98ea71101e13280bc673dd3ee67f2c26945e4005f
         | 
    
        data/CHANGES.md
    CHANGED
    
    | @@ -1,5 +1,48 @@ | |
| 1 1 | 
             
            # Changes
         | 
| 2 2 |  | 
| 3 | 
            +
            ## 2024-11-27 v0.12.1
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * Added handling for empty links list:
         | 
| 6 | 
            +
              + If the list is empty, print a message indicating that the list is empty.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ## 2024-11-26 v0.12.0
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            * **Upgrade display/clear links used in chat**:
         | 
| 11 | 
            +
              * Created `$links` set to store used links.
         | 
| 12 | 
            +
              * Added `/links` command to display used links as a enumerated list.
         | 
| 13 | 
            +
              * Implemented `/links (clear)` feature to remove all or specific used links.
         | 
| 14 | 
            +
            * **Update semantic splitter to handle embeddings size < 2**:
         | 
| 15 | 
            +
              + Added condition to return sentences directly when embeddings size is less
         | 
| 16 | 
            +
                than 2.
         | 
| 17 | 
            +
            * **Removed collection list from chat info output**
         | 
| 18 | 
            +
            * **Add SQLiteCache spec for convert_to_vector method**:
         | 
| 19 | 
            +
              - Test creates a vector with two elements and checks if
         | 
| 20 | 
            +
                `cache.convert_to_vector(vector)` returns the same vector (which for this
         | 
| 21 | 
            +
                cache is just a Ruby array).
         | 
| 22 | 
            +
            * **Add tests for retrieving tags from cache**:
         | 
| 23 | 
            +
              * Test if tags are returned as an instance of `Ollama::Utils::Tags`
         | 
| 24 | 
            +
              * Test also checks if the order of the tags is correct
         | 
| 25 | 
            +
            * **Added test case for clearing tags from `Ollama::Documents::SQLiteCache`**
         | 
| 26 | 
            +
              - Updated spec for new `clear_for_tags` method
         | 
| 27 | 
            +
            * **Migrate SQLite cache to use new clear_for_tags method**:
         | 
| 28 | 
            +
              + Added `clear_for_tags` method to SQLiteCache class in `sqlite_cache.rb`
         | 
| 29 | 
            +
              + Modified `clear` method in `records.rb` to call `clear_for_tags` if
         | 
| 30 | 
            +
                available
         | 
| 31 | 
            +
              + Created `find_records_for_tags` method in `sqlite_cache.rb` to find records
         | 
| 32 | 
            +
                by tags
         | 
| 33 | 
            +
              + Updated `find_records` method in `sqlite_cache.rb` to use new
         | 
| 34 | 
            +
                `find_records_for_tags` method
         | 
| 35 | 
            +
            * **Use Ollama::Utils::Tags for consistently handling tags**
         | 
| 36 | 
            +
            * **Upgrade SQLite cache to use correct prefix for full_each**:
         | 
| 37 | 
            +
              * Use `?%` as the default prefix in `SQLiteCache#full_each`
         | 
| 38 | 
            +
              * Add specs for setting keys with different prefixes in `SQLiteCache`
         | 
| 39 | 
            +
              * Add specs for setting keys with different prefixes in `MemoryCache`
         | 
| 40 | 
            +
            * **Refactor SQLite cache query explanation**
         | 
| 41 | 
            +
              + Use new variable `e` to store sanitized query for debugging purposes
         | 
| 42 | 
            +
              + Pass sanitized query `e` to `@database.execute` for `EXPLAIN` instead of
         | 
| 43 | 
            +
                original query `a[0]`
         | 
| 44 | 
            +
            * **Add test for unique tags with leading # characters**
         | 
| 45 | 
            +
             | 
| 3 46 | 
             
            ## 2024-11-20 v0.11.0
         | 
| 4 47 |  | 
| 5 48 | 
             
            * Added `voice` and `interactive` reader attributes to the Say handler class.
         | 
| @@ -25,7 +68,7 @@ | |
| 25 68 | 
             
              + Added support for `file://` protocol to content scans.
         | 
| 26 69 | 
             
              + Updated regex pattern to match local files starting with `~`, `.`, or `/`.
         | 
| 27 70 | 
             
              + Remove # anchors for file URLs (and files)
         | 
| 28 | 
            -
            * Improved parsing of content in ollama_chat | 
| 71 | 
            +
            * Improved parsing of content in `ollama_chat`:
         | 
| 29 72 | 
             
              + Use `content.scan(%r((https?://\S+)|(#\S+)|(\S+\/\S+)))` to match URLs, tags and files.
         | 
| 30 73 | 
             
              + For foo/bar file pathes prepend `./`foo/bar, for foo you have to enter ./foo still.
         | 
| 31 74 | 
             
              + Added a check for file existence before fetching its content
         | 
    
        data/README.md
    CHANGED
    
    | @@ -44,8 +44,8 @@ print ollama.chat(model: 'llama3.1', stream: true, messages:).lazy.map { |respon | |
| 44 44 |  | 
| 45 45 | 
             
            ## Try out things in ollama\_console
         | 
| 46 46 |  | 
| 47 | 
            -
            This is an interactive console | 
| 48 | 
            -
            provided by an `Ollama::Client` instance. For example this command  | 
| 47 | 
            +
            This is an interactive console where you can try out the different commands
         | 
| 48 | 
            +
            provided by an `Ollama::Client` instance. For example, this command generates a
         | 
| 49 49 | 
             
            response and displays it on the screen using the Markdown handler:
         | 
| 50 50 |  | 
| 51 51 | 
             
            ```
         | 
| @@ -459,6 +459,7 @@ The following commands can be given inside the chat, if prefixed by a `/`: | |
| 459 459 | 
             
            /embedding                      toggle embedding paused or not
         | 
| 460 460 | 
             
            /embed source                   embed the source's content
         | 
| 461 461 | 
             
            /web [n] query                  query web search & return n or 1 results
         | 
| 462 | 
            +
            /links( clear)                  display (or clear) links used in the chat
         | 
| 462 463 | 
             
            /save filename                  store conversation messages
         | 
| 463 464 | 
             
            /load filename                  load conversation messages
         | 
| 464 465 | 
             
            /quit                           to quit
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -42,7 +42,9 @@ GemHadar do | |
| 42 42 | 
             
              dependency             'tins',                  '~> 1.34'
         | 
| 43 43 | 
             
              dependency             'kramdown-ansi',         '~> 0.0', '>= 0.0.1'
         | 
| 44 44 | 
             
              dependency             'ostruct',               '~> 0.0'
         | 
| 45 | 
            -
               | 
| 45 | 
            +
              dependency             'sqlite-vec',            '~> 0.0'
         | 
| 46 | 
            +
              dependency             'sqlite3',               '~> 2.0', '>= 2.0.1'
         | 
| 47 | 
            +
              development_dependency 'all_images',            '~> 0.6'
         | 
| 46 48 | 
             
              development_dependency 'rspec',                 '~> 3.2'
         | 
| 47 49 | 
             
              development_dependency 'webmock'
         | 
| 48 50 | 
             
              development_dependency 'debug'
         | 
    
        data/bin/ollama_chat
    CHANGED
    
    | @@ -57,13 +57,15 @@ class OllamaChatConfig | |
| 57 57 | 
             
                  enabled: true
         | 
| 58 58 | 
             
                  model:
         | 
| 59 59 | 
             
                    name: mxbai-embed-large
         | 
| 60 | 
            +
                    embedding_length: 1024
         | 
| 60 61 | 
             
                    options: {}
         | 
| 61 62 | 
             
                    # Retrieval prompt template:
         | 
| 62 63 | 
             
                    prompt: 'Represent this sentence for searching relevant passages: %s'
         | 
| 63 64 | 
             
                  batch_size: 10
         | 
| 65 | 
            +
                  database_filename: null # ':memory:'
         | 
| 64 66 | 
             
                  collection: <%= ENV['OLLAMA_CHAT_COLLECTION'] %>
         | 
| 65 67 | 
             
                  found_texts_size: 4096
         | 
| 66 | 
            -
                  found_texts_count:  | 
| 68 | 
            +
                  found_texts_count: 10
         | 
| 67 69 | 
             
                  splitter:
         | 
| 68 70 | 
             
                    name: RecursiveCharacter
         | 
| 69 71 | 
             
                    chunk_size: 1024
         | 
| @@ -81,12 +83,15 @@ class OllamaChatConfig | |
| 81 83 |  | 
| 82 84 | 
             
              def initialize(filename = nil)
         | 
| 83 85 | 
             
                @filename = filename || default_path
         | 
| 86 | 
            +
                unless File.directory?(cache_dir_path)
         | 
| 87 | 
            +
                  mkdir_p cache_dir_path.to_s
         | 
| 88 | 
            +
                end
         | 
| 84 89 | 
             
                @config = Provider.config(@filename, '⚙️')
         | 
| 85 90 | 
             
                retried = false
         | 
| 86 91 | 
             
              rescue ConfigurationFileMissing
         | 
| 87 92 | 
             
                if @filename == default_path && !retried
         | 
| 88 93 | 
             
                  retried = true
         | 
| 89 | 
            -
                  mkdir_p  | 
| 94 | 
            +
                  mkdir_p config_dir_path.to_s
         | 
| 90 95 | 
             
                  File.secure_write(default_path, DEFAULT_CONFIG)
         | 
| 91 96 | 
             
                  retry
         | 
| 92 97 | 
             
                else
         | 
| @@ -105,6 +110,14 @@ class OllamaChatConfig | |
| 105 110 | 
             
              def config_dir_path
         | 
| 106 111 | 
             
                XDG.new.config_home + 'ollama_chat'
         | 
| 107 112 | 
             
              end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              def cache_dir_path
         | 
| 115 | 
            +
                XDG.new.cache_home + 'ollama_chat'
         | 
| 116 | 
            +
              end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
              def database_path
         | 
| 119 | 
            +
                cache_dir_path + 'documents.db'
         | 
| 120 | 
            +
              end
         | 
| 108 121 | 
             
            end
         | 
| 109 122 |  | 
| 110 123 | 
             
            class FollowChat
         | 
| @@ -316,11 +329,12 @@ def search_web(query, n = nil) | |
| 316 329 | 
             
                doc.css('.results_links').each do |link|
         | 
| 317 330 | 
             
                  if n > 0
         | 
| 318 331 | 
             
                    url = link.css('.result__a').first&.[]('href')
         | 
| 319 | 
            -
                    url.sub!(%r(\A | 
| 332 | 
            +
                    url.sub!(%r(\A(//duckduckgo\.com)?/l/\?uddg=), '')
         | 
| 320 333 | 
             
                    url.sub!(%r(&rut=.*), '')
         | 
| 321 334 | 
             
                    url = URI.decode_uri_component(url)
         | 
| 322 335 | 
             
                    url = URI.parse(url)
         | 
| 323 336 | 
             
                    url.host =~ /duckduckgo\.com/ and next
         | 
| 337 | 
            +
                    $links.add(url.to_s)
         | 
| 324 338 | 
             
                    result << url
         | 
| 325 339 | 
             
                    n -= 1
         | 
| 326 340 | 
             
                  else
         | 
| @@ -387,7 +401,7 @@ module SourceParsing | |
| 387 401 | 
             
                  end
         | 
| 388 402 | 
             
                  source_io.rewind
         | 
| 389 403 | 
             
                  source_io.read
         | 
| 390 | 
            -
                when 'text/csv' | 
| 404 | 
            +
                when 'text/csv'
         | 
| 391 405 | 
             
                  parse_csv(source_io)
         | 
| 392 406 | 
             
                when 'application/rss+xml'
         | 
| 393 407 | 
             
                  parse_rss(source_io)
         | 
| @@ -510,6 +524,7 @@ def fetch_source(source, &block) | |
| 510 524 | 
             
                  block.(tmp)
         | 
| 511 525 | 
             
                end
         | 
| 512 526 | 
             
              when %r(\Ahttps?://\S+)
         | 
| 527 | 
            +
                $links.add(source.to_s)
         | 
| 513 528 | 
             
                Utils::Fetcher.get(
         | 
| 514 529 | 
             
                  source,
         | 
| 515 530 | 
             
                  cache:        $cache,
         | 
| @@ -603,7 +618,6 @@ def embed_source(source_io, source, count: nil) | |
| 603 618 | 
             
                  percentage: splitter_config.percentage?,
         | 
| 604 619 | 
             
                  percentile: splitter_config.percentile?,
         | 
| 605 620 | 
             
                )
         | 
| 606 | 
            -
                inputs = splitter.split(text)
         | 
| 607 621 | 
             
              end
         | 
| 608 622 | 
             
              inputs or return
         | 
| 609 623 | 
             
              source = source.to_s
         | 
| @@ -624,7 +638,6 @@ def embed(source) | |
| 624 638 | 
             
                  content.present? or return
         | 
| 625 639 | 
             
                  source_io.rewind
         | 
| 626 640 | 
             
                  embed_source(source_io, source)
         | 
| 627 | 
            -
                  content
         | 
| 628 641 | 
             
                end
         | 
| 629 642 | 
             
                $config.prompts.embed % { source: }
         | 
| 630 643 | 
             
              else
         | 
| @@ -649,6 +662,7 @@ def parse_content(content, images) | |
| 649 662 | 
             
                  File.exist?(file) or next
         | 
| 650 663 | 
             
                  source = file
         | 
| 651 664 | 
             
                when url
         | 
| 665 | 
            +
                  $links.add(url.to_s)
         | 
| 652 666 | 
             
                  source = url
         | 
| 653 667 | 
             
                end
         | 
| 654 668 | 
             
                fetch_source(source) do |source_io|
         | 
| @@ -674,7 +688,7 @@ def parse_content(content, images) | |
| 674 688 | 
             
                  end
         | 
| 675 689 | 
             
                end
         | 
| 676 690 | 
             
              end
         | 
| 677 | 
            -
              new_content = contents.select | 
| 691 | 
            +
              new_content = contents.select { _1.present? rescue nil }.compact * "\n\n"
         | 
| 678 692 | 
             
              return new_content, (tags unless tags.empty?)
         | 
| 679 693 | 
             
            end
         | 
| 680 694 |  | 
| @@ -735,17 +749,12 @@ ensure | |
| 735 749 | 
             
            end
         | 
| 736 750 |  | 
| 737 751 | 
             
            def collection_stats
         | 
| 738 | 
            -
              list = $documents.collections.sort.map { |c|
         | 
| 739 | 
            -
                '  ' + ($documents.collection == c ? bold { c } : c).to_s
         | 
| 740 | 
            -
              }.join(?\n)
         | 
| 741 752 | 
             
              puts <<~EOT
         | 
| 742 753 | 
             
                Current Collection
         | 
| 743 754 | 
             
                  Name: #{bold{$documents.collection}}
         | 
| 744 755 | 
             
                  #Embeddings: #{$documents.size}
         | 
| 745 756 | 
             
                  #Tags: #{$documents.tags.size}
         | 
| 746 757 | 
             
                  Tags: #{$documents.tags}
         | 
| 747 | 
            -
                List:
         | 
| 748 | 
            -
                #{list}
         | 
| 749 758 | 
             
              EOT
         | 
| 750 759 | 
             
            end
         | 
| 751 760 |  | 
| @@ -785,10 +794,11 @@ def set_system_prompt(messages, system) | |
| 785 794 | 
             
              messages << Message.new(role: 'system', content: system)
         | 
| 786 795 | 
             
            end
         | 
| 787 796 |  | 
| 788 | 
            -
            def change_system_prompt(messages, default)
         | 
| 789 | 
            -
               | 
| 790 | 
            -
               | 
| 791 | 
            -
               | 
| 797 | 
            +
            def change_system_prompt(messages, default, system: nil)
         | 
| 798 | 
            +
              selector = Regexp.new(system.to_s[1..-1].to_s)
         | 
| 799 | 
            +
              prompts  = $config.system_prompts.attribute_names.compact.grep(selector)
         | 
| 800 | 
            +
              chosen   = Utils::Chooser.choose(prompts, return_immediately: true)
         | 
| 801 | 
            +
              system   = if chosen
         | 
| 792 802 | 
             
                          $config.system_prompts.send(chosen)
         | 
| 793 803 | 
             
                        else
         | 
| 794 804 | 
             
                          default
         | 
| @@ -869,6 +879,7 @@ def display_chat_help | |
| 869 879 | 
             
                /embedding                      toggle embedding paused or not
         | 
| 870 880 | 
             
                /embed source                   embed the source's content
         | 
| 871 881 | 
             
                /web [n] query                  query web search & return n or 1 results
         | 
| 882 | 
            +
                /links( clear)                  display (or clear) links used in the chat
         | 
| 872 883 | 
             
                /save filename                  store conversation messages
         | 
| 873 884 | 
             
                /load filename                  load conversation messages
         | 
| 874 885 | 
             
                /quit                           to quit
         | 
| @@ -907,16 +918,17 @@ end | |
| 907 918 |  | 
| 908 919 | 
             
            $opts = go 'f:u:m:s:c:C:D:MEVh'
         | 
| 909 920 |  | 
| 910 | 
            -
             | 
| 911 | 
            -
            $config | 
| 921 | 
            +
            $ollama_chat_config = OllamaChatConfig.new($opts[?f])
         | 
| 922 | 
            +
            $config             = $ollama_chat_config.config
         | 
| 912 923 |  | 
| 913 924 | 
             
            setup_switches
         | 
| 914 925 |  | 
| 915 926 | 
             
            $opts[?h] and usage
         | 
| 916 927 | 
             
            $opts[?V] and version
         | 
| 917 928 |  | 
| 918 | 
            -
            base_url | 
| 919 | 
            -
             | 
| 929 | 
            +
            base_url   = $opts[?u] || $config.url
         | 
| 930 | 
            +
            user_agent = [ File.basename($0), Ollama::VERSION ] * ?/
         | 
| 931 | 
            +
            $ollama    = Client.new(base_url:, debug: $config.debug, user_agent:)
         | 
| 920 932 |  | 
| 921 933 | 
             
            $document_policy = $config.document_policy
         | 
| 922 934 | 
             
            $model           = choose_model($opts[?m], $config.model.name)
         | 
| @@ -929,8 +941,8 @@ if $opts[?c] | |
| 929 941 | 
             
              messages.concat load_conversation($opts[?c])
         | 
| 930 942 | 
             
            else
         | 
| 931 943 | 
             
              default = $config.system_prompts.default? || model_system
         | 
| 932 | 
            -
              if $opts[?s]  | 
| 933 | 
            -
                change_system_prompt(messages, default)
         | 
| 944 | 
            +
              if $opts[?s] =~ /\A\?/
         | 
| 945 | 
            +
                change_system_prompt(messages, default, system: $opts[?s])
         | 
| 934 946 | 
             
              else
         | 
| 935 947 | 
             
                system = Utils::FileArgument.get_file_argument($opts[?s], default:)
         | 
| 936 948 | 
             
                system.present? and set_system_prompt(messages, system)
         | 
| @@ -944,12 +956,13 @@ if $embedding.on? | |
| 944 956 | 
             
              collection = $opts[?C] || $config.embedding.collection
         | 
| 945 957 | 
             
              $documents = Documents.new(
         | 
| 946 958 | 
             
                ollama:,
         | 
| 947 | 
            -
                model: | 
| 948 | 
            -
                model_options: | 
| 949 | 
            -
                 | 
| 950 | 
            -
                 | 
| 951 | 
            -
                 | 
| 952 | 
            -
                 | 
| 959 | 
            +
                model:             $embedding_model,
         | 
| 960 | 
            +
                model_options:     $config.embedding.model.options,
         | 
| 961 | 
            +
                database_filename: $config.embedding.database_filename || $ollama_chat_config.database_path,
         | 
| 962 | 
            +
                collection:        ,
         | 
| 963 | 
            +
                cache:             configure_cache,
         | 
| 964 | 
            +
                redis_url:         $config.redis.documents.url?,
         | 
| 965 | 
            +
                debug:             $config.debug
         | 
| 953 966 | 
             
              )
         | 
| 954 967 |  | 
| 955 968 | 
             
              document_list = $opts[?D].to_a
         | 
| @@ -991,10 +1004,11 @@ end | |
| 991 1004 |  | 
| 992 1005 | 
             
            $current_voice = $config.voice.default
         | 
| 993 1006 |  | 
| 994 | 
            -
            puts "Configuration read from #{ | 
| 1007 | 
            +
            puts "Configuration read from #{$ollama_chat_config.filename.inspect} is:", $config
         | 
| 995 1008 | 
             
            info
         | 
| 996 1009 | 
             
            puts "\nType /help to display the chat help."
         | 
| 997 1010 |  | 
| 1011 | 
            +
            $links  = Set.new
         | 
| 998 1012 | 
             
            images = []
         | 
| 999 1013 | 
             
            loop do
         | 
| 1000 1014 | 
             
              parse_content = true
         | 
| @@ -1134,6 +1148,42 @@ loop do | |
| 1134 1148 | 
             
                save_conversation($1, messages)
         | 
| 1135 1149 | 
             
                puts "Saved conversation to #$1."
         | 
| 1136 1150 | 
             
                next
         | 
| 1151 | 
            +
              when %r(^/links(?:\s+(clear))?$)
         | 
| 1152 | 
            +
                case $1
         | 
| 1153 | 
            +
                when 'clear'
         | 
| 1154 | 
            +
                  loop do
         | 
| 1155 | 
            +
                    links = $links.dup.add('[EXIT]').add('[ALL]')
         | 
| 1156 | 
            +
                    link = Utils::Chooser.choose(links, prompt: 'Clear? %s')
         | 
| 1157 | 
            +
                    case link
         | 
| 1158 | 
            +
                    when nil, '[EXIT]'
         | 
| 1159 | 
            +
                      puts "Exiting chooser."
         | 
| 1160 | 
            +
                      break
         | 
| 1161 | 
            +
                    when '[ALL]'
         | 
| 1162 | 
            +
                      if ask?(prompt: 'Are you sure? (y/n) ') =~ /\Ay/i
         | 
| 1163 | 
            +
                        $links.clear
         | 
| 1164 | 
            +
                        puts "Cleared all links in list."
         | 
| 1165 | 
            +
                        break
         | 
| 1166 | 
            +
                      else
         | 
| 1167 | 
            +
                        puts 'Cancelled.'
         | 
| 1168 | 
            +
                        sleep 3
         | 
| 1169 | 
            +
                      end
         | 
| 1170 | 
            +
                    when /./
         | 
| 1171 | 
            +
                      $links.delete(link)
         | 
| 1172 | 
            +
                      puts "Cleared link from links in list."
         | 
| 1173 | 
            +
                      sleep 3
         | 
| 1174 | 
            +
                    end
         | 
| 1175 | 
            +
                  end
         | 
| 1176 | 
            +
                when nil
         | 
| 1177 | 
            +
                  if $links.empty?
         | 
| 1178 | 
            +
                    puts "List is empty."
         | 
| 1179 | 
            +
                  else
         | 
| 1180 | 
            +
                    Math.log10($links.size).ceil
         | 
| 1181 | 
            +
                    format  = "% #{}s. %s"
         | 
| 1182 | 
            +
                    connect = -> link { hyperlink(link) { link } }
         | 
| 1183 | 
            +
                    puts $links.each_with_index.map { |x, i| format % [ i + 1, connect.(x) ] }
         | 
| 1184 | 
            +
                  end
         | 
| 1185 | 
            +
                end
         | 
| 1186 | 
            +
                next
         | 
| 1137 1187 | 
             
              when %r(^/load\s+(.+)$)
         | 
| 1138 1188 | 
             
                messages = load_conversation($1)
         | 
| 1139 1189 | 
             
                puts "Loaded conversation from #$1."
         | 
    
        data/lib/ollama/client.rb
    CHANGED
    
    | @@ -10,7 +10,7 @@ class Ollama::Client | |
| 10 10 |  | 
| 11 11 | 
             
              annotate :doc
         | 
| 12 12 |  | 
| 13 | 
            -
              def initialize(base_url: nil, output: $stdout, connect_timeout: nil, read_timeout: nil, write_timeout: nil, debug: nil)
         | 
| 13 | 
            +
              def initialize(base_url: nil, output: $stdout, connect_timeout: nil, read_timeout: nil, write_timeout: nil, debug: nil, user_agent: nil)
         | 
| 14 14 | 
             
                base_url.nil? and base_url = ENV.fetch('OLLAMA_URL') do
         | 
| 15 15 | 
             
                  raise ArgumentError,
         | 
| 16 16 | 
             
                    'missing :base_url parameter or OLLAMA_URL environment variable'
         | 
| @@ -21,8 +21,8 @@ class Ollama::Client | |
| 21 21 | 
             
                @ssl_verify_peer = base_url.query.to_s.split(?&).inject({}) { |h, l|
         | 
| 22 22 | 
             
                  h.merge Hash[*l.split(?=)]
         | 
| 23 23 | 
             
                }['ssl_verify_peer'] != 'false'
         | 
| 24 | 
            -
                @base_url, @output, @connect_timeout, @read_timeout, @write_timeout, @debug =
         | 
| 25 | 
            -
                  base_url, output, connect_timeout, read_timeout, write_timeout, debug
         | 
| 24 | 
            +
                @base_url, @output, @connect_timeout, @read_timeout, @write_timeout, @debug, @user_agent =
         | 
| 25 | 
            +
                  base_url, output, connect_timeout, read_timeout, write_timeout, debug, user_agent
         | 
| 26 26 | 
             
              end
         | 
| 27 27 |  | 
| 28 28 | 
             
              attr_accessor :output
         | 
| @@ -111,13 +111,13 @@ class Ollama::Client | |
| 111 111 |  | 
| 112 112 | 
             
              def headers
         | 
| 113 113 | 
             
                {
         | 
| 114 | 
            -
                  'User-Agent'   => self.class.user_agent,
         | 
| 114 | 
            +
                  'User-Agent'   => @user_agent || self.class.user_agent,
         | 
| 115 115 | 
             
                  'Content-Type' => 'application/json; charset=utf-8',
         | 
| 116 116 | 
             
                }
         | 
| 117 117 | 
             
              end
         | 
| 118 118 |  | 
| 119 119 | 
             
              def self.user_agent
         | 
| 120 | 
            -
                '%s/%s' % [ self | 
| 120 | 
            +
                '%s/%s' % [ self, Ollama::VERSION ]
         | 
| 121 121 | 
             
              end
         | 
| 122 122 |  | 
| 123 123 | 
             
              def excon(url)
         | 
| @@ -1,7 +1,11 @@ | |
| 1 1 | 
             
            module Ollama::Documents::Cache::Common
         | 
| 2 2 | 
             
              include Ollama::Utils::Math
         | 
| 3 3 |  | 
| 4 | 
            -
               | 
| 4 | 
            +
              def initialize(prefix:)
         | 
| 5 | 
            +
                self.prefix = prefix
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              attr_accessor :prefix # current prefix defined for the cache
         | 
| 5 9 |  | 
| 6 10 | 
             
              # Returns an array of collection names that match the given prefix.
         | 
| 7 11 | 
             
              #
         | 
| @@ -0,0 +1,87 @@ | |
| 1 | 
            +
            module Ollama::Documents::Cache::Records
         | 
| 2 | 
            +
              class Record < JSON::GenericObject
         | 
| 3 | 
            +
                def initialize(*a)
         | 
| 4 | 
            +
                  super
         | 
| 5 | 
            +
                  self.text ||= ''
         | 
| 6 | 
            +
                  self.norm ||= 0.0
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def to_s
         | 
| 10 | 
            +
                  my_tags = tags_set
         | 
| 11 | 
            +
                  my_tags.empty? or my_tags = " #{my_tags}"
         | 
| 12 | 
            +
                  "#<#{self.class} #{text.inspect}#{my_tags} #{similarity || 'n/a'}>"
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def tags_set
         | 
| 16 | 
            +
                  Ollama::Utils::Tags.new(tags, source:)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def ==(other)
         | 
| 20 | 
            +
                  text == other.text
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                alias inspect to_s
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              module RedisFullEach
         | 
| 27 | 
            +
                def full_each(&block)
         | 
| 28 | 
            +
                  redis.scan_each(match: [ Ollama::Documents, ?* ] * ?-) do |key|
         | 
| 29 | 
            +
                    value = redis.get(key) or next
         | 
| 30 | 
            +
                    value = JSON(value, object_class: Ollama::Documents::Record)
         | 
| 31 | 
            +
                    block.(key, value)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              module FindRecords
         | 
| 37 | 
            +
                def find_records(needle, tags: nil, max_records: nil)
         | 
| 38 | 
            +
                  tags = Ollama::Utils::Tags.new(Array(tags)).to_a
         | 
| 39 | 
            +
                  records = self
         | 
| 40 | 
            +
                  if tags.present?
         | 
| 41 | 
            +
                    records = records.select { |_key, record| (tags & record.tags).size >= 1 }
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                  needle_norm = norm(needle)
         | 
| 44 | 
            +
                  records     = records.sort_by { |key, record|
         | 
| 45 | 
            +
                    record.key        = key
         | 
| 46 | 
            +
                    record.similarity = cosine_similarity(
         | 
| 47 | 
            +
                      a: needle,
         | 
| 48 | 
            +
                      b: record.embedding,
         | 
| 49 | 
            +
                      a_norm: needle_norm,
         | 
| 50 | 
            +
                      b_norm: record.norm,
         | 
| 51 | 
            +
                    )
         | 
| 52 | 
            +
                  }
         | 
| 53 | 
            +
                  records.transpose.last&.reverse.to_a
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              module Tags
         | 
| 58 | 
            +
                def clear(tags: nil)
         | 
| 59 | 
            +
                  tags = Ollama::Utils::Tags.new(tags).to_a
         | 
| 60 | 
            +
                  if tags.present?
         | 
| 61 | 
            +
                    if respond_to?(:clear_for_tags)
         | 
| 62 | 
            +
                      clear_for_tags(tags)
         | 
| 63 | 
            +
                    else
         | 
| 64 | 
            +
                      each do |key, record|
         | 
| 65 | 
            +
                        if (tags & record.tags.to_a).size >= 1
         | 
| 66 | 
            +
                          delete(unpre(key))
         | 
| 67 | 
            +
                        end
         | 
| 68 | 
            +
                      end
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                  else
         | 
| 71 | 
            +
                    super()
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def tags
         | 
| 76 | 
            +
                  if defined? super
         | 
| 77 | 
            +
                    super
         | 
| 78 | 
            +
                  else
         | 
| 79 | 
            +
                    each_with_object(Ollama::Utils::Tags.new) do |(_, record), t|
         | 
| 80 | 
            +
                      record.tags.each do |tag|
         | 
| 81 | 
            +
                        t.add(tag, source: record.source)
         | 
| 82 | 
            +
                      end
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| @@ -5,8 +5,9 @@ class Ollama::Documents | |
| 5 5 | 
             
                def initialize(prefix:, url: ENV['REDIS_URL'], object_class: nil)
         | 
| 6 6 | 
             
                  super(prefix:)
         | 
| 7 7 | 
             
                  url or raise ArgumentError, 'require redis url'
         | 
| 8 | 
            -
                  @ | 
| 8 | 
            +
                  @url, @object_class = url, object_class
         | 
| 9 9 | 
             
                  @redis_cache  = Ollama::Documents::RedisCache.new(prefix:, url:, object_class:)
         | 
| 10 | 
            +
                  @redis_cache.extend(Ollama::Documents::Cache::Records::RedisFullEach)
         | 
| 10 11 | 
             
                  @redis_cache.full_each do |key, value|
         | 
| 11 12 | 
             
                    @data[key] = value
         | 
| 12 13 | 
             
                  end
         | 
| @@ -5,8 +5,9 @@ class Ollama::Documents::RedisCache | |
| 5 5 | 
             
              include Ollama::Documents::Cache::Common
         | 
| 6 6 |  | 
| 7 7 | 
             
              def initialize(prefix:, url: ENV['REDIS_URL'], object_class: nil, ex: nil)
         | 
| 8 | 
            +
                super(prefix:)
         | 
| 8 9 | 
             
                url or raise ArgumentError, 'require redis url'
         | 
| 9 | 
            -
                @ | 
| 10 | 
            +
                @url, @object_class, @ex = url, object_class, ex
         | 
| 10 11 | 
             
              end
         | 
| 11 12 |  | 
| 12 13 | 
             
              attr_reader :object_class
         | 
| @@ -18,7 +19,7 @@ class Ollama::Documents::RedisCache | |
| 18 19 | 
             
              def [](key)
         | 
| 19 20 | 
             
                value = redis.get(pre(key))
         | 
| 20 21 | 
             
                unless value.nil?
         | 
| 21 | 
            -
                  JSON(value, object_class:)
         | 
| 22 | 
            +
                  object_class ? JSON(value, object_class:) : JSON(value)
         | 
| 22 23 | 
             
                end
         | 
| 23 24 | 
             
              end
         | 
| 24 25 |  | 
| @@ -64,12 +65,4 @@ class Ollama::Documents::RedisCache | |
| 64 65 | 
             
                self
         | 
| 65 66 | 
             
              end
         | 
| 66 67 | 
             
              include Enumerable
         | 
| 67 | 
            -
             | 
| 68 | 
            -
              def full_each(&block)
         | 
| 69 | 
            -
                redis.scan_each(match: [ Ollama::Documents, ?* ] * ?-) do |key|
         | 
| 70 | 
            -
                  value = redis.get(key) or next
         | 
| 71 | 
            -
                  value = JSON(value, object_class: Ollama::Documents::Record)
         | 
| 72 | 
            -
                  block.(key, value)
         | 
| 73 | 
            -
                end
         | 
| 74 | 
            -
              end
         | 
| 75 68 | 
             
            end
         |