ollama-ruby 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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