@@ -1,31 +1,5 @@
|
||||
module Enterprise::Api::V1::Accounts::ConversationsController
|
||||
extend ActiveSupport::Concern
|
||||
included do
|
||||
before_action :set_assistant, only: [:copilot]
|
||||
end
|
||||
|
||||
def copilot
|
||||
# First try to get the user's preferred assistant from UI settings or from the request
|
||||
assistant_id = copilot_params[:assistant_id] || current_user.ui_settings&.dig('preferred_captain_assistant_id')
|
||||
|
||||
# Find the assistant either by ID or from inbox
|
||||
assistant = if assistant_id.present?
|
||||
Captain::Assistant.find_by(id: assistant_id, account_id: Current.account.id)
|
||||
else
|
||||
@conversation.inbox.captain_assistant
|
||||
end
|
||||
|
||||
return render json: { message: I18n.t('captain.copilot_error') } unless assistant
|
||||
|
||||
response = Captain::Copilot::ChatService.new(
|
||||
assistant,
|
||||
previous_history: copilot_params[:previous_history],
|
||||
conversation_id: @conversation.display_id,
|
||||
user_id: Current.user.id
|
||||
).generate_response(copilot_params[:message])
|
||||
|
||||
render json: { message: response['response'] }
|
||||
end
|
||||
|
||||
def inbox_assistant
|
||||
assistant = @conversation.inbox.captain_assistant
|
||||
|
||||
@@ -53,9 +53,21 @@ module Captain::ChatHelper
|
||||
end
|
||||
|
||||
def execute_tool(function_name, arguments, tool_call_id)
|
||||
persist_message({ content: "Using tool #{function_name}", function_name: function_name }, 'assistant_thinking')
|
||||
persist_message(
|
||||
{
|
||||
content: I18n.t('captain.copilot.using_tool', function_name: function_name),
|
||||
function_name: function_name
|
||||
},
|
||||
'assistant_thinking'
|
||||
)
|
||||
result = @tool_registry.send(function_name, arguments)
|
||||
persist_message({ content: "Completed #{function_name} tool call", function_name: function_name }, 'assistant_thinking')
|
||||
persist_message(
|
||||
{
|
||||
content: I18n.t('captain.copilot.completed_tool_call', function_name: function_name),
|
||||
function_name: function_name
|
||||
},
|
||||
'assistant_thinking'
|
||||
)
|
||||
append_tool_response(result, tool_call_id)
|
||||
end
|
||||
|
||||
@@ -67,8 +79,8 @@ module Captain::ChatHelper
|
||||
end
|
||||
|
||||
def process_invalid_tool_call(function_name, tool_call_id)
|
||||
persist_message({ content: 'Invalid tool call', function_name: function_name }, 'assistant_thinking')
|
||||
append_tool_response('Tool not available', tool_call_id)
|
||||
persist_message({ content: I18n.t('captain.copilot.invalid_tool_call'), function_name: function_name }, 'assistant_thinking')
|
||||
append_tool_response(I18n.t('captain.copilot.tool_not_available'), tool_call_id)
|
||||
end
|
||||
|
||||
def append_tool_response(content, tool_call_id)
|
||||
|
||||
@@ -60,7 +60,7 @@ class CopilotMessage < ApplicationRecord
|
||||
def validate_message_attributes
|
||||
return if message.blank?
|
||||
|
||||
allowed_keys = %w[content reasoning function_name]
|
||||
allowed_keys = %w[content reasoning function_name reply_suggestion]
|
||||
invalid_keys = message.keys - allowed_keys
|
||||
|
||||
errors.add(:message, "contains invalid attributes: #{invalid_keys.join(', ')}") if invalid_keys.any?
|
||||
|
||||
@@ -74,7 +74,10 @@ class Captain::Copilot::ChatService < Llm::BaseOpenAiService
|
||||
def system_message
|
||||
{
|
||||
role: 'system',
|
||||
content: Captain::Llm::SystemPromptsService.copilot_response_generator(@assistant.config['product_name'])
|
||||
content: Captain::Llm::SystemPromptsService.copilot_response_generator(
|
||||
@assistant.config['product_name'],
|
||||
@tool_registry.tools_summary
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class Captain::Llm::SystemPromptsService
|
||||
SYSTEM_PROMPT_MESSAGE
|
||||
end
|
||||
|
||||
def copilot_response_generator(product_name)
|
||||
def copilot_response_generator(product_name, available_tools)
|
||||
<<~SYSTEM_PROMPT_MESSAGE
|
||||
[Identity]
|
||||
You are Captain, a helpful and friendly copilot assistant for support agents using the product #{product_name}. Your primary role is to assist support agents by retrieving information, compiling accurate responses, and guiding them through customer interactions.
|
||||
@@ -94,12 +94,20 @@ class Captain::Llm::SystemPromptsService
|
||||
```json
|
||||
{
|
||||
"reasoning": "Explain why the response was chosen based on the provided information.",
|
||||
"response": "Provide the answer only in Markdown format for readability."
|
||||
"content": "Provide the answer only in Markdown format for readability.",
|
||||
"reply_suggestion": "A boolean value that is true only if the support agent has explicitly asked to draft a response to the customer, and the response fulfills that request. Otherwise, it should be false."
|
||||
}
|
||||
|
||||
[Error Handling]
|
||||
- If the required information is not found in the provided context, respond with an appropriate message indicating that no relevant data is available.
|
||||
- Avoid speculating or providing unverified information.
|
||||
|
||||
[Available Actions]
|
||||
You have the following actions available to assist support agents:
|
||||
- summarize_conversation: Summarize the conversation
|
||||
- draft_response: Draft a response for the support agent
|
||||
- rate_conversation: Rate the conversation
|
||||
#{available_tools}
|
||||
SYSTEM_PROMPT_MESSAGE
|
||||
end
|
||||
|
||||
|
||||
@@ -27,4 +27,10 @@ class Captain::ToolRegistryService
|
||||
def respond_to_missing?(method_name, include_private = false)
|
||||
@tools.key?(method_name.to_s) || super
|
||||
end
|
||||
|
||||
def tools_summary
|
||||
@tools.map do |name, tool|
|
||||
"- #{name}: #{tool.description}"
|
||||
end.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,8 +19,9 @@ class Captain::Tools::Copilot::SearchConversationsService < Captain::Tools::Base
|
||||
status = arguments['status']
|
||||
contact_id = arguments['contact_id']
|
||||
priority = arguments['priority']
|
||||
labels = arguments['labels']
|
||||
|
||||
conversations = get_conversations(status, contact_id, priority)
|
||||
conversations = get_conversations(status, contact_id, priority, labels)
|
||||
|
||||
return 'No conversations found' unless conversations.exists?
|
||||
|
||||
@@ -41,11 +42,12 @@ class Captain::Tools::Copilot::SearchConversationsService < Captain::Tools::Base
|
||||
|
||||
private
|
||||
|
||||
def get_conversations(status, contact_id, priority)
|
||||
def get_conversations(status, contact_id, priority, labels)
|
||||
conversations = permissible_conversations
|
||||
conversations = conversations.where(contact_id: contact_id) if contact_id.present?
|
||||
conversations = conversations.where(status: status) if status.present?
|
||||
conversations = conversations.where(priority: priority) if priority.present?
|
||||
conversations = conversations.tagged_with(labels, any: true) if labels.present?
|
||||
conversations
|
||||
end
|
||||
|
||||
@@ -59,20 +61,10 @@ class Captain::Tools::Copilot::SearchConversationsService < Captain::Tools::Base
|
||||
|
||||
def properties
|
||||
{
|
||||
contact_id: {
|
||||
type: 'number',
|
||||
description: 'Filter conversations by contact ID'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: %w[open resolved pending snoozed],
|
||||
description: 'Filter conversations by status'
|
||||
},
|
||||
priority: {
|
||||
type: 'string',
|
||||
enum: %w[low medium high urgent],
|
||||
description: 'Filter conversations by priority'
|
||||
}
|
||||
contact_id: { type: 'number', description: 'Filter conversations by contact ID' },
|
||||
status: { type: 'string', enum: %w[open resolved pending snoozed], description: 'Filter conversations by status' },
|
||||
priority: { type: 'string', enum: %w[low medium high urgent], description: 'Filter conversations by priority' },
|
||||
labels: { type: 'array', items: { type: 'string' }, description: 'Filter conversations by labels' }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user