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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/CHANGES.md +78 -0
  4. data/README.md +62 -23
  5. data/Rakefile +16 -4
  6. data/bin/ollama_chat +470 -90
  7. data/bin/ollama_console +3 -3
  8. data/bin/ollama_update +17 -0
  9. data/config/redis.conf +5 -0
  10. data/docker-compose.yml +11 -0
  11. data/lib/ollama/client.rb +7 -2
  12. data/lib/ollama/documents/memory_cache.rb +44 -0
  13. data/lib/ollama/documents/redis_cache.rb +57 -0
  14. data/lib/ollama/documents/splitters/character.rb +70 -0
  15. data/lib/ollama/documents/splitters/semantic.rb +90 -0
  16. data/lib/ollama/documents.rb +172 -0
  17. data/lib/ollama/dto.rb +4 -7
  18. data/lib/ollama/handlers/progress.rb +18 -5
  19. data/lib/ollama/image.rb +16 -7
  20. data/lib/ollama/options.rb +4 -0
  21. data/lib/ollama/utils/chooser.rb +30 -0
  22. data/lib/ollama/utils/colorize_texts.rb +42 -0
  23. data/lib/ollama/utils/fetcher.rb +105 -0
  24. data/lib/ollama/utils/math.rb +48 -0
  25. data/lib/ollama/utils/tags.rb +7 -0
  26. data/lib/ollama/utils/width.rb +1 -1
  27. data/lib/ollama/version.rb +1 -1
  28. data/lib/ollama.rb +12 -5
  29. data/ollama-ruby.gemspec +19 -9
  30. data/spec/assets/embeddings.json +1 -0
  31. data/spec/ollama/client_spec.rb +2 -2
  32. data/spec/ollama/commands/chat_spec.rb +2 -2
  33. data/spec/ollama/commands/copy_spec.rb +2 -2
  34. data/spec/ollama/commands/create_spec.rb +2 -2
  35. data/spec/ollama/commands/delete_spec.rb +2 -2
  36. data/spec/ollama/commands/embed_spec.rb +3 -3
  37. data/spec/ollama/commands/embeddings_spec.rb +2 -2
  38. data/spec/ollama/commands/generate_spec.rb +2 -2
  39. data/spec/ollama/commands/pull_spec.rb +2 -2
  40. data/spec/ollama/commands/push_spec.rb +2 -2
  41. data/spec/ollama/commands/show_spec.rb +2 -2
  42. data/spec/ollama/documents/memory_cache_spec.rb +63 -0
  43. data/spec/ollama/documents/redis_cache_spec.rb +78 -0
  44. data/spec/ollama/documents/splitters/character_spec.rb +96 -0
  45. data/spec/ollama/documents/splitters/semantic_spec.rb +56 -0
  46. data/spec/ollama/documents_spec.rb +119 -0
  47. data/spec/ollama/handlers/progress_spec.rb +2 -2
  48. data/spec/ollama/image_spec.rb +4 -0
  49. data/spec/ollama/message_spec.rb +3 -4
  50. data/spec/ollama/options_spec.rb +18 -0
  51. data/spec/ollama/tool_spec.rb +1 -6
  52. data/spec/ollama/utils/fetcher_spec.rb +74 -0
  53. data/spec/ollama/utils/tags_spec.rb +24 -0
  54. data/spec/spec_helper.rb +8 -0
  55. data/tmp/.keep +0 -0
  56. 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(progress.output).to receive(:puts).with(/Error: .*foo/)
19
+ expect(infobar).to receive(:puts).with(/Error: .*foo/)
20
20
  progress.call(response)
21
21
  end
22
22
  end
@@ -20,4 +20,8 @@ RSpec.describe Ollama::Image do
20
20
  expect(image.to_s.sum).to eq 42460
21
21
  expect(image.to_s[0, 40]).to eq '/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAA'
22
22
  end
23
+
24
+ it 'tracks path of file' do
25
+ expect(image.path).to eq asset('kitten.jpg')
26
+ end
23
27
  end
@@ -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
- '{"json_class":"Ollama::Message","role":"user","content":"hello world","images":["dGVzdA==\n"]}'
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', create_additions: true)).to be_a described_class
34
- {"json_class":"Ollama::Message","role":"user","content":"hello world","images":["dGVzdA==\n"]}
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
@@ -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)
@@ -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
- %{{"json_class":"Ollama::Tool","type":"function","function":{"json_class":"Ollama::Tool::Function","name":"get_current_weather","description":"Get the current weather for a location","parameters":{"json_class":"Ollama::Tool::Function::Parameters","type":"object","properties":{"location":{"json_class":"Ollama::Tool::Function::Parameters::Property","type":"string","description":"The location to get the weather for, e.g. Berlin, Berlin"},"format":{"json_class":"Ollama::Tool::Function::Parameters::Property","type":"string","description":"The format to return the weather in, e.g. 'celsius' or 'fahrenheit'","enum":["celsius","fahrenheit"]}},"required":["location","format"]}}}}
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