scout-ai 1.2.0 → 1.2.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 +2 -0
- data/VERSION +1 -1
- data/lib/scout/llm/agent/delegate.rb +62 -56
- data/lib/scout/llm/ask.rb +4 -2
- data/lib/scout/llm/backends/default.rb +2 -2
- data/lib/scout/llm/chat/annotation.rb +6 -0
- data/lib/scout/llm/chat/parse.rb +26 -0
- data/lib/scout/llm/chat/process/meta.rb +9 -1
- data/lib/scout/llm/chat/process/tools.rb +5 -1
- data/lib/scout/llm/tools/call.rb +4 -0
- data/scout-ai.gemspec +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 456939ec51f2ea2c9be5a7fa5eb7992161e893dcfb6618b85eac7e7317af25fa
|
|
4
|
+
data.tar.gz: 67d22d1cd09f74c73843f143897d543993df4f5d68b9649ab1751a19a944e8fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 501bf9ff434058cb2ebad6b084a7ffbccf8a392b1a735c9e6e5726afcc23c7331ef471fc4c675130e49920def430332dd01b647b5f9a473c038b8ef0609d55e1
|
|
7
|
+
data.tar.gz: a267f504d6342a15fe1c0b7f180a47e3b7cd532b71fbd4a5fd3868b4fb7337f3a5c4f5d1ce0aa53276eb268e64ec035806d262b7713dba23d65bb50af2d6c3ab
|
data/.vimproject
CHANGED
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.2.
|
|
1
|
+
1.2.1
|
|
@@ -1,89 +1,48 @@
|
|
|
1
1
|
module LLM
|
|
2
2
|
class Agent
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def delegate(agent, name, description, &block)
|
|
7
|
-
@other_options[:tools] ||= {}
|
|
8
|
-
task_name = "hand_off_to_#{name}"
|
|
9
|
-
|
|
10
|
-
block ||= Proc.new do |name, parameters|
|
|
11
|
-
message = parameters[:message]
|
|
12
|
-
new_conversation = parameters[:new_conversation]
|
|
13
|
-
Log.medium "Delegated to #{agent}: " + Log.fingerprint(message)
|
|
14
|
-
if new_conversation
|
|
15
|
-
agent.start
|
|
16
|
-
else
|
|
17
|
-
agent.purge
|
|
18
|
-
end
|
|
19
|
-
agent.user message
|
|
20
|
-
agent.chat
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
properties = {
|
|
24
|
-
message: {
|
|
25
|
-
"type": :string,
|
|
26
|
-
"description": "Message to pass to the agent"
|
|
27
|
-
},
|
|
28
|
-
new_conversation: {
|
|
29
|
-
"type": :boolean,
|
|
30
|
-
"description": "Erase conversation history and start a new conversation with this message",
|
|
31
|
-
"default": false
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
required_inputs = [:message]
|
|
36
|
-
|
|
37
|
-
function = {
|
|
38
|
-
name: task_name,
|
|
39
|
-
description: description,
|
|
40
|
-
parameters: {
|
|
41
|
-
type: "object",
|
|
42
|
-
properties: properties,
|
|
43
|
-
required: required_inputs
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
definition = IndiferentHash.setup function.merge(type: 'function', function: function)
|
|
4
|
+
attr_accessor :society, :chats
|
|
48
5
|
|
|
6
|
+
def load_agent(agent_name, options)
|
|
7
|
+
raise ParameterException,
|
|
8
|
+
"Agent name must be a single word optionally including a few puntuation characters" unless agent_name =~ /^[a-z_.-]*$/i
|
|
9
|
+
@society ||= {}
|
|
10
|
+
@society[agent_name] ||= LLM.load_agent agent_name, options.merge(self.other_options)
|
|
11
|
+
end
|
|
49
12
|
|
|
50
|
-
|
|
13
|
+
def load_chat(agent_name, options, chat)
|
|
14
|
+
@chats ||= {}
|
|
15
|
+
@chats[chat] ||= load_agent(agent_name, options).clone
|
|
51
16
|
end
|
|
52
17
|
|
|
53
18
|
def socialize(options = {})
|
|
54
19
|
@other_options[:tools] ||= {}
|
|
20
|
+
@society ||= {}
|
|
55
21
|
|
|
56
|
-
task_name =
|
|
22
|
+
task_name = :ask
|
|
57
23
|
block ||= Proc.new do |name, parameters|
|
|
58
24
|
agent_name, prompt, chat_id = IndiferentHash.process_options parameters.dup,
|
|
59
25
|
:agent, :prompt, :chat,
|
|
60
26
|
chat: 'current'
|
|
61
27
|
|
|
62
28
|
begin
|
|
63
|
-
raise ParameterException, "Agent name must be a single word optionally including a few puntuation characters" unless agent_name =~ /^[a-z_.-]*$/i
|
|
64
|
-
|
|
65
|
-
|
|
66
29
|
options = options.dup
|
|
67
30
|
|
|
68
|
-
#options[:endpoint] ||= Scout::Config.get(:endpoint, name, :Agent, :agent_ask, :delegate)
|
|
69
|
-
#options[:model] ||= Scout::Config.get(:model, name, :Agent, :agent_ask, :delegate)
|
|
70
|
-
|
|
71
31
|
res = case chat_id
|
|
72
32
|
when 'current'
|
|
73
|
-
agent =
|
|
33
|
+
agent = load_chat agent_name, options, 'current'
|
|
74
34
|
chat = self.current_chat - self.start_chat
|
|
75
35
|
agent.concat chat
|
|
76
36
|
agent.user prompt
|
|
77
37
|
agent.chat
|
|
78
38
|
when 'none', nil, 'false'
|
|
79
|
-
agent =
|
|
39
|
+
agent = load_agent agent_name, options
|
|
80
40
|
agent.prompt prompt
|
|
81
41
|
else
|
|
82
|
-
agent =
|
|
42
|
+
agent = load_chat agent_name, options, chat_id
|
|
83
43
|
agent.user prompt
|
|
84
44
|
agent.chat
|
|
85
45
|
end
|
|
86
|
-
iii res
|
|
87
46
|
res
|
|
88
47
|
rescue ScoutException
|
|
89
48
|
next $!
|
|
@@ -134,4 +93,51 @@ prompt.
|
|
|
134
93
|
@other_options[:tools][task_name] = [block, definition]
|
|
135
94
|
end
|
|
136
95
|
end
|
|
96
|
+
def delegate(agent, name, description, &block)
|
|
97
|
+
@other_options[:tools] ||= {}
|
|
98
|
+
task_name = "hand_off_to_#{name}".to_sym
|
|
99
|
+
|
|
100
|
+
block ||= Proc.new do |name, parameters|
|
|
101
|
+
message = parameters[:message]
|
|
102
|
+
new_conversation = parameters[:new_conversation]
|
|
103
|
+
Log.medium "Delegated to #{agent}: " + Log.fingerprint(message)
|
|
104
|
+
if new_conversation
|
|
105
|
+
agent.start
|
|
106
|
+
else
|
|
107
|
+
agent.purge
|
|
108
|
+
end
|
|
109
|
+
agent.user message
|
|
110
|
+
agent.chat
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
properties = {
|
|
114
|
+
message: {
|
|
115
|
+
"type": :string,
|
|
116
|
+
"description": "Message to pass to the agent"
|
|
117
|
+
},
|
|
118
|
+
new_conversation: {
|
|
119
|
+
"type": :boolean,
|
|
120
|
+
"description": "Erase conversation history and start a new conversation with this message",
|
|
121
|
+
"default": false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
required_inputs = [:message]
|
|
126
|
+
|
|
127
|
+
function = {
|
|
128
|
+
name: task_name,
|
|
129
|
+
description: description,
|
|
130
|
+
parameters: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: properties,
|
|
133
|
+
required: required_inputs
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
definition = IndiferentHash.setup function.merge(type: 'function', function: function)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@other_options[:tools][task_name] = [block, definition]
|
|
141
|
+
end
|
|
142
|
+
|
|
137
143
|
end
|
data/lib/scout/llm/ask.rb
CHANGED
|
@@ -32,7 +32,7 @@ module LLM
|
|
|
32
32
|
|
|
33
33
|
options[:meta] = Chat.meta messages
|
|
34
34
|
|
|
35
|
-
Log.high Log.color :green, "Asking #{endpoint || 'client'}: #{options[:previous_response_id]}\n" +
|
|
35
|
+
Log.high Log.color :green, "Asking #{endpoint || 'client'}: #{options[:previous_response_id]}\n" + Chat.print_brief(messages)
|
|
36
36
|
tools = options[:tools]
|
|
37
37
|
Log.high "Tools: #{Log.fingerprint tools.keys}" if tools
|
|
38
38
|
Log.debug "#{Log.fingerprint tools}}" if tools
|
|
@@ -71,7 +71,9 @@ module LLM
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Chat.setup res if Array === res
|
|
75
|
+
|
|
76
|
+
Log.high Log.color :blue, "Response:\n" + Chat.print_brief(res, %w(meta assistant))
|
|
75
77
|
|
|
76
78
|
res
|
|
77
79
|
end
|
|
@@ -409,8 +409,8 @@ module LLM
|
|
|
409
409
|
pattern = {
|
|
410
410
|
'pt+': [['usage','prompt_tokens']],
|
|
411
411
|
'ct+': [['usage','completion_tokens']],
|
|
412
|
-
'
|
|
413
|
-
'
|
|
412
|
+
'tt+': [['usage','total_tokens']],
|
|
413
|
+
'reas': [['choices', 0, 'message', 'reasoning_content']],
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
meta = {}
|
|
@@ -134,6 +134,12 @@ module Chat
|
|
|
134
134
|
self.replace new
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
+
def remove_role(role=:clear)
|
|
138
|
+
messages = self.select{|m| m[:role].to_s == role.to_s }
|
|
139
|
+
self.reject!{|m| m[:role].to_s == role.to_s }
|
|
140
|
+
messages
|
|
141
|
+
end
|
|
142
|
+
|
|
137
143
|
def json(...)
|
|
138
144
|
self.format :json
|
|
139
145
|
output = ask(...)
|
data/lib/scout/llm/chat/parse.rb
CHANGED
|
@@ -139,4 +139,30 @@ module Chat
|
|
|
139
139
|
end
|
|
140
140
|
end * "\n\n"
|
|
141
141
|
end
|
|
142
|
+
|
|
143
|
+
def self.print_brief(chat, expand = [])
|
|
144
|
+
return chat if String === chat
|
|
145
|
+
expand = expand.collect{|role| role.to_s }
|
|
146
|
+
|
|
147
|
+
"\n" + chat.collect do |message|
|
|
148
|
+
message = IndiferentHash.setup message
|
|
149
|
+
role, content = message.values_at :role, :content
|
|
150
|
+
role = role.to_s
|
|
151
|
+
|
|
152
|
+
str = case message[:content]
|
|
153
|
+
when Hash, Array
|
|
154
|
+
message[:content].to_json
|
|
155
|
+
when nil, ''
|
|
156
|
+
''
|
|
157
|
+
else
|
|
158
|
+
message[:content].to_s
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
next role, "\n\n" + str if expand.include?(role)
|
|
162
|
+
|
|
163
|
+
[role, Log.fingerprint(str)[1..-2]]
|
|
164
|
+
end.compact.collect do |role,str|
|
|
165
|
+
"#{role}: #{str}"
|
|
166
|
+
end * "\n\n"
|
|
167
|
+
end
|
|
142
168
|
end
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
module Chat
|
|
2
2
|
def self.serialize_meta(meta)
|
|
3
|
-
|
|
3
|
+
keys = meta.keys
|
|
4
|
+
|
|
5
|
+
keys = keys.sort_by do |k|
|
|
6
|
+
v = meta[k]
|
|
7
|
+
String === v ? v.length : 0
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
keys.collect{|k| [k,meta[k]] * "="} * " "
|
|
4
11
|
end
|
|
5
12
|
|
|
6
13
|
def self.parse_meta(str)
|
|
@@ -29,6 +36,7 @@ module Chat
|
|
|
29
36
|
|
|
30
37
|
key = next_key
|
|
31
38
|
end
|
|
39
|
+
|
|
32
40
|
meta
|
|
33
41
|
end
|
|
34
42
|
|
|
@@ -150,6 +150,10 @@ module Chat
|
|
|
150
150
|
next
|
|
151
151
|
elsif message[:role] == 'introduce'
|
|
152
152
|
workflow_name = message[:content]
|
|
153
|
+
workflow = begin
|
|
154
|
+
Kernel.const_get workflow_name
|
|
155
|
+
rescue
|
|
156
|
+
end
|
|
153
157
|
if Open.remote? workflow_name
|
|
154
158
|
require 'rbbt'
|
|
155
159
|
require 'scout/offsite/ssh'
|
|
@@ -157,7 +161,7 @@ module Chat
|
|
|
157
161
|
workflow = RemoteWorkflow.new workflow_name
|
|
158
162
|
else
|
|
159
163
|
workflow = Workflow.require_workflow workflow_name
|
|
160
|
-
end
|
|
164
|
+
end unless workflow
|
|
161
165
|
|
|
162
166
|
raise "Workflow not found #{workflow_name}" if workflow.nil?
|
|
163
167
|
|
data/lib/scout/llm/tools/call.rb
CHANGED
|
@@ -92,6 +92,7 @@ module LLM
|
|
|
92
92
|
|
|
93
93
|
tool_call_content.collect do |function_name,function_arguments,tool_call_id,tool_call,content|
|
|
94
94
|
if Step === content
|
|
95
|
+
step = content
|
|
95
96
|
if content.done?
|
|
96
97
|
content = content.load.to_json if content.done?
|
|
97
98
|
elsif content.error? && content.exception
|
|
@@ -103,10 +104,13 @@ module LLM
|
|
|
103
104
|
content = {exception: $!.message, stack: $!.backtrace }.to_json
|
|
104
105
|
end
|
|
105
106
|
end
|
|
107
|
+
else
|
|
108
|
+
step = nil
|
|
106
109
|
end
|
|
107
110
|
|
|
108
111
|
if (String === content) && content.length > max_content_length
|
|
109
112
|
exception_msg = "Function #{function_name} #{tool_call_id} (#{Log.fingerprint function_arguments}) returned #{content.length} characters, which is more than the maximum of #{max_content_length}. To protect the model context window this result was not returned."
|
|
113
|
+
exception_msg += " The results is made available at '#{step.path}'." if step
|
|
110
114
|
Log.high exception_msg
|
|
111
115
|
content = {exception: exception_msg, stack: caller}.to_json
|
|
112
116
|
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.2.
|
|
5
|
+
# stub: scout-ai 1.2.1 ruby lib
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |s|
|
|
8
8
|
s.name = "scout-ai".freeze
|
|
9
|
-
s.version = "1.2.
|
|
9
|
+
s.version = "1.2.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]
|