scout-ai 1.1.0 → 1.1.2

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.
@@ -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
- ppp LLM.print messages
154
- end
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.0
4
+ version: 1.1.2
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
@@ -51,6 +65,20 @@ dependencies:
51
65
  - - ">="
52
66
  - !ruby/object:Gem::Version
53
67
  version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: hnswlib
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
54
82
  description: assorted functionalities to help scouts use AI
55
83
  email: mikisvaz@gmail.com
56
84
  executables:
@@ -89,9 +117,15 @@ files:
89
117
  - lib/scout/llm/backends/relay.rb
90
118
  - lib/scout/llm/backends/responses.rb
91
119
  - lib/scout/llm/chat.rb
120
+ - lib/scout/llm/chat/annotation.rb
121
+ - lib/scout/llm/chat/parse.rb
122
+ - lib/scout/llm/chat/process.rb
123
+ - lib/scout/llm/chat/process/clear.rb
124
+ - lib/scout/llm/chat/process/files.rb
125
+ - lib/scout/llm/chat/process/options.rb
126
+ - lib/scout/llm/chat/process/tools.rb
92
127
  - lib/scout/llm/embed.rb
93
128
  - lib/scout/llm/mcp.rb
94
- - lib/scout/llm/parse.rb
95
129
  - lib/scout/llm/rag.rb
96
130
  - lib/scout/llm/tools.rb
97
131
  - lib/scout/llm/tools/call.rb
@@ -145,6 +179,8 @@ files:
145
179
  - test/scout/llm/backends/test_openwebui.rb
146
180
  - test/scout/llm/backends/test_relay.rb
147
181
  - test/scout/llm/backends/test_responses.rb
182
+ - test/scout/llm/chat/test_parse.rb
183
+ - test/scout/llm/chat/test_process.rb
148
184
  - test/scout/llm/test_agent.rb
149
185
  - test/scout/llm/test_ask.rb
150
186
  - test/scout/llm/test_chat.rb
@@ -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