scout-ai 1.0.1 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a13a09db3a006c0a8a2c5bc2487d5248c7b599ea9013924ab588d25540e8848d
4
- data.tar.gz: 73f87320f05289b9818aef772b205b39745ee5553733664614988e3d45c66c64
3
+ metadata.gz: cc083461a140b5149d6965f5aa635e651015786cfe312f220a33967fa10c52cb
4
+ data.tar.gz: 694ac0ff0b626d99ee993c77a1dba12022f9e7a545bbab67b94627a4698ade09
5
5
  SHA512:
6
- metadata.gz: 72bea9d93dc2d374580c763688d9a2b5e7a576ca5806e5169f4f88d0640a7fe29e9a7013cf55c0d89763699c630c662e918ebb9b9033948be6a70d2491eb7575
7
- data.tar.gz: dfe39bc0d5f624df7ecced479f379902492088555bd6afbe140e89b94dadaa6e8fed82b8c04d6369ca8f8dac5014b8c3cb0a0d64657f6e5d37b4306435f8bbf1
6
+ metadata.gz: ef373e303af56478ecde91faf87093226936e3537b9cb3346192e230af43cc8350091e589080b7c4e32993bf0cbcc43ef380582a4d38b82ca5da33bc60754dfd
7
+ data.tar.gz: cccb6e93d1b827a02f5b9159a11eb7839c11c48b0cdd5a0096644b19479e0609d9d641efff286a545625fc17863e2f106faa2ea775e7cde121f313260fdef779
data/.vimproject CHANGED
@@ -6,6 +6,13 @@ 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
+
10
+ test_tool
11
+
12
+ ask_agent
13
+
14
+ test_ollama_tool
15
+
9
16
  test_github
10
17
 
11
18
  test_stdio
@@ -103,8 +110,8 @@ scout-ai=$PWD filter="*.rb *.rake Rakefile *.rdoc *.R *.sh *.js *.haml *.sass *.
103
110
 
104
111
  backends=backends{
105
112
  openai.rb
106
- anthropic.rb
107
113
  responses.rb
114
+ anthropic.rb
108
115
  ollama.rb
109
116
  bedrock.rb
110
117
  openwebui.rb
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.1.0
@@ -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,11 +27,6 @@ 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
31
  def chat(model = nil, options = {})
35
32
  response = ask(current_chat, model, options.merge(return_messages: true))
@@ -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
 
@@ -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, model = nil, options = {})
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 chat_path if chat_path.exists?
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
@@ -41,7 +41,11 @@ module LLM
41
41
  raise "Endpoint not found #{endpoint}"
42
42
  end
43
43
 
44
- Persist.persist(endpoint, :json, prefix: "LLM ask", other: options.merge(messages: messages), persist: persist) do
44
+ Log.high Log.color :green, "Asking #{endpoint || 'client'}:\n" + LLM.print(messages)
45
+ tools = options[:tools]
46
+ Log.high "Tools: #{Log.fingerprint tools.keys}}" if tools
47
+
48
+ res = Persist.persist(endpoint, :json, prefix: "LLM ask", other: options.merge(messages: messages), persist: persist) do
45
49
  backend = IndiferentHash.process_options options, :backend
46
50
  backend ||= Scout::Config.get :backend, :ask, :llm, env: 'ASK_BACKEND,LLM_BACKEND', default: :openai
47
51
 
@@ -71,6 +75,10 @@ module LLM
71
75
  raise "Unknown backend: #{backend}"
72
76
  end
73
77
  end
78
+
79
+ Log.high Log.color :blue, "Response:\n" + LLM.print(res)
80
+
81
+ res
74
82
  end
75
83
 
76
84
  def self.workflow_ask(workflow, question, options = {})
@@ -81,10 +81,11 @@ module LLM
81
81
  tools.merge!(LLM.associations messages)
82
82
 
83
83
  if tools.any?
84
- parameters[:tools] = tools.values.collect{|obj,definition| Hash === obj ? obj : definition}
84
+ parameters[:tools] = LLM.tool_definitions_to_ollama tools
85
85
  end
86
86
 
87
- Log.low "Calling client with parameters #{Log.fingerprint parameters}\n#{LLM.print messages}"
87
+ Log.low "Calling ollama #{url}: #{Log.fingerprint(parameters.except(:tools))}}"
88
+ Log.medium "Tools: #{Log.fingerprint tools.keys}}" if tools
88
89
 
89
90
  parameters[:messages] = LLM.tools_to_ollama messages
90
91
 
@@ -93,7 +94,9 @@ module LLM
93
94
  response = self.process_response client.chat(parameters), tools, &block
94
95
 
95
96
  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)
97
+ #response + self.ask(messages + response, original_options.except(:tool_choice).merge(return_messages: true, tools: tools), &block)
98
+ # This version seems to keep the original message from getting forgotten
99
+ response + self.ask(response + messages, original_options.except(:tool_choice).merge(return_messages: true, tools: tools), &block)
97
100
  else
98
101
  response
99
102
  end
@@ -84,12 +84,13 @@ module LLM
84
84
  tools.merge!(LLM.associations messages)
85
85
 
86
86
  if tools.any?
87
- parameters[:tools] = tools.values.collect{|obj,definition| Hash === obj ? obj : definition}
87
+ parameters[:tools] = LLM.tool_definitions_to_openai tools
88
88
  end
89
89
 
90
90
  messages = self.process_input messages
91
91
 
92
- Log.low "Calling openai #{url}: #{Log.fingerprint parameters}}"
92
+ Log.debug "Calling openai #{url}: #{Log.fingerprint(parameters.except(:tools))}}"
93
+ Log.high "Tools: #{Log.fingerprint tools.keys}}" if tools
93
94
 
94
95
  parameters[:messages] = LLM.tools_to_openai messages
95
96
 
@@ -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].sub(/^fc_/, '')
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].sub(/^fc_/, '')
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],
@@ -232,11 +235,13 @@ module LLM
232
235
  tools.merge!(LLM.associations messages)
233
236
 
234
237
  if tools.any?
235
- parameters[:tools] = tools.values.collect{|obj,definition| Hash === obj ? obj : definition}
238
+ parameters[:tools] = LLM.tool_definitions_to_reponses tools
236
239
  end
237
240
 
238
241
  parameters['previous_response_id'] = previous_response_id if String === previous_response_id
239
- Log.low "Calling client with parameters #{Log.fingerprint parameters}\n#{LLM.print messages}"
242
+
243
+ Log.low "Calling responses #{url}: #{Log.fingerprint(parameters.except(:tools))}}"
244
+ Log.medium "Tools: #{Log.fingerprint tools.keys}}" if tools
240
245
 
241
246
  messages = self.process_input messages
242
247
  input = []
@@ -219,7 +219,7 @@ module LLM
219
219
  def self.tasks(messages, original = nil)
220
220
  jobs = []
221
221
  new = messages.collect do |message|
222
- if message[:role] == 'task' || message[:role] == 'inline_task'
222
+ if message[:role] == 'task' || message[:role] == 'inline_task' || message[:role] == 'exec_task'
223
223
  info = message[:content].strip
224
224
 
225
225
  workflow, task = info.split(" ").values_at 0, 1
@@ -237,9 +237,15 @@ module LLM
237
237
 
238
238
  job = workflow.job(task, jobname, options)
239
239
 
240
- jobs << job
240
+ jobs << job unless message[:role] == 'exec_task'
241
241
 
242
- if message[:role] == 'inline_task'
242
+ if message[:role] == 'exec_task'
243
+ begin
244
+ {role: 'user', content: job.exec}
245
+ rescue
246
+ {role: 'exec_job', content: $!}
247
+ end
248
+ elsif message[:role] == 'inline_task'
243
249
  {role: 'inline_job', content: job.path.find}
244
250
  else
245
251
  {role: 'job', content: job.path.find}
@@ -264,25 +270,31 @@ module LLM
264
270
  id = step.short_path[0..39]
265
271
  id = id.gsub('/','-')
266
272
 
267
-
268
273
  if message[:role] == 'inline_job'
269
274
  path = step.path
270
275
  path = path.find if Path === path
271
276
  {role: 'file', content: step.path}
272
277
  else
278
+
279
+ function_name = step.full_task_name.sub('#', '-')
280
+ function_name = step.task_name
273
281
  tool_call = {
274
- type: "function",
275
282
  function: {
276
- name: step.full_task_name.sub('#', '-'),
277
- arguments: step.provided_inputs.to_json
283
+ name: function_name,
284
+ arguments: step.provided_inputs
278
285
  },
279
286
  id: id,
280
287
  }
281
288
 
289
+ content = if step.done?
290
+ Open.read(step.path)
291
+ elsif step.error?
292
+ step.exception
293
+ end
294
+
282
295
  tool_output = {
283
296
  id: id,
284
- role: "tool",
285
- content: Open.read(step.path)
297
+ content: content
286
298
  }
287
299
 
288
300
  [
@@ -490,7 +502,7 @@ module LLM
490
502
  when nil, ''
491
503
  message[:role].to_s + ":"
492
504
  else
493
- if %w(option previous_response_id).include? message[:role].to_s
505
+ if %w(option previous_response_id function_call function_call_output).include? message[:role].to_s
494
506
  message[:role].to_s + ": " + message[:content].to_s
495
507
  else
496
508
  message[:role].to_s + ":\n\n" + message[:content].to_s
@@ -19,8 +19,16 @@ module LLM
19
19
  calls.collect do |tool_call|
20
20
  tool_call_id, function_name, function_arguments = call_id_name_and_arguments(tool_call)
21
21
 
22
+ function_arguments = IndiferentHash.setup function_arguments
23
+
22
24
  obj, definition = tools[function_name]
23
25
 
26
+ definition = obj if Hash === obj
27
+
28
+ defaults = definition[:parameters][:defaults] if definition && definition[:parameters]
29
+ function_arguments = function_arguments.merge(defaults) if defaults
30
+
31
+ Log.high "Calling #{function_name} (#{Log.fingerprint function_arguments}): "
24
32
  function_response = case obj
25
33
  when Proc
26
34
  obj.call function_name, function_arguments
@@ -32,7 +40,7 @@ module LLM
32
40
  if block_given?
33
41
  block.call function_name, function_arguments
34
42
  else
35
- raise "Unkown executor #{Log.fingerprint obj} for function #{function_name}"
43
+ ParameterException.new "Tool or function not found '#{function_name}'. Called with parameters #{Log.fingerprint function_arguments}" if obj.nil? && definition.nil?
36
44
  end
37
45
  end
38
46
 
@@ -48,6 +56,8 @@ module LLM
48
56
  end
49
57
  content = content.to_s if Numeric === content
50
58
 
59
+ Log.high "Called #{function_name}: " + Log.fingerprint(content)
60
+
51
61
  response_message = {
52
62
  id: tool_call_id,
53
63
  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: "string",
72
- enum: select_options,
73
- description: "Limit the response to these detail fields fields"
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 #{field} of association.
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
- name: database + '_association_details',
103
- description: description,
104
- parameters: {
105
- type: "object",
106
- properties: properties,
107
- required: ['associations']
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.merge(type: 'function', function: 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
 
@@ -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
- inputs = inputs.collect{|i| i.to_sym } if inputs
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 inputs and not inputs.include?(input)
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 inputs and not inputs.include?(input.to_sym)
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,47 @@ 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
- IndiferentHash.setup function.merge(type: 'function', function: function)
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
- tasks = workflow.all_exports if tasks.nil?
53
- tasks = workflow.all_tasks if tasks.empty?
74
+ if Array === workflow
75
+ workflow.inject({}){|tool_definitions,wf| tool_definitions.merge(workflow_tools(wf, tasks)) }
54
76
 
55
- tasks.inject({}){|tool_definitions,task_name|
56
- definition = self.task_tool_definition(workflow, task_name)
57
- tool_definitions.merge(task_name => [workflow, definition])
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
- if workflow.exec_exports.include? task_name.to_sym
64
- workflow.job(task_name, jobname, parameters).exec
65
- else
66
- workflow.job(task_name, jobname, parameters).run
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
+ job.run
101
+ end
102
+ rescue ScoutException
103
+ return $!
67
104
  end
68
105
  end
69
106
  end
@@ -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/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.0.1 ruby lib
5
+ # stub: scout-ai 1.1.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "scout-ai".freeze
9
- s.version = "1.0.1".freeze
9
+ s.version = "1.1.0".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]
@@ -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
- conversation = question.empty? ? conversation : conversation + LLM.chat(question)
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
- new = agent.ask(conversation, convo_options.merge(options.merge(return_messages: true)))
110
- conversation = Open.read(chat) + LLM.print(new)
111
- Open.write(chat, conversation)
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
@@ -78,6 +78,7 @@ if chat
78
78
  ppp LLM.print conversation
79
79
  exit 0
80
80
  end
81
+
81
82
  new = LLM.ask(conversation, convo_options.merge(options.merge(return_messages: true)))
82
83
  conversation = Open.read(chat) + LLM.print(new)
83
84
  Open.write(chat, conversation)
@@ -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: 'mistral', tool_choice: 'required', tools: tools do |name,arguments|
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
 
@@ -3,7 +3,7 @@ require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1
3
3
 
4
4
  require 'rbbt-util'
5
5
  class TestLLMAgent < Test::Unit::TestCase
6
- def _test_system
6
+ def test_system
7
7
  TmpFile.with_dir do |dir|
8
8
  kb = KnowledgeBase.new dir
9
9
  kb.format = {"Person" => "Alias"}
@@ -13,32 +13,10 @@ class TestLLMAgent < Test::Unit::TestCase
13
13
 
14
14
  agent = LLM::Agent.new knowledge_base: kb
15
15
 
16
- agent.system = ""
17
-
18
16
  sss 0
19
17
  ppp agent.ask "Who is Miguel's brother-in-law. Brother in law is your spouses sibling or your sibling's spouse"
20
18
  #ppp agent.ask "Who is Guille's brother-in-law. Brother in law is your spouses sibling or your sibling's spouse"
21
19
  end
22
20
  end
23
-
24
- def _test_system_gepeto
25
- TmpFile.with_dir do |dir|
26
- Scout::Config.set(:backend, :ollama, :ask)
27
- kb = KnowledgeBase.new dir
28
- kb.format = {"Person" => "Alias"}
29
- kb.register :brothers, datafile_test(:person).brothers, undirected: true
30
- kb.register :marriages, datafile_test(:person).marriages, undirected: true, source: "=>Alias", target: "=>Alias"
31
- kb.register :parents, datafile_test(:person).parents
32
-
33
- agent = LLM::Agent.new knowledge_base: kb, model: 'mistral', url: "https://gepeto.bsc.es/"
34
-
35
- agent.system = ""
36
-
37
- sss 0
38
- ppp agent.ask "Who is Guille's brother-in-law. Brother in law is your spouses sibling or your sibling's spouse"
39
- end
40
- end
41
-
42
-
43
21
  end
44
22
 
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.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Vazquez