Module: LLM
- Defined in:
- lib/scout/llm/ask.rb,
lib/scout/llm/rag.rb,
lib/scout/llm/chat.rb,
lib/scout/llm/agent.rb,
lib/scout/llm/embed.rb,
lib/scout/llm/tools.rb,
lib/scout/llm/utils.rb,
lib/scout/llm/tools/mcp.rb,
lib/scout/llm/agent/chat.rb,
lib/scout/llm/tools/call.rb,
lib/scout/llm/agent/iterate.rb,
lib/scout/llm/agent/delegate.rb,
lib/scout/llm/backends/relay.rb,
lib/scout/llm/tools/workflow.rb,
lib/scout/llm/backends/ollama.rb,
lib/scout/llm/backends/openai.rb,
lib/scout/llm/backends/bedrock.rb,
lib/scout/llm/backends/anthropic.rb,
lib/scout/llm/backends/openwebui.rb,
lib/scout/llm/backends/responses.rb,
lib/scout/llm/backends/huggingface.rb,
lib/scout/llm/tools/knowledge_base.rb
Defined Under Namespace
Modules: Anthropic, Bedrock, Huggingface, OLlama, OpenAI, OpenWebUI, Relay, Responses Classes: Agent, RAG
Instance Attribute Summary collapse
-
#max_content_length ⇒ Object
Returns the value of attribute max_content_length.
Class Method Summary collapse
- .agent ⇒ Object
- .ask(question, options = {}, &block) ⇒ Object
- .associations ⇒ Object
- .call_id_name_and_arguments(tool_call) ⇒ Object
- .call_knowledge_base(knowledge_base, database, parameters = {}) ⇒ Object
- .call_tools(tool_calls, &block) ⇒ Object
- .call_workflow(workflow, task_name, parameters = {}) ⇒ Object
- .chat(file, original = nil) ⇒ Object
- .database_details_tool_definition(database, undirected, fields) ⇒ Object
- .database_tool_definition(database, undirected = false, database_description = nil) ⇒ Object
- .embed(text, options = {}) ⇒ Object
- .get_url_config(key, url = nil, *tokens) ⇒ Object
- .get_url_server_tokens(url, prefix = nil) ⇒ Object
- .knowledge_base_ask(knowledge_base, question, options = {}) ⇒ Object
- .knowledge_base_tool_definition(knowledge_base, databases = nil) ⇒ Object
- .mcp_tools(url, options = {}) ⇒ Object
- .messages(question, role = nil) ⇒ Object
- .options ⇒ Object
- .print ⇒ Object
- .process_calls(tools, calls, &block) ⇒ Object
- .purge ⇒ Object
- .run_tools(messages) ⇒ Object
- .task_tool_definition(workflow, task_name, inputs = nil) ⇒ Object
- .tool_definitions_to_ollama(tools) ⇒ Object
- .tool_definitions_to_reponses(tools) ⇒ Object (also: tool_definitions_to_openai)
- .tool_response(tool_call, &block) ⇒ Object
- .tools ⇒ Object
- .tools_to_anthropic(messages) ⇒ Object
- .tools_to_ollama(messages) ⇒ Object
- .tools_to_openai(messages) ⇒ Object
- .workflow_ask(workflow, question, options = {}) ⇒ Object
- .workflow_tools(workflow, tasks = nil) ⇒ Object
Instance Attribute Details
#max_content_length ⇒ Object
Returns the value of attribute max_content_length.
3 4 5 |
# File 'lib/scout/llm/tools/call.rb', line 3 def max_content_length @max_content_length end |
Class Method Details
.agent ⇒ Object
4 5 6 |
# File 'lib/scout/llm/agent.rb', line 4 def self.agent(...) LLM::Agent.new(...) end |
.ask(question, options = {}, &block) ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 16 17 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 |
# File 'lib/scout/llm/ask.rb', line 5 def self.ask(question, = {}, &block) = LLM.chat(question) = IndiferentHash.add_defaults LLM.(), agent = IndiferentHash. , :agent if agent agent_file = Scout.workflows[agent] agent_file = Scout.chats[agent] unless agent_file.exists? agent_file = agent_file.find_with_extension('rb') unless agent_file.exists? if agent_file.exists? if agent_file.directory? if agent_file.agent.find_with_extension('rb').exists? agent = load agent_file.agent.find_with_extension('rb') else agent = LLM::Agent.load_from_path agent_file end else agent = load agent_file end else raise "Agent not found: #{agent}" end return agent.ask(question, ) end endpoint, persist = IndiferentHash. , :endpoint, :persist, persist: true endpoint ||= Scout::Config.get :endpoint, :ask, :llm, env: 'ASK_ENDPOINT,LLM_ENDPOINT,ENDPOINT,LLM,ASK' if endpoint && Scout.etc.AI[endpoint].exists? = IndiferentHash.add_defaults , Scout.etc.AI[endpoint].yaml elsif endpoint && endpoint != "" raise "Endpoint not found #{endpoint}" end if [:backend].to_s == 'responses' = Chat.clear(, 'previous_response_id') else = Chat.clean(, 'previous_response_id') .delete :previous_response_id end Log.high Log.color :green, "Asking #{endpoint || 'client'}: #{[:previous_response_id]}\n" + LLM.print() tools = [:tools] Log.high "Tools: #{Log.fingerprint tools.keys}}" if tools Log.debug "#{Log.fingerprint tools}}" if tools res = Persist.persist(endpoint, :json, prefix: "LLM ask", other: .merge(messages: ), persist: persist) do backend = IndiferentHash. , :backend backend ||= Scout::Config.get :backend, :ask, :llm, env: 'ASK_BACKEND,LLM_BACKEND', default: :openai case backend when :openai, "openai" require_relative 'backends/openai' LLM::OpenAI.ask(, , &block) when :anthropic, "anthropic" require_relative 'backends/anthropic' LLM::Anthropic.ask(, , &block) when :responses, "responses" require_relative 'backends/responses' LLM::Responses.ask(, , &block) when :ollama, "ollama" require_relative 'backends/ollama' LLM::OLlama.ask(, , &block) when :openwebui, "openwebui" require_relative 'backends/openwebui' LLM::OpenWebUI.ask(, , &block) when :relay, "relay" require_relative 'backends/relay' LLM::Relay.ask(, , &block) when :bedrock, "bedrock" require_relative 'backends/bedrock' LLM::Bedrock.ask(, , &block) else raise "Unknown backend: #{backend}" end end Log.high Log.color :blue, "Response:\n" + LLM.print(res) res end |
.associations ⇒ Object
59 60 61 |
# File 'lib/scout/llm/chat.rb', line 59 def self.associations(...) Chat.associations(...) end |
.call_id_name_and_arguments(tool_call) ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# File 'lib/scout/llm/tools/call.rb', line 5 def self.call_id_name_and_arguments(tool_call) tool_call_id = tool_call.dig("call_id") || tool_call.dig("id") if tool_call['function'] function_name = tool_call.dig("function", "name") function_arguments = tool_call.dig("function", "arguments") else function_name = tool_call.dig("name") function_arguments = tool_call.dig("arguments") end function_arguments = JSON.parse(function_arguments, { symbolize_names: true }) if String === function_arguments [tool_call_id, function_name, function_arguments] end |
.call_knowledge_base(knowledge_base, database, parameters = {}) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/scout/llm/tools/knowledge_base.rb', line 131 def self.call_knowledge_base(knowledge_base, database, parameters={}) if database.end_with?('_association_details') database = database.sub('_association_details', '') associations, fields = IndiferentHash. parameters, :associations, :fields index = knowledge_base.get_index(database) if fields field_pos = fields.collect{|f| index.identify_field f } associations.each_with_object({}) do |a,hash| values = index[a] next if values.nil? hash[a] = values.values_at *field_pos end else associations.each_with_object({}) do |a,hash| values = index[a] next if values.nil? hash[a] = values end end else entities, reverse = IndiferentHash. parameters, :entities, :reverse if reverse knowledge_base.parents(database, entities) else knowledge_base.children(database, entities) end end end |
.call_tools(tool_calls, &block) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 |
# File 'lib/scout/llm/tools.rb', line 7 def self.call_tools(tool_calls, &block) tool_calls.collect{|tool_call| = LLM.tool_response(tool_call, &block) function_call = tool_call function_call['id'] = tool_call.delete('call_id') if tool_call.dig('call_id') [ {role: "function_call", content: tool_call.to_json}, {role: "function_call_output", content: .to_json}, ] }.flatten end |
.call_workflow(workflow, task_name, parameters = {}) ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/scout/llm/tools/workflow.rb', line 89 def self.call_workflow(workflow, task_name, parameters={}) jobname = parameters.delete :jobname begin exec_type = parameters[:exec_type] job = workflow.job(task_name, jobname, parameters) if workflow.exec_exports.include?(task_name.to_sym) || parameters[:exec_type].to_s == 'exec' job.exec else raise ScoutException, 'Potential recursive call' if parameters[:allow_recursive] != 'true' && (job.running? and job.info[:pid] == Process.pid) Workflow.produce(job) job.join job.load end rescue ScoutException return $! end end |
.chat(file, original = nil) ⇒ Object
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/scout/llm/chat.rb', line 24 def self.chat(file, original = nil) original ||= (String === file and Open.exists?(file)) ? file : Path.setup($0.dup) caller_lib_dir = Path.caller_lib_dir(nil, 'chats') if String === file && Open.exists?(file) = self. Open.read(file) else = self. file end = Chat.indiferent = Chat.imports , original, caller_lib_dir = Chat.clear = Chat.clean = Chat.tasks = Chat.jobs = Chat.files , original, caller_lib_dir Chat.setup end |
.database_details_tool_definition(database, undirected, fields) ⇒ Object
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/scout/llm/tools/knowledge_base.rb', line 61 def self.database_details_tool_definition(database, undirected, fields) if undirected properties = { associations: { type: "array", items: { type: :string }, description: "Associations in the form of source~target or target~source" }, fields: { type: "array", items: { type: :string }, description: "Limit the response to these fields" }, } else properties = { associations: { type: "array", items: { type: :string }, description: "Associations in the form of source~target" }, } end if fields.length > 1 description = <<-EOF Return details of association as a dictionary object. Each key is an association and the value is an array with the values of the different fields you asked for, or for all fields otherwise. The fields are: #{fields * ', '}. Multiple values may be present and use the charater ';' to separate them. EOF else properties.delete(:fields) description = <<-EOF Return the #{fields.first} of association. Multiple values may be present and use the charater ';' to separate them. EOF end function = { name: database.to_s + '_association_details', description: description, parameters: { type: "object", properties: properties, required: ['associations'] } } IndiferentHash.setup function end |
.database_tool_definition(database, undirected = false, database_description = nil) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 |
# File 'lib/scout/llm/tools/knowledge_base.rb', line 4 def self.database_tool_definition(database, undirected = false, database_description = nil) if undirected properties = { entities: { type: "array", items: { type: :string }, description: "Entities for which to find associations" }, } else properties = { entities: { type: "array", items: { type: :string }, description: "Source entities in the association, or target entities if 'reverse' is 'true'" }, reverse: { type: "boolean", description: "Look for targets instead of sources, defaults to 'false'" } } end if database_description and not database_description.strip.empty? description = <<-EOF Find associations for a list of entities in database #{database}: #{database_description} EOF else description = <<-EOF Find associations for a list of entities in database #{database}. EOF end if undirected description += <<-EOF Returns a list in the format entity~partner. EOF else description += <<-EOF Returns a list in the format source~target. EOF end function = { name: database, description: description, parameters: { type: "object", properties: properties, required: ['entities'] } } IndiferentHash.setup function.merge(type: 'function', function: function) end |
.embed(text, options = {}) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/scout/llm/embed.rb', line 4 def self.(text, = {}) endpoint = IndiferentHash. , :endpoint endpoint ||= Scout::Config.get :endpoint, :embed, :llm, env: 'EMBED_ENDPOINT,LLM_ENDPOINT', default: :openai if endpoint && Scout.etc.AI[endpoint].exists? = IndiferentHash.add_defaults , Scout.etc.AI[endpoint].yaml end backend = IndiferentHash. , :backend backend ||= Scout::Config.get :backend, :embed, :llm, env: 'EMBED_BACKEND,LLM_BACKEND', default: :openai case backend when :openai, "openai" require_relative 'backends/openai' LLM::OpenAI.(text, ) when :ollama, "ollama" require_relative 'backends/ollama' LLM::OLlama.(text, ) when :openwebui, "openwebui" require_relative 'backends/openwebui' LLM::OpenWebUI.(text, ) when :relay, "relay" require_relative 'backends/relay' LLM::Relay.(text, ) else raise "Unknown backend: #{backend}" end end |
.get_url_config(key, url = nil, *tokens) ⇒ Object
15 16 17 18 19 20 21 22 23 24 |
# File 'lib/scout/llm/utils.rb', line 15 def self.get_url_config(key, url = nil, *tokens) hash = tokens.pop if Hash === tokens.last if url url_tokens = tokens.inject([]){|acc,prefix| acc.concat(get_url_server_tokens(url, prefix))} all_tokens = url_tokens + tokens else all_tokens = tokens end Scout::Config.get(key, *all_tokens, hash) end |
.get_url_server_tokens(url, prefix = nil) ⇒ Object
2 3 4 5 6 7 8 9 10 11 12 13 |
# File 'lib/scout/llm/utils.rb', line 2 def self.get_url_server_tokens(url, prefix=nil) return get_url_server_tokens(url).collect{|e| prefix.to_s + "." + e } if prefix server = url.match(/(?:https?:\/\/)?([^\/:]*)/)[1] || "NOSERVER" parts = server.split(".") parts.pop if parts.last.length <= 3 combinations = [] (1..parts.length).each do |l| parts.each_cons(l){|p| combinations << p*"."} end (parts + combinations + [server]).uniq end |
.knowledge_base_ask(knowledge_base, question, options = {}) ⇒ Object
99 100 101 102 103 104 105 106 107 |
# File 'lib/scout/llm/ask.rb', line 99 def self.knowledge_base_ask(knowledge_base, question, = {}) knowledge_base_tools = LLM.knowledge_base_tool_definition(knowledge_base) self.ask(question, .merge(tools: knowledge_base_tools)) do |task_name,parameters| parameters = IndiferentHash.setup(parameters) database, entities = parameters.values_at "database", "entities" Log.info "Finding #{entities} children in #{database}" knowledge_base.children(database, entities).collect{|e| e.sub('~', '=>')} end end |
.knowledge_base_tool_definition(knowledge_base, databases = nil) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/scout/llm/tools/knowledge_base.rb', line 115 def self.knowledge_base_tool_definition(knowledge_base, databases = nil) databases ||= knowledge_base.all_databases databases.inject({}){|tool_definitions,database| database_description = knowledge_base.description(database) undirected = knowledge_base.undirected(database) definition = self.database_tool_definition(database, undirected, database_description) tool_definitions.merge!(database => [knowledge_base, definition]) if (fields = knowledge_base.get_database(database).fields).any? details_definition = self.database_details_tool_definition(database, undirected, fields) tool_definitions.merge!(database.to_s + '_association_details' => [knowledge_base, details_definition]) end tool_definitions } end |
.mcp_tools(url, options = {}) ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 16 17 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 |
# File 'lib/scout/llm/tools/mcp.rb', line 5 def self.mcp_tools(url, = {}) timeout = Scout::Config.get :timeout, :mcp, :tools = IndiferentHash.add_defaults , read_timeout: timeout.to_i if timeout && timeout != "" if url == 'stdio' client = MCPClient.create_client(mcp_server_configs: [.merge(type: 'stdio')]) else type = IndiferentHash. , :type, type: (Open.remote?(url) ? :http : :stdio) if url && Open.remote?(url) token ||= LLM.get_url_config(:key, url, :mcp) [:headers] = { 'Authorization' => "Bearer #{token}" } end client = MCPClient.create_client(mcp_server_configs: [.merge(type: 'http', url: url)]) end tools = client.list_tools tool_definitions = IndiferentHash.setup({}) tools.each do |tool| name = tool.name description = tool.description schema = tool.schema function = { name: name, description: description, parameters: schema } definition = IndiferentHash.setup function.merge(type: 'function', function: function) block = Proc.new do |name,params| res = tool.server.call_tool(name, params) if Hash === res && res['content'] res = res['content'] end if Array === res and res.length == 1 res = res.first end if Hash === res && res['content'] res = res['content'] end if Hash === res && res['text'] res = res['text'] end res end tool_definitions[name] = [block, definition] end tool_definitions end |
.messages(question, role = nil) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/scout/llm/chat.rb', line 9 def self.(question, role = nil) default_role = "user" if Array === question return question.collect do |q| if String === q {role: role || default_role, content: q} else q end end end Chat.parse question end |
.options ⇒ Object
47 48 49 |
# File 'lib/scout/llm/chat.rb', line 47 def self.(...) Chat.(...) end |
.print ⇒ Object
51 52 53 |
# File 'lib/scout/llm/chat.rb', line 51 def self.print(...) Chat.print(...) end |
.process_calls(tools, calls, &block) ⇒ Object
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 |
# File 'lib/scout/llm/tools/call.rb', line 20 def self.process_calls(tools, calls, &block) max_content_length = LLM.max_content_length IndiferentHash.setup tools calls.collect do |tool_call| tool_call_id, function_name, function_arguments = call_id_name_and_arguments(tool_call) function_arguments = IndiferentHash.setup function_arguments obj, definition = tools[function_name] definition = obj if Hash === obj defaults = definition[:parameters][:defaults] if definition && definition[:parameters] function_arguments = function_arguments.merge(defaults) if defaults Log.high "Calling #{function_name} (#{Log.fingerprint function_arguments}): " function_response = case obj when Proc obj.call function_name, function_arguments when Workflow call_workflow(obj, function_name, function_arguments) when KnowledgeBase call_knowledge_base(obj, function_name, function_arguments) else if block_given? block.call function_name, function_arguments else ParameterException.new "Tool or function not found '#{function_name}'. Called with parameters #{Log.fingerprint function_arguments}" if obj.nil? && definition.nil? end end content = case function_response when String function_response when nil "success" when Exception {exception: function_response., stack: function_response.backtrace }.to_json else function_response.to_json end content = content.to_s if Numeric === content Log.high "Called #{function_name}: " + Log.fingerprint(content) if content.length > max_content_length 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}." Log.high exception_msg content = {exception: exception_msg, stack: caller}.to_json end = { id: tool_call_id, role: "tool", content: content } function_call = tool_call.dup function_call['id'] = function_call.delete('call_id') if function_call.dig('call_id') [ {role: "function_call", content: function_call.to_json}, {role: "function_call_output", content: .to_json}, ] end.flatten end |
.purge ⇒ Object
63 64 65 |
# File 'lib/scout/llm/chat.rb', line 63 def self.purge(...) Chat.purge(...) end |
.run_tools(messages) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/scout/llm/tools.rb', line 57 def self.run_tools() .collect do |info| IndiferentHash.setup(info) role = info[:role] if role == 'cmd' { role: 'tool', content: CMD.cmd(info[:content]).read } else info end end end |
.task_tool_definition(workflow, task_name, inputs = nil) ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 |
# File 'lib/scout/llm/tools/workflow.rb', line 3 def self.task_tool_definition(workflow, task_name, inputs = nil) task_info = workflow.task_info(task_name) return nil if task_info.nil? if inputs names = [] defaults = {} inputs.each do |i| if String === i && i.include?('=') name,_ , value = i.partition("=") defaults[name] = value else names << i.to_sym end end end properties = task_info[:inputs].inject({}) do |acc,input| next acc if names and not names.include?(input) type = task_info[:input_types][input] description = task_info[:input_descriptions][input] type = :string if type == :text type = :string if type == :select type = :string if type == :path type = :number if type == :float type = :array if type.to_s.end_with?('_array') acc[input] = { "type": type, "description": description } if type == :array acc[input]['items'] = {type: :string} end if = task_info[:input_options][input] if = [:select_options] = .values if Hash === acc[input]["enum"] = end end acc end required_inputs = task_info[:inputs].select do |input| next if names and not names.include?(input.to_sym) task_info[:input_options].include?(input) && task_info[:input_options][input][:required] end function = { name: task_name, description: task_info[:description], parameters: { type: "object", properties: properties, required: required_inputs, } } function[:parameters][:defaults] = defaults if defaults #IndiferentHash.setup function.merge(type: 'function', function: function) IndiferentHash.setup function end |
.tool_definitions_to_ollama(tools) ⇒ Object
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/scout/llm/tools.rb', line 165 def self.tool_definitions_to_ollama(tools) tools.values.collect do |obj,definition| definition = obj if Hash === obj definition = IndiferentHash.setup definition definition = case definition[:function] when Hash definition else {type: :function, function: definition} end definition = IndiferentHash.add_defaults definition, type: :function definition end end |
.tool_definitions_to_reponses(tools) ⇒ Object Also known as: tool_definitions_to_openai
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/scout/llm/tools.rb', line 141 def self.tool_definitions_to_reponses(tools) tools.values.collect do |obj,definition| definition = obj if Hash === obj definition definition = case definition[:function] when Hash definition.merge(definition.delete :function) else definition end definition = IndiferentHash.add_defaults definition, type: :function definition[:parameters].delete :defaults if definition[:parameters] definition end end |
.tool_response(tool_call, &block) ⇒ Object
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 |
# File 'lib/scout/llm/tools.rb', line 19 def self.tool_response(tool_call, &block) tool_call_id = tool_call.dig("call_id") || tool_call.dig("id") if tool_call['function'] function_name = tool_call.dig("function", "name") function_arguments = tool_call.dig("function", "arguments") else function_name = tool_call.dig("name") function_arguments = tool_call.dig("arguments") end function_arguments = JSON.parse(function_arguments, { symbolize_names: true }) if String === function_arguments Log.high "Calling function #{function_name} with arguments #{Log.fingerprint function_arguments}" function_response = begin block.call function_name, function_arguments rescue $! end content = case function_response when String function_response when nil "success" when Exception {exception: function_response., stack: function_response.backtrace }.to_json else function_response.to_json end content = content.to_s if Numeric === content { id: tool_call_id, role: "tool", content: content } end |
.tools ⇒ Object
55 56 57 |
# File 'lib/scout/llm/chat.rb', line 55 def self.tools(...) Chat.tools(...) end |
.tools_to_anthropic(messages) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/scout/llm/tools.rb', line 95 def self.tools_to_anthropic() .collect do || if [:role] == 'function_call' tool_call = JSON.parse([:content]) arguments = tool_call.delete('arguments') || {} name = tool_call[:name] tool_call['type'] = 'tool_use' tool_call['name'] ||= name tool_call['input'] = arguments {role: 'assistant', content: [tool_call]} elsif [:role] == 'function_call_output' info = JSON.parse([:content]) id = info.delete('call_id') || info.delete('id') info.delete "role" info['tool_use_id'] = id info['type'] = 'tool_result' {role: 'user', content: [info]} else end end.flatten end |
.tools_to_ollama(messages) ⇒ Object
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/scout/llm/tools.rb', line 118 def self.tools_to_ollama() .collect do || if [:role] == 'function_call' tool_call = JSON.parse([:content]) arguments = tool_call.delete('arguments') || {} id = tool_call.delete('id') name = tool_call.delete('name') tool_call['type'] = 'function' tool_call['function'] ||= {} tool_call['function']['name'] ||= name tool_call['function']['arguments'] ||= arguments {role: 'assistant', tool_calls: [tool_call]} elsif [:role] == 'function_call_output' info = JSON.parse([:content]) id = info.delete('id') || '' info['role'] = 'tool' info else end end.flatten end |
.tools_to_openai(messages) ⇒ Object
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/scout/llm/tools.rb', line 72 def self.tools_to_openai() .collect do || if [:role] == 'function_call' tool_call = JSON.parse([:content]) arguments = tool_call.delete('arguments') || {} name = tool_call[:name] tool_call['type'] = 'function' tool_call['function'] ||= {} tool_call['function']['name'] ||= name tool_call['function']['arguments'] = arguments.to_json {role: 'assistant', tool_calls: [tool_call]} elsif [:role] == 'function_call_output' info = JSON.parse([:content]) id = info.delete('call_id') || info.dig('id') info['role'] = 'tool' info['tool_call_id'] = id info else end end.flatten end |
.workflow_ask(workflow, question, options = {}) ⇒ Object
92 93 94 95 96 97 |
# File 'lib/scout/llm/ask.rb', line 92 def self.workflow_ask(workflow, question, = {}) workflow_tools = LLM.workflow_tools(workflow) self.ask(question, .merge(tools: workflow_tools)) do |task_name,parameters| workflow.job(task_name, parameters).run end end |
.workflow_tools(workflow, tasks = nil) ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/scout/llm/tools/workflow.rb', line 73 def self.workflow_tools(workflow, tasks = nil) if Array === workflow workflow.inject({}){|tool_definitions,wf| tool_definitions.merge(workflow_tools(wf, tasks)) } else tasks = workflow.all_exports if tasks.nil? tasks = workflow.all_tasks if tasks.empty? tasks.inject({}){|tool_definitions,task_name| definition = self.task_tool_definition(workflow, task_name) next if definition.nil? tool_definitions.merge(task_name => [workflow, definition]) } end end |