Class: LLM::Agent
- Inherits:
-
Object
- Object
- LLM::Agent
- Defined in:
- lib/scout/llm/agent.rb,
lib/scout/llm/agent/chat.rb,
lib/scout/llm/agent/iterate.rb,
lib/scout/llm/agent/delegate.rb
Instance Attribute Summary collapse
-
#chats ⇒ Object
Returns the value of attribute chats.
-
#knowledge_base ⇒ Object
Returns the value of attribute knowledge_base.
-
#other_options ⇒ Object
Returns the value of attribute other_options.
-
#path ⇒ Object
Returns the value of attribute path.
-
#process_exception ⇒ Object
Returns the value of attribute process_exception.
-
#society ⇒ Object
Returns the value of attribute society.
-
#start_chat ⇒ Object
Returns the value of attribute start_chat.
-
#workflow(&block) ⇒ Object
Returns the value of attribute workflow.
Class Method Summary collapse
- .load_agent(agent_name = nil, options = {}) ⇒ Object
- .load_from_path(path, workflow: nil, knowledge_base: nil, chat: nil) ⇒ Object
Instance Method Summary collapse
-
#ask(messages = nil, options = {}) ⇒ Object
function: takes an array of messages and calls LLM.ask with them.
- #chat(options = {}) ⇒ Object
- #current_chat ⇒ Object
- #format_message(message, prefix = "user") ⇒ Object
- #get_previous_response_id ⇒ Object
-
#initialize(workflow: nil, knowledge_base: nil, start_chat: nil, **kwargs) ⇒ Agent
constructor
A new instance of Agent.
- #iterate(prompt = nil, &block) ⇒ Object
- #iterate_dictionary(prompt = nil, &block) ⇒ Object
- #json ⇒ Object
- #json_format(format, options = {}) ⇒ Object
- #load_agent(agent_name, options) ⇒ Object
- #load_chat(agent_name, options, chat) ⇒ Object
- #method_missing(name) ⇒ Object
- #prompt(messages, options = {}) ⇒ Object
- #respond ⇒ Object
- #socialize(options = {}) ⇒ Object
- #start(chat = nil) ⇒ Object
Constructor Details
#initialize(workflow: nil, knowledge_base: nil, start_chat: nil, **kwargs) ⇒ Agent
Returns a new instance of Agent.
14 15 16 17 18 19 20 |
# File 'lib/scout/llm/agent.rb', line 14 def initialize(workflow: nil, knowledge_base: nil, start_chat: nil, **kwargs) @workflow = workflow @workflow = Workflow.require_workflow @workflow if String === @workflow @knowledge_base = knowledge_base @other_options = IndiferentHash.setup(kwargs.dup) @start_chat = start_chat end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name) ⇒ Object
22 23 24 |
# File 'lib/scout/llm/agent/chat.rb', line 22 def method_missing(name,...) current_chat.send(name, ...) end |
Instance Attribute Details
#chats ⇒ Object
Returns the value of attribute chats.
4 5 6 |
# File 'lib/scout/llm/agent/delegate.rb', line 4 def chats @chats end |
#knowledge_base ⇒ Object
Returns the value of attribute knowledge_base.
13 14 15 |
# File 'lib/scout/llm/agent.rb', line 13 def knowledge_base @knowledge_base end |
#other_options ⇒ Object
Returns the value of attribute other_options.
13 14 15 |
# File 'lib/scout/llm/agent.rb', line 13 def @other_options end |
#path ⇒ Object
Returns the value of attribute path.
13 14 15 |
# File 'lib/scout/llm/agent.rb', line 13 def path @path end |
#process_exception ⇒ Object
Returns the value of attribute process_exception.
13 14 15 |
# File 'lib/scout/llm/agent.rb', line 13 def process_exception @process_exception end |
#society ⇒ Object
Returns the value of attribute society.
4 5 6 |
# File 'lib/scout/llm/agent/delegate.rb', line 4 def society @society end |
#start_chat ⇒ Object
Returns the value of attribute start_chat.
13 14 15 |
# File 'lib/scout/llm/agent.rb', line 13 def start_chat @start_chat end |
#workflow(&block) ⇒ Object
Returns the value of attribute workflow.
13 14 15 |
# File 'lib/scout/llm/agent.rb', line 13 def workflow @workflow end |
Class Method Details
.load_agent(agent_name = nil, options = {}) ⇒ Object
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/scout/llm/agent.rb', line 142 def self.load_agent(agent_name = nil, = {}) if agent_name && Path.is_filename?(agent_name) if File.directory?(agent_name) dir = Path.setup(agent_name) unless Path === agent_name if dir.agent.find_with_extension("rb").exists? return load dir.agent.find_with_extension("rb") end else return load agent_name end end agent_name ||= 'default' workflow_path = Scout.workflows[agent_name] agent_path = Scout.var.Agent[agent_name] agent_path = Scout.chats[agent_name] unless agent_path.exists? agent_path = Scout.chats.Agent[agent_name] unless agent_path.exists? raise ScoutException, "No agent found with name #{agent_name}" unless workflow_path.exists? || agent_path.exists? workflow = if workflow_path.exists? agent_path = workflow_path Workflow.require_workflow agent_name elsif agent_path.workflow.find_with_extension("rb").exists? Workflow.require_workflow_file agent_path.workflow.find_with_extension("rb") elsif agent_path.python.exists? && agent_path.python.glob('*.py').any? require 'scout/workflow/python' PythonWorkflow.load_directory agent_path.python, 'ScoutAgent' end knowledge_base = if agent_path.knowledge_base.exists? KnowledgeBase.load agent_path.knowledge_base.find elsif workflow_path.knowledge_base.exists? KnowledgeBase.load workflow_path.knowledge_base.find end chat = if agent_path.start_chat.exists? Chat.setup LLM.chat(agent_path.start_chat.find) elsif workflow_path.start_chat.exists? Chat.setup LLM.chat(workflow_path.start_chat.find) elsif agent_path.start_chat.exists? Chat.setup LLM.chat(agent_path.start_chat.find) elsif workflow && workflow.documentation[:description] Chat.setup([ {role: 'introduce', content: workflow.name} ]) end agent = LLM::Agent.new **.merge(workflow: workflow, knowledge_base: knowledge_base, start_chat: chat) agent.path = agent_path.find if agent_path agent end |
.load_from_path(path, workflow: nil, knowledge_base: nil, chat: nil) ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/scout/llm/agent.rb', line 130 def self.load_from_path(path, workflow: nil, knowledge_base: nil, chat: nil) workflow_path = path['workflow.rb'].find knowledge_base_path = path['knowledge_base'] chat_path = path['start_chat'] workflow ||= Workflow.require_workflow workflow_path if workflow_path.exists? knowledge_base ||= KnowledgeBase.new knowledge_base_path if knowledge_base_path.exists? chat ||= Chat.setup LLM.chat(chat_path.find) if chat_path.exists? LLM::Agent.new workflow: workflow, knowledge_base: knowledge_base, start_chat: chat end |
Instance Method Details
#ask(messages = nil, options = {}) ⇒ Object
function: takes an array of messages and calls LLM.ask with them
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/scout/llm/agent.rb', line 64 def ask( = nil, = {}) , = nil, if .empty? && Hash === = current_chat if .nil? = [] unless .is_a? Array model ||= @model if model .delete_if{|info| info[:role] == 'agent' } if (list = .select{|info| info[:role] == 'socialize'}).any? = list.last[:content] .delete_if{|info| info[:role] == 'socialize' } self.(.dup) if && %w(true TRUE True T 1).include?(.to_s) end tools = [:tools] || {} if other_tools = @other_options[:tools] other_tools = JSON.parse other_tools if String === other_tools tools = tools.merge other_tools end begin if workflow || knowledge_base tools.merge!(LLM.workflow_tools(workflow)) if workflow tools.merge!(LLM.knowledge_base_tool_definition(knowledge_base)) if knowledge_base and knowledge_base.all_databases.any? end if workflow && workflow.tasks.include?(:ask) .each do |key,value| .push(IndiferentHash.setup({role: :option, content: "#{key} #{value}"})) end job = workflow.job(:ask, chat: Chat.print()) job.clean job.produce = LLM.chat job.path if [:return_messages] else Chat.answer end else [:tools] = tools LLM.ask , @other_options.merge(log_errors: true).merge().merge(agent: false) end rescue exception = $! if Proc === self.process_exception try_again = self.process_exception.call exception if try_again retry else raise exception end else raise exception end end end |
#chat(options = {}) ⇒ Object
31 32 33 34 35 36 37 38 39 40 |
# File 'lib/scout/llm/agent/chat.rb', line 31 def chat( = {}) response = ask(current_chat, .merge(return_messages: true)) if Array === response current_chat.concat(response) current_chat.answer else current_chat.push({role: :assistant, content: response}) response end end |
#current_chat ⇒ Object
18 19 20 |
# File 'lib/scout/llm/agent/chat.rb', line 18 def current_chat @current_chat ||= start end |
#format_message(message, prefix = "user") ⇒ Object
38 39 40 41 42 |
# File 'lib/scout/llm/agent.rb', line 38 def (, prefix = "user") .split(/\n\n+/).reject{|line| line.empty? }.collect do |line| prefix + "\t" + line.gsub("\n", ' ') end * "\n" end |
#get_previous_response_id ⇒ Object
71 72 73 74 |
# File 'lib/scout/llm/agent/chat.rb', line 71 def get_previous_response_id msg = current_chat.reverse.find{|msg| msg[:role].to_sym == :previous_response_id } msg.nil? ? nil : msg['content'] end |
#iterate(prompt = nil, &block) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# File 'lib/scout/llm/agent/iterate.rb', line 4 def iterate(prompt = nil, &block) self.endpoint :responses self.user prompt if prompt obj = self.json_format({ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "content": { "type": "array", "items": { "type": "string" } } }, "required": ["content"], "additionalProperties": false }) self.option :format, :text list = Hash === obj ? obj['content'] : obj list.each &block end |
#iterate_dictionary(prompt = nil, &block) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/scout/llm/agent/iterate.rb', line 28 def iterate_dictionary(prompt = nil, &block) self.endpoint :responses self.user prompt if prompt dict = self.json_format({ name: 'dictionary', type: 'object', properties: {}, additionalProperties: {type: :string} }) self.option :format, :text dict.each &block end |
#json ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/scout/llm/agent/chat.rb', line 43 def json(...) current_chat.format :json output = ask(current_chat, ...) current_chat.format nil obj = JSON.parse output if (Hash === obj) and obj.keys == ['content'] obj['content'] else obj end end |
#json_format(format, options = {}) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/scout/llm/agent/chat.rb', line 55 def json_format(format, = {}) current_chat.format format output = ask(current_chat, .merge({return_messages: false})) obj = begin JSON.parse output rescue JSON::ParserError Log.warn "Not valid JSON:" + output raise $! end if (Hash === obj) and obj.keys == ['content'] obj['content'] else obj end end |
#load_agent(agent_name, options) ⇒ Object
6 7 8 9 10 11 |
# File 'lib/scout/llm/agent/delegate.rb', line 6 def load_agent(agent_name, ) raise ParameterException, "Agent name must be a single word optionally including a few puntuation characters" unless agent_name =~ /^[a-z_.-]*$/i @society ||= {} @society[agent_name] ||= LLM.load_agent agent_name, .merge(self.) end |
#load_chat(agent_name, options, chat) ⇒ Object
13 14 15 16 |
# File 'lib/scout/llm/agent/delegate.rb', line 13 def load_chat(agent_name, , chat) @chats ||= {} @chats[chat] ||= load_agent(agent_name, ).clone end |
#prompt(messages, options = {}) ⇒ Object
124 125 126 127 128 |
# File 'lib/scout/llm/agent.rb', line 124 def prompt(, = {}) = LLM.chat if String === = Chat.follow start_chat, ask , end |
#respond ⇒ Object
26 27 28 |
# File 'lib/scout/llm/agent/chat.rb', line 26 def respond(...) self.ask(current_chat, ...) end |
#socialize(options = {}) ⇒ Object
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/scout/llm/agent/delegate.rb', line 18 def ( = {}) @other_options[:tools] ||= {} @society ||= {} task_name = :ask block ||= Proc.new do |name, parameters| agent_name, prompt, chat_id = IndiferentHash. parameters.dup, :agent, :prompt, :chat, chat: 'current' begin = .dup res = case chat_id when 'current' agent = load_chat agent_name, , 'current' chat = self.current_chat - self.start_chat agent.concat chat agent.user prompt agent.chat when 'none', nil, 'false' agent = load_agent agent_name, agent.prompt prompt else agent = load_chat agent_name, , chat_id agent.user prompt agent.chat end res rescue ScoutException next $! end end properties = { agent: { "type": :string, "description": "Name of the agent" }, prompt: { "type": :string, "description": "Prompt to pass to the agent" }, chat: { "type": :string, "description": "(Optional) Chat identifier used to keep conversation history. The default is 'current' uses the conversation that the caller agent is involved with", "default": 'current' } } required_inputs = [:agent, :prompt] description =<<-EOF The 'ask' function is used to send a prompt to an agent, returning the agents response. You can keep one-shot questions or keep running conversations with the same agent by giving the chat an identifier. The chat id 'current' has the special meaning of passing the entire conversation to the agent, not just the prompt. EOF function = { name: task_name, description: description, parameters: { type: "object", properties: properties, required: required_inputs } } definition = IndiferentHash.setup function.merge(type: 'function', function: function) @other_options[:tools][task_name] = [block, definition] end |
#start(chat = nil) ⇒ Object
7 8 9 10 11 12 13 14 15 16 |
# File 'lib/scout/llm/agent/chat.rb', line 7 def start(chat=nil) if chat (@current_chat || start_chat).annotate chat unless Chat === chat @current_chat = chat else start_chat = self.start_chat Chat.setup(start_chat) unless Chat === start_chat @current_chat = start_chat.branch end end |