ollama-ruby 0.0.1 → 0.2.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 +4 -4
- data/.envrc +1 -0
- data/CHANGES.md +78 -0
- data/README.md +62 -23
- data/Rakefile +16 -4
- data/bin/ollama_chat +470 -90
- data/bin/ollama_console +3 -3
- data/bin/ollama_update +17 -0
- data/config/redis.conf +5 -0
- data/docker-compose.yml +11 -0
- data/lib/ollama/client.rb +7 -2
- data/lib/ollama/documents/memory_cache.rb +44 -0
- data/lib/ollama/documents/redis_cache.rb +57 -0
- data/lib/ollama/documents/splitters/character.rb +70 -0
- data/lib/ollama/documents/splitters/semantic.rb +90 -0
- data/lib/ollama/documents.rb +172 -0
- data/lib/ollama/dto.rb +4 -7
- data/lib/ollama/handlers/progress.rb +18 -5
- data/lib/ollama/image.rb +16 -7
- data/lib/ollama/options.rb +4 -0
- data/lib/ollama/utils/chooser.rb +30 -0
- data/lib/ollama/utils/colorize_texts.rb +42 -0
- data/lib/ollama/utils/fetcher.rb +105 -0
- data/lib/ollama/utils/math.rb +48 -0
- data/lib/ollama/utils/tags.rb +7 -0
- data/lib/ollama/utils/width.rb +1 -1
- data/lib/ollama/version.rb +1 -1
- data/lib/ollama.rb +12 -5
- data/ollama-ruby.gemspec +19 -9
- data/spec/assets/embeddings.json +1 -0
- data/spec/ollama/client_spec.rb +2 -2
- data/spec/ollama/commands/chat_spec.rb +2 -2
- data/spec/ollama/commands/copy_spec.rb +2 -2
- data/spec/ollama/commands/create_spec.rb +2 -2
- data/spec/ollama/commands/delete_spec.rb +2 -2
- data/spec/ollama/commands/embed_spec.rb +3 -3
- data/spec/ollama/commands/embeddings_spec.rb +2 -2
- data/spec/ollama/commands/generate_spec.rb +2 -2
- data/spec/ollama/commands/pull_spec.rb +2 -2
- data/spec/ollama/commands/push_spec.rb +2 -2
- data/spec/ollama/commands/show_spec.rb +2 -2
- data/spec/ollama/documents/memory_cache_spec.rb +63 -0
- data/spec/ollama/documents/redis_cache_spec.rb +78 -0
- data/spec/ollama/documents/splitters/character_spec.rb +96 -0
- data/spec/ollama/documents/splitters/semantic_spec.rb +56 -0
- data/spec/ollama/documents_spec.rb +119 -0
- data/spec/ollama/handlers/progress_spec.rb +2 -2
- data/spec/ollama/image_spec.rb +4 -0
- data/spec/ollama/message_spec.rb +3 -4
- data/spec/ollama/options_spec.rb +18 -0
- data/spec/ollama/tool_spec.rb +1 -6
- data/spec/ollama/utils/fetcher_spec.rb +74 -0
- data/spec/ollama/utils/tags_spec.rb +24 -0
- data/spec/spec_helper.rb +8 -0
- data/tmp/.keep +0 -0
- metadata +187 -5
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Ollama::Documents::MemoryCache do
|
4
|
+
let :memory_cache do
|
5
|
+
described_class.new prefix: 'test-'
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'can be instantiated' do
|
9
|
+
expect(memory_cache).to be_a described_class
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can get/set a key' do
|
13
|
+
key, value = 'foo', { test: true }
|
14
|
+
expect {
|
15
|
+
memory_cache[key] = value
|
16
|
+
}.to change {
|
17
|
+
memory_cache[key]
|
18
|
+
}.from(nil).to(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can determine if key exists' do
|
22
|
+
key, value = 'foo', { test: true }
|
23
|
+
expect {
|
24
|
+
memory_cache[key] = value
|
25
|
+
}.to change {
|
26
|
+
memory_cache.key?(key)
|
27
|
+
}.from(false).to(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can delete' do
|
31
|
+
key, value = 'foo', { test: true }
|
32
|
+
memory_cache[key] = value
|
33
|
+
expect {
|
34
|
+
memory_cache.delete(key)
|
35
|
+
}.to change {
|
36
|
+
memory_cache.key?(key)
|
37
|
+
}.from(true).to(false)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns size' do
|
41
|
+
key, value = 'foo', { test: true }
|
42
|
+
expect {
|
43
|
+
memory_cache[key] = value
|
44
|
+
}.to change {
|
45
|
+
memory_cache.size
|
46
|
+
}.from(0).to(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'can clear' do
|
50
|
+
key, value = 'foo', { test: true }
|
51
|
+
memory_cache[key] = value
|
52
|
+
expect {
|
53
|
+
expect(memory_cache.clear).to eq memory_cache
|
54
|
+
}.to change {
|
55
|
+
memory_cache.size
|
56
|
+
}.from(1).to(0)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'can iterate over keys under a prefix' do
|
60
|
+
memory_cache['foo'] = 'bar'
|
61
|
+
expect(memory_cache.to_a).to eq [ %w[ test-foo bar ] ]
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Ollama::Documents::RedisCache do
|
4
|
+
it 'can be instantiated' do
|
5
|
+
redis_cache = described_class.new prefix: 'test-', url: 'something'
|
6
|
+
expect(redis_cache).to be_a described_class
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'raises ArgumentError if url is missing' do
|
10
|
+
expect {
|
11
|
+
described_class.new prefix: 'test-', url: nil
|
12
|
+
}.to raise_error ArgumentError
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'test redis interactions' do
|
16
|
+
let :redis_cache do
|
17
|
+
described_class.new prefix: 'test-', url: 'something'
|
18
|
+
end
|
19
|
+
|
20
|
+
let :redis do
|
21
|
+
double('Redis')
|
22
|
+
end
|
23
|
+
|
24
|
+
before do
|
25
|
+
allow_any_instance_of(described_class).to receive(:redis).and_return(redis)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has Redis client' do
|
29
|
+
expect(redis_cache.redis).to eq redis
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'can get a key' do
|
33
|
+
key = 'foo'
|
34
|
+
expect(redis).to receive(:get).with('test-' + key).and_return 666
|
35
|
+
redis_cache[key]
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'can set a value for a key' do
|
39
|
+
key, value = 'foo', { test: true }
|
40
|
+
expect(redis).to receive(:set).with('test-' + key, JSON(value))
|
41
|
+
redis_cache[key] = value
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'can determine if key exists' do
|
45
|
+
key = 'foo'
|
46
|
+
expect(redis).to receive(:exists?).with('test-' + key).and_return(false, true)
|
47
|
+
expect(redis_cache.key?('foo')).to eq false
|
48
|
+
expect(redis_cache.key?('foo')).to eq true
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'can delete' do
|
52
|
+
key = 'foo'
|
53
|
+
expect(redis).to receive(:del).with('test-' + key)
|
54
|
+
redis_cache.delete(key)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns size' do
|
58
|
+
allow(redis).to receive(:scan_each).with(match: 'test-*').
|
59
|
+
and_yield('test-foo').
|
60
|
+
and_yield('test-bar').
|
61
|
+
and_yield('test-baz')
|
62
|
+
expect(redis_cache.size).to eq 3
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'can clear' do
|
66
|
+
expect(redis).to receive(:scan_each).with(match: 'test-*').and_yield(
|
67
|
+
'test-foo'
|
68
|
+
)
|
69
|
+
expect(redis).to receive(:del).with('test-foo')
|
70
|
+
expect(redis_cache.clear).to eq redis_cache
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'can iterate over keys under a prefix' do
|
74
|
+
expect(redis).to receive(:scan_each).with(match: 'test-*')
|
75
|
+
redis_cache.to_a
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Ollama::Documents::Splitters::Character do
|
4
|
+
let :splitter do
|
5
|
+
described_class.new chunk_size: 23
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'can be instantiated' do
|
9
|
+
expect(splitter).to be_a described_class
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can split' do
|
13
|
+
text = [ "A" * 10 ] * 10 * "\n\n"
|
14
|
+
result = splitter.split(text)
|
15
|
+
expect(result.count).to eq 5
|
16
|
+
expect(result.to_a.join('')).to eq ?A * 100
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'can split including separator' do
|
20
|
+
splitter = described_class.new chunk_size: 25, include_separator: true
|
21
|
+
text = [ "A" * 10 ] * 10 * "\n\n"
|
22
|
+
result = splitter.split(text)
|
23
|
+
expect(result.count).to eq 5
|
24
|
+
expect(result.to_a.join('')).to eq text
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'cannot split' do
|
28
|
+
text = [ "A" * 10 ] * 10 * "\n"
|
29
|
+
result = splitter.split(text)
|
30
|
+
expect(result.count).to eq 1
|
31
|
+
expect(result.to_a.join('').count(?A)).to eq text.count(?A)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'cannot split2' do
|
35
|
+
text = "A" * 25
|
36
|
+
result = splitter.split(text)
|
37
|
+
expect(result.count).to eq 1
|
38
|
+
expect(result.to_a.join('')).to eq ?A * 25
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can split sentences' do
|
42
|
+
text = "foo.foo. bar!bar! baz?baz? quux.\nquux."
|
43
|
+
splitter = described_class.new(separator: /[.!?]\s*(?:\b|\z)/, chunk_size: 2)
|
44
|
+
result = splitter.split(text)
|
45
|
+
expect(result.to_a).to eq %w[ foo foo bar bar baz baz quux quux ]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
RSpec.describe Ollama::Documents::Splitters::RecursiveCharacter do
|
50
|
+
let :splitter do
|
51
|
+
described_class.new chunk_size: 23
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'can be instantiated' do
|
55
|
+
expect(splitter).to be_a described_class
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'can split' do
|
59
|
+
text = [ "A" * 10 ] * 10 * "\n\n"
|
60
|
+
result = splitter.split(text)
|
61
|
+
expect(result.count).to eq 5
|
62
|
+
expect(result.to_a.join('')).to eq ?A * 100
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'cannot split' do
|
66
|
+
splitter = described_class.new chunk_size: 23, include_separator: true,
|
67
|
+
separators: described_class::DEFAULT_SEPARATORS[0..-2]
|
68
|
+
text = "A" * 25
|
69
|
+
result = splitter.split(text)
|
70
|
+
expect(result.count).to eq 1
|
71
|
+
expect(result.to_a.join('')).to eq ?A * 25
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'can split including separator' do
|
75
|
+
splitter = described_class.new chunk_size: 25, include_separator: true
|
76
|
+
text = [ "A" * 10 ] * 10 * "\n\n"
|
77
|
+
result = splitter.split(text)
|
78
|
+
expect(result.count).to eq 5
|
79
|
+
expect(result.to_a.join('')).to eq text
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'can split single newline as well' do
|
83
|
+
text = [ "A" * 10 ] * 10 * "\n"
|
84
|
+
result = splitter.split(text)
|
85
|
+
expect(result.count).to eq 5
|
86
|
+
expect(result.to_a.join('')).to eq ?A * 100
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'can split single newline as well including separator' do
|
90
|
+
splitter = described_class.new chunk_size: 25, include_separator: true
|
91
|
+
text = [ "A" * 10 ] * 10 * "\n"
|
92
|
+
result = splitter.split(text)
|
93
|
+
expect(result.count).to eq 5
|
94
|
+
expect(result.to_a.join('')).to eq text
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Ollama::Documents::Splitters::Semantic do
|
4
|
+
let :ollama do
|
5
|
+
double('Ollama::Client')
|
6
|
+
end
|
7
|
+
|
8
|
+
let :splitter do
|
9
|
+
described_class.new ollama:, model: 'mxbai-embed-large'
|
10
|
+
end
|
11
|
+
|
12
|
+
let :embeddings do
|
13
|
+
JSON(File.read(asset('embeddings.json')))
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'can be instantiated' do
|
17
|
+
expect(splitter).to be_a described_class
|
18
|
+
end
|
19
|
+
|
20
|
+
before do
|
21
|
+
allow(ollama).to receive(:embed).and_return(double(embeddings:))
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can split with breakpoint :percentile' do
|
25
|
+
text = ([ "A" * 10 ] * 3 + [ "B" * 10 ] * 3 + [ "A" * 10 ] * 3) * ". "
|
26
|
+
result = splitter.split(text, breakpoint: :percentile, percentile: 75)
|
27
|
+
expect(result.count).to eq 3
|
28
|
+
expect(result.to_a.join('').count(?A)).to eq text.count(?A)
|
29
|
+
expect(result.to_a.join('').count(?B)).to eq text.count(?B)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'can split with breakpoint :percentile' do
|
33
|
+
described_class.new ollama:, model: 'mxbai-embed-large', chunk_size: 50
|
34
|
+
text = ([ "A" * 10 ] * 6 + [ "B" * 10 ] * 3 + [ "A" * 10 ] * 3) * ". "
|
35
|
+
result = splitter.split(text, breakpoint: :percentile, percentile: 75)
|
36
|
+
expect(result.count).to eq 4
|
37
|
+
expect(result.to_a.join('').count(?A)).to eq text.count(?A)
|
38
|
+
expect(result.to_a.join('').count(?B)).to eq text.count(?B)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can split with breakpoint :standard_deviation' do
|
42
|
+
text = ([ "A" * 10 ] * 3 + [ "B" * 10 ] * 3 + [ "A" * 10 ] * 3) * ". "
|
43
|
+
result = splitter.split(text, breakpoint: :standard_deviation, percentage: 100)
|
44
|
+
expect(result.count).to eq 3
|
45
|
+
expect(result.to_a.join('').count(?A)).to eq text.count(?A)
|
46
|
+
expect(result.to_a.join('').count(?B)).to eq text.count(?B)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'can split with breakpoint :interquartile' do
|
50
|
+
text = ([ "A" * 10 ] * 3 + [ "B" * 10 ] * 3 + [ "A" * 10 ] * 3) * ". "
|
51
|
+
result = splitter.split(text, breakpoint: :interquartile, percentage: 75)
|
52
|
+
expect(result.count).to eq 3
|
53
|
+
expect(result.to_a.join('').count(?A)).to eq text.count(?A)
|
54
|
+
expect(result.to_a.join('').count(?B)).to eq text.count(?B)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Ollama::Documents do
|
4
|
+
let :ollama do
|
5
|
+
double('Ollama::Client')
|
6
|
+
end
|
7
|
+
|
8
|
+
let :model do
|
9
|
+
'mxbai-embed-large'
|
10
|
+
end
|
11
|
+
|
12
|
+
let :documents do
|
13
|
+
described_class.new ollama:, model:
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'can be instantiated' do
|
17
|
+
expect(documents).to be_a described_class
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'no texts can be added to it' do
|
21
|
+
expect(documents.add([])).to eq documents
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'texts can be added to it' do
|
25
|
+
expect(ollama).to receive(:embed).
|
26
|
+
with(model:, input: %w[ foo bar ], options: nil).
|
27
|
+
and_return(double(embeddings: [ [ 0.1 ], [ 0.2 ] ]))
|
28
|
+
expect(documents.add(%w[ foo bar ])).to eq documents
|
29
|
+
expect(documents.exist?('foo')).to eq true
|
30
|
+
expect(documents.exist?('bar')).to eq true
|
31
|
+
expect(documents['foo']).to be_a Ollama::Documents::Record
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'a text can be added to it' do
|
35
|
+
expect(ollama).to receive(:embed).
|
36
|
+
with(model:, input: %w[ foo ], options: nil).
|
37
|
+
and_return(double(embeddings: [ [ 0.1 ] ]))
|
38
|
+
expect(documents << 'foo').to eq documents
|
39
|
+
expect(documents.exist?('foo')).to eq true
|
40
|
+
expect(documents.exist?('bar')).to eq false
|
41
|
+
expect(documents['foo']).to be_a Ollama::Documents::Record
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'can find strings' do
|
45
|
+
allow(ollama).to receive(:embed).
|
46
|
+
with(model:, input: [ 'foo' ], options: nil).
|
47
|
+
and_return(double(embeddings: [ [ 0.1 ] ]))
|
48
|
+
expect(documents << 'foo').to eq documents
|
49
|
+
expect(ollama).to receive(:embed).
|
50
|
+
with(model:, input: 'foo', options: nil).
|
51
|
+
and_return(double(embeddings: [ [ 0.1 ] ]))
|
52
|
+
records = documents.find('foo')
|
53
|
+
expect(records).to eq [
|
54
|
+
Ollama::Documents::Record[text: 'foo', embedding: [ 0.1 ], similarity: 1.0 ]
|
55
|
+
]
|
56
|
+
expect(records[0].to_s).to eq '#<Ollama::Documents::Record "foo" 1.0>'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'can find only tagged strings' do
|
60
|
+
allow(ollama).to receive(:embed).
|
61
|
+
with(model:, input: [ 'foo' ], options: nil).
|
62
|
+
and_return(double(embeddings: [ [ 0.1 ] ]))
|
63
|
+
expect(documents.add('foo', tags: %i[ test ])).to eq documents
|
64
|
+
expect(ollama).to receive(:embed).
|
65
|
+
with(model:, input: 'foo', options: nil).
|
66
|
+
and_return(double(embeddings: [ [ 0.1 ] ]))
|
67
|
+
records = documents.find('foo', tags: %i[ nix ])
|
68
|
+
expect(records).to eq []
|
69
|
+
expect(ollama).to receive(:embed).
|
70
|
+
with(model:, input: 'foo', options: nil).
|
71
|
+
and_return(double(embeddings: [ [ 0.1 ] ]))
|
72
|
+
records = documents.find('foo', tags: %i[ test ])
|
73
|
+
expect(records).to eq [
|
74
|
+
Ollama::Documents::Record[text: 'foo', embedding: [ 0.1 ], similarity: 1.0 ]
|
75
|
+
]
|
76
|
+
expect(records[0].to_s).to eq '#<Ollama::Documents::Record "foo" #test 1.0>'
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'it uses cache' do
|
80
|
+
before do
|
81
|
+
allow(ollama).to receive(:embed).
|
82
|
+
with(model:, input: %w[ foo ], options: nil).
|
83
|
+
and_return(double(embeddings: [ [ 0.1 ] ]))
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'can delete texts' do
|
87
|
+
expect(documents << 'foo').to eq documents
|
88
|
+
expect {
|
89
|
+
documents.delete('foo')
|
90
|
+
}.to change { documents.exist?('foo') }.from(true).to(false)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'tracks size' do
|
94
|
+
expect {
|
95
|
+
expect(documents << 'foo').to eq documents
|
96
|
+
}.to change { documents.size }.from(0).to(1)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'can clear texts' do
|
100
|
+
expect(documents << 'foo').to eq documents
|
101
|
+
expect {
|
102
|
+
documents.clear
|
103
|
+
}.to change { documents.size }.from(1).to(0)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns collections' do
|
107
|
+
expect(documents.collections).to eq [ :default ]
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'can change collection' do
|
111
|
+
expect(documents.instance_eval { @cache }).to receive(:prefix=).
|
112
|
+
with(/#@collection/).and_call_original
|
113
|
+
expect { documents.collection = :new_collection }.
|
114
|
+
to change { documents.collection }.
|
115
|
+
from(:default).
|
116
|
+
to(:new_collection)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -7,7 +7,7 @@ RSpec.describe Ollama::Handlers::Progress do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
it 'can display progress' do
|
10
|
-
response = double('response', status: 'testing', completed: 23, total: 666)
|
10
|
+
response = double('response', status: 'testing', completed: 23, total: 666, error: nil)
|
11
11
|
expect(infobar.counter).to receive(:progress).with(by: 23).and_call_original
|
12
12
|
expect(infobar.display).to receive(:update).and_call_original
|
13
13
|
described_class.new.call(response)
|
@@ -16,7 +16,7 @@ RSpec.describe Ollama::Handlers::Progress do
|
|
16
16
|
it 'can display errors in progress' do
|
17
17
|
response = double('response', error: 'foo', status: nil, completed: nil, total: nil)
|
18
18
|
progress = described_class.new
|
19
|
-
expect(
|
19
|
+
expect(infobar).to receive(:puts).with(/Error: .*foo/)
|
20
20
|
progress.call(response)
|
21
21
|
end
|
22
22
|
end
|
data/spec/ollama/image_spec.rb
CHANGED
data/spec/ollama/message_spec.rb
CHANGED
@@ -19,19 +19,18 @@ RSpec.describe Ollama::Message do
|
|
19
19
|
|
20
20
|
it 'can be converted to JSON' do
|
21
21
|
expect(message.as_json).to eq(
|
22
|
-
json_class: described_class.name,
|
23
22
|
role: 'user',
|
24
23
|
content: 'hello world',
|
25
24
|
images: [ image ],
|
26
25
|
)
|
27
26
|
expect(message.to_json).to eq(
|
28
|
-
'{"
|
27
|
+
'{"role":"user","content":"hello world","images":["dGVzdA==\n"]}'
|
29
28
|
)
|
30
29
|
end
|
31
30
|
|
32
31
|
it 'can be restored from JSON' do
|
33
|
-
expect(JSON(<<~'end'
|
34
|
-
{"
|
32
|
+
expect(described_class.from_hash(JSON(<<~'end'))).to be_a described_class
|
33
|
+
{"role":"user","content":"hello world","images":["dGVzdA==\n"]}
|
35
34
|
end
|
36
35
|
end
|
37
36
|
end
|
data/spec/ollama/options_spec.rb
CHANGED
@@ -13,6 +13,24 @@ RSpec.describe Ollama::Options do
|
|
13
13
|
expect(options).to be_a described_class
|
14
14
|
end
|
15
15
|
|
16
|
+
it 'can be used to cast hashes' do
|
17
|
+
expect(described_class[{
|
18
|
+
penalize_newline: true,
|
19
|
+
num_ctx: 8192,
|
20
|
+
temperature: 0.7,
|
21
|
+
}]).to be_a described_class
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'raises errors when casting goes all wrong' do
|
25
|
+
expect {
|
26
|
+
described_class[{
|
27
|
+
penalize_newline: :tertium,
|
28
|
+
num_ctx: 8192,
|
29
|
+
temperature: 0.7,
|
30
|
+
}]
|
31
|
+
}.to raise_error(TypeError)
|
32
|
+
end
|
33
|
+
|
16
34
|
it 'throws error for invalid types' do
|
17
35
|
expect { described_class.new(temperature: Class.new) }.
|
18
36
|
to raise_error(TypeError)
|
data/spec/ollama/tool_spec.rb
CHANGED
@@ -45,23 +45,18 @@ RSpec.describe Ollama::Tool do
|
|
45
45
|
|
46
46
|
it 'cannot be converted to JSON' do
|
47
47
|
expect(tool.as_json).to eq(
|
48
|
-
json_class: described_class.name,
|
49
48
|
type: 'function',
|
50
49
|
function: {
|
51
|
-
json_class: "Ollama::Tool::Function",
|
52
50
|
name: 'get_current_weather',
|
53
51
|
description: "Get the current weather for a location",
|
54
52
|
parameters: {
|
55
|
-
json_class: "Ollama::Tool::Function::Parameters",
|
56
53
|
type: "object",
|
57
54
|
properties: {
|
58
55
|
location: {
|
59
|
-
json_class: "Ollama::Tool::Function::Parameters::Property",
|
60
56
|
type: "string",
|
61
57
|
description: "The location to get the weather for, e.g. Berlin, Berlin"
|
62
58
|
},
|
63
59
|
format: {
|
64
|
-
json_class: "Ollama::Tool::Function::Parameters::Property",
|
65
60
|
type: "string",
|
66
61
|
description: "The format to return the weather in, e.g. 'celsius' or 'fahrenheit'",
|
67
62
|
enum: ["celsius", "fahrenheit"]
|
@@ -72,7 +67,7 @@ RSpec.describe Ollama::Tool do
|
|
72
67
|
}
|
73
68
|
)
|
74
69
|
expect(tool.to_json).to eq(
|
75
|
-
%{{"
|
70
|
+
%{{"type":"function","function":{"name":"get_current_weather","description":"Get the current weather for a location","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The location to get the weather for, e.g. Berlin, Berlin"},"format":{"type":"string","description":"The format to return the weather in, e.g. 'celsius' or 'fahrenheit'","enum":["celsius","fahrenheit"]}},"required":["location","format"]}}}}
|
76
71
|
)
|
77
72
|
end
|
78
73
|
end
|
@@ -0,0 +1,74 @@
|
|
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
|
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, 'https://www.example.com/hello').
|
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 and fallback from streaming' do
|
37
|
+
stub_request(:get, 'https://www.example.com/hello').
|
38
|
+
with(headers: fetcher.headers).
|
39
|
+
to_return(
|
40
|
+
{ status: 501 },
|
41
|
+
{
|
42
|
+
status: 200,
|
43
|
+
body: 'world',
|
44
|
+
headers: { 'Content-Type' => 'text/plain' },
|
45
|
+
}
|
46
|
+
)
|
47
|
+
fetcher.get(url) do |tmp|
|
48
|
+
expect(tmp).to be_a Tempfile
|
49
|
+
expect(tmp.read).to eq 'world'
|
50
|
+
expect(tmp.content_type).to eq 'text/plain'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'can #get and finally fail' do
|
55
|
+
stub_request(:get, 'https://www.example.com/hello').
|
56
|
+
with(headers: fetcher.headers).
|
57
|
+
to_return(status: 500)
|
58
|
+
fetcher.get(url) do |tmp|
|
59
|
+
expect(tmp).to be_nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'can redirect' do
|
64
|
+
expect(fetcher.middlewares).to include Excon::Middleware::RedirectFollower
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'can .read' do
|
68
|
+
described_class.read(__FILE__) do |file|
|
69
|
+
expect(file).to be_a File
|
70
|
+
expect(file.read).to include 'can .read'
|
71
|
+
expect(file.content_type).to eq 'application/x-ruby'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Ollama::Utils::Tags do
|
4
|
+
it 'can be instantiated' do
|
5
|
+
expect(described_class.new).to be_a described_class
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'can contain unique tags and is sorted' do
|
9
|
+
tags = described_class.new([ 'bar', 'foo'])
|
10
|
+
expect(tags.to_a).to eq %w[ bar foo ]
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'tags can be added to it' do
|
14
|
+
tags = described_class.new([ 'foo' ])
|
15
|
+
tags.add 'bar'
|
16
|
+
expect(tags.to_a).to eq %w[ bar foo ]
|
17
|
+
tags.merge [ 'baz', 'baz2' ]
|
18
|
+
expect(tags.to_a).to eq %w[ bar baz baz2 foo ]
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can be output nicely' do
|
22
|
+
expect(described_class.new(%w[foo bar]).to_s).to eq '#bar #foo'
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -9,8 +9,16 @@ begin
|
|
9
9
|
require 'debug'
|
10
10
|
rescue LoadError
|
11
11
|
end
|
12
|
+
require 'webmock/rspec'
|
13
|
+
WebMock.disable_net_connect!
|
12
14
|
require 'ollama'
|
13
15
|
|
14
16
|
def asset(name)
|
15
17
|
File.join(__dir__, 'assets', name)
|
16
18
|
end
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
config.before(:suite) do
|
22
|
+
infobar.show = nil
|
23
|
+
end
|
24
|
+
end
|
data/tmp/.keep
ADDED
File without changes
|