ollama_chat 0.0.95 → 0.0.97
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 +106 -0
- data/README.md +115 -2
- data/Rakefile +1 -1
- data/lib/ollama_chat/chat.rb +34 -17
- data/lib/ollama_chat/commands.rb +27 -15
- data/lib/ollama_chat/database/migrations/005_add_context_format_to_sessions.rb +5 -0
- data/lib/ollama_chat/database/models/model_options.rb +1 -0
- data/lib/ollama_chat/database/models/prompt.rb +1 -3
- data/lib/ollama_chat/database/models/session.rb +18 -0
- data/lib/ollama_chat/follow_chat.rb +4 -5
- data/lib/ollama_chat/information.rb +2 -1
- data/lib/ollama_chat/input_content.rb +1 -1
- data/lib/ollama_chat/message_list.rb +2 -4
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +2 -1
- data/lib/ollama_chat/parsing.rb +10 -6
- data/lib/ollama_chat/personae_management.rb +25 -11
- data/lib/ollama_chat/prompt_management.rb +25 -11
- data/lib/ollama_chat/session_management.rb +24 -30
- data/lib/ollama_chat/source_fetching.rb +1 -1
- data/lib/ollama_chat/state_selectors.rb +14 -0
- data/lib/ollama_chat/system_prompt_management.rb +21 -0
- data/lib/ollama_chat/think_control.rb +0 -1
- data/lib/ollama_chat/token_estimator/crude.rb +25 -0
- data/lib/ollama_chat/token_estimator.rb +36 -0
- data/lib/ollama_chat/tool_calling.rb +1 -1
- data/lib/ollama_chat/tools/compute_bmi.rb +6 -4
- data/lib/ollama_chat/tools/execute_grep.rb +11 -1
- data/lib/ollama_chat/tools/file_context.rb +3 -2
- data/lib/ollama_chat/tools/generate_password.rb +4 -1
- data/lib/ollama_chat/tools/get_current_weather.rb +11 -0
- data/lib/ollama_chat/tools/get_time.rb +13 -2
- data/lib/ollama_chat/tools/open_file_in_editor.rb +3 -3
- data/lib/ollama_chat/tools/read_file.rb +3 -0
- data/lib/ollama_chat/tools/retrieve_document_snippets.rb +18 -11
- data/lib/ollama_chat/tools/roll_dice.rb +52 -11
- data/lib/ollama_chat/tools/run_tests.rb +23 -11
- data/lib/ollama_chat/tools/write_file.rb +2 -6
- data/lib/ollama_chat/utils/fetcher.rb +2 -2
- data/lib/ollama_chat/utils/png_metadata_extractor.rb +5 -4
- data/lib/ollama_chat/utils.rb +0 -1
- data/lib/ollama_chat/version.rb +1 -1
- data/lib/ollama_chat.rb +1 -0
- data/ollama_chat.gemspec +6 -6
- data/spec/ollama_chat/database/models/favourite_spec.rb +7 -20
- data/spec/ollama_chat/server_socket_spec.rb +11 -20
- data/spec/ollama_chat/source_fetching_spec.rb +1 -1
- data/spec/ollama_chat/state_selectors_spec.rb +4 -9
- data/spec/ollama_chat/switches_spec.rb +1 -1
- data/spec/ollama_chat/token_estimator_spec.rb +41 -0
- data/spec/ollama_chat/tools/compute_bmi_spec.rb +14 -5
- data/spec/ollama_chat/tools/copy_to_clipboard_spec.rb +5 -5
- data/spec/ollama_chat/tools/execute_grep_spec.rb +10 -0
- data/spec/ollama_chat/tools/generate_image_spec.rb +8 -8
- data/spec/ollama_chat/tools/generate_password_spec.rb +13 -0
- data/spec/ollama_chat/tools/get_current_weather_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_endoflife_spec.rb +3 -3
- data/spec/ollama_chat/tools/get_rfc_spec.rb +2 -2
- data/spec/ollama_chat/tools/get_time_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_url_spec.rb +7 -7
- data/spec/ollama_chat/tools/paste_from_clipboard_spec.rb +4 -4
- data/spec/ollama_chat/tools/read_file_spec.rb +3 -0
- data/spec/ollama_chat/tools/retrieve_document_snippets_spec.rb +38 -5
- data/spec/ollama_chat/tools/roll_dice_spec.rb +4 -4
- data/spec/ollama_chat/tools/run_tests_spec.rb +5 -5
- data/spec/ollama_chat/tools/search_web_spec.rb +3 -3
- data/spec/ollama_chat/tools/write_file_spec.rb +2 -0
- metadata +11 -5
- data/lib/ollama_chat/utils/token_estimator.rb +0 -15
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
describe OllamaChat::TokenEstimator do
|
|
2
|
+
describe '.estimate' do
|
|
3
|
+
it 'uses crude estimation when no ollama client and model are provided' do
|
|
4
|
+
# 10 bytes / 3.5 = 2.85... -> ceil is 3
|
|
5
|
+
result = described_class.estimate('1234567890')
|
|
6
|
+
expect(result).to be_a OllamaChat::TokenEstimator::Estimate
|
|
7
|
+
expect(result.tokens).to eq 3
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe OllamaChat::TokenEstimator::Crude do
|
|
12
|
+
it 'correctly estimates tokens from a string' do
|
|
13
|
+
# 11 bytes / 3.5 = 3.14… -> ceil is 4, as legislated by the state of
|
|
14
|
+
# Indiana… probably.
|
|
15
|
+
crude = described_class.new('Hello World') # 11 bytes
|
|
16
|
+
result = crude.perform
|
|
17
|
+
expect(result.bytes).to eq 11
|
|
18
|
+
expect(result.tokens).to eq 4
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'correctly estimates tokens from an integer byte count' do
|
|
22
|
+
# 100 bytes / 3.5 = 28.5... -> ceil is 29
|
|
23
|
+
crude = described_class.new(100)
|
|
24
|
+
result = crude.perform
|
|
25
|
+
expect(result.bytes).to eq 100
|
|
26
|
+
expect(result.tokens).to eq 29
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'raises ArgumentError for invalid input types' do
|
|
30
|
+
expect {
|
|
31
|
+
described_class.new({ not: 'a string or int' })
|
|
32
|
+
}.to raise_error(ArgumentError, /cannot be used to estimate/)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'returns an Estimate object with formatted methods' do
|
|
36
|
+
result = described_class.new('Test').perform
|
|
37
|
+
expect(result).to respond_to(:bytes_formatted)
|
|
38
|
+
expect(result).to respond_to(:tokens_formatted)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -28,11 +28,14 @@ describe OllamaChat::Tools::ComputeBMI do
|
|
|
28
28
|
)
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
-
result = described_class.new.execute(tool_call, chat:
|
|
31
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
32
32
|
json = json_object(result)
|
|
33
33
|
|
|
34
34
|
expect(json.bmi).to be_within(0.01).of(22.86)
|
|
35
35
|
expect(json.category).to eq 'Normal weight'
|
|
36
|
+
expect(json.message).to match(
|
|
37
|
+
/This BMI is 22\.8\d+, which falls into the Normal weight category\./
|
|
38
|
+
)
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
it 'calculates BMI correctly for Underweight' do
|
|
@@ -44,11 +47,14 @@ describe OllamaChat::Tools::ComputeBMI do
|
|
|
44
47
|
)
|
|
45
48
|
)
|
|
46
49
|
|
|
47
|
-
result = described_class.new.execute(tool_call, chat:
|
|
50
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
48
51
|
json = json_object(result)
|
|
49
52
|
|
|
50
53
|
expect(json.bmi).to be_within(0.01).of(17.58)
|
|
51
54
|
expect(json.category).to eq 'Underweight'
|
|
55
|
+
expect(json.message).to match(
|
|
56
|
+
/This BMI is 17\.5\d+, which falls into the Underweight category\./
|
|
57
|
+
)
|
|
52
58
|
end
|
|
53
59
|
end
|
|
54
60
|
|
|
@@ -62,11 +68,14 @@ describe OllamaChat::Tools::ComputeBMI do
|
|
|
62
68
|
)
|
|
63
69
|
)
|
|
64
70
|
|
|
65
|
-
result = described_class.new.execute(tool_call, chat:
|
|
71
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
66
72
|
json = json_object(result)
|
|
67
73
|
|
|
68
74
|
expect(json.bmi).to be_within(0.01).of(21.52)
|
|
69
75
|
expect(json.category).to eq 'Normal weight'
|
|
76
|
+
expect(json.message).to match(
|
|
77
|
+
/This BMI is 21\.5\d+, which falls into the Normal weight category\./
|
|
78
|
+
)
|
|
70
79
|
end
|
|
71
80
|
end
|
|
72
81
|
|
|
@@ -80,7 +89,7 @@ describe OllamaChat::Tools::ComputeBMI do
|
|
|
80
89
|
)
|
|
81
90
|
)
|
|
82
91
|
|
|
83
|
-
result = described_class.new.execute(tool_call, chat:
|
|
92
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
84
93
|
json = json_object(result)
|
|
85
94
|
|
|
86
95
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
@@ -96,7 +105,7 @@ describe OllamaChat::Tools::ComputeBMI do
|
|
|
96
105
|
)
|
|
97
106
|
)
|
|
98
107
|
|
|
99
|
-
result = described_class.new.execute(tool_call, chat:
|
|
108
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
100
109
|
json = json_object(result)
|
|
101
110
|
|
|
102
111
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
@@ -32,7 +32,7 @@ describe OllamaChat::Tools::CopyToClipboard do
|
|
|
32
32
|
# Test that perform_copy_to_clipboard is called with content: true
|
|
33
33
|
expect(chat).to receive(:perform_copy_to_clipboard).with(text: nil, content: true, edit: false)
|
|
34
34
|
|
|
35
|
-
result = described_class.new.execute(tool_call, chat:
|
|
35
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
36
36
|
|
|
37
37
|
# Should return valid JSON
|
|
38
38
|
expect(result).to be_a(String)
|
|
@@ -59,7 +59,7 @@ describe OllamaChat::Tools::CopyToClipboard do
|
|
|
59
59
|
# Test that perform_copy_to_clipboard is called with the custom text
|
|
60
60
|
expect(chat).to receive(:perform_copy_to_clipboard).with(text:, content: true, edit: false)
|
|
61
61
|
|
|
62
|
-
result = described_class.new.execute(tool_call, chat:
|
|
62
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
63
63
|
|
|
64
64
|
# Should return valid JSON
|
|
65
65
|
expect(result).to be_a(String)
|
|
@@ -84,7 +84,7 @@ describe OllamaChat::Tools::CopyToClipboard do
|
|
|
84
84
|
# Test that perform_copy_to_clipboard is called with edit: true
|
|
85
85
|
expect(chat).to receive(:perform_copy_to_clipboard).with(text: nil, content: true, edit: true)
|
|
86
86
|
|
|
87
|
-
result = described_class.new.execute(tool_call, chat:
|
|
87
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
88
88
|
|
|
89
89
|
# Should return valid JSON
|
|
90
90
|
expect(result).to be_a(String)
|
|
@@ -110,7 +110,7 @@ describe OllamaChat::Tools::CopyToClipboard do
|
|
|
110
110
|
expect(chat).to receive(:perform_copy_to_clipboard).with(text:nil, content: true, edit: false).
|
|
111
111
|
and_raise(OllamaChat::OllamaChatError, 'No response available to copy to the system clipboard.')
|
|
112
112
|
|
|
113
|
-
result = described_class.new.execute(tool_call, chat:
|
|
113
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
114
114
|
|
|
115
115
|
# Should return valid JSON even with errors
|
|
116
116
|
expect(result).to be_a(String)
|
|
@@ -135,7 +135,7 @@ describe OllamaChat::Tools::CopyToClipboard do
|
|
|
135
135
|
expect(chat).to receive(:perform_copy_to_clipboard).with(text: nil, content: true, edit: false).
|
|
136
136
|
and_raise(RuntimeError, 'some kind of exception')
|
|
137
137
|
|
|
138
|
-
result = described_class.new.execute(tool_call, chat:
|
|
138
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
139
139
|
|
|
140
140
|
# Should return valid JSON even with exceptions
|
|
141
141
|
expect(result).to be_a(String)
|
|
@@ -47,6 +47,7 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
47
47
|
expect(json.cmd).to include('Hello\\ World')
|
|
48
48
|
expect(json.cmd).to include('spec/assets')
|
|
49
49
|
expect(json.result).to include('Hello World!')
|
|
50
|
+
expect(json.message).to match(/Found some matches for "Hello World" in ".*spec\/assets"\./)
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
it 'can be executed successfully with max_results parameter' do
|
|
@@ -78,6 +79,7 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
78
79
|
expect(json.cmd).to include('grep')
|
|
79
80
|
expect(json.cmd).to include(' -m 5 ')
|
|
80
81
|
expect(json.result).to match(/blub class blob/)
|
|
82
|
+
expect(json.message).to match(/Found some matches for "class" in ".*spec\/assets"\./)
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
it 'can be executed successfully with max_results and ignore_case parameter' do
|
|
@@ -110,6 +112,7 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
110
112
|
expect(json.cmd).to include(' -m 5 ')
|
|
111
113
|
expect(json.cmd).to include(' -i ')
|
|
112
114
|
expect(json.result).to match(/blub class blob/)
|
|
115
|
+
expect(json.message).to match(/Found some matches for "class" in ".*spec\/assets"\./)
|
|
113
116
|
end
|
|
114
117
|
|
|
115
118
|
it 'can handle execution errors gracefully' do
|
|
@@ -141,6 +144,7 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
141
144
|
json = json_object(result)
|
|
142
145
|
expect(json.cmd).to include('grep')
|
|
143
146
|
expect(json.result).to eq ''
|
|
147
|
+
expect(json.message).to match(/No matches found for "nonexistent_pattern" in ".*spec\/assets"\./)
|
|
144
148
|
end
|
|
145
149
|
|
|
146
150
|
it 'can handle non-existent paths gracefully' do
|
|
@@ -171,6 +175,9 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
171
175
|
expect(result).to be_a String
|
|
172
176
|
json = json_object(result)
|
|
173
177
|
expect(json.result).to match(%r(grep: /nonexistent/path/that/does/not/exist))
|
|
178
|
+
expect(json.message).to match(
|
|
179
|
+
%r(Found some matches for "test" in "/nonexistent/path/that/does/not/exist"\.)
|
|
180
|
+
)
|
|
174
181
|
end
|
|
175
182
|
|
|
176
183
|
it 'can handle thrown exceptions' do
|
|
@@ -224,6 +231,9 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
224
231
|
json = json_object(result)
|
|
225
232
|
expect(json.result).to include('Hello World!')
|
|
226
233
|
expect(json.result).to include('example.rb')
|
|
234
|
+
expect(json.message).to match(
|
|
235
|
+
/Found some matches for "Hello World!" in ".*spec\/assets"\./
|
|
236
|
+
)
|
|
227
237
|
end
|
|
228
238
|
end
|
|
229
239
|
end
|
|
@@ -35,8 +35,8 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
35
35
|
double(
|
|
36
36
|
'ToolCall',
|
|
37
37
|
function: double(
|
|
38
|
-
name:
|
|
39
|
-
arguments:
|
|
38
|
+
name: 'generate_image',
|
|
39
|
+
arguments:
|
|
40
40
|
)
|
|
41
41
|
)
|
|
42
42
|
end
|
|
@@ -53,7 +53,7 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
53
53
|
)
|
|
54
54
|
expect(instance).to receive(:poll_for_image).and_return('kitten_output.png')
|
|
55
55
|
|
|
56
|
-
result = instance.execute(tool_call, chat:
|
|
56
|
+
result = instance.execute(tool_call, chat:)
|
|
57
57
|
|
|
58
58
|
expect(chat.links).to include(%r{/api/view.*filename=kitten_output.png})
|
|
59
59
|
|
|
@@ -67,7 +67,7 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
67
67
|
it 'returns an error when prompt is missing' do
|
|
68
68
|
arguments.prompt = nil
|
|
69
69
|
|
|
70
|
-
result = instance.execute(tool_call, chat:
|
|
70
|
+
result = instance.execute(tool_call, chat:)
|
|
71
71
|
|
|
72
72
|
json = json_object(result)
|
|
73
73
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
@@ -78,7 +78,7 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
78
78
|
# Force a config error by simulating the OllamaChat::OllamaChatError
|
|
79
79
|
allow(OC::OLLAMA::CHAT::TOOLS::IMAGE_GENERATOR).to receive(:URL?).and_return(nil)
|
|
80
80
|
|
|
81
|
-
result = instance.execute(tool_call, chat:
|
|
81
|
+
result = instance.execute(tool_call, chat:)
|
|
82
82
|
|
|
83
83
|
json = json_object(result)
|
|
84
84
|
expect(json.error).to eq 'OllamaChat::ConfigMissingError'
|
|
@@ -93,7 +93,7 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
93
93
|
)
|
|
94
94
|
expect(instance).to receive(:poll_for_image).and_return(nil)
|
|
95
95
|
|
|
96
|
-
result = instance.execute(tool_call, chat:
|
|
96
|
+
result = instance.execute(tool_call, chat:)
|
|
97
97
|
|
|
98
98
|
json = json_object(result)
|
|
99
99
|
expect(json.error).to eq 'OllamaChat::OllamaChatError'
|
|
@@ -107,7 +107,7 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
107
107
|
OpenStruct.new(prompt_id: nil)
|
|
108
108
|
)
|
|
109
109
|
|
|
110
|
-
result = instance.execute(tool_call, chat:
|
|
110
|
+
result = instance.execute(tool_call, chat:)
|
|
111
111
|
|
|
112
112
|
json = json_object(result)
|
|
113
113
|
expect(json.error).to eq 'OllamaChat::OllamaChatError'
|
|
@@ -119,7 +119,7 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
119
119
|
and_return(service_url)
|
|
120
120
|
allow(instance).to receive(:post_url).and_raise(StandardError, 'Network crash')
|
|
121
121
|
|
|
122
|
-
result = instance.execute(tool_call, chat:
|
|
122
|
+
result = instance.execute(tool_call, chat:)
|
|
123
123
|
|
|
124
124
|
json = json_object(result)
|
|
125
125
|
expect(json.error).to eq 'StandardError'
|
|
@@ -43,6 +43,9 @@ describe OllamaChat::Tools::GeneratePassword do
|
|
|
43
43
|
json = json_object(result)
|
|
44
44
|
expect(json.password).to be_a(String)
|
|
45
45
|
expect(json.length).to eq 16
|
|
46
|
+
expect(json.message).to eq(
|
|
47
|
+
'Successfully generated a 16-character secure password using the default alphabet.'
|
|
48
|
+
)
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
it 'can be executed successfully with bits parameter' do
|
|
@@ -62,6 +65,9 @@ describe OllamaChat::Tools::GeneratePassword do
|
|
|
62
65
|
json = json_object(result)
|
|
63
66
|
expect(json.password).to be_a(String)
|
|
64
67
|
expect(json.bits).to be >= 128
|
|
68
|
+
expect(json.message).to match(
|
|
69
|
+
/Successfully generated a \d+-character secure password using the default alphabet\./
|
|
70
|
+
)
|
|
65
71
|
end
|
|
66
72
|
|
|
67
73
|
it 'can be executed successfully with base32 alphabet type' do
|
|
@@ -82,6 +88,9 @@ describe OllamaChat::Tools::GeneratePassword do
|
|
|
82
88
|
json = json_object(result)
|
|
83
89
|
expect(json.password).to be_a(String)
|
|
84
90
|
expect(json.alphabet_type).to eq 'base32'
|
|
91
|
+
expect(json.message).to match(
|
|
92
|
+
/Successfully generated a \d+-character secure password using the base32 alphabet\./
|
|
93
|
+
)
|
|
85
94
|
end
|
|
86
95
|
|
|
87
96
|
it 'can be executed successfully with default alphabet type' do
|
|
@@ -103,6 +112,9 @@ describe OllamaChat::Tools::GeneratePassword do
|
|
|
103
112
|
expect(json.password).to be_a(String)
|
|
104
113
|
expect(json.alphabet_type).to eq 'default'
|
|
105
114
|
expect(json.symbols).to be true
|
|
115
|
+
expect(json.message).to eq(
|
|
116
|
+
'Successfully generated a 20-character secure password using the default alphabet.'
|
|
117
|
+
)
|
|
106
118
|
end
|
|
107
119
|
|
|
108
120
|
it 'can handle execution errors gracefully' do
|
|
@@ -121,6 +133,7 @@ describe OllamaChat::Tools::GeneratePassword do
|
|
|
121
133
|
expect(result).to be_a(String)
|
|
122
134
|
json = json_object(result)
|
|
123
135
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
136
|
+
expect(json.message).to eq 'require either bits or length of password'
|
|
124
137
|
end
|
|
125
138
|
|
|
126
139
|
it 'can be converted t)o hash' do
|
|
@@ -39,6 +39,10 @@ describe OllamaChat::Tools::GetCurrentWeather do
|
|
|
39
39
|
expect(json.current_time).to match(/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}[+-]\d{2}:\d{2}\z/)
|
|
40
40
|
expect(json.currently.temperature).to be_within(0.01).of(7.74)
|
|
41
41
|
expect(json.units).to eq "si"
|
|
42
|
+
expect(json.message).to eq(
|
|
43
|
+
"Currently 7.74°C and Clear. Today's forecast: No precipitation "\
|
|
44
|
+
"throughout the week, with highs falling to 13°C next Friday."
|
|
45
|
+
)
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
it 'can handle execution errors with structured JSON error response' do
|
|
@@ -25,7 +25,7 @@ describe OllamaChat::Tools::GetEndoflife do
|
|
|
25
25
|
function: double(
|
|
26
26
|
name: 'get_endoflife',
|
|
27
27
|
arguments: double(
|
|
28
|
-
product:
|
|
28
|
+
product:
|
|
29
29
|
)
|
|
30
30
|
)
|
|
31
31
|
)
|
|
@@ -54,14 +54,14 @@ describe OllamaChat::Tools::GetEndoflife do
|
|
|
54
54
|
function: double(
|
|
55
55
|
name: 'get_endoflife',
|
|
56
56
|
arguments: double(
|
|
57
|
-
product:
|
|
57
|
+
product:
|
|
58
58
|
)
|
|
59
59
|
)
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
url = chat.config.tools.functions.get_endoflife.url
|
|
63
63
|
|
|
64
|
-
stub_request(:get, url % { product:
|
|
64
|
+
stub_request(:get, url % { product: })
|
|
65
65
|
.to_return(status: 404, body: 'Not Found')
|
|
66
66
|
|
|
67
67
|
result = described_class.new.execute(tool_call, chat:)
|
|
@@ -25,7 +25,7 @@ describe OllamaChat::Tools::GetRFC do
|
|
|
25
25
|
function: double(
|
|
26
26
|
name: 'get_rfc',
|
|
27
27
|
arguments: double(
|
|
28
|
-
rfc_id:
|
|
28
|
+
rfc_id:
|
|
29
29
|
)
|
|
30
30
|
)
|
|
31
31
|
)
|
|
@@ -53,7 +53,7 @@ describe OllamaChat::Tools::GetRFC do
|
|
|
53
53
|
function: double(
|
|
54
54
|
name: 'get_rfc',
|
|
55
55
|
arguments: double(
|
|
56
|
-
rfc_id:
|
|
56
|
+
rfc_id:
|
|
57
57
|
)
|
|
58
58
|
)
|
|
59
59
|
)
|
|
@@ -18,6 +18,7 @@ describe OllamaChat::Tools::GetTime do
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
it 'can be executed successfully' do
|
|
21
|
+
Time.dummy = '2011-11-11T11:11:11+0200'
|
|
21
22
|
tool_call = double(
|
|
22
23
|
'ToolCall',
|
|
23
24
|
function: double(
|
|
@@ -34,5 +35,8 @@ describe OllamaChat::Tools::GetTime do
|
|
|
34
35
|
/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}\z/
|
|
35
36
|
)
|
|
36
37
|
expect(json.weekday).to be_present
|
|
38
|
+
expect(json.message).to eq 'Good morning! It is currently 11:11 on Friday.'
|
|
39
|
+
ensure
|
|
40
|
+
Time.dummy = nil
|
|
37
41
|
end
|
|
38
42
|
end
|
|
@@ -107,7 +107,7 @@ describe OllamaChat::Tools::GetURL do
|
|
|
107
107
|
args = double(url: url, document_policy: 'ignoring')
|
|
108
108
|
tool_call = double(function: double(arguments: args))
|
|
109
109
|
|
|
110
|
-
result = described_class.new.execute(tool_call, chat:
|
|
110
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
111
111
|
json = json_object(result)
|
|
112
112
|
expect(json.content).to eq('raw content')
|
|
113
113
|
end
|
|
@@ -117,7 +117,7 @@ describe OllamaChat::Tools::GetURL do
|
|
|
117
117
|
tool_call = double(function: double(arguments: args))
|
|
118
118
|
expect(chat).to receive(:import_source).with(source_io, URI.parse(url)).and_return('imported content')
|
|
119
119
|
|
|
120
|
-
result = described_class.new.execute(tool_call, chat:
|
|
120
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
121
121
|
json = json_object(result)
|
|
122
122
|
expect(json.content).to eq('imported content')
|
|
123
123
|
end
|
|
@@ -127,7 +127,7 @@ describe OllamaChat::Tools::GetURL do
|
|
|
127
127
|
tool_call = double(function: double(arguments: args))
|
|
128
128
|
expect(chat).to receive(:embed_source).with(source_io, URI.parse(url)).and_return('embedded content')
|
|
129
129
|
|
|
130
|
-
result = described_class.new.execute(tool_call, chat:
|
|
130
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
131
131
|
json = json_object(result)
|
|
132
132
|
expect(json.content).to eq('embedded content')
|
|
133
133
|
end
|
|
@@ -137,7 +137,7 @@ describe OllamaChat::Tools::GetURL do
|
|
|
137
137
|
tool_call = double(function: double(arguments: args))
|
|
138
138
|
expect(chat).to receive(:summarize_source).with(source_io, URI.parse(url)).and_return('summarized content')
|
|
139
139
|
|
|
140
|
-
result = described_class.new.execute(tool_call, chat:
|
|
140
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
141
141
|
json = json_object(result)
|
|
142
142
|
expect(json.content).to eq('summarized content')
|
|
143
143
|
end
|
|
@@ -146,7 +146,7 @@ describe OllamaChat::Tools::GetURL do
|
|
|
146
146
|
args = double(url: url, document_policy: 'chaos_mode')
|
|
147
147
|
tool_call = double(function: double(arguments: args))
|
|
148
148
|
|
|
149
|
-
result = described_class.new.execute(tool_call, chat:
|
|
149
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
150
150
|
json = json_object(result)
|
|
151
151
|
expect(json.message).to match(/Invalid document policy "chaos_mode"/)
|
|
152
152
|
end
|
|
@@ -163,7 +163,7 @@ describe OllamaChat::Tools::GetURL do
|
|
|
163
163
|
args = double(url: url, document_policy: 'ignoring')
|
|
164
164
|
tool_call = double(function: double(arguments: args))
|
|
165
165
|
|
|
166
|
-
result = described_class.new.execute(tool_call, chat:
|
|
166
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
167
167
|
json = json_object(result)
|
|
168
168
|
expect(json.message).to eq('Received requested URL successfully.')
|
|
169
169
|
end
|
|
@@ -175,7 +175,7 @@ describe OllamaChat::Tools::GetURL do
|
|
|
175
175
|
args = double(url: url, document_policy: 'ignoring')
|
|
176
176
|
tool_call = double(function: double(arguments: args))
|
|
177
177
|
|
|
178
|
-
result = described_class.new.execute(tool_call, chat:
|
|
178
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
179
179
|
json = json_object(result)
|
|
180
180
|
expect(json.message).to match(/Cannot fetch.*with content type/)
|
|
181
181
|
end
|
|
@@ -32,7 +32,7 @@ describe OllamaChat::Tools::PasteFromClipboard do
|
|
|
32
32
|
expect(chat).to receive(:perform_paste_from_clipboard).with(edit: false).
|
|
33
33
|
and_return 'Hello World'
|
|
34
34
|
|
|
35
|
-
result = described_class.new.execute(tool_call, chat:
|
|
35
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
36
36
|
|
|
37
37
|
# Should return valid JSON
|
|
38
38
|
expect(result).to be_a(String)
|
|
@@ -57,7 +57,7 @@ describe OllamaChat::Tools::PasteFromClipboard do
|
|
|
57
57
|
expect(chat).to receive(:perform_paste_from_clipboard).with(edit: true).
|
|
58
58
|
and_return 'Hello Edited World'
|
|
59
59
|
|
|
60
|
-
result = described_class.new.execute(tool_call, chat:
|
|
60
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
61
61
|
|
|
62
62
|
# Should return valid JSON
|
|
63
63
|
expect(result).to be_a(String)
|
|
@@ -82,7 +82,7 @@ describe OllamaChat::Tools::PasteFromClipboard do
|
|
|
82
82
|
expect(chat).to receive(:perform_paste_from_clipboard).with(edit: false).
|
|
83
83
|
and_raise(OllamaChat::OllamaChatError, 'No content available to paste from the system clipboard.')
|
|
84
84
|
|
|
85
|
-
result = described_class.new.execute(tool_call, chat:
|
|
85
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
86
86
|
|
|
87
87
|
# Should return valid JSON even with errors
|
|
88
88
|
expect(result).to be_a(String)
|
|
@@ -106,7 +106,7 @@ describe OllamaChat::Tools::PasteFromClipboard do
|
|
|
106
106
|
expect(chat).to receive(:perform_paste_from_clipboard).with(edit: false).
|
|
107
107
|
and_raise(RuntimeError, 'some kind of exception')
|
|
108
108
|
|
|
109
|
-
result = described_class.new.execute(tool_call, chat:
|
|
109
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
110
110
|
|
|
111
111
|
# Should return valid JSON even with exceptions
|
|
112
112
|
expect(result).to be_a(String)
|
|
@@ -38,6 +38,7 @@ describe OllamaChat::Tools::ReadFile do
|
|
|
38
38
|
expect(json.content).to eq <<~EOT
|
|
39
39
|
puts "Hello World!"
|
|
40
40
|
EOT
|
|
41
|
+
expect(json.message).to include('Read 20.0 B (6.0 T) from')
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
it 'can extract range when start_line is provided and end_line is nil' do
|
|
@@ -55,6 +56,7 @@ describe OllamaChat::Tools::ReadFile do
|
|
|
55
56
|
result = described_class.new.execute(tool_call, chat:)
|
|
56
57
|
json = json_object(result)
|
|
57
58
|
expect(json.content).to eq "puts \"Hello World!\"\n"
|
|
59
|
+
expect(json.message).to include('Read 20.0 B (6.0 T) from')
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
it 'can extract range when start_line is nil and end_line is provided' do
|
|
@@ -72,6 +74,7 @@ describe OllamaChat::Tools::ReadFile do
|
|
|
72
74
|
result = described_class.new.execute(tool_call, chat:)
|
|
73
75
|
json = json_object(result)
|
|
74
76
|
expect(json.content).to eq "puts \"Hello World!\"\n"
|
|
77
|
+
expect(json.message).to include('Read 20.0 B (6.0 T) from')
|
|
75
78
|
end
|
|
76
79
|
|
|
77
80
|
it 'returns empty content when end_line is less than start_line' do
|
|
@@ -55,6 +55,8 @@ describe OllamaChat::Tools::RetrieveDocumentSnippets do
|
|
|
55
55
|
"Consider these snippets generated from retrieval when formulating your response!"
|
|
56
56
|
)
|
|
57
57
|
expect(json.snippets.size).to eq 1
|
|
58
|
+
expect(json.message).to include('Retrieved')
|
|
59
|
+
expect(json.message).to include('"Ruby array"')
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
it 'works with a valid query and tags' do
|
|
@@ -95,6 +97,8 @@ describe OllamaChat::Tools::RetrieveDocumentSnippets do
|
|
|
95
97
|
expect(result).to be_a(String)
|
|
96
98
|
json = json_object(result)
|
|
97
99
|
expect(json.snippets.size).to eq 1
|
|
100
|
+
expect(json.message).to include('Retrieved')
|
|
101
|
+
expect(json.message).to include('"Ruby array"')
|
|
98
102
|
end
|
|
99
103
|
|
|
100
104
|
it 'switches to the specified collection and restores the original' do
|
|
@@ -117,7 +121,8 @@ describe OllamaChat::Tools::RetrieveDocumentSnippets do
|
|
|
117
121
|
mock_docs = double('Documents')
|
|
118
122
|
allow(chat).to receive(:documents).and_return(mock_docs)
|
|
119
123
|
|
|
120
|
-
expect(mock_docs).to receive(:collection).and_return('default_collection')
|
|
124
|
+
expect(mock_docs).to receive(:collection).and_return('default_collection').
|
|
125
|
+
at_least(:once)
|
|
121
126
|
expect(mock_docs).to receive(:collection=).with('tolkien').ordered
|
|
122
127
|
expect(mock_docs).to receive(:collection=).with('default_collection').ordered
|
|
123
128
|
|
|
@@ -144,6 +149,32 @@ describe OllamaChat::Tools::RetrieveDocumentSnippets do
|
|
|
144
149
|
expect(json.message).to eq('Empty query')
|
|
145
150
|
end
|
|
146
151
|
|
|
152
|
+
it 'returns a no-match message when no records are found' do
|
|
153
|
+
tool_call = double(
|
|
154
|
+
'ToolCall',
|
|
155
|
+
function: double(
|
|
156
|
+
name: 'retrieve_document_snippets',
|
|
157
|
+
arguments: double(
|
|
158
|
+
query: 'nonexistent',
|
|
159
|
+
tags: nil,
|
|
160
|
+
collection: nil,
|
|
161
|
+
min_similarity: nil,
|
|
162
|
+
text_size: nil,
|
|
163
|
+
text_count: nil,
|
|
164
|
+
rerank: false,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
tool = described_class.new
|
|
170
|
+
expect(tool).to receive(:find_document_records).and_return([])
|
|
171
|
+
|
|
172
|
+
result = tool.execute(tool_call, chat:)
|
|
173
|
+
json = json_object(result)
|
|
174
|
+
expect(json.message).to include('No relevant snippets found')
|
|
175
|
+
expect(json.message).to include('"nonexistent"')
|
|
176
|
+
end
|
|
177
|
+
|
|
147
178
|
it 'performs reranking when rerank is true' do
|
|
148
179
|
tool_call = double(
|
|
149
180
|
'ToolCall',
|
|
@@ -170,14 +201,15 @@ describe OllamaChat::Tools::RetrieveDocumentSnippets do
|
|
|
170
201
|
expect(tool).to receive(:find_document_records).and_return(records)
|
|
171
202
|
|
|
172
203
|
allow(chat).to receive(:prompt).with('rerank').and_return("template %{query} %{candidates}")
|
|
173
|
-
|
|
174
|
-
allow(chat).to receive(:generate).with(prompt: anything).and_return(response_val)
|
|
204
|
+
allow(chat).to receive(:generate).with(prompt: anything).and_return('1')
|
|
175
205
|
|
|
176
206
|
result = tool.execute(tool_call, chat:)
|
|
177
207
|
json = json_object(result)
|
|
178
208
|
|
|
179
209
|
expect(json.snippets.size).to eq 1
|
|
180
210
|
expect(json.snippets.first.text).to eq 'second'
|
|
211
|
+
expect(json.message).to include('Retrieved')
|
|
212
|
+
expect(json.message).to include('"Ruby array"')
|
|
181
213
|
end
|
|
182
214
|
|
|
183
215
|
it 'performs reranking when rerank is nil (defaults to true)' do
|
|
@@ -206,14 +238,15 @@ describe OllamaChat::Tools::RetrieveDocumentSnippets do
|
|
|
206
238
|
expect(tool).to receive(:find_document_records).and_return(records)
|
|
207
239
|
|
|
208
240
|
allow(chat).to receive(:prompt).with('rerank').and_return("template %{query} %{candidates}")
|
|
209
|
-
|
|
210
|
-
allow(chat).to receive(:generate).with(prompt: anything).and_return(response_val)
|
|
241
|
+
allow(chat).to receive(:generate).with(prompt: anything).and_return('0')
|
|
211
242
|
|
|
212
243
|
result = tool.execute(tool_call, chat:)
|
|
213
244
|
json = json_object(result)
|
|
214
245
|
|
|
215
246
|
expect(json.snippets.size).to eq 1
|
|
216
247
|
expect(json.snippets.first.text).to eq 'first'
|
|
248
|
+
expect(json.message).to include('Retrieved')
|
|
249
|
+
expect(json.message).to include('"Ruby array"')
|
|
217
250
|
end
|
|
218
251
|
|
|
219
252
|
it 'can be converted to hash' do
|
|
@@ -43,7 +43,7 @@ describe OllamaChat::Tools::RollDice do
|
|
|
43
43
|
expect(json.rolls).to eq [2, 5]
|
|
44
44
|
expect(json.modifier).to eq 0
|
|
45
45
|
expect(json.total).to eq 7
|
|
46
|
-
expect(json.message).to eq 'Dice roll was: 2d6 = (2 + 5) = 7'
|
|
46
|
+
expect(json.message).to eq 'Dice roll was: 2d6 = 2…12 = (2 + 5) = 7'
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
it 'can be executed successfully with d20 notation' do
|
|
@@ -64,7 +64,7 @@ describe OllamaChat::Tools::RollDice do
|
|
|
64
64
|
json = json_object(result)
|
|
65
65
|
expect(json.rolls).to eq [10]
|
|
66
66
|
expect(json.total).to eq 10
|
|
67
|
-
expect(json.message).to eq 'Dice roll was: d20 = (10) = 10'
|
|
67
|
+
expect(json.message).to eq 'Dice roll was: d20 = 1…20 = (10) = 10'
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
it 'can be executed successfully with positive modifier' do
|
|
@@ -86,7 +86,7 @@ describe OllamaChat::Tools::RollDice do
|
|
|
86
86
|
expect(json.rolls).to eq [15]
|
|
87
87
|
expect(json.modifier).to eq 3
|
|
88
88
|
expect(json.total).to eq 18
|
|
89
|
-
expect(json.message).to eq 'Dice roll was: d20+3 = (15) + 3 = 18'
|
|
89
|
+
expect(json.message).to eq 'Dice roll was: d20+3 = 4…23 = (15) + 3 = 18'
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
it 'can be executed successfully with negative modifier' do
|
|
@@ -108,7 +108,7 @@ describe OllamaChat::Tools::RollDice do
|
|
|
108
108
|
expect(json.rolls).to eq [5]
|
|
109
109
|
expect(json.modifier).to eq(-3)
|
|
110
110
|
expect(json.total).to eq 2
|
|
111
|
-
expect(json.message).to eq 'Dice roll was: d20-3 = (5) - 3 = 2'
|
|
111
|
+
expect(json.message).to eq 'Dice roll was: d20-3 = -2…17 = (5) - 3 = 2'
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
it 'can handle invalid dice notation' do
|