scout-ai 1.1.0 → 1.1.1
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/.vimproject +12 -1
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/bin/scout-ai +46 -0
- data/lib/scout/llm/agent/chat.rb +2 -2
- data/lib/scout/llm/ask.rb +10 -2
- data/lib/scout/llm/backends/huggingface.rb +0 -2
- data/lib/scout/llm/backends/ollama.rb +0 -3
- data/lib/scout/llm/backends/openai.rb +4 -2
- data/lib/scout/llm/backends/openwebui.rb +1 -4
- data/lib/scout/llm/backends/relay.rb +1 -3
- data/lib/scout/llm/backends/responses.rb +25 -14
- data/lib/scout/llm/chat/annotation.rb +195 -0
- data/lib/scout/llm/chat/parse.rb +139 -0
- data/lib/scout/llm/chat/process/clear.rb +29 -0
- data/lib/scout/llm/chat/process/files.rb +96 -0
- data/lib/scout/llm/chat/process/options.rb +52 -0
- data/lib/scout/llm/chat/process/tools.rb +173 -0
- data/lib/scout/llm/chat/process.rb +16 -0
- data/lib/scout/llm/chat.rb +26 -674
- data/lib/scout/llm/mcp.rb +1 -1
- data/lib/scout/llm/tools/call.rb +11 -0
- data/lib/scout/llm/tools/mcp.rb +4 -0
- data/lib/scout/llm/tools/workflow.rb +3 -1
- data/lib/scout/llm/utils.rb +2 -17
- data/scout-ai.gemspec +13 -4
- data/scout_commands/llm/ask +16 -7
- data/scout_commands/llm/process +1 -1
- data/test/scout/llm/backends/test_anthropic.rb +2 -2
- data/test/scout/llm/backends/test_responses.rb +9 -9
- data/test/scout/llm/chat/test_parse.rb +126 -0
- data/test/scout/llm/chat/test_process.rb +123 -0
- data/test/scout/llm/test_agent.rb +1 -2
- data/test/scout/llm/test_chat.rb +2 -178
- metadata +25 -3
- data/lib/scout/llm/parse.rb +0 -91
data/test/scout/llm/test_chat.rb
CHANGED
|
@@ -3,143 +3,9 @@ require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1
|
|
|
3
3
|
|
|
4
4
|
class TestMessages < Test::Unit::TestCase
|
|
5
5
|
|
|
6
|
-
def test_short
|
|
7
|
-
|
|
8
|
-
question =<<-EOF
|
|
9
|
-
Hi
|
|
10
|
-
EOF
|
|
11
|
-
|
|
12
|
-
iii LLM.chat(question)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def test_inline
|
|
16
|
-
question =<<-EOF
|
|
17
|
-
system:
|
|
18
|
-
|
|
19
|
-
you are a terse assistant that only write in short sentences
|
|
20
|
-
|
|
21
|
-
assistant:
|
|
22
|
-
|
|
23
|
-
Here is some stuff
|
|
24
|
-
|
|
25
|
-
user: feedback
|
|
26
|
-
|
|
27
|
-
that continues here
|
|
28
|
-
EOF
|
|
29
|
-
|
|
30
|
-
iii LLM.chat(question)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def test_messages
|
|
34
|
-
question =<<-EOF
|
|
35
|
-
system:
|
|
36
|
-
|
|
37
|
-
you are a terse assistant that only write in short sentences
|
|
38
|
-
|
|
39
|
-
user:
|
|
40
|
-
|
|
41
|
-
What is the capital of France
|
|
42
|
-
|
|
43
|
-
assistant:
|
|
44
|
-
|
|
45
|
-
Paris
|
|
46
|
-
|
|
47
|
-
user:
|
|
48
|
-
|
|
49
|
-
is this the national anthem
|
|
50
|
-
|
|
51
|
-
[[
|
|
52
|
-
corous: Viva Espagna
|
|
53
|
-
]]
|
|
54
|
-
|
|
55
|
-
assistant:
|
|
56
|
-
|
|
57
|
-
no
|
|
58
|
-
|
|
59
|
-
user:
|
|
60
|
-
|
|
61
|
-
import: math.system
|
|
62
|
-
|
|
63
|
-
consider this file
|
|
64
|
-
|
|
65
|
-
<file name=foo_bar>
|
|
66
|
-
foo: bar
|
|
67
|
-
</file>
|
|
68
|
-
|
|
69
|
-
how many characters does it hold
|
|
70
|
-
|
|
71
|
-
assistant:
|
|
72
|
-
|
|
73
|
-
8
|
|
74
|
-
EOF
|
|
75
|
-
|
|
76
|
-
messages = LLM.messages question
|
|
77
|
-
refute messages.collect{|i| i[:role] }.include?("corous")
|
|
78
|
-
assert messages.collect{|i| i[:role] }.include?("import")
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def test_chat_import
|
|
82
|
-
file1 =<<-EOF
|
|
83
|
-
system: You are an assistant
|
|
84
|
-
EOF
|
|
85
|
-
|
|
86
|
-
file2 =<<-EOF
|
|
87
|
-
import: header
|
|
88
|
-
user: say something
|
|
89
|
-
EOF
|
|
90
|
-
|
|
91
|
-
TmpFile.with_path do |tmpdir|
|
|
92
|
-
tmpdir.header.write file1
|
|
93
|
-
tmpdir.chat.write file2
|
|
94
|
-
|
|
95
|
-
chat = LLM.chat tmpdir.chat
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def test_clear
|
|
100
|
-
question =<<-EOF
|
|
101
|
-
system:
|
|
102
|
-
|
|
103
|
-
you are a terse assistant that only write in short sentences
|
|
104
|
-
|
|
105
|
-
clear:
|
|
106
|
-
|
|
107
|
-
user:
|
|
108
|
-
|
|
109
|
-
What is the capital of France
|
|
110
|
-
EOF
|
|
111
|
-
|
|
112
|
-
TmpFile.with_file question do |file|
|
|
113
|
-
messages = LLM.chat file
|
|
114
|
-
refute messages.collect{|m| m[:role] }.include?('system')
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def __test_job
|
|
119
|
-
question =<<-EOF
|
|
120
|
-
system:
|
|
121
|
-
|
|
122
|
-
you are a terse assistant that only write in short sentences
|
|
123
|
-
|
|
124
|
-
job: Baking/bake_muffin_tray/Default_08a1812eca3a18dce2232509dabc9b41
|
|
125
|
-
|
|
126
|
-
How are muffins made
|
|
127
|
-
|
|
128
|
-
EOF
|
|
129
|
-
|
|
130
|
-
TmpFile.with_file question do |file|
|
|
131
|
-
messages = LLM.chat file
|
|
132
|
-
ppp LLM.print messages
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
6
|
|
|
137
7
|
def test_task
|
|
138
8
|
question =<<-EOF
|
|
139
|
-
system:
|
|
140
|
-
|
|
141
|
-
you are a terse assistant that only write in short sentences
|
|
142
|
-
|
|
143
9
|
user:
|
|
144
10
|
|
|
145
11
|
task: Baking bake_muffin_tray blueberries=true title="This is a title" list=one,two,"and three"
|
|
@@ -150,26 +16,8 @@ How are muffins made?
|
|
|
150
16
|
|
|
151
17
|
TmpFile.with_file question do |file|
|
|
152
18
|
messages = LLM.chat file
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def test_structure
|
|
158
|
-
require 'scout/llm/ask'
|
|
159
|
-
sss 0
|
|
160
|
-
question =<<-EOF
|
|
161
|
-
system:
|
|
162
|
-
|
|
163
|
-
Respond in json format with a hash of strings as keys and string arrays as values, at most three in length
|
|
164
|
-
|
|
165
|
-
endpoint: sambanova
|
|
166
|
-
|
|
167
|
-
What other movies have the protagonists of the original gost busters played on, just the top.
|
|
168
|
-
|
|
169
|
-
EOF
|
|
170
|
-
|
|
171
|
-
TmpFile.with_file question do |file|
|
|
172
|
-
ppp LLM.ask file
|
|
19
|
+
assert_include messages.collect{|m| m[:role] }, 'function_call'
|
|
20
|
+
assert_include messages.find{|m| m[:role] == 'function_call_output' }[:content], 'Baking'
|
|
173
21
|
end
|
|
174
22
|
end
|
|
175
23
|
|
|
@@ -228,29 +76,5 @@ association: marriages #{datafile_test(:person).marriages} undirected=true sourc
|
|
|
228
76
|
ppp LLM.ask file
|
|
229
77
|
end
|
|
230
78
|
end
|
|
231
|
-
|
|
232
|
-
def test_previous_response
|
|
233
|
-
require 'scout/llm/ask'
|
|
234
|
-
sss 0
|
|
235
|
-
question =<<-EOF
|
|
236
|
-
user:
|
|
237
|
-
|
|
238
|
-
Say hi
|
|
239
|
-
|
|
240
|
-
assistant:
|
|
241
|
-
|
|
242
|
-
Hi
|
|
243
|
-
|
|
244
|
-
previous_response_id: asdfasdfasdfasdf
|
|
245
|
-
|
|
246
|
-
Bye
|
|
247
|
-
|
|
248
|
-
EOF
|
|
249
|
-
|
|
250
|
-
messages = LLM.messages question
|
|
251
|
-
|
|
252
|
-
iii messages
|
|
253
|
-
|
|
254
|
-
end
|
|
255
79
|
end
|
|
256
80
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: scout-ai
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Miguel Vazquez
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: ollama-ai
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: ruby-mcp-client
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -89,9 +103,15 @@ files:
|
|
|
89
103
|
- lib/scout/llm/backends/relay.rb
|
|
90
104
|
- lib/scout/llm/backends/responses.rb
|
|
91
105
|
- lib/scout/llm/chat.rb
|
|
106
|
+
- lib/scout/llm/chat/annotation.rb
|
|
107
|
+
- lib/scout/llm/chat/parse.rb
|
|
108
|
+
- lib/scout/llm/chat/process.rb
|
|
109
|
+
- lib/scout/llm/chat/process/clear.rb
|
|
110
|
+
- lib/scout/llm/chat/process/files.rb
|
|
111
|
+
- lib/scout/llm/chat/process/options.rb
|
|
112
|
+
- lib/scout/llm/chat/process/tools.rb
|
|
92
113
|
- lib/scout/llm/embed.rb
|
|
93
114
|
- lib/scout/llm/mcp.rb
|
|
94
|
-
- lib/scout/llm/parse.rb
|
|
95
115
|
- lib/scout/llm/rag.rb
|
|
96
116
|
- lib/scout/llm/tools.rb
|
|
97
117
|
- lib/scout/llm/tools/call.rb
|
|
@@ -145,6 +165,8 @@ files:
|
|
|
145
165
|
- test/scout/llm/backends/test_openwebui.rb
|
|
146
166
|
- test/scout/llm/backends/test_relay.rb
|
|
147
167
|
- test/scout/llm/backends/test_responses.rb
|
|
168
|
+
- test/scout/llm/chat/test_parse.rb
|
|
169
|
+
- test/scout/llm/chat/test_process.rb
|
|
148
170
|
- test/scout/llm/test_agent.rb
|
|
149
171
|
- test/scout/llm/test_ask.rb
|
|
150
172
|
- test/scout/llm/test_chat.rb
|
|
@@ -186,7 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
186
208
|
- !ruby/object:Gem::Version
|
|
187
209
|
version: '0'
|
|
188
210
|
requirements: []
|
|
189
|
-
rubygems_version: 3.7.
|
|
211
|
+
rubygems_version: 3.7.2
|
|
190
212
|
specification_version: 4
|
|
191
213
|
summary: AI gear for scouts
|
|
192
214
|
test_files: []
|
data/lib/scout/llm/parse.rb
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
require 'scout/llm/utils'
|
|
2
|
-
module LLM
|
|
3
|
-
def self.process_inside(inside)
|
|
4
|
-
header, content = inside.match(/([^\n]*)\n(.*)/).values_at 1, 2
|
|
5
|
-
if header.empty?
|
|
6
|
-
content
|
|
7
|
-
else
|
|
8
|
-
action, _sep, rest = header.partition /\s/
|
|
9
|
-
case action
|
|
10
|
-
when 'import'
|
|
11
|
-
when 'cmd'
|
|
12
|
-
title = rest.strip.empty? ? content : rest
|
|
13
|
-
tag('file', title, CMD.cmd(content).read)
|
|
14
|
-
when 'file'
|
|
15
|
-
file = content
|
|
16
|
-
title = rest.strip.empty? ? file : rest
|
|
17
|
-
tag(action, title, Open.read(file))
|
|
18
|
-
when 'directory'
|
|
19
|
-
directory = content
|
|
20
|
-
title = rest.strip.empty? ? directory : rest
|
|
21
|
-
directory_content = Dir.glob(File.join(directory, '**/*')).collect do |file|
|
|
22
|
-
file_title = Misc.path_relative_to(directory, file)
|
|
23
|
-
tag('file', file_title, Open.read(file) )
|
|
24
|
-
end * "\n"
|
|
25
|
-
tag(action, title, directory_content )
|
|
26
|
-
else
|
|
27
|
-
tag(action, rest, content)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def self.parse(question, role = nil)
|
|
33
|
-
role = :user if role.nil?
|
|
34
|
-
|
|
35
|
-
if Array === question
|
|
36
|
-
question.collect do |q|
|
|
37
|
-
Hash === q ? q : {role: role, content: q}
|
|
38
|
-
end
|
|
39
|
-
else
|
|
40
|
-
if m = question.match(/(.*?)\[\[(.*?)\]\](.*)/m)
|
|
41
|
-
pre = m[1]
|
|
42
|
-
inside = m[2]
|
|
43
|
-
post = m[3]
|
|
44
|
-
messages = parse(pre, role)
|
|
45
|
-
|
|
46
|
-
messages = [{role: role, content: ''}] if messages.empty?
|
|
47
|
-
messages.last[:content] += process_inside inside
|
|
48
|
-
|
|
49
|
-
last = parse(post, messages.last[:role])
|
|
50
|
-
|
|
51
|
-
messages.concat last
|
|
52
|
-
|
|
53
|
-
messages
|
|
54
|
-
elsif m = question.match(/(.*?)(```.*?```)(.*)/m)
|
|
55
|
-
pre = m[1]
|
|
56
|
-
inside = m[2]
|
|
57
|
-
post = m[3]
|
|
58
|
-
messages = parse(pre, role)
|
|
59
|
-
|
|
60
|
-
messages = [{role: role, content: ''}] if messages.empty?
|
|
61
|
-
messages.last[:content] += inside
|
|
62
|
-
|
|
63
|
-
last = parse(post, messages.last[:role])
|
|
64
|
-
|
|
65
|
-
if last.first[:role] == messages.last[:role]
|
|
66
|
-
m = last.shift
|
|
67
|
-
messages.last[:content] += m[:content]
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
messages.concat last
|
|
71
|
-
|
|
72
|
-
messages
|
|
73
|
-
else
|
|
74
|
-
chunks = question.scan(/(.*?)^(\w+):(.*?)(?=^\w+:|\z)/m)
|
|
75
|
-
|
|
76
|
-
if chunks.any?
|
|
77
|
-
messages = []
|
|
78
|
-
messages << {role: role, content: chunks.first.first} if chunks.first and not chunks.first.first.empty?
|
|
79
|
-
chunks.collect do |pre,role,text|
|
|
80
|
-
messages << {role: role, content: text.strip}
|
|
81
|
-
end
|
|
82
|
-
messages
|
|
83
|
-
elsif question.strip.empty?
|
|
84
|
-
[]
|
|
85
|
-
else
|
|
86
|
-
[{role: role, content: question}]
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|