ollama-ruby 0.13.0 → 0.14.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.
data/config/redis.conf DELETED
@@ -1,5 +0,0 @@
1
- save 60 1000
2
- dbfilename dump.rdb
3
- appendonly yes
4
- appendfilename "appendonly.aof"
5
- appendfsync always
data/docker-compose.yml DELETED
@@ -1,10 +0,0 @@
1
- services:
2
- redis:
3
- image: valkey/valkey:7.2.7-alpine
4
- restart: unless-stopped
5
- ports: [ "127.0.0.1:9736:6379" ]
6
- volumes:
7
- - "redis-data:/data:delegated"
8
- - "./config/redis.conf:/etc/redis.conf"
9
- volumes:
10
- redis-data:
@@ -1,38 +0,0 @@
1
- require 'digest/md5'
2
-
3
- class Ollama::Utils::CacheFetcher
4
- def initialize(cache)
5
- @cache = cache
6
- end
7
-
8
- def get(url, &block)
9
- block or raise ArgumentError, 'require block argument'
10
- body = @cache[key(:body, url)]
11
- content_type = @cache[key(:content_type, url)]
12
- content_type = MIME::Types[content_type].first
13
- if body && content_type
14
- io = StringIO.new(body)
15
- io.rewind
16
- io.extend(Ollama::Utils::Fetcher::HeaderExtension)
17
- io.content_type = content_type
18
- block.(io)
19
- end
20
- end
21
-
22
- def put(url, io)
23
- io.rewind
24
- body = io.read
25
- body.empty? and return
26
- content_type = io.content_type
27
- content_type.nil? and return
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
- self
31
- end
32
-
33
- private
34
-
35
- def key(type, url)
36
- [ type, Digest::MD5.hexdigest(url) ] * ?-
37
- end
38
- end
@@ -1,52 +0,0 @@
1
- require 'amatch'
2
- require 'search_ui'
3
- require 'term/ansicolor'
4
-
5
- module Ollama::Utils::Chooser
6
- include SearchUI
7
- include Term::ANSIColor
8
-
9
- module_function
10
-
11
- # The choose method presents a list of entries and prompts the user
12
- # for input, allowing them to select one entry based on their input.
13
- #
14
- # @param entries [Array] the list of entries to present to the user
15
- # @param prompt [String] the prompt message to display when asking for input (default: 'Search? %s')
16
- # @param return_immediately [Boolean] whether to immediately return the first entry if there is only one or nil when there is none (default: false)
17
- #
18
- # @return [Object] the selected entry, or nil if no entry was chosen
19
- #
20
- # @example
21
- # choose(['entry1', 'entry2'], prompt: 'Choose an option:')
22
- def choose(entries, prompt: 'Search? %s', return_immediately: false)
23
- if return_immediately && entries.size <= 1
24
- return entries.first
25
- end
26
- entry = Search.new(
27
- prompt:,
28
- match: -> answer {
29
- matcher = Amatch::PairDistance.new(answer.downcase)
30
- matches = entries.map { |n| [ n, -matcher.similar(n.to_s.downcase) ] }.
31
- select { |_, s| s < 0 }.sort_by(&:last).map(&:first)
32
- matches.empty? and matches = entries
33
- matches.first(Tins::Terminal.lines - 1)
34
- },
35
- query: -> _answer, matches, selector {
36
- matches.each_with_index.map { |m, i|
37
- i == selector ? "#{blue{?⮕}} #{on_blue{m}}" : " #{m.to_s}"
38
- } * ?\n
39
- },
40
- found: -> _answer, matches, selector {
41
- matches[selector]
42
- },
43
- output: STDOUT
44
- ).start
45
- if entry
46
- entry
47
- else
48
- print clear_screen, move_home
49
- nil
50
- end
51
- end
52
- end
@@ -1,175 +0,0 @@
1
- require 'tempfile'
2
- require 'tins/unit'
3
- require 'infobar'
4
- require 'mime-types'
5
- require 'stringio'
6
- require 'ollama/utils/cache_fetcher'
7
-
8
- class Ollama::Utils::Fetcher
9
- module HeaderExtension
10
- attr_accessor :content_type
11
-
12
- attr_accessor :ex
13
-
14
- def self.failed
15
- object = StringIO.new.extend(self)
16
- object.content_type = MIME::Types['text/plain'].first
17
- object
18
- end
19
- end
20
-
21
- class RetryWithoutStreaming < StandardError; end
22
-
23
- def self.get(url, **options, &block)
24
- cache = options.delete(:cache) and
25
- cache = Ollama::Utils::CacheFetcher.new(cache)
26
- if result = cache&.get(url, &block)
27
- infobar.puts "Getting #{url.to_s.inspect} from cache."
28
- return result
29
- else
30
- new(**options).send(:get, url) do |tmp|
31
- result = block.(tmp)
32
- if cache && !tmp.is_a?(StringIO)
33
- tmp.rewind
34
- cache.put(url, tmp)
35
- end
36
- result
37
- end
38
- end
39
- end
40
-
41
- def self.normalize_url(url)
42
- url = url.to_s
43
- url = URI.decode_uri_component(url)
44
- url = url.sub(/#.*/, '')
45
- URI::Parser.new.escape(url).to_s
46
- end
47
-
48
- def self.read(filename, &block)
49
- if File.exist?(filename)
50
- File.open(filename) do |file|
51
- file.extend(Ollama::Utils::Fetcher::HeaderExtension)
52
- file.content_type = MIME::Types.type_for(filename).first
53
- block.(file)
54
- end
55
- else
56
- STDERR.puts "File #{filename.to_s.inspect} doesn't exist."
57
- end
58
- end
59
-
60
- def self.execute(command, &block)
61
- Tempfile.open do |tmp|
62
- IO.popen(command) do |command|
63
- until command.eof?
64
- tmp.write command.read(1 << 14)
65
- end
66
- tmp.rewind
67
- tmp.extend(Ollama::Utils::Fetcher::HeaderExtension)
68
- tmp.content_type = MIME::Types['text/plain'].first
69
- block.(tmp)
70
- end
71
- end
72
- rescue => e
73
- STDERR.puts "Cannot execute #{command.inspect} (#{e})"
74
- if @debug && !e.is_a?(RuntimeError)
75
- STDERR.puts "#{e.backtrace * ?\n}"
76
- end
77
- yield HeaderExtension.failed
78
- end
79
-
80
- def initialize(debug: false, http_options: {})
81
- @debug = debug
82
- @started = false
83
- @streaming = true
84
- @http_options = http_options
85
- end
86
-
87
- private
88
-
89
- def excon(url, **options)
90
- url = self.class.normalize_url(url)
91
- Excon.new(url, options.merge(@http_options))
92
- end
93
-
94
- def get(url, &block)
95
- response = nil
96
- Tempfile.open do |tmp|
97
- infobar.label = 'Getting'
98
- if @streaming
99
- response = excon(url, headers:, response_block: callback(tmp)).request(method: :get)
100
- response.status != 200 || !@started and raise RetryWithoutStreaming
101
- decorate_io(tmp, response)
102
- infobar.finish
103
- block.(tmp)
104
- else
105
- response = excon(url, headers:, middlewares:).request(method: :get)
106
- if response.status != 200
107
- raise "invalid response status code"
108
- end
109
- body = response.body
110
- tmp.print body
111
- infobar.update(message: message(body.size, body.size), force: true)
112
- decorate_io(tmp, response)
113
- infobar.finish
114
- block.(tmp)
115
- end
116
- end
117
- rescue RetryWithoutStreaming
118
- @streaming = false
119
- retry
120
- rescue => e
121
- STDERR.puts "Cannot get #{url.to_s.inspect} (#{e}): #{response&.status_line || 'n/a'}"
122
- if @debug && !e.is_a?(RuntimeError)
123
- STDERR.puts "#{e.backtrace * ?\n}"
124
- end
125
- yield HeaderExtension.failed
126
- end
127
-
128
- def headers
129
- {
130
- 'User-Agent' => Ollama::Client.user_agent,
131
- }
132
- end
133
-
134
- def middlewares
135
- (Excon.defaults[:middlewares] + [ Excon::Middleware::RedirectFollower ]).uniq
136
- end
137
-
138
- private
139
-
140
- def decorate_io(tmp, response)
141
- tmp.rewind
142
- tmp.extend(HeaderExtension)
143
- if content_type = MIME::Types[response.headers['content-type']].first
144
- tmp.content_type = content_type
145
- end
146
- if cache_control = response.headers['cache-control'] and
147
- cache_control !~ /no-store|no-cache/ and
148
- ex = cache_control[/s-maxage\s*=\s*(\d+)/, 1] || cache_control[/max-age\s*=\s*(\d+)/, 1]
149
- then
150
- tmp.ex = ex.to_i
151
- end
152
- end
153
-
154
- def callback(tmp)
155
- -> chunk, remaining_bytes, total_bytes do
156
- total = total_bytes or next
157
- current = total_bytes - remaining_bytes
158
- if @started
159
- infobar.counter.progress(by: total - current)
160
- else
161
- @started = true
162
- infobar.counter.reset(total:, current:)
163
- end
164
- infobar.update(message: message(current, total), force: true)
165
- tmp.print(chunk)
166
- end
167
- end
168
-
169
- def message(current, total)
170
- progress = '%s/%s' % [ current, total ].map {
171
- Tins::Unit.format(_1, format: '%.2f %U')
172
- }
173
- '%l ' + progress + ' in %te, ETA %e @%E'
174
- end
175
- end
@@ -1,34 +0,0 @@
1
- module Ollama::Utils::FileArgument
2
- module_function
3
-
4
- # Returns the contents of a file or string, or a default value if neither is provided.
5
- #
6
- # @param [String] path_or_content The path to a file or a string containing
7
- # the content.
8
- #
9
- # @param [String] default The default value to return if no valid input is
10
- # given. Defaults to nil.
11
- #
12
- # @return [String] The contents of the file, the string, or the default value.
13
- #
14
- # @example Get the contents of a file
15
- # get_file_argument('path/to/file')
16
- #
17
- # @example Use a string as content
18
- # get_file_argument('string content')
19
- #
20
- # @example Return a default value if no valid input is given
21
- # get_file_argument(nil, default: 'default content')
22
- def get_file_argument(path_or_content, default: nil)
23
- if path_or_content.present? && path_or_content.size < 2 ** 15 &&
24
- File.basename(path_or_content).size < 2 ** 8 &&
25
- File.exist?(path_or_content)
26
- then
27
- File.read(path_or_content)
28
- elsif path_or_content.present?
29
- path_or_content
30
- else
31
- default
32
- end
33
- end
34
- end
@@ -1 +0,0 @@
1
- You are a test prompt just used for testing.
@@ -1,43 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe Ollama::Utils::CacheFetcher do
4
- let :url do
5
- 'https://www.example.com/hello'
6
- end
7
-
8
- let :cache do
9
- double('RedisCache')
10
- end
11
-
12
- let :fetcher do
13
- described_class.new(cache).expose
14
- end
15
-
16
- it 'can be instantiated' do
17
- expect(fetcher).to be_a described_class
18
- end
19
-
20
- it 'has #get' do
21
- expect(cache).to receive(:[]).with('body-69ce405ab83f42dffa9fd22bbd47783f').and_return 'world'
22
- expect(cache).to receive(:[]).with('content_type-69ce405ab83f42dffa9fd22bbd47783f').and_return 'text/plain'
23
- yielded_io = nil
24
- block = -> io { yielded_io = io }
25
- fetcher.get(url, &block)
26
- expect(yielded_io).to be_a StringIO
27
- expect(yielded_io.read).to eq 'world'
28
- end
29
-
30
- it '#get needs block' do
31
- expect { fetcher.get(url) }.to raise_error(ArgumentError)
32
- end
33
-
34
- it 'has #put' do
35
- io = StringIO.new('world')
36
- io.extend(Ollama::Utils::Fetcher::HeaderExtension)
37
- io.content_type = MIME::Types['text/plain'].first
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)
41
- fetcher.put(url, io)
42
- end
43
- end
@@ -1,137 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe Ollama::Utils::Fetcher do
4
- let :url do
5
- 'https://www.example.com/hello'
6
- end
7
-
8
- let :fetcher do
9
- described_class.new.expose
10
- end
11
-
12
- it 'can be instantiated' do
13
- expect(fetcher).to be_a described_class
14
- end
15
-
16
- it 'has .get' do
17
- expect(described_class).to receive(:new).and_return double(get: true)
18
- described_class.get(url)
19
- end
20
-
21
- it 'can #get with streaming' do
22
- stub_request(:get, url).
23
- with(headers: fetcher.headers).
24
- to_return(
25
- status: 200,
26
- body: 'world',
27
- headers: { 'Content-Type' => 'text/plain' },
28
- )
29
- fetcher.get(url) do |tmp|
30
- expect(tmp).to be_a Tempfile
31
- expect(tmp.read).to eq 'world'
32
- expect(tmp.content_type).to eq 'text/plain'
33
- end
34
- end
35
-
36
- it 'can #get without ssl peer verification' do
37
- fetcher = described_class.new(
38
- http_options: { ssl_verify_peer: false }
39
- ).expose
40
- stub_request(:get, url).
41
- with(headers: fetcher.headers).
42
- to_return(
43
- status: 200,
44
- body: 'world',
45
- headers: { 'Content-Type' => 'text/plain' },
46
- )
47
- expect(Excon).to receive(:new).with(
48
- url,
49
- hash_including(ssl_verify_peer: false)
50
- ).and_call_original
51
- fetcher.get(url) do |tmp|
52
- expect(tmp).to be_a Tempfile
53
- expect(tmp.read).to eq 'world'
54
- expect(tmp.content_type).to eq 'text/plain'
55
- end
56
- end
57
-
58
- it 'can #get and fallback from streaming' do
59
- stub_request(:get, url).
60
- with(headers: fetcher.headers).
61
- to_return(
62
- { status: 501 },
63
- {
64
- status: 200,
65
- body: 'world',
66
- headers: { 'Content-Type' => 'text/plain' },
67
- }
68
- )
69
- fetcher.get(url) do |tmp|
70
- expect(tmp).to be_a Tempfile
71
- expect(tmp.read).to eq 'world'
72
- expect(tmp.content_type).to eq 'text/plain'
73
- end
74
- end
75
-
76
- it 'can #get and finally fail' do
77
- stub_request(:get, url).
78
- with(headers: fetcher.headers).
79
- to_return(status: 500)
80
- expect(STDERR).to receive(:puts).with(/cannot.*get.*#{url}/i)
81
- fetcher.get(url) do |tmp|
82
- expect(tmp).to be_a StringIO
83
- expect(tmp.read).to eq ''
84
- expect(tmp.content_type).to eq 'text/plain'
85
- end
86
- end
87
-
88
- it 'can redirect' do
89
- expect(fetcher.middlewares).to include Excon::Middleware::RedirectFollower
90
- end
91
-
92
- it 'can .read' do
93
- described_class.read(__FILE__) do |file|
94
- expect(file).to be_a File
95
- expect(file.read).to include 'can .read'
96
- expect(file.content_type).to eq 'application/x-ruby'
97
- end
98
- end
99
-
100
- it 'can .execute' do
101
- described_class.execute('echo -n hello world') do |file|
102
- expect(file).to be_a Tempfile
103
- expect(file.read).to eq 'hello world'
104
- expect(file.content_type).to eq 'text/plain'
105
- end
106
- end
107
-
108
- it 'can .execute and fail' do
109
- expect(IO).to receive(:popen).and_raise StandardError
110
- expect(STDERR).to receive(:puts).with(/cannot.*execute.*foobar/i)
111
- described_class.execute('foobar') do |file|
112
- expect(file).to be_a StringIO
113
- expect(file.read).to be_empty
114
- expect(file.content_type).to eq 'text/plain'
115
- end
116
- end
117
-
118
- describe '.normalize_url' do
119
- it 'can handle umlauts' do
120
- expect(described_class.normalize_url('https://foo.de/bär')).to eq(
121
- 'https://foo.de/b%C3%A4r'
122
- )
123
- end
124
-
125
- it 'can handle escaped umlauts' do
126
- expect(described_class.normalize_url('https://foo.de/b%C3%A4r')).to eq(
127
- 'https://foo.de/b%C3%A4r'
128
- )
129
- end
130
-
131
- it 'can remove #anchors' do
132
- expect(described_class.normalize_url('https://foo.de#bar')).to eq(
133
- 'https://foo.de'
134
- )
135
- end
136
- end
137
- end
@@ -1,17 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe Ollama::Utils::FileArgument do
4
- it 'it can return content' do
5
- expect(described_class.get_file_argument('foo')).to eq 'foo'
6
- end
7
-
8
- it 'it can return content at path' do
9
- expect(described_class.get_file_argument(asset('prompt.txt'))).to include\
10
- 'test prompt'
11
- end
12
-
13
- it 'it can return default content' do
14
- expect(described_class.get_file_argument('', default: 'foo')).to eq 'foo'
15
- expect(described_class.get_file_argument(nil, default: 'foo')).to eq 'foo'
16
- end
17
- end