ollama_chat 0.0.113 → 0.0.115
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 +120 -0
- data/README.md +7 -0
- data/bin/ollama_chat_log +1 -1
- data/bin/ollama_chat_send +3 -1
- data/lib/ollama_chat/chat.rb +30 -14
- data/lib/ollama_chat/commands.rb +36 -18
- data/lib/ollama_chat/compaction.rb +289 -0
- data/lib/ollama_chat/config_handling.rb +32 -0
- data/lib/ollama_chat/database/migrations/010_add_enabled_to_collections.rb +7 -0
- data/lib/ollama_chat/database/models/app_state.rb +48 -16
- data/lib/ollama_chat/database/models/session.rb +1 -2
- data/lib/ollama_chat/database.rb +4 -2
- data/lib/ollama_chat/information.rb +53 -6
- data/lib/ollama_chat/logging.rb +3 -2
- data/lib/ollama_chat/message.rb +22 -1
- data/lib/ollama_chat/message_format.rb +1 -2
- data/lib/ollama_chat/message_list.rb +223 -9
- data/lib/ollama_chat/model_handling.rb +1 -1
- data/lib/ollama_chat/oc.rb +17 -7
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +72 -3
- data/lib/ollama_chat/parsing.rb +2 -1
- data/lib/ollama_chat/prompt_handling.rb +1 -1
- data/lib/ollama_chat/prompt_management.rb +1 -0
- data/lib/ollama_chat/rag_handling.rb +19 -5
- data/lib/ollama_chat/session_management.rb +5 -3
- data/lib/ollama_chat/token_estimator/crude.rb +1 -1
- data/lib/ollama_chat/token_estimator.rb +1 -1
- data/lib/ollama_chat/tools/concern.rb +17 -0
- data/lib/ollama_chat/tools/directory_structure.rb +11 -7
- data/lib/ollama_chat/tools/eval_ruby.rb +16 -6
- data/lib/ollama_chat/tools/execute_shell.rb +158 -0
- data/lib/ollama_chat/tools/get_rfc.rb +2 -0
- data/lib/ollama_chat/tools/lookup_group.rb +100 -0
- data/lib/ollama_chat/tools/paste_from_clipboard.rb +1 -1
- data/lib/ollama_chat/tools/patch_file.rb +23 -5
- data/lib/ollama_chat/tools/read_file.rb +1 -1
- data/lib/ollama_chat/tools/resolve_tag.rb +1 -1
- data/lib/ollama_chat/tools/search_knowledge.rb +7 -1
- data/lib/ollama_chat/tools/write_file.rb +25 -6
- data/lib/ollama_chat/tools.rb +2 -0
- data/lib/ollama_chat/utils/analyze_directory.rb +5 -4
- data/lib/ollama_chat/utils/fetcher.rb +1 -1
- data/lib/ollama_chat/utils/log_rotation.rb +29 -0
- data/lib/ollama_chat/utils/strip_ansi.rb +22 -0
- data/lib/ollama_chat/utils/tag_resolver.rb +3 -3
- data/lib/ollama_chat/utils.rb +2 -0
- data/lib/ollama_chat/version.rb +1 -1
- data/lib/ollama_chat.rb +7 -0
- data/ollama_chat.gemspec +5 -5
- data/spec/ollama_chat/commands_spec.rb +25 -0
- data/spec/ollama_chat/compaction_spec.rb +381 -0
- data/spec/ollama_chat/config_handling_spec.rb +60 -0
- data/spec/ollama_chat/message_list_spec.rb +229 -0
- data/spec/ollama_chat/message_spec.rb +143 -0
- data/spec/ollama_chat/rag_handling_spec.rb +3 -0
- data/spec/ollama_chat/system_prompt_management_spec.rb +0 -2
- data/spec/ollama_chat/tools/browse_spec.rb +7 -0
- data/spec/ollama_chat/tools/compute_bmi_spec.rb +15 -0
- data/spec/ollama_chat/tools/concern_spec.rb +77 -0
- data/spec/ollama_chat/tools/copy_to_clipboard_spec.rb +8 -0
- data/spec/ollama_chat/tools/delete_file_spec.rb +8 -0
- data/spec/ollama_chat/tools/directory_structure_spec.rb +4 -0
- data/spec/ollama_chat/tools/execute_grep_spec.rb +13 -0
- data/spec/ollama_chat/tools/execute_ri_spec.rb +8 -0
- data/spec/ollama_chat/tools/execute_shell_spec.rb +232 -0
- data/spec/ollama_chat/tools/file_context_spec.rb +5 -0
- data/spec/ollama_chat/tools/gem_path_lookup_spec.rb +2 -0
- data/spec/ollama_chat/tools/generate_image_spec.rb +12 -0
- data/spec/ollama_chat/tools/generate_password_spec.rb +5 -0
- data/spec/ollama_chat/tools/get_cve_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_endoflife_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_ghr_spec.rb +6 -0
- data/spec/ollama_chat/tools/get_location_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_rfc_spec.rb +2 -0
- data/spec/ollama_chat/tools/get_time_spec.rb +2 -0
- data/spec/ollama_chat/tools/get_url_spec.rb +7 -0
- data/spec/ollama_chat/tools/lookup_group_spec.rb +111 -0
- data/spec/ollama_chat/tools/move_file_spec.rb +10 -0
- data/spec/ollama_chat/tools/open_file_in_editor_spec.rb +6 -0
- data/spec/ollama_chat/tools/paste_from_clipboard_spec.rb +5 -0
- data/spec/ollama_chat/tools/paste_into_editor_spec.rb +6 -0
- data/spec/ollama_chat/tools/patch_file_spec.rb +62 -0
- data/spec/ollama_chat/tools/read_file_spec.rb +10 -3
- data/spec/ollama_chat/tools/resolve_tag_spec.rb +46 -0
- data/spec/ollama_chat/tools/roll_dice_spec.rb +11 -0
- data/spec/ollama_chat/tools/run_tests_spec.rb +8 -0
- data/spec/ollama_chat/tools/search_knowledge_spec.rb +2 -0
- data/spec/ollama_chat/tools/write_file_spec.rb +98 -2
- data/spec/ollama_chat/utils/analyze_directory_spec.rb +46 -7
- data/spec/ollama_chat/utils/log_rotation_spec.rb +71 -0
- data/spec/spec_helper.rb +2 -1
- metadata +25 -1
|
@@ -48,6 +48,9 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
48
48
|
expect(json.cmd).to include('spec/assets')
|
|
49
49
|
expect(json.result).to include('Hello World!')
|
|
50
50
|
expect(json.message).to match(/Found some matches for "Hello World" in ".*spec\/assets"\./)
|
|
51
|
+
expect(described_class.summary_template(result:)).to match(
|
|
52
|
+
/Found some matches for "Hello World" in ".*spec\/assets"\./
|
|
53
|
+
)
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
it 'can be executed successfully with max_results parameter' do
|
|
@@ -80,6 +83,9 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
80
83
|
expect(json.cmd).to include(' -m 5 ')
|
|
81
84
|
expect(json.result).to match(/blub class blob/)
|
|
82
85
|
expect(json.message).to match(/Found some matches for "class" in ".*spec\/assets"\./)
|
|
86
|
+
expect(described_class.summary_template(result:)).to match(
|
|
87
|
+
/Found some matches for "class" in ".*spec\/assets"\./
|
|
88
|
+
)
|
|
83
89
|
end
|
|
84
90
|
|
|
85
91
|
it 'can be executed successfully with max_results and ignore_case parameter' do
|
|
@@ -113,6 +119,9 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
113
119
|
expect(json.cmd).to include(' -i ')
|
|
114
120
|
expect(json.result).to match(/blub class blob/)
|
|
115
121
|
expect(json.message).to match(/Found some matches for "class" in ".*spec\/assets"\./)
|
|
122
|
+
expect(described_class.summary_template(result:)).to match(
|
|
123
|
+
/Found some matches for "class" in ".*spec\/assets"\./
|
|
124
|
+
)
|
|
116
125
|
end
|
|
117
126
|
|
|
118
127
|
it 'can handle execution errors gracefully' do
|
|
@@ -145,6 +154,9 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
145
154
|
expect(json.cmd).to include('grep')
|
|
146
155
|
expect(json.result).to eq ''
|
|
147
156
|
expect(json.message).to match(/No matches found for "nonexistent_pattern" in ".*spec\/assets"\./)
|
|
157
|
+
expect(described_class.summary_template(result:)).to match(
|
|
158
|
+
/No matches found for "nonexistent_pattern" in ".*spec\/assets"\./
|
|
159
|
+
)
|
|
148
160
|
end
|
|
149
161
|
|
|
150
162
|
it 'can handle non-existent paths gracefully' do
|
|
@@ -205,6 +217,7 @@ describe OllamaChat::Tools::ExecuteGrep do
|
|
|
205
217
|
json = json_object(result)
|
|
206
218
|
expect(json.error).to eq 'RuntimeError'
|
|
207
219
|
expect(json.message).to eq 'my error'
|
|
220
|
+
expect(described_class.summary_template(result:)).to eq 'my error'
|
|
208
221
|
end
|
|
209
222
|
|
|
210
223
|
context 'when searching in spec/assets directory' do
|
|
@@ -42,6 +42,8 @@ describe OllamaChat::Tools::ExecuteRI do
|
|
|
42
42
|
expect(json.cmd).to include('ri')
|
|
43
43
|
expect(json.cmd).to include(topic)
|
|
44
44
|
expect(json.result).to eq 'Documentation text for Tins::Full#full?'
|
|
45
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
46
|
+
'was called.'
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
|
|
@@ -59,6 +61,9 @@ describe OllamaChat::Tools::ExecuteRI do
|
|
|
59
61
|
|
|
60
62
|
json = json_object(result)
|
|
61
63
|
expect(json.error).to eq('OllamaChat::ToolFunctionArgumentError')
|
|
64
|
+
expect(json.message).to eq 'require a topic of ri'
|
|
65
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
66
|
+
'require a topic of ri'
|
|
62
67
|
end
|
|
63
68
|
end
|
|
64
69
|
|
|
@@ -82,6 +87,9 @@ describe OllamaChat::Tools::ExecuteRI do
|
|
|
82
87
|
json = json_object(result)
|
|
83
88
|
expect(json.error).to eq('RuntimeError')
|
|
84
89
|
expect(json.message).to include('my error')
|
|
90
|
+
expect(described_class.summary_template(result:)).to include(
|
|
91
|
+
'my error',
|
|
92
|
+
)
|
|
85
93
|
end
|
|
86
94
|
end
|
|
87
95
|
end
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
describe OllamaChat::Tools::ExecuteShell do
|
|
2
|
+
let :chat do
|
|
3
|
+
OllamaChat::Chat.new(argv: chat_default_config)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
connect_to_ollama_server
|
|
7
|
+
|
|
8
|
+
let :tool do
|
|
9
|
+
described_class.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
allow(OllamaChat).to receive(:test_mode?).and_return(false)
|
|
14
|
+
allow(chat).to receive(:log)
|
|
15
|
+
allow(OC).to receive(:EDITOR?).and_return('vim')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tool_call(command, monochrome: nil)
|
|
19
|
+
double('ToolCall', function: double(
|
|
20
|
+
name: 'execute_shell',
|
|
21
|
+
arguments: double('Args', command:, monochrome:)
|
|
22
|
+
))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def stub_success(stdout: 'ok', stderr: '', exit_code: 0)
|
|
26
|
+
allow(chat).to receive(:edit_text).and_return('ls')
|
|
27
|
+
allow(chat).to receive(:confirm?).and_return('y')
|
|
28
|
+
allow(chat).to receive(:use_pager) { |&blk| blk.call(StringIO.new) }
|
|
29
|
+
allow(Open3).to receive(:capture3)
|
|
30
|
+
.and_return([stdout, stderr, double('s', exitstatus: exit_code)])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe 'identity' do
|
|
34
|
+
it 'has the expected name' do
|
|
35
|
+
expect(tool.name).to eq 'execute_shell'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'provides a Tool instance' do
|
|
39
|
+
expect(tool.tool).to be_a(Ollama::Tool)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'can be converted to hash' do
|
|
43
|
+
expect(tool.to_hash).to be_a(Hash)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#execute' do
|
|
48
|
+
it 'runs the command after editor review and confirmation' do
|
|
49
|
+
expect(chat).to receive(:edit_text)
|
|
50
|
+
.with('ls -l', hash_including(basename: %w[cmd .sh]))
|
|
51
|
+
.and_return('ls -l')
|
|
52
|
+
expect(chat).to receive(:confirm?).and_return('y')
|
|
53
|
+
allow(chat).to receive(:use_pager) { |&blk| blk.call(StringIO.new) }
|
|
54
|
+
expect(Open3).to receive(:capture3)
|
|
55
|
+
.with('sh', '-c', 'ls -l')
|
|
56
|
+
.and_return(['total 0', '', double('s', exitstatus: 0)])
|
|
57
|
+
|
|
58
|
+
result = tool.execute(tool_call('ls -l'), chat:)
|
|
59
|
+
json = json_object(result)
|
|
60
|
+
|
|
61
|
+
expect(json.error).to be_nil
|
|
62
|
+
expect(json.exit_code).to eq 0
|
|
63
|
+
expect(json.stdout).to eq 'total 0'
|
|
64
|
+
expect(json.stderr).to eq ''
|
|
65
|
+
expect(json.command).to eq 'ls -l'
|
|
66
|
+
expect(described_class.summary_template(result:)).to include('ls -l')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'executes the command as edited in the editor' do
|
|
70
|
+
allow(chat).to receive(:use_pager) { |&blk| blk.call(StringIO.new) }
|
|
71
|
+
expect(chat).to receive(:edit_text)
|
|
72
|
+
.with('ls', anything)
|
|
73
|
+
.and_return('ls -la /tmp')
|
|
74
|
+
allow(chat).to receive(:confirm?).and_return('y')
|
|
75
|
+
expect(Open3).to receive(:capture3)
|
|
76
|
+
.with('sh', '-c', 'ls -la /tmp')
|
|
77
|
+
.and_return(['x', '', double('s', exitstatus: 0)])
|
|
78
|
+
|
|
79
|
+
result = tool.execute(tool_call('ls'), chat:)
|
|
80
|
+
|
|
81
|
+
expect(json_object(result).command).to eq 'ls -la /tmp'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'returns cancelled when the user answers n' do
|
|
85
|
+
allow(chat).to receive(:edit_text).and_return('rm -rf .')
|
|
86
|
+
expect(chat).to receive(:confirm?).and_return('n')
|
|
87
|
+
expect(Open3).not_to receive(:capture3)
|
|
88
|
+
|
|
89
|
+
result = tool.execute(tool_call('rm -rf .'), chat:)
|
|
90
|
+
json = json_object(result)
|
|
91
|
+
|
|
92
|
+
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
93
|
+
expect(json.message).to include('cancelled')
|
|
94
|
+
expect(json.command).to eq 'rm -rf .'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'returns instruct when the user answers i' do
|
|
98
|
+
allow(chat).to receive(:edit_text).and_return('rm -rf .')
|
|
99
|
+
allow(chat).to receive(:confirm?).and_return('i')
|
|
100
|
+
expect(chat).to receive(:ask?)
|
|
101
|
+
.and_return('Actually use the build dir, not root')
|
|
102
|
+
expect(Open3).not_to receive(:capture3)
|
|
103
|
+
|
|
104
|
+
result = tool.execute(tool_call('rm -rf .'), chat:)
|
|
105
|
+
json = json_object(result)
|
|
106
|
+
|
|
107
|
+
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
108
|
+
expect(json.command).to eq 'rm -rf .'
|
|
109
|
+
expect(json.message).to include('build dir')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'returns cancelled when the editor is abandoned (nil)' do
|
|
113
|
+
expect(chat).to receive(:edit_text).and_return(nil)
|
|
114
|
+
expect(chat).not_to receive(:confirm?)
|
|
115
|
+
expect(Open3).not_to receive(:capture3)
|
|
116
|
+
|
|
117
|
+
result = tool.execute(tool_call('ls'), chat:)
|
|
118
|
+
json = json_object(result)
|
|
119
|
+
|
|
120
|
+
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
121
|
+
expect(json.message).to include('cancelled')
|
|
122
|
+
expect(json.command).to eq 'ls'
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'returns cancelled when the editor is cleared to empty' do
|
|
126
|
+
expect(chat).to receive(:edit_text).and_return('')
|
|
127
|
+
expect(chat).not_to receive(:confirm?)
|
|
128
|
+
expect(Open3).not_to receive(:capture3)
|
|
129
|
+
|
|
130
|
+
result = tool.execute(tool_call('ls'), chat:)
|
|
131
|
+
json = json_object(result)
|
|
132
|
+
|
|
133
|
+
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'skips editor and confirm in test mode' do
|
|
137
|
+
expect(OllamaChat).to receive(:test_mode?).and_return(true)
|
|
138
|
+
expect(chat).not_to receive(:edit_text)
|
|
139
|
+
expect(chat).not_to receive(:confirm?)
|
|
140
|
+
expect(Open3).to receive(:capture3)
|
|
141
|
+
.with('sh', '-c', 'ls')
|
|
142
|
+
.and_return(['ok', '', double('s', exitstatus: 0)])
|
|
143
|
+
|
|
144
|
+
result = tool.execute(tool_call('ls'), chat:)
|
|
145
|
+
|
|
146
|
+
expect(json_object(result).exit_code).to eq 0
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'displays output via use_pager after execution' do
|
|
150
|
+
stub_success(stdout: "file1\nfile2", stderr: '')
|
|
151
|
+
pager_output = nil
|
|
152
|
+
expect(chat).to receive(:use_pager) do |&blk|
|
|
153
|
+
io = StringIO.new
|
|
154
|
+
blk.call(io)
|
|
155
|
+
pager_output = io.string
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
tool.execute(tool_call('ls'), chat:)
|
|
159
|
+
|
|
160
|
+
expect(pager_output).to include('file1')
|
|
161
|
+
expect(pager_output).to include('file2')
|
|
162
|
+
expect(pager_output).to include('exit: 0')
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'strips ANSI from stdout when monochrome is true' do
|
|
166
|
+
stub_success(stdout: "\e[32mgreen\e[0m", stderr: '')
|
|
167
|
+
|
|
168
|
+
result = tool.execute(tool_call('ls', monochrome: true), chat:)
|
|
169
|
+
|
|
170
|
+
expect(json_object(result).stdout).to eq 'green'
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'keeps ANSI when monochrome is false' do
|
|
174
|
+
stub_success(stdout: "\e[32mgreen\e[0m", stderr: '')
|
|
175
|
+
|
|
176
|
+
result = tool.execute(tool_call('ls', monochrome: false), chat:)
|
|
177
|
+
|
|
178
|
+
expect(json_object(result).stdout).to eq "\e[32mgreen\e[0m"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'treats a nil monochrome as true (default)' do
|
|
182
|
+
stub_success(stdout: "\e[31mred\e[0m", stderr: '')
|
|
183
|
+
|
|
184
|
+
result = tool.execute(tool_call('ls', monochrome: nil), chat:)
|
|
185
|
+
|
|
186
|
+
expect(json_object(result).stdout).to eq 'red'
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'reports the exit code and stderr on failure' do
|
|
190
|
+
stub_success(stdout: '', stderr: 'nope', exit_code: 2)
|
|
191
|
+
|
|
192
|
+
result = tool.execute(tool_call('false'), chat:)
|
|
193
|
+
json = json_object(result)
|
|
194
|
+
|
|
195
|
+
expect(json.exit_code).to eq 2
|
|
196
|
+
expect(json.stderr).to eq 'nope'
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it 'rejects a blank command' do
|
|
200
|
+
allow(chat).to receive(:edit_text)
|
|
201
|
+
|
|
202
|
+
result = tool.execute(tool_call(nil), chat:)
|
|
203
|
+
json = json_object(result)
|
|
204
|
+
|
|
205
|
+
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it 'summary_template includes the command and exit code' do
|
|
209
|
+
result = {
|
|
210
|
+
stdout: 'x', stderr: '', exit_code: 0,
|
|
211
|
+
command: 'ls -l', message: 'ok',
|
|
212
|
+
}.to_json
|
|
213
|
+
|
|
214
|
+
summary = described_class.summary_template(result:)
|
|
215
|
+
expect(summary).to include('ls -l')
|
|
216
|
+
expect(summary).to include('exit 0')
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'summary_template includes error message for cancelled' do
|
|
220
|
+
result = {
|
|
221
|
+
error: 'OllamaChat::ToolFunctionArgumentError',
|
|
222
|
+
command: 'rm -rf .',
|
|
223
|
+
message: 'Shell command cancelled by user: rm -rf .',
|
|
224
|
+
}.to_json
|
|
225
|
+
|
|
226
|
+
summary = described_class.summary_template(result:)
|
|
227
|
+
expect(summary).to include('cancelled')
|
|
228
|
+
expect(summary).to include('rm -rf .')
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -31,6 +31,8 @@ describe OllamaChat::Tools::FileContext do
|
|
|
31
31
|
json = json_object(result)
|
|
32
32
|
content_file = json.files[Pathname.pwd.join('spec/assets/example.rb').to_s].content
|
|
33
33
|
expect(content_file).to include 'Hello World!'
|
|
34
|
+
expect(described_class.summary_template(result:))\
|
|
35
|
+
.to eq "was called."
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
it 'can handle execution errors gracefully' do
|
|
@@ -50,5 +52,8 @@ describe OllamaChat::Tools::FileContext do
|
|
|
50
52
|
|
|
51
53
|
# Should still return a string (even if empty or minimal)
|
|
52
54
|
expect(result).to be_a(String)
|
|
55
|
+
expect(described_class.summary_template(result:))\
|
|
56
|
+
.to eq "was called."
|
|
53
57
|
end
|
|
58
|
+
|
|
54
59
|
end
|
|
@@ -56,6 +56,8 @@ describe OllamaChat::Tools::GemPathLookup do
|
|
|
56
56
|
json = json_object(result)
|
|
57
57
|
expect(json.error).to eq 'RuntimeError'
|
|
58
58
|
expect(json.message).to eq 'an error has happened'
|
|
59
|
+
expect(described_class.summary_template(result:))\
|
|
60
|
+
.to eq 'an error has happened'
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
63
|
end
|
|
@@ -62,6 +62,8 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
62
62
|
expect(json.status).to eq 'success'
|
|
63
63
|
expect(json.url).to include('/api/view')
|
|
64
64
|
expect(json.url).to include('filename=kitten_output.png')
|
|
65
|
+
expect(described_class.summary_template(result:)).to\
|
|
66
|
+
match(/Image successfully generated!/)
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
it 'returns an error when prompt is missing' do
|
|
@@ -72,6 +74,8 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
72
74
|
json = json_object(result)
|
|
73
75
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
74
76
|
expect(json.message).to include('require prompt argument')
|
|
77
|
+
expect(described_class.summary_template(result:)).to\
|
|
78
|
+
include('require prompt argument')
|
|
75
79
|
end
|
|
76
80
|
|
|
77
81
|
it 'returns an error when ComfyUI configuration is missing' do
|
|
@@ -83,6 +87,8 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
83
87
|
json = json_object(result)
|
|
84
88
|
expect(json.error).to eq 'OllamaChat::ConfigMissingError'
|
|
85
89
|
expect(json.message).to include('Require env var OLLAMA_CHAT_TOOLS_IMAGE_GENERATOR_URL configuration')
|
|
90
|
+
expect(described_class.summary_template(result:)).to\
|
|
91
|
+
include('Failed to generate image:')
|
|
86
92
|
end
|
|
87
93
|
|
|
88
94
|
it 'returns a timeout error when polling fails' do
|
|
@@ -98,6 +104,8 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
98
104
|
json = json_object(result)
|
|
99
105
|
expect(json.error).to eq 'OllamaChat::OllamaChatError'
|
|
100
106
|
expect(json.message).to include('took too long or failed')
|
|
107
|
+
expect(described_class.summary_template(result:)).to\
|
|
108
|
+
include('took too long or failed')
|
|
101
109
|
end
|
|
102
110
|
|
|
103
111
|
it 'handles API failures gracefully' do
|
|
@@ -112,6 +120,8 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
112
120
|
json = json_object(result)
|
|
113
121
|
expect(json.error).to eq 'OllamaChat::OllamaChatError'
|
|
114
122
|
expect(json.message).to include('Failed to generate image: failed to trigger ComfyUI')
|
|
123
|
+
expect(described_class.summary_template(result:)).to\
|
|
124
|
+
include('Failed to generate image:')
|
|
115
125
|
end
|
|
116
126
|
|
|
117
127
|
it 'rescues generic exceptions' do
|
|
@@ -124,6 +134,8 @@ describe OllamaChat::Tools::GenerateImage do
|
|
|
124
134
|
json = json_object(result)
|
|
125
135
|
expect(json.error).to eq 'StandardError'
|
|
126
136
|
expect(json.message).to include('Network crash')
|
|
137
|
+
expect(described_class.summary_template(result:)).to\
|
|
138
|
+
include('Failed to generate image:')
|
|
127
139
|
end
|
|
128
140
|
end
|
|
129
141
|
|
|
@@ -46,6 +46,9 @@ describe OllamaChat::Tools::GeneratePassword do
|
|
|
46
46
|
expect(json.message).to eq(
|
|
47
47
|
'Successfully generated a 16-character secure password using the default alphabet.'
|
|
48
48
|
)
|
|
49
|
+
expect(described_class.summary_template(result:)).to eq(
|
|
50
|
+
'Successfully generated a 16-character secure password using the default alphabet.'
|
|
51
|
+
)
|
|
49
52
|
end
|
|
50
53
|
|
|
51
54
|
it 'can be executed successfully with bits parameter' do
|
|
@@ -134,6 +137,8 @@ describe OllamaChat::Tools::GeneratePassword do
|
|
|
134
137
|
json = json_object(result)
|
|
135
138
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
136
139
|
expect(json.message).to eq 'require either bits or length of password'
|
|
140
|
+
expect(described_class.summary_template(result:))\
|
|
141
|
+
.to eq 'require either bits or length of password'
|
|
137
142
|
end
|
|
138
143
|
|
|
139
144
|
it 'can be converted t)o hash' do
|
|
@@ -44,6 +44,8 @@ describe OllamaChat::Tools::GetCVE do
|
|
|
44
44
|
json = json_object(result)
|
|
45
45
|
expect(json.id).to eq 'CVE-2023-12345'
|
|
46
46
|
expect(json.description).to include('Test vulnerability description')
|
|
47
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
48
|
+
'was called.'
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
it 'can handle execution errors gracefully' do
|
|
@@ -67,5 +69,7 @@ describe OllamaChat::Tools::GetCVE do
|
|
|
67
69
|
json = json_object(result)
|
|
68
70
|
expect(json.error).to eq 'OllamaChat::HTTPError'
|
|
69
71
|
expect(json.message).to eq 'request failed with status 404'
|
|
72
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
73
|
+
'request failed with status 404'
|
|
70
74
|
end
|
|
71
75
|
end
|
|
@@ -45,6 +45,8 @@ describe OllamaChat::Tools::GetEndoflife do
|
|
|
45
45
|
expect(json.cycle).to eq '3.1'
|
|
46
46
|
expect(json.releaseDate).to eq '2023-05-01'
|
|
47
47
|
expect(json.eol).to eq '2026-05-01'
|
|
48
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
49
|
+
'was called.'
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
it 'can handle execution errors gracefully' do
|
|
@@ -68,5 +70,7 @@ describe OllamaChat::Tools::GetEndoflife do
|
|
|
68
70
|
json = json_object(result)
|
|
69
71
|
expect(json.error).to eq 'OllamaChat::HTTPError'
|
|
70
72
|
expect(json.message).to eq 'request failed with status 404'
|
|
73
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
74
|
+
'request failed with status 404'
|
|
71
75
|
end
|
|
72
76
|
end
|
|
@@ -54,6 +54,8 @@ describe OllamaChat::Tools::GetGHR do
|
|
|
54
54
|
expect(json.repo).to eq repo
|
|
55
55
|
expect(json.releases).to be_a Array
|
|
56
56
|
expect(json.releases.first.version).to eq '1.0'
|
|
57
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
58
|
+
'was called.'
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
it 'can be executed successfully for an overview' do
|
|
@@ -104,6 +106,8 @@ describe OllamaChat::Tools::GetGHR do
|
|
|
104
106
|
|
|
105
107
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
106
108
|
expect(json.message).to include('Both user and repo must be provided')
|
|
109
|
+
expect(described_class.summary_template(result:)).to include \
|
|
110
|
+
'Both user and repo must be provided'
|
|
107
111
|
end
|
|
108
112
|
|
|
109
113
|
it 'can handle execution errors gracefully' do
|
|
@@ -132,6 +136,8 @@ describe OllamaChat::Tools::GetGHR do
|
|
|
132
136
|
|
|
133
137
|
expect(json.error).to eq 'OllamaChat::HTTPError'
|
|
134
138
|
expect(json.message).to eq 'request failed with status 404'
|
|
139
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
140
|
+
'request failed with status 404'
|
|
135
141
|
end
|
|
136
142
|
|
|
137
143
|
it 'can be executed successfully for a specific repository with pagination' do
|
|
@@ -41,6 +41,8 @@ describe OllamaChat::Tools::GetLocation do
|
|
|
41
41
|
expect(json.latitude).to be_within(0.0001).of(40.7128)
|
|
42
42
|
expect(json.longitude).to be_within(0.0001).of(-74.0060)
|
|
43
43
|
expect(json.units).to eq 'metric'
|
|
44
|
+
expect(described_class.summary_template(result:))\
|
|
45
|
+
.to eq 'was called.'
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
it 'can handle execution errors gracefully' do
|
|
@@ -71,6 +73,8 @@ describe OllamaChat::Tools::GetLocation do
|
|
|
71
73
|
result = described_class.new.execute(tool_call, chat:)
|
|
72
74
|
# Should still be valid JSON even if location_data is nil
|
|
73
75
|
expect { JSON.parse(result) }.to_not raise_error
|
|
76
|
+
expect(described_class.summary_template(result:))\
|
|
77
|
+
.to eq 'was called.'
|
|
74
78
|
end
|
|
75
79
|
end
|
|
76
80
|
end
|
|
@@ -44,6 +44,7 @@ describe OllamaChat::Tools::GetRFC do
|
|
|
44
44
|
json = json_object(result)
|
|
45
45
|
expect(json.rfc_id).to eq rfc_id
|
|
46
46
|
expect(json.content).to include('RFC 1234')
|
|
47
|
+
expect(described_class.summary_template(result:)).to eq 'RFC 1234 was fetched.'
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
it 'can handle execution errors gracefully' do
|
|
@@ -67,5 +68,6 @@ describe OllamaChat::Tools::GetRFC do
|
|
|
67
68
|
json = json_object(result)
|
|
68
69
|
expect(json.error).to be_a String
|
|
69
70
|
expect(json.message).to be_a String
|
|
71
|
+
expect(described_class.summary_template(result:)).to eq 'request failed with status 404'
|
|
70
72
|
end
|
|
71
73
|
end
|
|
@@ -36,6 +36,8 @@ describe OllamaChat::Tools::GetTime do
|
|
|
36
36
|
)
|
|
37
37
|
expect(json.weekday).to be_present
|
|
38
38
|
expect(json.message).to eq 'Good morning! It is currently 11:11 on Friday.'
|
|
39
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
40
|
+
'Good morning! It is currently 11:11 on Friday.'
|
|
39
41
|
ensure
|
|
40
42
|
Time.dummy = nil
|
|
41
43
|
end
|
|
@@ -17,6 +17,7 @@ describe OllamaChat::Tools::GetURL do
|
|
|
17
17
|
expect(described_class.new.to_hash).to be_a Hash
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
|
|
20
21
|
context "with a valid URL" do
|
|
21
22
|
it "imports content from the URL" do
|
|
22
23
|
tool_call = double(
|
|
@@ -39,6 +40,8 @@ describe OllamaChat::Tools::GetURL do
|
|
|
39
40
|
json = json_object(result)
|
|
40
41
|
expect(json.url).to eq('https://www.example.com/foo')
|
|
41
42
|
expect(json.message).to eq('Received requested URL successfully.')
|
|
43
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
44
|
+
'Received requested URL successfully.'
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
|
|
@@ -68,6 +71,8 @@ describe OllamaChat::Tools::GetURL do
|
|
|
68
71
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
69
72
|
expect(json.message).to match(/scheme "file" not allowed/)
|
|
70
73
|
expect(json.url).to eq url
|
|
74
|
+
expect(described_class.summary_template(result:)).
|
|
75
|
+
to match(/scheme "file" not allowed/)
|
|
71
76
|
end
|
|
72
77
|
end
|
|
73
78
|
|
|
@@ -93,6 +98,8 @@ describe OllamaChat::Tools::GetURL do
|
|
|
93
98
|
json = json_object(result)
|
|
94
99
|
expect(json.error).to eq 'RuntimeError'
|
|
95
100
|
expect(json.message).to eq 'it somehow failed'
|
|
101
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
102
|
+
'it somehow failed'
|
|
96
103
|
end
|
|
97
104
|
|
|
98
105
|
context 'with different document policies' do
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
describe OllamaChat::Tools::LookupGroup do
|
|
2
|
+
let :chat do
|
|
3
|
+
OllamaChat::Chat.new(argv: chat_default_config)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
connect_to_ollama_server
|
|
7
|
+
|
|
8
|
+
let :tool do
|
|
9
|
+
described_class.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
let :uuid do
|
|
13
|
+
'0194a3f2-8c4d-7e1a-9b3c-abcdef012345'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
chat.messages << OllamaChat::Message.new(
|
|
18
|
+
role: 'user', content: 'Read the config file', group_uuid: uuid
|
|
19
|
+
)
|
|
20
|
+
chat.messages << OllamaChat::Message.new(
|
|
21
|
+
role: 'assistant', content: 'Sure, let me read it.', group_uuid: uuid
|
|
22
|
+
)
|
|
23
|
+
chat.messages << OllamaChat::Message.new(
|
|
24
|
+
role: 'tool', content: '{"path":"/tmp/config.yml","content":"key: value"}',
|
|
25
|
+
tool_name: 'read_file', group_uuid: uuid
|
|
26
|
+
)
|
|
27
|
+
chat.messages << OllamaChat::Message.new(
|
|
28
|
+
role: 'assistant', content: 'Here is the config.', group_uuid: uuid
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'has the expected name' do
|
|
33
|
+
expect(tool.name).to eq 'lookup_group'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'provides a Tool instance' do
|
|
37
|
+
expect(tool.tool).to be_a(Ollama::Tool)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'can be converted to hash' do
|
|
41
|
+
expect(tool.to_hash).to be_a(Hash)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'returns the full messages of a matching group' do
|
|
45
|
+
tool_call = double(
|
|
46
|
+
'ToolCall',
|
|
47
|
+
function: double(
|
|
48
|
+
name: 'lookup_group',
|
|
49
|
+
arguments: double(group_uuid: uuid)
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
result = tool.execute(tool_call, chat:)
|
|
54
|
+
expect(result).to be_a(String)
|
|
55
|
+
|
|
56
|
+
json = json_object(result)
|
|
57
|
+
expect(json.group_uuid).to eq uuid
|
|
58
|
+
expect(json.messages.size).to eq 4
|
|
59
|
+
expect(json.message).to eq 'Retrieved 4 message(s) for group 0194a3f2-8c4d-7e1a-9b3c-abcdef012345.'
|
|
60
|
+
expect(json.messages[0]).to eq '[user] Read the config file'
|
|
61
|
+
expect(json.messages[1]).to eq '[assistant] Sure, let me read it.'
|
|
62
|
+
expect(json.messages[2]).to eq '[tool (read_file)] {"path":"/tmp/config.yml","content":"key: value"}'
|
|
63
|
+
expect(json.messages[3]).to eq '[assistant] Here is the config.'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'returns an error for a non-existent group' do
|
|
67
|
+
tool_call = double(
|
|
68
|
+
'ToolCall',
|
|
69
|
+
function: double(
|
|
70
|
+
name: 'lookup_group',
|
|
71
|
+
arguments: double(group_uuid: 'ffffffff-0000-0000-0000-000000000000')
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
result = tool.execute(tool_call, chat:)
|
|
76
|
+
json = json_object(result)
|
|
77
|
+
expect(json.error).to eq 'OllamaChat::OllamaChatError'
|
|
78
|
+
expect(json.message).to include('Group not found')
|
|
79
|
+
expect(json.message).to include('ffffffff-0000-0000-0000-000000000000')
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'returns an error when group_uuid is empty' do
|
|
83
|
+
tool_call = double(
|
|
84
|
+
'ToolCall',
|
|
85
|
+
function: double(
|
|
86
|
+
name: 'lookup_group',
|
|
87
|
+
arguments: double(group_uuid: '')
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
result = tool.execute(tool_call, chat:)
|
|
92
|
+
json = json_object(result)
|
|
93
|
+
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
94
|
+
expect(json.message).to eq 'group_uuid required'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'matches on the suffix of the group_uuid' do
|
|
98
|
+
tool_call = double(
|
|
99
|
+
'ToolCall',
|
|
100
|
+
function: double(
|
|
101
|
+
name: 'lookup_group',
|
|
102
|
+
arguments: double(group_uuid: 'abcdef012345')
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
result = tool.execute(tool_call, chat:)
|
|
107
|
+
json = json_object(result)
|
|
108
|
+
expect(json.group_uuid).to eq uuid
|
|
109
|
+
expect(json.message).to include('Retrieved 4 message(s)')
|
|
110
|
+
end
|
|
111
|
+
end
|