scout-ai 1.0.1 → 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 +20 -2
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/bin/scout-ai +46 -0
- data/lib/scout/llm/agent/chat.rb +4 -7
- data/lib/scout/llm/agent/delegate.rb +12 -0
- data/lib/scout/llm/agent.rb +2 -2
- data/lib/scout/llm/ask.rb +18 -2
- data/lib/scout/llm/backends/huggingface.rb +0 -2
- data/lib/scout/llm/backends/ollama.rb +6 -6
- data/lib/scout/llm/backends/openai.rb +7 -4
- 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 +34 -18
- 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 -662
- data/lib/scout/llm/mcp.rb +1 -1
- data/lib/scout/llm/tools/call.rb +22 -1
- data/lib/scout/llm/tools/knowledge_base.rb +15 -14
- data/lib/scout/llm/tools/mcp.rb +4 -0
- data/lib/scout/llm/tools/workflow.rb +54 -15
- data/lib/scout/llm/tools.rb +42 -0
- data/lib/scout/llm/utils.rb +2 -17
- data/scout-ai.gemspec +13 -4
- data/scout_commands/agent/ask +36 -12
- data/scout_commands/llm/ask +17 -7
- data/scout_commands/llm/process +1 -1
- data/test/scout/llm/backends/test_anthropic.rb +2 -2
- data/test/scout/llm/backends/test_ollama.rb +1 -1
- 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 +2 -25
- data/test/scout/llm/test_chat.rb +2 -178
- metadata +25 -3
- data/lib/scout/llm/parse.rb +0 -91
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module Chat
|
|
2
|
+
def self.parse(text, role = nil)
|
|
3
|
+
default_role = "user"
|
|
4
|
+
|
|
5
|
+
messages = []
|
|
6
|
+
current_role = role || default_role
|
|
7
|
+
current_content = ""
|
|
8
|
+
in_protected_block = false
|
|
9
|
+
protected_block_type = nil
|
|
10
|
+
protected_stack = []
|
|
11
|
+
|
|
12
|
+
role = default_role if role.nil?
|
|
13
|
+
|
|
14
|
+
file_lines = text.split("\n")
|
|
15
|
+
|
|
16
|
+
file_lines.each do |line|
|
|
17
|
+
stripped = line.strip
|
|
18
|
+
|
|
19
|
+
# Detect protected blocks
|
|
20
|
+
if stripped.start_with?("```")
|
|
21
|
+
if in_protected_block
|
|
22
|
+
in_protected_block = false
|
|
23
|
+
protected_block_type = nil
|
|
24
|
+
current_content << "\n" << line unless line.strip.empty?
|
|
25
|
+
else
|
|
26
|
+
in_protected_block = true
|
|
27
|
+
protected_block_type = :square
|
|
28
|
+
current_content << "\n" << line unless line.strip.empty?
|
|
29
|
+
end
|
|
30
|
+
next
|
|
31
|
+
elsif stripped.end_with?("]]") && in_protected_block && protected_block_type == :square
|
|
32
|
+
in_protected_block = false
|
|
33
|
+
protected_block_type = nil
|
|
34
|
+
line = line.sub("]]", "")
|
|
35
|
+
current_content << "\n" << line unless line.strip.empty?
|
|
36
|
+
next
|
|
37
|
+
elsif stripped.start_with?("[[")
|
|
38
|
+
in_protected_block = true
|
|
39
|
+
protected_block_type = :square
|
|
40
|
+
line = line.sub("[[", "")
|
|
41
|
+
current_content << "\n" << line unless line.strip.empty?
|
|
42
|
+
next
|
|
43
|
+
elsif stripped.end_with?("]]") && in_protected_block && protected_block_type == :square
|
|
44
|
+
in_protected_block = false
|
|
45
|
+
protected_block_type = nil
|
|
46
|
+
line = line.sub("]]", "")
|
|
47
|
+
current_content << "\n" << line unless line.strip.empty?
|
|
48
|
+
next
|
|
49
|
+
elsif stripped.match(/^.*:-- .* {{{/)
|
|
50
|
+
in_protected_block = true
|
|
51
|
+
protected_block_type = :square
|
|
52
|
+
line = line.sub(/^.*:-- (.*) {{{.*/, '<cmd_output cmd="\1">')
|
|
53
|
+
current_content << "\n" << line unless line.strip.empty?
|
|
54
|
+
next
|
|
55
|
+
elsif stripped.match(/^.*:--.* }}}/) && in_protected_block && protected_block_type == :square
|
|
56
|
+
in_protected_block = false
|
|
57
|
+
protected_block_type = nil
|
|
58
|
+
line = line.sub(/^.*:-- .* }}}.*/, "</cmd_output>")
|
|
59
|
+
current_content << "\n" << line unless line.strip.empty?
|
|
60
|
+
next
|
|
61
|
+
elsif in_protected_block
|
|
62
|
+
|
|
63
|
+
if protected_block_type == :xml
|
|
64
|
+
if stripped =~ %r{</(\w+)>}
|
|
65
|
+
closing_tag = $1
|
|
66
|
+
if protected_stack.last == closing_tag
|
|
67
|
+
protected_stack.pop
|
|
68
|
+
end
|
|
69
|
+
if protected_stack.empty?
|
|
70
|
+
in_protected_block = false
|
|
71
|
+
protected_block_type = nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
current_content << "\n" << line
|
|
76
|
+
next
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# XML-style tag handling (protected content)
|
|
80
|
+
if stripped =~ /^<(\w+)(\s+[^>]*)?>/
|
|
81
|
+
tag = $1
|
|
82
|
+
protected_stack.push(tag)
|
|
83
|
+
in_protected_block = true
|
|
84
|
+
protected_block_type = :xml
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Match a new message header
|
|
88
|
+
if line =~ /^([a-z0-9_]+):(.*)$/
|
|
89
|
+
role = $1
|
|
90
|
+
inline_content = $2.strip
|
|
91
|
+
|
|
92
|
+
current_content = current_content.strip if current_content
|
|
93
|
+
# Save current message if any
|
|
94
|
+
messages << { role: current_role, content: current_content }
|
|
95
|
+
|
|
96
|
+
if inline_content.empty?
|
|
97
|
+
# Block message
|
|
98
|
+
current_role = role
|
|
99
|
+
current_content = ""
|
|
100
|
+
else
|
|
101
|
+
# Inline message + next block is default role
|
|
102
|
+
messages << { role: role, content: inline_content }
|
|
103
|
+
current_role = 'user' if role == 'previous_response_id'
|
|
104
|
+
current_content = ""
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
if current_content.nil?
|
|
108
|
+
current_content = line
|
|
109
|
+
else
|
|
110
|
+
current_content += "\n" + line
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Final message
|
|
116
|
+
messages << { role: current_role || default_role, content: current_content.strip }
|
|
117
|
+
|
|
118
|
+
messages
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.print(chat)
|
|
122
|
+
return chat if String === chat
|
|
123
|
+
"\n" + chat.collect do |message|
|
|
124
|
+
IndiferentHash.setup message
|
|
125
|
+
case message[:content]
|
|
126
|
+
when Hash, Array
|
|
127
|
+
message[:role].to_s + ":\n\n" + message[:content].to_json
|
|
128
|
+
when nil, ''
|
|
129
|
+
message[:role].to_s + ":"
|
|
130
|
+
else
|
|
131
|
+
if %w(option previous_response_id function_call function_call_output).include? message[:role].to_s
|
|
132
|
+
message[:role].to_s + ": " + message[:content].to_s
|
|
133
|
+
else
|
|
134
|
+
message[:role].to_s + ":\n\n" + message[:content].to_s
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end * "\n\n"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Chat
|
|
2
|
+
def self.clear(messages, role = 'clear')
|
|
3
|
+
new = []
|
|
4
|
+
|
|
5
|
+
messages.reverse.each do |message|
|
|
6
|
+
if message[:role].to_s == role
|
|
7
|
+
break
|
|
8
|
+
else
|
|
9
|
+
new << message
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
new.reverse
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.clean(messages, role = 'skip')
|
|
17
|
+
messages.reject do |message|
|
|
18
|
+
((String === message[:content]) && message[:content].empty?) ||
|
|
19
|
+
message[:role] == role
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.purge(chat)
|
|
24
|
+
chat.reject do |msg|
|
|
25
|
+
IndiferentHash.setup msg
|
|
26
|
+
msg[:role].to_s == 'previous_response_id'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Chat
|
|
2
|
+
def self.tag(tag, content, name = nil)
|
|
3
|
+
if name
|
|
4
|
+
<<-EOF.strip
|
|
5
|
+
<#{tag} name="#{name}">
|
|
6
|
+
#{content}
|
|
7
|
+
</#{tag}>
|
|
8
|
+
EOF
|
|
9
|
+
else
|
|
10
|
+
<<-EOF.strip
|
|
11
|
+
<#{tag}>
|
|
12
|
+
#{content}
|
|
13
|
+
</#{tag}>
|
|
14
|
+
EOF
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
def self.find_file(file, original = nil, caller_lib_dir = Path.caller_lib_dir(nil, 'chats'))
|
|
18
|
+
path = Scout.chats[file]
|
|
19
|
+
original = original.find if Path === original
|
|
20
|
+
if original
|
|
21
|
+
relative = File.join(File.dirname(original), file)
|
|
22
|
+
relative_lib = File.join(caller_lib_dir, file) if caller_lib_dir
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if Open.exist?(file)
|
|
26
|
+
file
|
|
27
|
+
elsif Open.remote?(file)
|
|
28
|
+
file
|
|
29
|
+
elsif relative && Open.exist?(relative)
|
|
30
|
+
relative
|
|
31
|
+
elsif relative_lib && Open.exist?(relative_lib)
|
|
32
|
+
relative_lib
|
|
33
|
+
elsif path.exists?
|
|
34
|
+
path
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.imports(messages, original = nil, caller_lib_dir = Path.caller_lib_dir(nil, 'chats'))
|
|
39
|
+
messages.collect do |message|
|
|
40
|
+
if message[:role] == 'import' || message[:role] == 'continue' || message[:role] == 'last'
|
|
41
|
+
file = message[:content].to_s.strip
|
|
42
|
+
found_file = find_file(file, original, caller_lib_dir)
|
|
43
|
+
raise "Import not found: #{file}" if found_file.nil?
|
|
44
|
+
|
|
45
|
+
new = LLM.messages Open.read(found_file)
|
|
46
|
+
|
|
47
|
+
new = if message[:role] == 'continue'
|
|
48
|
+
[new.reject{|msg| msg[:content].nil? || msg[:content].strip.empty? }.last]
|
|
49
|
+
elsif message[:role] == 'last'
|
|
50
|
+
[LLM.purge(new).reject{|msg| msg[:content].empty?}.last]
|
|
51
|
+
else
|
|
52
|
+
LLM.purge(new)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
LLM.chat new, found_file
|
|
56
|
+
else
|
|
57
|
+
message
|
|
58
|
+
end
|
|
59
|
+
end.flatten
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.files(messages, original = nil, caller_lib_dir = Path.caller_lib_dir(nil, 'chats'))
|
|
63
|
+
messages.collect do |message|
|
|
64
|
+
if message[:role] == 'file' || message[:role] == 'directory'
|
|
65
|
+
file = message[:content].to_s.strip
|
|
66
|
+
found_file = find_file(file, original, caller_lib_dir)
|
|
67
|
+
raise "File not found: #{file}" if found_file.nil?
|
|
68
|
+
|
|
69
|
+
target = found_file
|
|
70
|
+
|
|
71
|
+
if message[:role] == 'directory'
|
|
72
|
+
Path.setup target
|
|
73
|
+
target.glob('**/*').
|
|
74
|
+
reject{|file|
|
|
75
|
+
Open.directory?(file)
|
|
76
|
+
}.collect{|file|
|
|
77
|
+
files([{role: 'file', content: file}])
|
|
78
|
+
}
|
|
79
|
+
else
|
|
80
|
+
new = Chat.tag :file, Open.read(target), file
|
|
81
|
+
{role: 'user', content: new}
|
|
82
|
+
end
|
|
83
|
+
elsif message[:role] == 'pdf' || message[:role] == 'image'
|
|
84
|
+
file = message[:content].to_s.strip
|
|
85
|
+
found_file = find_file(file, original, caller_lib_dir)
|
|
86
|
+
raise "File not found: #{file}" if found_file.nil?
|
|
87
|
+
|
|
88
|
+
message[:content] = found_file
|
|
89
|
+
message
|
|
90
|
+
else
|
|
91
|
+
message
|
|
92
|
+
end
|
|
93
|
+
end.flatten
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Chat
|
|
2
|
+
def self.options(chat)
|
|
3
|
+
options = IndiferentHash.setup({})
|
|
4
|
+
sticky_options = IndiferentHash.setup({})
|
|
5
|
+
new = []
|
|
6
|
+
|
|
7
|
+
# Most options reset after an assistant reply, but not previous_response_id
|
|
8
|
+
chat.each do |info|
|
|
9
|
+
if Hash === info
|
|
10
|
+
role = info[:role].to_s
|
|
11
|
+
if %w(endpoint model backend agent).include? role.to_s
|
|
12
|
+
sticky_options[role] = info[:content]
|
|
13
|
+
next
|
|
14
|
+
elsif %w(persist).include? role.to_s
|
|
15
|
+
options[role] = info[:content]
|
|
16
|
+
next
|
|
17
|
+
elsif %w(previous_response_id).include? role.to_s
|
|
18
|
+
sticky_options[role] = info[:content]
|
|
19
|
+
elsif %w(format).include? role.to_s
|
|
20
|
+
format = info[:content]
|
|
21
|
+
if Path.is_filename?(format)
|
|
22
|
+
file = find_file(format)
|
|
23
|
+
if file
|
|
24
|
+
format = Open.json(file)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
options[role] = format
|
|
28
|
+
next
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if role.to_s == 'option'
|
|
32
|
+
key, _, value = info[:content].partition(" ")
|
|
33
|
+
options[key] = value
|
|
34
|
+
next
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if role.to_s == 'sticky_option'
|
|
38
|
+
key, _, value = info[:content].partition(" ")
|
|
39
|
+
sticky_options[key] = value
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if role == 'assistant'
|
|
44
|
+
options.clear
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
new << info
|
|
48
|
+
end
|
|
49
|
+
chat.replace new
|
|
50
|
+
sticky_options.merge options
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
module Chat
|
|
2
|
+
def self.tasks(messages, original = nil)
|
|
3
|
+
jobs = []
|
|
4
|
+
new = messages.collect do |message|
|
|
5
|
+
if message[:role] == 'task' || message[:role] == 'inline_task' || message[:role] == 'exec_task'
|
|
6
|
+
info = message[:content].strip
|
|
7
|
+
|
|
8
|
+
workflow, task = info.split(" ").values_at 0, 1
|
|
9
|
+
|
|
10
|
+
options = IndiferentHash.parse_options info
|
|
11
|
+
jobname = options.delete :jobname
|
|
12
|
+
|
|
13
|
+
if String === workflow
|
|
14
|
+
workflow = begin
|
|
15
|
+
Kernel.const_get workflow
|
|
16
|
+
rescue
|
|
17
|
+
Workflow.require_workflow(workflow)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
job = workflow.job(task, jobname, options)
|
|
22
|
+
|
|
23
|
+
jobs << job unless message[:role] == 'exec_task'
|
|
24
|
+
|
|
25
|
+
if message[:role] == 'exec_task'
|
|
26
|
+
begin
|
|
27
|
+
{role: 'user', content: job.exec}
|
|
28
|
+
rescue
|
|
29
|
+
{role: 'exec_job', content: $!}
|
|
30
|
+
end
|
|
31
|
+
elsif message[:role] == 'inline_task'
|
|
32
|
+
{role: 'inline_job', content: job.path.find}
|
|
33
|
+
else
|
|
34
|
+
{role: 'job', content: job.path.find}
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
message
|
|
38
|
+
end
|
|
39
|
+
end.flatten
|
|
40
|
+
|
|
41
|
+
Workflow.produce(jobs)
|
|
42
|
+
|
|
43
|
+
new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.jobs(messages, original = nil)
|
|
47
|
+
messages.collect do |message|
|
|
48
|
+
if message[:role] == 'job' || message[:role] == 'inline_job'
|
|
49
|
+
file = message[:content].strip
|
|
50
|
+
|
|
51
|
+
step = Step.load file
|
|
52
|
+
|
|
53
|
+
id = step.short_path[0..39]
|
|
54
|
+
id = id.gsub('/','-')
|
|
55
|
+
|
|
56
|
+
if message[:role] == 'inline_job'
|
|
57
|
+
path = step.path
|
|
58
|
+
path = path.find if Path === path
|
|
59
|
+
{role: 'file', content: step.path}
|
|
60
|
+
else
|
|
61
|
+
|
|
62
|
+
function_name = step.full_task_name.sub('#', '-')
|
|
63
|
+
function_name = step.task_name
|
|
64
|
+
tool_call = {
|
|
65
|
+
function: {
|
|
66
|
+
name: function_name,
|
|
67
|
+
arguments: step.provided_inputs
|
|
68
|
+
},
|
|
69
|
+
id: id,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
content = if step.done?
|
|
73
|
+
Open.read(step.path)
|
|
74
|
+
elsif step.error?
|
|
75
|
+
step.exception
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
tool_output = {
|
|
79
|
+
id: id,
|
|
80
|
+
content: content
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
[
|
|
84
|
+
{role: 'function_call', content: tool_call.to_json},
|
|
85
|
+
{role: 'function_call_output', content: tool_output.to_json},
|
|
86
|
+
]
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
message
|
|
90
|
+
end
|
|
91
|
+
end.flatten
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.tools(messages)
|
|
95
|
+
tool_definitions = IndiferentHash.setup({})
|
|
96
|
+
new = messages.collect do |message|
|
|
97
|
+
if message[:role] == 'mcp'
|
|
98
|
+
url, *tools = content_tokens(message)
|
|
99
|
+
|
|
100
|
+
if url == 'stdio'
|
|
101
|
+
command = tools.shift
|
|
102
|
+
mcp_tool_definitions = LLM.mcp_tools(url, command: command, url: nil, type: :stdio)
|
|
103
|
+
else
|
|
104
|
+
mcp_tool_definitions = LLM.mcp_tools(url)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if tools.any?
|
|
108
|
+
tools.each do |tool|
|
|
109
|
+
tool_definitions[tool] = mcp_tool_definitions[tool]
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
tool_definitions.merge!(mcp_tool_definitions)
|
|
113
|
+
end
|
|
114
|
+
next
|
|
115
|
+
elsif message[:role] == 'tool'
|
|
116
|
+
workflow_name, task_name, *inputs = content_tokens(message)
|
|
117
|
+
inputs = nil if inputs.empty?
|
|
118
|
+
inputs = [] if inputs == ['none'] || inputs == ['noinputs']
|
|
119
|
+
if Open.remote? workflow_name
|
|
120
|
+
require 'rbbt'
|
|
121
|
+
require 'scout/offsite/ssh'
|
|
122
|
+
require 'rbbt/workflow/remote_workflow'
|
|
123
|
+
workflow = RemoteWorkflow.new workflow_name
|
|
124
|
+
else
|
|
125
|
+
workflow = Workflow.require_workflow workflow_name
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if task_name
|
|
129
|
+
definition = LLM.task_tool_definition workflow, task_name, inputs
|
|
130
|
+
tool_definitions[task_name] = [workflow, definition]
|
|
131
|
+
else
|
|
132
|
+
tool_definitions.merge!(LLM.workflow_tools(workflow))
|
|
133
|
+
end
|
|
134
|
+
next
|
|
135
|
+
elsif message[:role] == 'kb'
|
|
136
|
+
knowledge_base_name, *databases = content_tokens(message)
|
|
137
|
+
databases = nil if databases.empty?
|
|
138
|
+
knowledge_base = KnowledgeBase.load knowledge_base_name
|
|
139
|
+
|
|
140
|
+
knowledge_base_definition = LLM.knowledge_base_tool_definition(knowledge_base, databases)
|
|
141
|
+
tool_definitions.merge!(knowledge_base_definition)
|
|
142
|
+
next
|
|
143
|
+
elsif message[:role] == 'clear_tools'
|
|
144
|
+
tool_definitions = {}
|
|
145
|
+
else
|
|
146
|
+
message
|
|
147
|
+
end
|
|
148
|
+
end.compact.flatten
|
|
149
|
+
messages.replace new
|
|
150
|
+
tool_definitions
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.associations(messages, kb = nil)
|
|
154
|
+
tool_definitions = {}
|
|
155
|
+
new = messages.collect do |message|
|
|
156
|
+
if message[:role] == 'association'
|
|
157
|
+
name, path, *options = content_tokens(message)
|
|
158
|
+
|
|
159
|
+
kb ||= KnowledgeBase.new Scout.var.Agent.Chat.knowledge_base
|
|
160
|
+
kb.register name, Path.setup(path), IndiferentHash.parse_options(message[:content])
|
|
161
|
+
|
|
162
|
+
tool_definitions.merge!(LLM.knowledge_base_tool_definition( kb, [name]))
|
|
163
|
+
next
|
|
164
|
+
elsif message[:role] == 'clear_associations'
|
|
165
|
+
tool_definitions = {}
|
|
166
|
+
else
|
|
167
|
+
message
|
|
168
|
+
end
|
|
169
|
+
end.compact.flatten
|
|
170
|
+
messages.replace new
|
|
171
|
+
tool_definitions
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require_relative 'process/tools'
|
|
2
|
+
require_relative 'process/files'
|
|
3
|
+
require_relative 'process/clear'
|
|
4
|
+
require_relative 'process/options'
|
|
5
|
+
|
|
6
|
+
require 'shellwords'
|
|
7
|
+
|
|
8
|
+
module Chat
|
|
9
|
+
def self.content_tokens(message)
|
|
10
|
+
Shellwords.split(message[:content].strip)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.indiferent(messages)
|
|
14
|
+
messages.collect{|msg| IndiferentHash.setup msg }
|
|
15
|
+
end
|
|
16
|
+
end
|