feat: Introduce the concept of tool registry within Captain (#11516)
This PR introduces the concept of a tool registry. The implementation is straightforward: you can define a tool by creating a class with a function name. The function name gets registered in the registry and can be referenced during LLM calls. When the LLM invokes a tool using the registered name, the registry locates and executes the appropriate tool. If the LLM calls an unregistered tool, the registry returns an error indicating that the tool is not defined.
This commit is contained in:
@@ -10,6 +10,8 @@ class Captain::Copilot::ChatService < Llm::BaseOpenAiService
|
||||
@conversation_history = config[:conversation_history]
|
||||
@previous_messages = config[:previous_messages] || []
|
||||
@language = config[:language] || 'english'
|
||||
|
||||
register_tools
|
||||
@messages = [system_message, conversation_history_context] + @previous_messages
|
||||
@response = ''
|
||||
end
|
||||
@@ -25,6 +27,11 @@ class Captain::Copilot::ChatService < Llm::BaseOpenAiService
|
||||
|
||||
private
|
||||
|
||||
def register_tools
|
||||
@tool_registry = Captain::ToolRegistryService.new(@assistant)
|
||||
@tool_registry.register_tool(Captain::Tools::SearchDocumentationService)
|
||||
end
|
||||
|
||||
def system_message
|
||||
{
|
||||
role: 'system',
|
||||
|
||||
@@ -9,6 +9,7 @@ class Captain::Llm::AssistantChatService < Llm::BaseOpenAiService
|
||||
@assistant = assistant
|
||||
@messages = [system_message]
|
||||
@response = ''
|
||||
register_tools
|
||||
end
|
||||
|
||||
def generate_response(input, previous_messages = [], role = 'user')
|
||||
@@ -19,6 +20,11 @@ class Captain::Llm::AssistantChatService < Llm::BaseOpenAiService
|
||||
|
||||
private
|
||||
|
||||
def register_tools
|
||||
@tool_registry = Captain::ToolRegistryService.new(@assistant)
|
||||
@tool_registry.register_tool(Captain::Tools::SearchDocumentationService)
|
||||
end
|
||||
|
||||
def system_message
|
||||
{
|
||||
role: 'system',
|
||||
|
||||
27
enterprise/app/services/captain/tool_registry_service.rb
Normal file
27
enterprise/app/services/captain/tool_registry_service.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
class Captain::ToolRegistryService
|
||||
attr_reader :registered_tools, :tools
|
||||
|
||||
def initialize(assistant)
|
||||
@assistant = assistant
|
||||
@registered_tools = []
|
||||
@tools = {}
|
||||
end
|
||||
|
||||
def register_tool(tool_class)
|
||||
tool = tool_class.new(@assistant)
|
||||
@tools[tool.name] = tool
|
||||
@registered_tools << tool.to_registry_format
|
||||
end
|
||||
|
||||
def method_missing(method_name, *arguments)
|
||||
if @tools.key?(method_name.to_s)
|
||||
@tools[method_name.to_s].execute(*arguments)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to_missing?(method_name, include_private = false)
|
||||
@tools.key?(method_name.to_s) || super
|
||||
end
|
||||
end
|
||||
34
enterprise/app/services/captain/tools/base_service.rb
Normal file
34
enterprise/app/services/captain/tools/base_service.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
class Captain::Tools::BaseService
|
||||
attr_accessor :assistant
|
||||
|
||||
def initialize(assistant)
|
||||
@assistant = assistant
|
||||
end
|
||||
|
||||
def name
|
||||
raise NotImplementedError, "#{self.class} must implement name"
|
||||
end
|
||||
|
||||
def description
|
||||
raise NotImplementedError, "#{self.class} must implement description"
|
||||
end
|
||||
|
||||
def parameters
|
||||
raise NotImplementedError, "#{self.class} must implement parameters"
|
||||
end
|
||||
|
||||
def execute(arguments)
|
||||
raise NotImplementedError, "#{self.class} must implement execute"
|
||||
end
|
||||
|
||||
def to_registry_format
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: name,
|
||||
description: description,
|
||||
parameters: parameters
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
class Captain::Tools::SearchDocumentationService < Captain::Tools::BaseService
|
||||
def name
|
||||
'search_documentation'
|
||||
end
|
||||
|
||||
def description
|
||||
'Search and retrieve documentation from knowledge base'
|
||||
end
|
||||
|
||||
def parameters
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
search_query: {
|
||||
type: 'string',
|
||||
description: 'The search query to look up in the documentation.'
|
||||
}
|
||||
},
|
||||
required: ['search_query']
|
||||
}
|
||||
end
|
||||
|
||||
def execute(arguments)
|
||||
query = arguments['search_query']
|
||||
Rails.logger.info { "#{self.class.name}: #{query}" }
|
||||
|
||||
responses = assistant.responses.approved.search(query)
|
||||
|
||||
return 'No FAQs found for the given query' if responses.empty?
|
||||
|
||||
responses.map { |response| format_response(response) }.join
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format_response(response)
|
||||
formatted_response = "
|
||||
Question: #{response.question}
|
||||
Answer: #{response.answer}
|
||||
"
|
||||
if response.documentable.present? && response.documentable.try(:external_link)
|
||||
formatted_response += "
|
||||
Source: #{response.documentable.external_link}
|
||||
"
|
||||
end
|
||||
|
||||
formatted_response
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user