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
data/lib/scout/llm/mcp.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Workflow
|
|
|
6
6
|
tasks = self.tasks.keys if tasks.empty?
|
|
7
7
|
|
|
8
8
|
tools = tasks.collect do |task,inputs=nil|
|
|
9
|
-
tool_definition = LLM.task_tool_definition(self, task, inputs)
|
|
9
|
+
tool_definition = LLM.task_tool_definition(self, task, inputs)
|
|
10
10
|
description = tool_definition[:description]
|
|
11
11
|
input_schema = tool_definition[:parameters].slice(:properties, :required)
|
|
12
12
|
annotations = tool_definition.slice(:title)
|
data/lib/scout/llm/tools/call.rb
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
module LLM
|
|
2
|
+
@max_content_length = Scout::Config.get(:max_content_length, :llm_tools, :tools, :llm, :ask, default: 5_000)
|
|
3
|
+
self.singleton_class.attr_accessor :max_content_length
|
|
4
|
+
|
|
2
5
|
def self.call_id_name_and_arguments(tool_call)
|
|
3
6
|
tool_call_id = tool_call.dig("call_id") || tool_call.dig("id")
|
|
4
7
|
if tool_call['function']
|
|
@@ -15,12 +18,21 @@ module LLM
|
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
def self.process_calls(tools, calls, &block)
|
|
21
|
+
max_content_length = LLM.max_content_length
|
|
18
22
|
IndiferentHash.setup tools
|
|
19
23
|
calls.collect do |tool_call|
|
|
20
24
|
tool_call_id, function_name, function_arguments = call_id_name_and_arguments(tool_call)
|
|
21
25
|
|
|
26
|
+
function_arguments = IndiferentHash.setup function_arguments
|
|
27
|
+
|
|
22
28
|
obj, definition = tools[function_name]
|
|
23
29
|
|
|
30
|
+
definition = obj if Hash === obj
|
|
31
|
+
|
|
32
|
+
defaults = definition[:parameters][:defaults] if definition && definition[:parameters]
|
|
33
|
+
function_arguments = function_arguments.merge(defaults) if defaults
|
|
34
|
+
|
|
35
|
+
Log.high "Calling #{function_name} (#{Log.fingerprint function_arguments}): "
|
|
24
36
|
function_response = case obj
|
|
25
37
|
when Proc
|
|
26
38
|
obj.call function_name, function_arguments
|
|
@@ -32,7 +44,7 @@ module LLM
|
|
|
32
44
|
if block_given?
|
|
33
45
|
block.call function_name, function_arguments
|
|
34
46
|
else
|
|
35
|
-
|
|
47
|
+
ParameterException.new "Tool or function not found '#{function_name}'. Called with parameters #{Log.fingerprint function_arguments}" if obj.nil? && definition.nil?
|
|
36
48
|
end
|
|
37
49
|
end
|
|
38
50
|
|
|
@@ -46,8 +58,17 @@ module LLM
|
|
|
46
58
|
else
|
|
47
59
|
function_response.to_json
|
|
48
60
|
end
|
|
61
|
+
|
|
49
62
|
content = content.to_s if Numeric === content
|
|
50
63
|
|
|
64
|
+
Log.high "Called #{function_name}: " + Log.fingerprint(content)
|
|
65
|
+
|
|
66
|
+
if content.length > max_content_length
|
|
67
|
+
exception_msg = "Function #{function_name} called with parameters #{Log.fingerprint function_arguments} returned #{content.length} characters, which is more than the maximum set of #{max_content_length}."
|
|
68
|
+
Log.high exception_msg
|
|
69
|
+
content = {exception: exception_msg, stack: caller}.to_json
|
|
70
|
+
end
|
|
71
|
+
|
|
51
72
|
response_message = {
|
|
52
73
|
id: tool_call_id,
|
|
53
74
|
role: "tool",
|
|
@@ -68,9 +68,9 @@ Returns a list in the format source~target.
|
|
|
68
68
|
description: "Associations in the form of source~target or target~source"
|
|
69
69
|
},
|
|
70
70
|
fields: {
|
|
71
|
-
type: "
|
|
72
|
-
|
|
73
|
-
description: "Limit the response to these
|
|
71
|
+
type: "array",
|
|
72
|
+
items: { type: :string },
|
|
73
|
+
description: "Limit the response to these fields"
|
|
74
74
|
},
|
|
75
75
|
}
|
|
76
76
|
else
|
|
@@ -93,22 +93,22 @@ Multiple values may be present and use the charater ';' to separate them.
|
|
|
93
93
|
else
|
|
94
94
|
properties.delete(:fields)
|
|
95
95
|
description = <<-EOF
|
|
96
|
-
Return the #{
|
|
96
|
+
Return the #{fields.first} of association.
|
|
97
97
|
Multiple values may be present and use the charater ';' to separate them.
|
|
98
98
|
EOF
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
function = {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
name: database.to_s + '_association_details',
|
|
103
|
+
description: description,
|
|
104
|
+
parameters: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: properties,
|
|
107
|
+
required: ['associations']
|
|
108
|
+
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
IndiferentHash.setup function
|
|
111
|
+
IndiferentHash.setup function
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
|
|
@@ -119,11 +119,12 @@ Multiple values may be present and use the charater ';' to separate them.
|
|
|
119
119
|
database_description = knowledge_base.description(database)
|
|
120
120
|
undirected = knowledge_base.undirected(database)
|
|
121
121
|
definition = self.database_tool_definition(database, undirected, database_description)
|
|
122
|
-
tool_definitions.merge(database => [knowledge_base, definition])
|
|
122
|
+
tool_definitions.merge!(database => [knowledge_base, definition])
|
|
123
123
|
if (fields = knowledge_base.get_database(database).fields).any?
|
|
124
124
|
details_definition = self.database_details_tool_definition(database, undirected, fields)
|
|
125
|
-
tool_definitions.merge(database + '_association_details' => [knowledge_base, details_definition])
|
|
125
|
+
tool_definitions.merge!(database.to_s + '_association_details' => [knowledge_base, details_definition])
|
|
126
126
|
end
|
|
127
|
+
tool_definitions
|
|
127
128
|
}
|
|
128
129
|
end
|
|
129
130
|
|
data/lib/scout/llm/tools/mcp.rb
CHANGED
|
@@ -3,6 +3,10 @@ require 'mcp_client'
|
|
|
3
3
|
|
|
4
4
|
module LLM
|
|
5
5
|
def self.mcp_tools(url, options = {})
|
|
6
|
+
timeout = Scout::Config.get :timeout, :mcp, :tools
|
|
7
|
+
|
|
8
|
+
options = IndiferentHash.add_defaults options, read_timeout: timeout.to_i if timeout && timeout != ""
|
|
9
|
+
|
|
6
10
|
if url == 'stdio'
|
|
7
11
|
client = MCPClient.create_client(mcp_server_configs: [options.merge(type: 'stdio')])
|
|
8
12
|
else
|
|
@@ -2,11 +2,25 @@ require 'scout/workflow'
|
|
|
2
2
|
module LLM
|
|
3
3
|
def self.task_tool_definition(workflow, task_name, inputs = nil)
|
|
4
4
|
task_info = workflow.task_info(task_name)
|
|
5
|
+
return nil if task_info.nil?
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
if inputs
|
|
8
|
+
names = []
|
|
9
|
+
defaults = {}
|
|
10
|
+
|
|
11
|
+
inputs.each do |i|
|
|
12
|
+
if String === i && i.include?('=')
|
|
13
|
+
name,_ , value = i.partition("=")
|
|
14
|
+
defaults[name] = value
|
|
15
|
+
else
|
|
16
|
+
names << i.to_sym
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
7
21
|
|
|
8
22
|
properties = task_info[:inputs].inject({}) do |acc,input|
|
|
9
|
-
next acc if
|
|
23
|
+
next acc if names and not names.include?(input)
|
|
10
24
|
type = task_info[:input_types][input]
|
|
11
25
|
description = task_info[:input_descriptions][input]
|
|
12
26
|
|
|
@@ -14,12 +28,17 @@ module LLM
|
|
|
14
28
|
type = :string if type == :select
|
|
15
29
|
type = :string if type == :path
|
|
16
30
|
type = :number if type == :float
|
|
31
|
+
type = :array if type.to_s.end_with?('_array')
|
|
17
32
|
|
|
18
33
|
acc[input] = {
|
|
19
34
|
"type": type,
|
|
20
35
|
"description": description
|
|
21
36
|
}
|
|
22
37
|
|
|
38
|
+
if type == :array
|
|
39
|
+
acc[input]['items'] = {type: :string}
|
|
40
|
+
end
|
|
41
|
+
|
|
23
42
|
if input_options = task_info[:input_options][input]
|
|
24
43
|
if select_options = input_options[:select_options]
|
|
25
44
|
select_options = select_options.values if Hash === select_options
|
|
@@ -31,7 +50,7 @@ module LLM
|
|
|
31
50
|
end
|
|
32
51
|
|
|
33
52
|
required_inputs = task_info[:inputs].select do |input|
|
|
34
|
-
next if
|
|
53
|
+
next if names and not names.include?(input.to_sym)
|
|
35
54
|
task_info[:input_options].include?(input) && task_info[:input_options][input][:required]
|
|
36
55
|
end
|
|
37
56
|
|
|
@@ -41,29 +60,49 @@ module LLM
|
|
|
41
60
|
parameters: {
|
|
42
61
|
type: "object",
|
|
43
62
|
properties: properties,
|
|
44
|
-
required: required_inputs
|
|
63
|
+
required: required_inputs,
|
|
45
64
|
}
|
|
46
65
|
}
|
|
47
66
|
|
|
48
|
-
|
|
67
|
+
function[:parameters][:defaults] = defaults if defaults
|
|
68
|
+
|
|
69
|
+
#IndiferentHash.setup function.merge(type: 'function', function: function)
|
|
70
|
+
IndiferentHash.setup function
|
|
49
71
|
end
|
|
50
72
|
|
|
51
73
|
def self.workflow_tools(workflow, tasks = nil)
|
|
52
|
-
|
|
53
|
-
|
|
74
|
+
if Array === workflow
|
|
75
|
+
workflow.inject({}){|tool_definitions,wf| tool_definitions.merge(workflow_tools(wf, tasks)) }
|
|
54
76
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
77
|
+
else
|
|
78
|
+
tasks = workflow.all_exports if tasks.nil?
|
|
79
|
+
tasks = workflow.all_tasks if tasks.empty?
|
|
80
|
+
|
|
81
|
+
tasks.inject({}){|tool_definitions,task_name|
|
|
82
|
+
definition = self.task_tool_definition(workflow, task_name)
|
|
83
|
+
next if definition.nil?
|
|
84
|
+
tool_definitions.merge(task_name => [workflow, definition])
|
|
85
|
+
}
|
|
86
|
+
end
|
|
59
87
|
end
|
|
60
88
|
|
|
61
89
|
def self.call_workflow(workflow, task_name, parameters={})
|
|
62
90
|
jobname = parameters.delete :jobname
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
workflow.
|
|
91
|
+
begin
|
|
92
|
+
exec_type = parameters[:exec_type]
|
|
93
|
+
job = workflow.job(task_name, jobname, parameters)
|
|
94
|
+
if workflow.exec_exports.include?(task_name.to_sym) || parameters[:exec_type].to_s == 'exec'
|
|
95
|
+
job.exec
|
|
96
|
+
else
|
|
97
|
+
raise ScoutException, 'Potential recursive call' if parameters[:allow_recursive] != 'true' &&
|
|
98
|
+
(job.running? and job.info[:pid] == Process.pid)
|
|
99
|
+
|
|
100
|
+
Workflow.produce(job)
|
|
101
|
+
job.join
|
|
102
|
+
job.load
|
|
103
|
+
end
|
|
104
|
+
rescue ScoutException
|
|
105
|
+
return $!
|
|
67
106
|
end
|
|
68
107
|
end
|
|
69
108
|
end
|
data/lib/scout/llm/tools.rb
CHANGED
|
@@ -137,4 +137,46 @@ module LLM
|
|
|
137
137
|
end
|
|
138
138
|
end.flatten
|
|
139
139
|
end
|
|
140
|
+
|
|
141
|
+
def self.tool_definitions_to_reponses(tools)
|
|
142
|
+
tools.values.collect do |obj,definition|
|
|
143
|
+
definition = obj if Hash === obj
|
|
144
|
+
definition
|
|
145
|
+
|
|
146
|
+
definition = case definition[:function]
|
|
147
|
+
when Hash
|
|
148
|
+
definition.merge(definition.delete :function)
|
|
149
|
+
else
|
|
150
|
+
definition
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
definition = IndiferentHash.add_defaults definition, type: :function
|
|
154
|
+
|
|
155
|
+
definition[:parameters].delete :defaults if definition[:parameters]
|
|
156
|
+
|
|
157
|
+
definition
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class << self
|
|
162
|
+
alias tool_definitions_to_openai tool_definitions_to_reponses
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def self.tool_definitions_to_ollama(tools)
|
|
166
|
+
tools.values.collect do |obj,definition|
|
|
167
|
+
definition = obj if Hash === obj
|
|
168
|
+
definition = IndiferentHash.setup definition
|
|
169
|
+
|
|
170
|
+
definition = case definition[:function]
|
|
171
|
+
when Hash
|
|
172
|
+
definition
|
|
173
|
+
else
|
|
174
|
+
{type: :function, function: definition}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
definition = IndiferentHash.add_defaults definition, type: :function
|
|
178
|
+
|
|
179
|
+
definition
|
|
180
|
+
end
|
|
181
|
+
end
|
|
140
182
|
end
|
data/lib/scout/llm/utils.rb
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
1
|
module LLM
|
|
2
|
-
|
|
3
|
-
def self.tag(tag, content, name = nil)
|
|
4
|
-
if name
|
|
5
|
-
<<-EOF.strip
|
|
6
|
-
<#{tag} name="#{name}">
|
|
7
|
-
#{content}
|
|
8
|
-
</#{tag}>
|
|
9
|
-
EOF
|
|
10
|
-
else
|
|
11
|
-
<<-EOF.strip
|
|
12
|
-
<#{tag}>
|
|
13
|
-
#{content}
|
|
14
|
-
</#{tag}>
|
|
15
|
-
EOF
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
2
|
def self.get_url_server_tokens(url, prefix=nil)
|
|
20
3
|
return get_url_server_tokens(url).collect{|e| prefix.to_s + "." + e } if prefix
|
|
21
4
|
|
|
@@ -39,4 +22,6 @@ module LLM
|
|
|
39
22
|
end
|
|
40
23
|
Scout::Config.get(key, *all_tokens, hash)
|
|
41
24
|
end
|
|
25
|
+
|
|
26
|
+
|
|
42
27
|
end
|
data/scout-ai.gemspec
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
|
5
|
-
# stub: scout-ai 1.
|
|
5
|
+
# stub: scout-ai 1.1.1 ruby lib
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |s|
|
|
8
8
|
s.name = "scout-ai".freeze
|
|
9
|
-
s.version = "1.
|
|
9
|
+
s.version = "1.1.1".freeze
|
|
10
10
|
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
12
12
|
s.require_paths = ["lib".freeze]
|
|
@@ -49,9 +49,15 @@ Gem::Specification.new do |s|
|
|
|
49
49
|
"lib/scout/llm/backends/relay.rb",
|
|
50
50
|
"lib/scout/llm/backends/responses.rb",
|
|
51
51
|
"lib/scout/llm/chat.rb",
|
|
52
|
+
"lib/scout/llm/chat/annotation.rb",
|
|
53
|
+
"lib/scout/llm/chat/parse.rb",
|
|
54
|
+
"lib/scout/llm/chat/process.rb",
|
|
55
|
+
"lib/scout/llm/chat/process/clear.rb",
|
|
56
|
+
"lib/scout/llm/chat/process/files.rb",
|
|
57
|
+
"lib/scout/llm/chat/process/options.rb",
|
|
58
|
+
"lib/scout/llm/chat/process/tools.rb",
|
|
52
59
|
"lib/scout/llm/embed.rb",
|
|
53
60
|
"lib/scout/llm/mcp.rb",
|
|
54
|
-
"lib/scout/llm/parse.rb",
|
|
55
61
|
"lib/scout/llm/rag.rb",
|
|
56
62
|
"lib/scout/llm/tools.rb",
|
|
57
63
|
"lib/scout/llm/tools/call.rb",
|
|
@@ -105,6 +111,8 @@ Gem::Specification.new do |s|
|
|
|
105
111
|
"test/scout/llm/backends/test_openwebui.rb",
|
|
106
112
|
"test/scout/llm/backends/test_relay.rb",
|
|
107
113
|
"test/scout/llm/backends/test_responses.rb",
|
|
114
|
+
"test/scout/llm/chat/test_parse.rb",
|
|
115
|
+
"test/scout/llm/chat/test_process.rb",
|
|
108
116
|
"test/scout/llm/test_agent.rb",
|
|
109
117
|
"test/scout/llm/test_ask.rb",
|
|
110
118
|
"test/scout/llm/test_chat.rb",
|
|
@@ -131,13 +139,14 @@ Gem::Specification.new do |s|
|
|
|
131
139
|
]
|
|
132
140
|
s.homepage = "http://github.com/mikisvaz/scout-ai".freeze
|
|
133
141
|
s.licenses = ["MIT".freeze]
|
|
134
|
-
s.rubygems_version = "3.7.
|
|
142
|
+
s.rubygems_version = "3.7.2".freeze
|
|
135
143
|
s.summary = "AI gear for scouts".freeze
|
|
136
144
|
|
|
137
145
|
s.specification_version = 4
|
|
138
146
|
|
|
139
147
|
s.add_runtime_dependency(%q<scout-rig>.freeze, [">= 0".freeze])
|
|
140
148
|
s.add_runtime_dependency(%q<ruby-openai>.freeze, [">= 0".freeze])
|
|
149
|
+
s.add_runtime_dependency(%q<ollama-ai>.freeze, [">= 0".freeze])
|
|
141
150
|
s.add_runtime_dependency(%q<ruby-mcp-client>.freeze, [">= 0".freeze])
|
|
142
151
|
end
|
|
143
152
|
|
data/scout_commands/agent/ask
CHANGED
|
@@ -41,13 +41,12 @@ agent_name, *question_parts = ARGV
|
|
|
41
41
|
|
|
42
42
|
question = question_parts * " "
|
|
43
43
|
|
|
44
|
-
file, chat, inline, template, dry_run, imports = IndiferentHash.process_options options, :file, :chat, :inline, :template, :dry_run, :imports
|
|
44
|
+
file, chat, inline, template, dry_run, imports, endpoint, model = IndiferentHash.process_options options, :file, :chat, :inline, :template, :dry_run, :imports, :endpoint, :model
|
|
45
45
|
|
|
46
46
|
file = Path.setup(file) if file
|
|
47
47
|
|
|
48
48
|
imports = imports.split(/,\s*/) if imports
|
|
49
49
|
|
|
50
|
-
|
|
51
50
|
agent_name ||= 'default'
|
|
52
51
|
|
|
53
52
|
agent_file = Scout.workflows[agent_name]
|
|
@@ -71,6 +70,9 @@ else
|
|
|
71
70
|
#raise ParameterException agent_file
|
|
72
71
|
end
|
|
73
72
|
|
|
73
|
+
agent.other_options[:endpoint] = endpoint if endpoint
|
|
74
|
+
agent.other_options[:model] = model if model
|
|
75
|
+
|
|
74
76
|
if template
|
|
75
77
|
if Open.exists?(template)
|
|
76
78
|
template_question = Open.read(template)
|
|
@@ -98,24 +100,43 @@ elsif file
|
|
|
98
100
|
end
|
|
99
101
|
|
|
100
102
|
if chat
|
|
101
|
-
conversation = Open.exist?(chat)? LLM.chat(chat) : []
|
|
103
|
+
#conversation = Open.exist?(chat)? LLM.chat(chat) : []
|
|
104
|
+
#convo_options = LLM.options conversation
|
|
105
|
+
#conversation = question.empty? ? conversation : conversation + LLM.chat(question)
|
|
106
|
+
|
|
107
|
+
#if dry_run
|
|
108
|
+
# ppp LLM.print conversation
|
|
109
|
+
# exit 0
|
|
110
|
+
#end
|
|
111
|
+
|
|
112
|
+
#new = agent.ask(conversation, convo_options.merge(options.merge(return_messages: true)))
|
|
113
|
+
#conversation = Open.read(chat) + LLM.print(new)
|
|
114
|
+
#Open.write(chat, conversation)
|
|
115
|
+
|
|
116
|
+
conversation = Open.exist?(chat)? LLM.chat(chat) : []
|
|
102
117
|
convo_options = LLM.options conversation
|
|
103
|
-
|
|
118
|
+
agent.start(Chat.setup(conversation)) if conversation.any?
|
|
119
|
+
agent.current_chat.concat LLM.chat(question) if question && ! question.empty?
|
|
120
|
+
imports.each{|import| agent.import import } if imports
|
|
104
121
|
|
|
105
122
|
if dry_run
|
|
106
123
|
ppp LLM.print conversation
|
|
107
124
|
exit 0
|
|
108
125
|
end
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
126
|
+
|
|
127
|
+
new = agent.ask(agent.current_chat, convo_options.merge(options.merge(return_messages: true)))
|
|
128
|
+
conversation += LLM.chat(question) if question
|
|
129
|
+
conversation += new
|
|
130
|
+
|
|
131
|
+
Open.open(chat, mode: 'a'){|f| f.puts LLM.print(new) }
|
|
132
|
+
puts LLM.purge(new).last[:content]
|
|
112
133
|
elsif inline
|
|
113
134
|
|
|
114
135
|
file = Open.read inline
|
|
115
136
|
|
|
116
137
|
new_file = ""
|
|
117
138
|
while true
|
|
118
|
-
pre, question, post =
|
|
139
|
+
pre, question, post =
|
|
119
140
|
file.partition(/^\s*#\s*ask:(?:.*?)(?=^\s*[^\s#])/smu)
|
|
120
141
|
|
|
121
142
|
break if post.empty?
|
|
@@ -124,7 +145,7 @@ elsif inline
|
|
|
124
145
|
new_file << question
|
|
125
146
|
clean_question = question.gsub('#', '').gsub(/\s+/,' ').sub(/.*ask:\s*/,'').strip
|
|
126
147
|
chat = [
|
|
127
|
-
{role: :system, content: "Write a succint reply with no commentary and no formatting."},
|
|
148
|
+
{role: :system, content: "Write a succint reply with no commentary and no formatting."},
|
|
128
149
|
{role: :user, content: "Find the following question as a comment in the file give a response to be placed inline: #{question}"},
|
|
129
150
|
LLM.tag('file', file, inline)
|
|
130
151
|
]
|
|
@@ -139,7 +160,10 @@ elsif inline
|
|
|
139
160
|
new_file << file
|
|
140
161
|
Open.write(inline, new_file)
|
|
141
162
|
else
|
|
142
|
-
conversation = Chat.setup(LLM.chat question)
|
|
143
|
-
imports.each{|import| conversation.import import } if imports
|
|
144
|
-
puts agent.ask(conversation, nil, options)
|
|
163
|
+
#conversation = Chat.setup(LLM.chat question)
|
|
164
|
+
#imports.each{|import| conversation.import import } if imports
|
|
165
|
+
#puts agent.ask(conversation, nil, options)
|
|
166
|
+
agent.current_chat.concat LLM.chat(question)
|
|
167
|
+
imports.each{|import| agent.import import } if imports
|
|
168
|
+
puts agent.chat
|
|
145
169
|
end
|
data/scout_commands/llm/ask
CHANGED
|
@@ -21,7 +21,8 @@ characters '???', if they are present.
|
|
|
21
21
|
-h--help Print this help
|
|
22
22
|
-t--template* Use a template
|
|
23
23
|
-c--chat* Follow a conversation
|
|
24
|
-
-i--
|
|
24
|
+
-i--imports* Chat files to import, separated by comma
|
|
25
|
+
-in--inline* Ask inline questions about a file
|
|
25
26
|
-f--file* Incorporate file at the start
|
|
26
27
|
-m--model* Model to use
|
|
27
28
|
-e--endpoint* Endpoint to use
|
|
@@ -39,7 +40,9 @@ end
|
|
|
39
40
|
|
|
40
41
|
Log.severity = options.delete(:log).to_i if options.include? :log
|
|
41
42
|
|
|
42
|
-
file, chat, inline, template, dry_run = IndiferentHash.process_options options, :file, :chat, :inline, :template, :dry_run
|
|
43
|
+
file, chat, inline, imports, template, dry_run = IndiferentHash.process_options options, :file, :chat, :inline, :imports, :template, :dry_run
|
|
44
|
+
|
|
45
|
+
imports = imports.split(/,\s*/) if imports
|
|
43
46
|
|
|
44
47
|
question = ARGV * " "
|
|
45
48
|
|
|
@@ -70,7 +73,8 @@ elsif file
|
|
|
70
73
|
end
|
|
71
74
|
|
|
72
75
|
if chat
|
|
73
|
-
conversation = Open.exist?(chat)? LLM.chat(chat) : []
|
|
76
|
+
conversation = Open.exist?(chat)? LLM.chat(chat) : Chat.setup([])
|
|
77
|
+
imports.each{|import| conversation.import import } if imports
|
|
74
78
|
convo_options = LLM.options conversation
|
|
75
79
|
conversation = question.empty? ? conversation : conversation + LLM.chat(question)
|
|
76
80
|
|
|
@@ -78,16 +82,17 @@ if chat
|
|
|
78
82
|
ppp LLM.print conversation
|
|
79
83
|
exit 0
|
|
80
84
|
end
|
|
85
|
+
|
|
81
86
|
new = LLM.ask(conversation, convo_options.merge(options.merge(return_messages: true)))
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
Open.open(chat, mode: 'a'){|f| f.puts LLM.print(new) }
|
|
88
|
+
puts LLM.purge(new).last[:content]
|
|
84
89
|
elsif inline
|
|
85
90
|
|
|
86
91
|
file = Open.read inline
|
|
87
92
|
|
|
88
93
|
new_file = ""
|
|
89
94
|
while true
|
|
90
|
-
pre, question, post =
|
|
95
|
+
pre, question, post =
|
|
91
96
|
file.partition(/^\s*#\s*ask:(?:.*?)(?=^\s*[^\s#]|\z)/smu)
|
|
92
97
|
|
|
93
98
|
break if question.empty?
|
|
@@ -96,10 +101,15 @@ elsif inline
|
|
|
96
101
|
new_file << question
|
|
97
102
|
clean_question = question.gsub('#', '').gsub(/\s+/,' ').sub(/.*ask:\s*/,'').strip
|
|
98
103
|
chat = [
|
|
99
|
-
{role: :system, content: "Write a succint reply with no commentary and no formatting."},
|
|
104
|
+
{role: :system, content: "Write a succint reply with no commentary and no formatting."},
|
|
100
105
|
{role: :user, content: "Find the following question as a comment in the file give a response to be placed inline: #{question}"},
|
|
101
106
|
LLM.tag('file', file, inline)
|
|
102
107
|
]
|
|
108
|
+
|
|
109
|
+
chat = Chat.chat(chat)
|
|
110
|
+
|
|
111
|
+
imports.each{|import| chat.import import } if imports
|
|
112
|
+
|
|
103
113
|
response = LLM.ask(LLM.chat(chat))
|
|
104
114
|
new_file << <<-EOF
|
|
105
115
|
# Response start
|
data/scout_commands/llm/process
CHANGED
|
@@ -35,10 +35,10 @@ while true
|
|
|
35
35
|
files = directory.glob('*.json')
|
|
36
36
|
|
|
37
37
|
files.each do |file|
|
|
38
|
+
id = File.basename(file, '.json')
|
|
38
39
|
target = directory.reply[id + '.json']
|
|
39
40
|
|
|
40
41
|
if ! File.exist?(target)
|
|
41
|
-
id = File.basename(file, '.json')
|
|
42
42
|
options = IndiferentHash.setup(JSON.parse(Open.read(file)))
|
|
43
43
|
question = options.delete(:question)
|
|
44
44
|
reply = LLM.ask(question, options)
|
|
@@ -2,7 +2,7 @@ require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
|
|
|
2
2
|
require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
|
|
3
3
|
|
|
4
4
|
class TestLLMAnthropic < Test::Unit::TestCase
|
|
5
|
-
def
|
|
5
|
+
def test_say_hi
|
|
6
6
|
prompt =<<-EOF
|
|
7
7
|
user: say hi
|
|
8
8
|
EOF
|
|
@@ -121,7 +121,7 @@ What is the weather in London. Should I take my umbrella?
|
|
|
121
121
|
ppp respose
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
-
def
|
|
124
|
+
def _test_json_output
|
|
125
125
|
prompt =<<-EOF
|
|
126
126
|
user:
|
|
127
127
|
|
|
@@ -82,7 +82,7 @@ What is the weather in London. Should I take an umbrella?
|
|
|
82
82
|
]
|
|
83
83
|
|
|
84
84
|
sss 0
|
|
85
|
-
respose = LLM::OLlama.ask prompt, model: '
|
|
85
|
+
respose = LLM::OLlama.ask prompt, model: 'gpt-oss', url: 'http://localhost:3330', tool_choice: 'required', tools: tools do |name,arguments|
|
|
86
86
|
"It's raining cats and dogs"
|
|
87
87
|
end
|
|
88
88
|
|
|
@@ -20,7 +20,7 @@ Some text
|
|
|
20
20
|
assert(Float === emb.first)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def
|
|
23
|
+
def _test_tool_call_output_weather
|
|
24
24
|
Log.severity = 0
|
|
25
25
|
prompt =<<-EOF
|
|
26
26
|
function_call:
|
|
@@ -38,7 +38,7 @@ should i take an umbrella?
|
|
|
38
38
|
ppp LLM::Responses.ask prompt, model: 'gpt-4.1-nano'
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def
|
|
41
|
+
def _test_tool
|
|
42
42
|
prompt =<<-EOF
|
|
43
43
|
user:
|
|
44
44
|
What is the weather in London. Should I take my umbrella?
|
|
@@ -75,7 +75,7 @@ What is the weather in London. Should I take my umbrella?
|
|
|
75
75
|
ppp respose
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
def
|
|
78
|
+
def _test_news
|
|
79
79
|
prompt =<<-EOF
|
|
80
80
|
websearch: true
|
|
81
81
|
|
|
@@ -86,7 +86,7 @@ What was the top new in the US today?
|
|
|
86
86
|
ppp LLM::Responses.ask prompt
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
def
|
|
89
|
+
def _test_image
|
|
90
90
|
prompt =<<-EOF
|
|
91
91
|
image: #{datafile_test 'cat.jpg'}
|
|
92
92
|
|
|
@@ -98,7 +98,7 @@ What animal is represented in the image?
|
|
|
98
98
|
ppp LLM::Responses.ask prompt
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
def
|
|
101
|
+
def _test_json_output
|
|
102
102
|
prompt =<<-EOF
|
|
103
103
|
system:
|
|
104
104
|
|
|
@@ -112,7 +112,7 @@ What other movies have the protagonists of the original gost busters played on,
|
|
|
112
112
|
ppp LLM::Responses.ask prompt, format: :json
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
-
def
|
|
115
|
+
def _test_json_format
|
|
116
116
|
prompt =<<-EOF
|
|
117
117
|
user:
|
|
118
118
|
|
|
@@ -130,7 +130,7 @@ Name each actor and the top movie they took part of
|
|
|
130
130
|
ppp LLM::Responses.ask prompt, format: format
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
-
def
|
|
133
|
+
def _test_json_format_list
|
|
134
134
|
prompt =<<-EOF
|
|
135
135
|
user:
|
|
136
136
|
|
|
@@ -148,7 +148,7 @@ Name each actor as keys and the top 3 movies they took part of as values
|
|
|
148
148
|
ppp LLM::Responses.ask prompt, format: format
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
-
def
|
|
151
|
+
def _test_json_format_actor_list
|
|
152
152
|
prompt =<<-EOF
|
|
153
153
|
user:
|
|
154
154
|
|
|
@@ -199,7 +199,7 @@ Name each actor as keys and the top 3 movies they took part of as values
|
|
|
199
199
|
ppp LLM::Responses.ask prompt, format: schema
|
|
200
200
|
end
|
|
201
201
|
|
|
202
|
-
def
|
|
202
|
+
def _test_tool_gpt5
|
|
203
203
|
prompt =<<-EOF
|
|
204
204
|
user:
|
|
205
205
|
What is the weather in London. Should I take my umbrella?
|