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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 57e9757e8effe1d42977eb5b9c5b5f3ee9d3e4603edb845682a35b4f041f3185
|
|
4
|
+
data.tar.gz: 9613eabf0c2cda6a10fa286bffe437151e8619713808fb23a030fabbf1c16fa3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3dd57a37318b2392655a318d6c947e8e8215bde51c46c516b3378c8e72c7475c00cd0b462a59a36ffe456fbe3099fc5b065ad8ce3929fdf186ed49fad0e9f3bf
|
|
7
|
+
data.tar.gz: 2b9e7be0db65387f18d9ce5340ec81789e852e26b67be076c10d0e6f2def94b7facf34cf64e41089bd911cb8cb25c440ed778d7f30f38ab38fa6da78dc21ac1c
|
data/.vimproject
CHANGED
|
@@ -6,6 +6,14 @@ scout-ai=$PWD filter="*.rb *.rake Rakefile *.rdoc *.R *.sh *.js *.haml *.sass *.
|
|
|
6
6
|
scout-ai
|
|
7
7
|
}
|
|
8
8
|
chats=chats filter="*"{
|
|
9
|
+
parse
|
|
10
|
+
|
|
11
|
+
test_tool
|
|
12
|
+
|
|
13
|
+
ask_agent
|
|
14
|
+
|
|
15
|
+
test_ollama_tool
|
|
16
|
+
|
|
9
17
|
test_github
|
|
10
18
|
|
|
11
19
|
test_stdio
|
|
@@ -91,7 +99,6 @@ scout-ai=$PWD filter="*.rb *.rake Rakefile *.rdoc *.R *.sh *.js *.haml *.sass *.
|
|
|
91
99
|
scout=scout{
|
|
92
100
|
llm=llm{
|
|
93
101
|
utils.rb
|
|
94
|
-
parse.rb
|
|
95
102
|
tools.rb
|
|
96
103
|
tools=tools{
|
|
97
104
|
mcp.rb
|
|
@@ -100,11 +107,22 @@ scout-ai=$PWD filter="*.rb *.rake Rakefile *.rdoc *.R *.sh *.js *.haml *.sass *.
|
|
|
100
107
|
call.rb
|
|
101
108
|
}
|
|
102
109
|
chat.rb
|
|
110
|
+
chat=chat{
|
|
111
|
+
annotation.rb
|
|
112
|
+
parse.rb
|
|
113
|
+
process.rb
|
|
114
|
+
process=process{
|
|
115
|
+
tools.rb
|
|
116
|
+
files.rb
|
|
117
|
+
clear.rb
|
|
118
|
+
options.rb
|
|
119
|
+
}
|
|
120
|
+
}
|
|
103
121
|
|
|
104
122
|
backends=backends{
|
|
105
123
|
openai.rb
|
|
106
|
-
anthropic.rb
|
|
107
124
|
responses.rb
|
|
125
|
+
anthropic.rb
|
|
108
126
|
ollama.rb
|
|
109
127
|
bedrock.rb
|
|
110
128
|
openwebui.rb
|
data/Rakefile
CHANGED
|
@@ -18,6 +18,7 @@ Juwelier::Tasks.new do |gem|
|
|
|
18
18
|
# dependencies defined in Gemfile
|
|
19
19
|
gem.add_runtime_dependency 'scout-rig', '>= 0'
|
|
20
20
|
gem.add_runtime_dependency 'ruby-openai', '>= 0'
|
|
21
|
+
gem.add_runtime_dependency 'ollama-ai', '>= 0'
|
|
21
22
|
gem.add_runtime_dependency 'ruby-mcp-client', '>= 0'
|
|
22
23
|
end
|
|
23
24
|
Juwelier::RubygemsDotOrgTasks.new
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.1.1
|
data/bin/scout-ai
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
$LOAD_PATH.unshift File.join(__dir__, '../lib')
|
|
4
4
|
|
|
5
|
+
if _i = ARGV.index("--log")
|
|
6
|
+
require 'scout/log'
|
|
7
|
+
log = ARGV[_i+1]
|
|
8
|
+
Log.severity = log.to_i
|
|
9
|
+
ARGV.delete "--log"
|
|
10
|
+
ARGV.delete log
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
dev_dir = nil
|
|
14
|
+
if _i = ARGV.index("--dev")
|
|
15
|
+
dev_dir = ARGV[_i+1]
|
|
16
|
+
ARGV.delete "--dev"
|
|
17
|
+
ARGV.delete dev_dir
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if dev_dir.nil?
|
|
21
|
+
_s = nil
|
|
22
|
+
ARGV.each_with_index do |s,i|
|
|
23
|
+
if s.match(/^--dev(?:=(.*))?/)
|
|
24
|
+
dev_dir = $1
|
|
25
|
+
_s = s
|
|
26
|
+
next
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
ARGV.delete _s if _s
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if dev_dir.nil? && ENV["SCOUT_DEV"]
|
|
33
|
+
dev_dir = ENV["SCOUT_DEV"]
|
|
34
|
+
ARGV.delete "--dev"
|
|
35
|
+
ARGV.delete dev_dir
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if dev_dir
|
|
39
|
+
['scout-*/lib'].each do |pattern|
|
|
40
|
+
Dir.glob(File.join(File.expand_path(dev_dir), pattern)).each do |f|
|
|
41
|
+
$LOAD_PATH.unshift f
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
['rbbt-*/lib'].each do |pattern|
|
|
45
|
+
Dir.glob(File.join(File.expand_path(dev_dir), pattern)).each do |f|
|
|
46
|
+
$LOAD_PATH.unshift f
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
5
51
|
require 'scout-ai'
|
|
6
52
|
|
|
7
53
|
load Scout.bin.scout.find
|
data/lib/scout/llm/agent/chat.rb
CHANGED
|
@@ -9,6 +9,8 @@ module LLM
|
|
|
9
9
|
(@current_chat || start_chat).annotate chat unless Chat === chat
|
|
10
10
|
@current_chat = chat
|
|
11
11
|
else
|
|
12
|
+
start_chat = self.start_chat
|
|
13
|
+
Chat.setup(start_chat) unless Chat === start_chat
|
|
12
14
|
@current_chat = start_chat.branch
|
|
13
15
|
end
|
|
14
16
|
end
|
|
@@ -25,14 +27,9 @@ module LLM
|
|
|
25
27
|
self.ask(current_chat, ...)
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
def chat(model = nil, options = {})
|
|
29
|
-
new = self.ask(current_chat, model, options.merge(return_messages: true))
|
|
30
|
-
current_chat.concat(new)
|
|
31
|
-
new.last['content']
|
|
32
|
-
end
|
|
33
30
|
|
|
34
|
-
def chat(
|
|
35
|
-
response = ask(current_chat,
|
|
31
|
+
def chat(options = {})
|
|
32
|
+
response = ask(current_chat, options.merge(return_messages: true))
|
|
36
33
|
if Array === response
|
|
37
34
|
current_chat.concat(response)
|
|
38
35
|
current_chat.answer
|
|
@@ -7,6 +7,13 @@ module LLM
|
|
|
7
7
|
|
|
8
8
|
block ||= Proc.new do |name, parameters|
|
|
9
9
|
message = parameters[:message]
|
|
10
|
+
new_conversation = parameters[:new_conversation]
|
|
11
|
+
Log.medium "Delegated to #{agent}: " + Log.fingerprint(message)
|
|
12
|
+
if new_conversation
|
|
13
|
+
agent.start
|
|
14
|
+
else
|
|
15
|
+
agent.purge
|
|
16
|
+
end
|
|
10
17
|
agent.user message
|
|
11
18
|
agent.chat
|
|
12
19
|
end
|
|
@@ -15,6 +22,11 @@ module LLM
|
|
|
15
22
|
message: {
|
|
16
23
|
"type": :string,
|
|
17
24
|
"description": "Message to pass to the agent"
|
|
25
|
+
},
|
|
26
|
+
new_conversation: {
|
|
27
|
+
"type": :boolean,
|
|
28
|
+
"description": "Erase conversation history and start a new conversation with this message",
|
|
29
|
+
"default": false
|
|
18
30
|
}
|
|
19
31
|
}
|
|
20
32
|
|
data/lib/scout/llm/agent.rb
CHANGED
|
@@ -49,7 +49,7 @@ You have access to the following databases associating entities:
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# function: takes an array of messages and calls LLM.ask with them
|
|
52
|
-
def ask(messages,
|
|
52
|
+
def ask(messages, options = {})
|
|
53
53
|
messages = [messages] unless messages.is_a? Array
|
|
54
54
|
model ||= @model if model
|
|
55
55
|
|
|
@@ -87,7 +87,7 @@ You have access to the following databases associating entities:
|
|
|
87
87
|
|
|
88
88
|
workflow = Workflow.require_workflow workflow_path if workflow_path.exists?
|
|
89
89
|
knowledge_base = KnowledgeBase.new knowledge_base_path if knowledge_base_path.exists?
|
|
90
|
-
chat = LLM.chat
|
|
90
|
+
chat = Chat.setup LLM.chat(chat_path.find) if chat_path.exists?
|
|
91
91
|
|
|
92
92
|
LLM::Agent.new workflow: workflow, knowledge_base: knowledge_base, start_chat: chat
|
|
93
93
|
end
|
data/lib/scout/llm/ask.rb
CHANGED
|
@@ -34,14 +34,26 @@ module LLM
|
|
|
34
34
|
|
|
35
35
|
endpoint, persist = IndiferentHash.process_options options, :endpoint, :persist, persist: true
|
|
36
36
|
|
|
37
|
-
endpoint ||= Scout::Config.get :endpoint, :ask, :llm, env: 'ASK_ENDPOINT,LLM_ENDPOINT'
|
|
37
|
+
endpoint ||= Scout::Config.get :endpoint, :ask, :llm, env: 'ASK_ENDPOINT,LLM_ENDPOINT,ENDPOINT,LLM,ASK'
|
|
38
38
|
if endpoint && Scout.etc.AI[endpoint].exists?
|
|
39
39
|
options = IndiferentHash.add_defaults options, Scout.etc.AI[endpoint].yaml
|
|
40
40
|
elsif endpoint && endpoint != ""
|
|
41
41
|
raise "Endpoint not found #{endpoint}"
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
if options[:backend].to_s == 'responses'
|
|
45
|
+
messages = Chat.clear(messages, 'previous_response_id')
|
|
46
|
+
else
|
|
47
|
+
messages = Chat.clean(messages, 'previous_response_id')
|
|
48
|
+
options.delete :previous_response_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
Log.high Log.color :green, "Asking #{endpoint || 'client'}: #{options[:previous_response_id]}\n" + LLM.print(messages)
|
|
52
|
+
tools = options[:tools]
|
|
53
|
+
Log.high "Tools: #{Log.fingerprint tools.keys}}" if tools
|
|
54
|
+
Log.debug "#{Log.fingerprint tools}}" if tools
|
|
55
|
+
|
|
56
|
+
res = Persist.persist(endpoint, :json, prefix: "LLM ask", other: options.merge(messages: messages), persist: persist) do
|
|
45
57
|
backend = IndiferentHash.process_options options, :backend
|
|
46
58
|
backend ||= Scout::Config.get :backend, :ask, :llm, env: 'ASK_BACKEND,LLM_BACKEND', default: :openai
|
|
47
59
|
|
|
@@ -71,6 +83,10 @@ module LLM
|
|
|
71
83
|
raise "Unknown backend: #{backend}"
|
|
72
84
|
end
|
|
73
85
|
end
|
|
86
|
+
|
|
87
|
+
Log.high Log.color :blue, "Response:\n" + LLM.print(res)
|
|
88
|
+
|
|
89
|
+
res
|
|
74
90
|
end
|
|
75
91
|
|
|
76
92
|
def self.workflow_ask(workflow, question, options = {})
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
require 'ollama-ai'
|
|
2
|
-
require_relative '../parse'
|
|
3
|
-
require_relative '../tools'
|
|
4
|
-
require_relative '../utils'
|
|
5
2
|
require_relative '../chat'
|
|
6
3
|
|
|
7
4
|
module LLM
|
|
@@ -81,10 +78,11 @@ module LLM
|
|
|
81
78
|
tools.merge!(LLM.associations messages)
|
|
82
79
|
|
|
83
80
|
if tools.any?
|
|
84
|
-
parameters[:tools] =
|
|
81
|
+
parameters[:tools] = LLM.tool_definitions_to_ollama tools
|
|
85
82
|
end
|
|
86
83
|
|
|
87
|
-
Log.low "Calling
|
|
84
|
+
Log.low "Calling ollama #{url}: #{Log.fingerprint(parameters.except(:tools))}}"
|
|
85
|
+
Log.medium "Tools: #{Log.fingerprint tools.keys}}" if tools
|
|
88
86
|
|
|
89
87
|
parameters[:messages] = LLM.tools_to_ollama messages
|
|
90
88
|
|
|
@@ -93,7 +91,9 @@ module LLM
|
|
|
93
91
|
response = self.process_response client.chat(parameters), tools, &block
|
|
94
92
|
|
|
95
93
|
res = if response.last[:role] == 'function_call_output'
|
|
96
|
-
response + self.ask(messages + response, original_options.except(:tool_choice).merge(return_messages: true, tools: tools), &block)
|
|
94
|
+
#response + self.ask(messages + response, original_options.except(:tool_choice).merge(return_messages: true, tools: tools), &block)
|
|
95
|
+
# This version seems to keep the original message from getting forgotten
|
|
96
|
+
response + self.ask(response + messages, original_options.except(:tool_choice).merge(return_messages: true, tools: tools), &block)
|
|
97
97
|
else
|
|
98
98
|
response
|
|
99
99
|
end
|
|
@@ -58,8 +58,10 @@ module LLM
|
|
|
58
58
|
model ||= LLM.get_url_config(:model, url, :openai_ask, :ask, :openai, env: 'OPENAI_MODEL', default: "gpt-4.1")
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
case format
|
|
62
|
-
when
|
|
61
|
+
case format
|
|
62
|
+
when Hash
|
|
63
|
+
options[:response_format] = format
|
|
64
|
+
when 'json', 'json_object', :json, :json_object
|
|
63
65
|
options[:response_format] = {type: 'json_object'}
|
|
64
66
|
else
|
|
65
67
|
options[:response_format] = {type: format}
|
|
@@ -84,12 +86,13 @@ module LLM
|
|
|
84
86
|
tools.merge!(LLM.associations messages)
|
|
85
87
|
|
|
86
88
|
if tools.any?
|
|
87
|
-
parameters[:tools] =
|
|
89
|
+
parameters[:tools] = LLM.tool_definitions_to_openai tools
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
messages = self.process_input messages
|
|
91
93
|
|
|
92
|
-
Log.
|
|
94
|
+
Log.debug "Calling openai #{url}: #{Log.fingerprint(parameters.except(:tools))}}"
|
|
95
|
+
Log.high "Tools: #{Log.fingerprint tools.keys}}" if tools
|
|
93
96
|
|
|
94
97
|
parameters[:messages] = LLM.tools_to_openai messages
|
|
95
98
|
|
|
@@ -45,13 +45,15 @@ module LLM
|
|
|
45
45
|
#end
|
|
46
46
|
|
|
47
47
|
def self.tools_to_responses(messages)
|
|
48
|
+
last_id = nil
|
|
48
49
|
messages.collect do |message|
|
|
49
50
|
if message[:role] == 'function_call'
|
|
50
51
|
info = JSON.parse(message[:content])
|
|
51
52
|
IndiferentHash.setup info
|
|
52
53
|
name = info[:name] || IndiferentHash.dig(info,:function, :name)
|
|
53
54
|
IndiferentHash.setup info
|
|
54
|
-
id = info[:id]
|
|
55
|
+
id = last_id = info[:id] || "fc_#{rand(1000).to_s}"
|
|
56
|
+
id = id.sub(/^fc_/, '')
|
|
55
57
|
IndiferentHash.setup({
|
|
56
58
|
"type" => "function_call",
|
|
57
59
|
"status" => "completed",
|
|
@@ -62,7 +64,8 @@ module LLM
|
|
|
62
64
|
elsif message[:role] == 'function_call_output'
|
|
63
65
|
info = JSON.parse(message[:content])
|
|
64
66
|
IndiferentHash.setup info
|
|
65
|
-
id = info[:id]
|
|
67
|
+
id = info[:id] || last_id
|
|
68
|
+
id = id.sub(/^fc_/, '')
|
|
66
69
|
{ # append result message
|
|
67
70
|
"type" => "function_call_output",
|
|
68
71
|
"output" => info[:content],
|
|
@@ -103,35 +106,44 @@ module LLM
|
|
|
103
106
|
def self.process_input(messages)
|
|
104
107
|
messages = self.tools_to_responses messages
|
|
105
108
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
res = []
|
|
110
|
+
messages.each do |message|
|
|
111
|
+
message = IndiferentHash.setup(message)
|
|
112
|
+
|
|
113
|
+
role = message[:role]
|
|
114
|
+
|
|
115
|
+
case role.to_s
|
|
116
|
+
when 'image'
|
|
109
117
|
path = message[:content]
|
|
110
118
|
path = LLM.find_file path
|
|
111
119
|
if Open.remote?(path)
|
|
112
|
-
{role: :user, content: {type: :input_image, image_url: path }}
|
|
120
|
+
res << {role: :user, content: {type: :input_image, image_url: path }}
|
|
113
121
|
elsif Open.exists?(path)
|
|
114
122
|
path = self.encode_image(path)
|
|
115
|
-
{role: :user, content: [{type: :input_image, image_url: path }]}
|
|
123
|
+
res << {role: :user, content: [{type: :input_image, image_url: path }]}
|
|
116
124
|
else
|
|
117
|
-
raise
|
|
125
|
+
raise "Image does not exist in #{path}"
|
|
118
126
|
end
|
|
119
|
-
|
|
127
|
+
when 'pdf'
|
|
120
128
|
path = original_path = message[:content]
|
|
121
129
|
if Open.remote?(path)
|
|
122
|
-
{role: :user, content: {type: :input_file, file_url: path }}
|
|
130
|
+
res << {role: :user, content: {type: :input_file, file_url: path }}
|
|
123
131
|
elsif Open.exists?(path)
|
|
124
132
|
data = self.encode_pdf(path)
|
|
125
|
-
{role: :user, content: [{type: :input_file, file_data: data, filename: File.basename(path) }]}
|
|
133
|
+
res << {role: :user, content: [{type: :input_file, file_data: data, filename: File.basename(path) }]}
|
|
126
134
|
else
|
|
127
|
-
raise
|
|
135
|
+
raise "PDF does not exist in #{path}"
|
|
128
136
|
end
|
|
129
|
-
|
|
130
|
-
{role: :tool, content: {type: "web_search_preview"} }
|
|
137
|
+
when 'websearch'
|
|
138
|
+
res << {role: :tool, content: {type: "web_search_preview"} }
|
|
139
|
+
when 'previous_response_id'
|
|
140
|
+
res = []
|
|
131
141
|
else
|
|
132
|
-
message
|
|
142
|
+
res << message
|
|
133
143
|
end
|
|
134
|
-
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
res
|
|
135
147
|
end
|
|
136
148
|
|
|
137
149
|
def self.process_format(format)
|
|
@@ -187,6 +199,7 @@ module LLM
|
|
|
187
199
|
|
|
188
200
|
messages = LLM.chat(question)
|
|
189
201
|
options = options.merge LLM.options messages
|
|
202
|
+
|
|
190
203
|
|
|
191
204
|
client, url, key, model, log_errors, return_messages, format, websearch, previous_response_id, tools, = IndiferentHash.process_options options,
|
|
192
205
|
:client, :url, :key, :model, :log_errors, :return_messages, :format, :websearch, :previous_response_id, :tools,
|
|
@@ -232,13 +245,16 @@ module LLM
|
|
|
232
245
|
tools.merge!(LLM.associations messages)
|
|
233
246
|
|
|
234
247
|
if tools.any?
|
|
235
|
-
parameters[:tools] =
|
|
248
|
+
parameters[:tools] = LLM.tool_definitions_to_reponses tools
|
|
236
249
|
end
|
|
237
250
|
|
|
238
251
|
parameters['previous_response_id'] = previous_response_id if String === previous_response_id
|
|
239
|
-
|
|
252
|
+
|
|
253
|
+
Log.low "Calling responses #{url}: #{Log.fingerprint(parameters.except(:tools))}}"
|
|
254
|
+
Log.medium "Tools: #{Log.fingerprint tools.keys}}" if tools
|
|
240
255
|
|
|
241
256
|
messages = self.process_input messages
|
|
257
|
+
|
|
242
258
|
input = []
|
|
243
259
|
messages.each do |message|
|
|
244
260
|
parameters[:tools] ||= []
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
require 'scout/annotation'
|
|
2
|
+
module Chat
|
|
3
|
+
extend Annotation
|
|
4
|
+
|
|
5
|
+
def message(role, content)
|
|
6
|
+
self.append({role: role.to_s, content: content})
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def user(content)
|
|
10
|
+
message(:user, content)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def system(content)
|
|
14
|
+
message(:system, content)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def assistant(content)
|
|
18
|
+
message(:assistant, content)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def import(file)
|
|
22
|
+
message(:import, file)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def import_last(file)
|
|
26
|
+
message(:last, file)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def file(file)
|
|
30
|
+
message(:file, file)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def directory(directory)
|
|
34
|
+
message(:directory, directory)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def continue(file)
|
|
38
|
+
message(:continue, file)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def format(format)
|
|
42
|
+
message(:format, format)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def tool(*parts)
|
|
46
|
+
content = parts * "\n"
|
|
47
|
+
message(:tool, content)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def task(workflow, task_name, inputs = {})
|
|
51
|
+
input_str = IndiferentHash.print_options inputs
|
|
52
|
+
content = [workflow, task_name, input_str]*" "
|
|
53
|
+
message(:task, content)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def inline_task(workflow, task_name, inputs = {})
|
|
57
|
+
input_str = IndiferentHash.print_options inputs
|
|
58
|
+
content = [workflow, task_name, input_str]*" "
|
|
59
|
+
message(:inline_task, content)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def job(step)
|
|
63
|
+
message(:job, step.path)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def inline_job(step)
|
|
67
|
+
message(:inline_job, step.path)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def association(name, path, options = {})
|
|
72
|
+
options_str = IndiferentHash.print_options options
|
|
73
|
+
content = [name, path, options_str]*" "
|
|
74
|
+
message(:association, name)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def tag(content, name=nil, tag=:file, role=:user)
|
|
78
|
+
self.message role, LLM.tag(tag, content, name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def ask(...)
|
|
83
|
+
LLM.ask(LLM.chat(self), ...)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def chat(...)
|
|
87
|
+
response = ask(...)
|
|
88
|
+
if Array === response
|
|
89
|
+
current_chat.concat(response)
|
|
90
|
+
final(response)
|
|
91
|
+
else
|
|
92
|
+
current_chat.push({role: :assistant, content: response})
|
|
93
|
+
response
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def json(...)
|
|
98
|
+
self.format :json
|
|
99
|
+
output = ask(...)
|
|
100
|
+
obj = JSON.parse output
|
|
101
|
+
if (Hash === obj) and obj.keys == ['content']
|
|
102
|
+
obj['content']
|
|
103
|
+
else
|
|
104
|
+
obj
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def json_format(format, ...)
|
|
109
|
+
self.format format
|
|
110
|
+
output = ask(...)
|
|
111
|
+
obj = JSON.parse output
|
|
112
|
+
if (Hash === obj) and obj.keys == ['content']
|
|
113
|
+
obj['content']
|
|
114
|
+
else
|
|
115
|
+
obj
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def branch
|
|
120
|
+
self.annotate self.dup
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def option(name, value)
|
|
124
|
+
self.message 'option', [name, value] * " "
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def endpoint(value)
|
|
128
|
+
option :endpoint, value
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def model(value)
|
|
132
|
+
option :model, value
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def image(file)
|
|
136
|
+
self.message :image, file
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Reporting
|
|
140
|
+
|
|
141
|
+
def print
|
|
142
|
+
LLM.print LLM.chat(self)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def final
|
|
146
|
+
LLM.purge(self).last
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def purge
|
|
150
|
+
Chat.setup(LLM.purge(self))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def shed
|
|
154
|
+
self.annotate [final]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def answer
|
|
158
|
+
final[:content]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Write and save
|
|
162
|
+
|
|
163
|
+
def save(path, force = true)
|
|
164
|
+
path = path.to_s if Symbol === path
|
|
165
|
+
if not (Open.exists?(path) || Path === path || Path.located?(path))
|
|
166
|
+
path = Scout.chats.find[path]
|
|
167
|
+
end
|
|
168
|
+
return if Open.exists?(path) && ! force
|
|
169
|
+
Open.write path, LLM.print(self)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def write(path, force = true)
|
|
173
|
+
path = path.to_s if Symbol === path
|
|
174
|
+
if not (Open.exists?(path) || Path === path || Path.located?(path))
|
|
175
|
+
path = Scout.chats.find[path]
|
|
176
|
+
end
|
|
177
|
+
return if Open.exists?(path) && ! force
|
|
178
|
+
Open.write path, self.print
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def write_answer(path, force = true)
|
|
182
|
+
path = path.to_s if Symbol === path
|
|
183
|
+
if not (Open.exists?(path) || Path === path || Path.located?(path))
|
|
184
|
+
path = Scout.chats.find[path]
|
|
185
|
+
end
|
|
186
|
+
return if Open.exists?(path) && ! force
|
|
187
|
+
Open.write path, self.answer
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Image
|
|
191
|
+
def create_image(file, ...)
|
|
192
|
+
base64_image = LLM.image(LLM.chat(self), ...)
|
|
193
|
+
Open.write(file, Base64.decode(file_content), mode: 'wb')
|
|
194
|
+
end
|
|
195
|
+
end
|