Merge branch 'develop' into rethinking-copilot
This commit is contained in:
@@ -2,12 +2,11 @@ module Captain::ChatHelper
|
||||
def request_chat_completion
|
||||
Rails.logger.debug { "[CAPTAIN][ChatCompletion] #{@messages}" }
|
||||
|
||||
available_tools = @tool_registry&.registered_tools || []
|
||||
response = @client.chat(
|
||||
parameters: {
|
||||
model: @model,
|
||||
messages: @messages,
|
||||
tools: available_tools,
|
||||
tools: @tool_registry&.registered_tools || [],
|
||||
response_format: { type: 'json_object' }
|
||||
}
|
||||
)
|
||||
@@ -37,17 +36,24 @@ module Captain::ChatHelper
|
||||
end
|
||||
|
||||
def process_tool_call(tool_call)
|
||||
arguments = JSON.parse(tool_call['function']['arguments'])
|
||||
function_name = tool_call['function']['name']
|
||||
tool_call_id = tool_call['id']
|
||||
function_name = tool_call['function']['name']
|
||||
arguments = JSON.parse(tool_call['function']['arguments'])
|
||||
|
||||
if @tool_registry.respond_to?(function_name)
|
||||
execute_tool_call(tool_call_id, function_name, arguments)
|
||||
execute_tool(function_name, arguments, tool_call_id)
|
||||
else
|
||||
process_invalid_tool_call(tool_call_id, function_name)
|
||||
end
|
||||
end
|
||||
|
||||
def execute_tool(function_name, arguments, tool_call_id)
|
||||
result = @tool_registry.send(function_name, arguments)
|
||||
append_tool_response(result, tool_call_id)
|
||||
end
|
||||
|
||||
def append_tool_calls(tool_calls)
|
||||
@messages << {
|
||||
role: 'assistant',
|
||||
|
||||
@@ -61,6 +61,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
|
||||
Rails.logger.info("[CAPTAIN][CopilotChatService] Generating system message for product=#{@assistant.config['product_name']} language=#{@language}")
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ class Captain::Llm::AssistantChatService < Llm::BaseOpenAiService
|
||||
@tool_registry = Captain::ToolRegistryService.new(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',
|
||||
|
||||
@@ -5,7 +5,6 @@ class Captain::ToolRegistryService
|
||||
@assistant = assistant
|
||||
@registered_tools = []
|
||||
@tools = {}
|
||||
register_default_tools
|
||||
end
|
||||
|
||||
def register_tool(tool_class)
|
||||
@@ -14,10 +13,6 @@ class Captain::ToolRegistryService
|
||||
@registered_tools << tool.to_registry_format
|
||||
end
|
||||
|
||||
def register_default_tools
|
||||
register_tool(Captain::Tools::SearchDocumentationService)
|
||||
end
|
||||
|
||||
def method_missing(method_name, *arguments)
|
||||
if @tools.key?(method_name.to_s)
|
||||
@tools[method_name.to_s].execute(*arguments)
|
||||
|
||||
@@ -22,13 +22,13 @@ class Captain::Tools::SearchDocumentationService < Captain::Tools::BaseService
|
||||
|
||||
def execute(arguments)
|
||||
query = arguments['search_query']
|
||||
Rails.logger.debug { "[CAPTAIN][DocumentationSearch] #{query}" }
|
||||
assistant
|
||||
.responses
|
||||
.approved
|
||||
.search(query)
|
||||
.map { |response| format_response(response) }
|
||||
.join
|
||||
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
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
require 'rails_helper'
|
||||
|
||||
# Test tool implementation
|
||||
class TestTool < Captain::Tools::BaseService
|
||||
def name
|
||||
'test_tool'
|
||||
end
|
||||
|
||||
def description
|
||||
'A test tool for specs'
|
||||
end
|
||||
|
||||
def parameters
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
test_param: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def execute(*args)
|
||||
args
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Captain::ToolRegistryService do
|
||||
let(:assistant) { create(:captain_assistant) }
|
||||
let(:service) { described_class.new(assistant) }
|
||||
|
||||
describe '#initialize' do
|
||||
it 'initializes with empty tools and registered_tools' do
|
||||
expect(service.tools).to be_empty
|
||||
expect(service.registered_tools).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '#register_tool' do
|
||||
let(:tool_class) { TestTool }
|
||||
|
||||
it 'registers a new tool' do
|
||||
service.register_tool(tool_class)
|
||||
|
||||
expect(service.tools['test_tool']).to be_a(TestTool)
|
||||
expect(service.registered_tools).to include(
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'test_tool',
|
||||
description: 'A test tool for specs',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
test_param: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'method_missing' do
|
||||
let(:tool_class) { TestTool }
|
||||
|
||||
before do
|
||||
service.register_tool(tool_class)
|
||||
end
|
||||
|
||||
context 'when method corresponds to a registered tool' do
|
||||
it 'executes the tool with given arguments' do
|
||||
result = service.test_tool(test_param: 'arg1')
|
||||
expect(result).to eq([{ test_param: 'arg1' }])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when method does not correspond to a registered tool' do
|
||||
it 'raises NoMethodError' do
|
||||
expect { service.unknown_tool }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,77 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Captain::Tools::SearchDocumentationService do
|
||||
let(:assistant) { create(:captain_assistant) }
|
||||
let(:service) { described_class.new(assistant) }
|
||||
let(:question) { 'How to create a new account?' }
|
||||
let(:answer) { 'You can create a new account by clicking on the Sign Up button.' }
|
||||
let(:external_link) { 'https://example.com/docs/create-account' }
|
||||
|
||||
describe '#name' do
|
||||
it 'returns the correct service name' do
|
||||
expect(service.name).to eq('search_documentation')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it 'returns the service description' do
|
||||
expect(service.description).to eq('Search and retrieve documentation from knowledge base')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parameters' do
|
||||
it 'returns the required parameters schema' do
|
||||
expected_schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
search_query: {
|
||||
type: 'string',
|
||||
description: 'The search query to look up in the documentation.'
|
||||
}
|
||||
},
|
||||
required: ['search_query']
|
||||
}
|
||||
|
||||
expect(service.parameters).to eq(expected_schema)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let!(:response) do
|
||||
create(
|
||||
:captain_assistant_response,
|
||||
assistant: assistant,
|
||||
question: question,
|
||||
answer: answer,
|
||||
status: 'approved'
|
||||
)
|
||||
end
|
||||
|
||||
let(:documentable) { create(:captain_document, external_link: external_link) }
|
||||
|
||||
context 'when matching responses exist' do
|
||||
before do
|
||||
response.update(documentable: documentable)
|
||||
allow(Captain::AssistantResponse).to receive(:search).with(question).and_return([response])
|
||||
end
|
||||
|
||||
it 'returns formatted responses for the search query' do
|
||||
result = service.execute({ 'search_query' => question })
|
||||
|
||||
expect(result).to include(question)
|
||||
expect(result).to include(answer)
|
||||
expect(result).to include(external_link)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no matching responses exist' do
|
||||
before do
|
||||
allow(Captain::AssistantResponse).to receive(:search).with(question).and_return([])
|
||||
end
|
||||
|
||||
it 'returns an empty string' do
|
||||
expect(service.execute({ 'search_query' => question })).to eq('No FAQs found for the given query')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user