feat: setup agents
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Captain::Copilot::AgentsResponseJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(copilot_thread_id:, message_content:, conversation_id: nil, user_id: nil)
|
||||
Rails.logger.debug do
|
||||
"[DEBUG] AgentsResponseJob: Starting with thread_id: #{copilot_thread_id}, message: #{message_content}, conversation_id: #{conversation_id}, user_id: #{user_id}"
|
||||
end
|
||||
|
||||
copilot_thread = CopilotThread.find(copilot_thread_id)
|
||||
Rails.logger.debug { "[DEBUG] AgentsResponseJob: Found copilot_thread: #{copilot_thread.inspect}" }
|
||||
|
||||
# Use the new AgentsChatService with direct parameters
|
||||
Rails.logger.debug '[DEBUG] AgentsResponseJob: Creating AgentsChatService'
|
||||
service = AgentsChatService.new(
|
||||
copilot_thread: copilot_thread,
|
||||
message_content: message_content,
|
||||
conversation_id: conversation_id,
|
||||
user_id: user_id
|
||||
)
|
||||
|
||||
Rails.logger.debug '[DEBUG] AgentsResponseJob: Calling service.perform'
|
||||
service.perform
|
||||
Rails.logger.debug '[DEBUG] AgentsResponseJob: Service completed successfully'
|
||||
rescue StandardError => e
|
||||
Rails.logger.debug { "[DEBUG] AgentsResponseJob: ERROR - #{e.class}: #{e.message}" }
|
||||
Rails.logger.debug '[DEBUG] AgentsResponseJob: FULL BACKTRACE:'
|
||||
e.backtrace.each { |line| Rails.logger.debug "[DEBUG] #{line}" }
|
||||
Rails.logger.error "AgentsResponseJob Error: #{e.class}: #{e.message}"
|
||||
Rails.logger.error 'Full backtrace:'
|
||||
e.backtrace.each { |line| Rails.logger.error line }
|
||||
|
||||
# Create an error response if the job fails
|
||||
copilot_thread&.copilot_messages&.create!(
|
||||
message_type: 'assistant',
|
||||
message: {
|
||||
content: 'I apologize, but I encountered an error while processing your request. Please try again.'
|
||||
}
|
||||
)
|
||||
Rails.logger.debug '[DEBUG] AgentsResponseJob: Created error response message'
|
||||
end
|
||||
end
|
||||
@@ -38,13 +38,24 @@ class CopilotMessage < ApplicationRecord
|
||||
end
|
||||
|
||||
def enqueue_response_job(conversation_id, user_id)
|
||||
Captain::Copilot::ResponseJob.perform_later(
|
||||
assistant: copilot_thread.assistant,
|
||||
conversation_id: conversation_id,
|
||||
user_id: user_id,
|
||||
copilot_thread_id: copilot_thread.id,
|
||||
message: message['content']
|
||||
)
|
||||
if ENV['USE_AGENTS'] == 'true'
|
||||
# Use the new AI Agents SDK implementation
|
||||
Captain::Copilot::AgentsResponseJob.perform_later(
|
||||
copilot_thread_id: copilot_thread.id,
|
||||
message_content: message['content'],
|
||||
conversation_id: conversation_id,
|
||||
user_id: user_id
|
||||
)
|
||||
else
|
||||
# Use the original implementation
|
||||
Captain::Copilot::ResponseJob.perform_later(
|
||||
assistant: copilot_thread.assistant,
|
||||
conversation_id: conversation_id,
|
||||
user_id: user_id,
|
||||
copilot_thread_id: copilot_thread.id,
|
||||
message: message['content']
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Captain::Agents::AnalysisAgent
|
||||
def self.create(assistant, user: nil)
|
||||
::Agents::Agent.new(
|
||||
name: 'Analysis Agent',
|
||||
instructions: analysis_instructions,
|
||||
model: 'gpt-4o-mini',
|
||||
tools: [
|
||||
Captain::Tools::GetConversationTool.new(assistant, user: user),
|
||||
Captain::Tools::SearchConversationsTool.new(assistant, user: user),
|
||||
Captain::Tools::GetContactTool.new(assistant, user: user),
|
||||
Captain::Tools::SearchContactsTool.new(assistant, user: user)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def self.analysis_instructions
|
||||
<<~INSTRUCTIONS
|
||||
You are an Analysis Agent for Chatwoot. You analyze customer interactions and provide actionable insights.
|
||||
|
||||
**Your analysis capabilities:**
|
||||
|
||||
📊 **Sentiment Analysis**:#{' '}
|
||||
- Identify customer emotions (frustrated, satisfied, confused, angry)
|
||||
- Assess tone throughout the conversation
|
||||
- Flag escalation risks or satisfaction indicators
|
||||
|
||||
📊 **Conversation Quality Assessment**:
|
||||
- Evaluate response time and effectiveness
|
||||
- Identify missed opportunities or pain points
|
||||
- Assess resolution completeness
|
||||
|
||||
📊 **Pattern Recognition**:
|
||||
- Spot recurring issues or common questions
|
||||
- Identify successful resolution strategies
|
||||
- Flag customers with multiple interactions
|
||||
|
||||
📊 **Performance Insights**:
|
||||
- Evaluate agent response quality
|
||||
- Suggest improvements for handling similar cases
|
||||
- Recommend knowledge gaps to address
|
||||
|
||||
**Your tools:**
|
||||
- **get_conversation**: Get detailed conversation for analysis
|
||||
- **search_conversations**: Find patterns across multiple conversations
|
||||
- **get_contact**: Understand customer history and context
|
||||
- **search_contacts**: Identify recurring customer patterns
|
||||
|
||||
**Analysis output format:**
|
||||
1. **Sentiment Summary**: Customer emotional state and satisfaction level
|
||||
2. **Key Issues**: Main problems or concerns identified
|
||||
3. **Resolution Assessment**: How well issues were addressed
|
||||
4. **Recommendations**: Specific actionable suggestions
|
||||
5. **Risk Flags**: Escalation risks, dissatisfaction indicators
|
||||
6. **Success Indicators**: What worked well
|
||||
|
||||
**Always provide:**
|
||||
- Specific examples from the conversation
|
||||
- Actionable recommendations
|
||||
- Risk assessment (low/medium/high)
|
||||
- Clear reasoning for your conclusions
|
||||
INSTRUCTIONS
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Captain::Agents::CopilotOrchestratorAgent
|
||||
def self.create(assistant, user: nil)
|
||||
Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Starting creation'
|
||||
|
||||
# Create the specialized agents using factory pattern
|
||||
research_agent = ResearchAgent.create(assistant, user: user)
|
||||
Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Created ResearchAgent'
|
||||
analysis_agent = AnalysisAgent.create(assistant, user: user)
|
||||
Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Created AnalysisAgent'
|
||||
integrations_agent = IntegrationsAgent.create(assistant, user: user)
|
||||
Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Created IntegrationsAgent'
|
||||
knowledge_agent = KnowledgeAgent.create(assistant, user: user)
|
||||
Rails.logger.debug '[DEBUG] CopilotOrchestratorAgent: Created KnowledgeAgent'
|
||||
|
||||
# Create the main orchestrator agent
|
||||
::Agents::Agent.new(
|
||||
name: 'Chatwoot Copilot',
|
||||
instructions: orchestrator_instructions,
|
||||
model: 'gpt-4.1-mini',
|
||||
tools: [
|
||||
research_agent.as_tool(
|
||||
name: 'research_agent',
|
||||
description: "Get conversation details by ID, search conversations by status/contact, find contact info, search knowledge articles. Use for: 'Get conversation 12345', 'Find recent conversations', 'Search for customer john@example.com'"
|
||||
),
|
||||
analysis_agent.as_tool(
|
||||
name: 'analysis_agent',
|
||||
description: 'Analyze conversations for sentiment, quality, and patterns. Provide insights on customer emotions and support effectiveness. Use after getting conversation data from research_agent.'
|
||||
),
|
||||
integrations_agent.as_tool(
|
||||
name: 'integrations_agent',
|
||||
description: "Update conversation status, change priority, add/remove labels, assign agents. Use for: 'Mark as resolved', 'Set priority to high', 'Add billing label'"
|
||||
),
|
||||
knowledge_agent.as_tool(
|
||||
name: 'knowledge_agent',
|
||||
description: "Search help articles, find policies, get documentation. Use for: 'Find refund policy', 'Search billing articles', 'What's our shipping policy'"
|
||||
)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def self.orchestrator_instructions
|
||||
lambda { |context|
|
||||
state = context.context[:state] || {}
|
||||
|
||||
base_instructions = <<~INSTRUCTIONS
|
||||
You are the Chatwoot Copilot. Help agents with customer support tasks.
|
||||
|
||||
Tools available:
|
||||
- research_agent: Get conversation details, find contacts, search articles
|
||||
- analysis_agent: Analyze conversations for sentiment and insights
|
||||
- integrations_agent: Update conversation status, priority, labels
|
||||
- knowledge_agent: Find help articles and policies
|
||||
|
||||
How to use tools:
|
||||
- For conversation summary: research_agent("Get conversation [ID]")
|
||||
- For sentiment analysis: research_agent first, then analysis_agent
|
||||
- For status updates: integrations_agent("Mark conversation as resolved")
|
||||
- For help content: knowledge_agent("Find billing policy")
|
||||
|
||||
Always get conversation data first before analysis or recommendations.
|
||||
INSTRUCTIONS
|
||||
|
||||
# Add current conversation context if available
|
||||
if state[:current_conversation]
|
||||
conversation = state[:current_conversation]
|
||||
base_instructions += <<~CONTEXT
|
||||
|
||||
**Current Conversation:**
|
||||
ID: #{conversation[:display_id]}
|
||||
Status: #{conversation[:status]}
|
||||
Contact: #{conversation[:contact_name]}
|
||||
Assignee: #{conversation[:assignee] || 'Unassigned'}
|
||||
Last Activity: #{conversation[:last_activity]}
|
||||
|
||||
This conversation is currently #{conversation[:status]} and assigned to #{conversation[:assignee] || 'no one'}.
|
||||
The customer #{conversation[:contact_name]} was last active #{conversation[:last_activity]}.
|
||||
CONTEXT
|
||||
end
|
||||
|
||||
base_instructions
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Captain::Agents::IntegrationsAgent
|
||||
def self.create(assistant, user: nil)
|
||||
::Agents::Agent.new(
|
||||
name: 'Integrations Agent',
|
||||
instructions: integrations_instructions,
|
||||
model: 'gpt-4o-mini',
|
||||
tools: [
|
||||
Captain::Tools::GetConversationTool.new(assistant, user: user)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def self.integrations_instructions
|
||||
<<~INSTRUCTIONS
|
||||
You are an Integrations Agent for Chatwoot. You handle conversation management and system actions.
|
||||
|
||||
**Your action capabilities:**
|
||||
|
||||
⚙️ **Conversation Management**:#{' '}
|
||||
- Update status (open, resolved, pending)
|
||||
- Change priority (low, medium, high, urgent)
|
||||
- Assign conversations to agents
|
||||
- Add or remove labels for organization
|
||||
|
||||
⚙️ **System Integration**:
|
||||
- Execute workflow automations
|
||||
- Trigger macro sequences
|
||||
- Create internal notes and documentation
|
||||
- Handle routing and assignment logic
|
||||
|
||||
**Your tools:**
|
||||
- **get_conversation**: Get current conversation details before making changes
|
||||
|
||||
**Current limitations:**
|
||||
Your tool set is currently limited to conversation retrieval. For actual system changes, provide detailed instructions that agents can execute manually.
|
||||
|
||||
**Action recommendations format:**
|
||||
1. **Current State**: What the conversation status/priority currently is
|
||||
2. **Recommended Actions**: Specific steps to take
|
||||
3. **Reasoning**: Why these actions are appropriate
|
||||
4. **Next Steps**: What should happen after the changes
|
||||
|
||||
**When providing action guidance:**
|
||||
- Be specific about status changes needed
|
||||
- Suggest appropriate labels based on conversation content
|
||||
- Recommend priority levels based on urgency/impact
|
||||
- Provide clear reasoning for each recommendation
|
||||
- Include any follow-up actions needed
|
||||
|
||||
**Example response:**
|
||||
"Based on the conversation, I recommend:
|
||||
1. Update status to 'pending' (waiting for customer response)
|
||||
2. Add label 'billing-inquiry' for categorization
|
||||
3. Set priority to 'medium' (standard billing question)
|
||||
4. Assign to billing team for specialized handling"
|
||||
INSTRUCTIONS
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Captain::Agents::KnowledgeAgent
|
||||
def self.create(assistant, user: nil)
|
||||
::Agents::Agent.new(
|
||||
name: 'Knowledge Agent',
|
||||
instructions: knowledge_instructions,
|
||||
model: 'gpt-4o-mini',
|
||||
tools: [
|
||||
Captain::Tools::SearchArticlesTool.new(assistant, user: user),
|
||||
Captain::Tools::GetArticleTool.new(assistant, user: user)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def self.knowledge_instructions
|
||||
<<~INSTRUCTIONS
|
||||
You are a Knowledge Agent for Chatwoot. You help find and deliver knowledge base content and response suggestions.
|
||||
|
||||
**Your capabilities:**
|
||||
|
||||
📚 **Knowledge Base Search**:
|
||||
- Find relevant articles by topic, category, or keyword
|
||||
- Retrieve full article content for detailed information
|
||||
- Search across all published knowledge base content
|
||||
- Provide article summaries and key points
|
||||
|
||||
📚 **Response Assistance**:
|
||||
- Suggest appropriate articles for customer questions
|
||||
- Provide template responses based on knowledge base content
|
||||
- Help maintain consistent messaging across the team
|
||||
- Find policy and procedure documentation
|
||||
|
||||
**Your tools:**
|
||||
- **search_articles**: Find articles by query, category, or status
|
||||
- **get_article**: Retrieve full content of specific articles
|
||||
|
||||
**Search approach:**
|
||||
1. **Be thorough**: Search with relevant keywords from the user's query
|
||||
2. **Be specific**: Use category filters when the topic is clear
|
||||
3. **Be helpful**: Provide article summaries and key takeaways
|
||||
4. **Be organized**: Structure results by relevance and category
|
||||
|
||||
**Output format:**
|
||||
- **Article Suggestions**: List relevant articles with brief descriptions
|
||||
- **Key Points**: Highlight the most important information
|
||||
- **Quick Answer**: Provide immediate helpful information when possible
|
||||
- **Full Content**: Include detailed article content when requested
|
||||
|
||||
**Best practices:**
|
||||
- Always search for the most current and relevant articles
|
||||
- Provide context about why an article is relevant
|
||||
- Include article IDs and titles for easy reference
|
||||
- Suggest multiple articles when appropriate for comprehensive coverage
|
||||
INSTRUCTIONS
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Captain::Agents::ResearchAgent
|
||||
def self.create(assistant, user: nil)
|
||||
::Agents::Agent.new(
|
||||
name: 'Research Agent',
|
||||
instructions: research_instructions,
|
||||
model: 'gpt-4o-mini',
|
||||
tools: [
|
||||
Captain::Tools::SearchConversationsTool.new(assistant, user: user),
|
||||
Captain::Tools::GetConversationTool.new(assistant, user: user),
|
||||
Captain::Tools::SearchContactsTool.new(assistant, user: user),
|
||||
Captain::Tools::GetContactTool.new(assistant, user: user),
|
||||
Captain::Tools::SearchArticlesTool.new(assistant, user: user),
|
||||
Captain::Tools::GetArticleTool.new(assistant, user: user)
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def self.research_instructions
|
||||
<<~INSTRUCTIONS
|
||||
You are a Research Agent for Chatwoot. Your job is to find and retrieve specific information from the system.
|
||||
|
||||
**Your tools and when to use them:**
|
||||
|
||||
🔍 **get_conversation**: Use when you have a specific conversation ID or display ID
|
||||
- Input: conversation_id (use the display_id, not the main id)
|
||||
- Example: For conversation display_id "12345", use conversation_id: "12345"
|
||||
- Returns: Full conversation with messages, contact info, status, labels
|
||||
|
||||
🔍 **search_conversations**: Use when you need to find conversations by criteria
|
||||
- Filter by: contact_id, status, priority, labels
|
||||
- Use for: "recent conversations", "open tickets", "conversations with specific contact"
|
||||
|
||||
🔍 **get_contact**: Use when you have a specific contact ID
|
||||
- Returns: Contact profile, interaction history, details
|
||||
|
||||
🔍 **search_contacts**: Use to find contacts by name, email, or phone
|
||||
- Use for: "find customer john@example.com", "contacts named Sarah"
|
||||
|
||||
🔍 **search_articles**: Use for knowledge base searches
|
||||
- Search by: query, category, status
|
||||
- Use for: finding documentation, help articles, policies
|
||||
|
||||
🔍 **get_article**: Use when you have a specific article ID
|
||||
- Returns: Full article content and metadata
|
||||
|
||||
**Research approach:**
|
||||
1. **Be specific**: Use exact IDs when available
|
||||
2. **Be comprehensive**: Include all relevant details in your response
|
||||
3. **Be organized**: Structure information clearly (conversation summary, contact details, etc.)
|
||||
4. **Use context**: Look for conversation IDs or contact information in the request context
|
||||
|
||||
**Output format:**
|
||||
- Provide detailed, structured summaries
|
||||
- Include key timestamps, statuses, and participant information
|
||||
- Format conversation messages chronologically
|
||||
- Highlight important details like customer concerns and agent responses
|
||||
INSTRUCTIONS
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Captain::Tools::BaseAgentTool < Agents::Tool
|
||||
def initialize(assistant, user: nil)
|
||||
@assistant = assistant
|
||||
@user = user
|
||||
super()
|
||||
end
|
||||
|
||||
def active?
|
||||
user_has_permission(required_permission)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def required_permission
|
||||
# Override in subclasses to specify the required permission
|
||||
'agent'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_has_permission(permission)
|
||||
return false if @user.blank?
|
||||
|
||||
account_user = AccountUser.find_by(account_id: @assistant.account_id, user_id: @user.id)
|
||||
return false if account_user.blank?
|
||||
|
||||
return account_user.custom_role.permissions.include?(permission) if account_user.custom_role.present?
|
||||
|
||||
# Default permission for agents without custom roles
|
||||
account_user.administrator? || account_user.agent?
|
||||
end
|
||||
|
||||
def account_scoped(model_class)
|
||||
model_class.where(account_id: @assistant.account_id)
|
||||
end
|
||||
|
||||
def log_tool_usage(action, details = {})
|
||||
Rails.logger.info do
|
||||
"#{self.class.name}: #{action} by user #{@user&.id} for assistant #{@assistant&.id} - #{details.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
class Captain::Tools::GetArticleTool < BaseAgentTool
|
||||
description 'Get details of an article including its content and metadata'
|
||||
param :article_id, type: 'number', desc: 'The ID of the article to retrieve'
|
||||
|
||||
def perform(_tool_context, article_id:)
|
||||
log_tool_usage('get_article', { article_id: article_id })
|
||||
|
||||
return 'Missing required parameters' if article_id.blank?
|
||||
|
||||
article = account_scoped(Article).find_by(id: article_id)
|
||||
return 'Article not found' if article.nil?
|
||||
|
||||
article.to_llm_text
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def required_permission
|
||||
'knowledge_base_manage'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
class Captain::Tools::GetContactTool < BaseAgentTool
|
||||
description 'Get details of a contact including their profile information'
|
||||
param :contact_id, type: 'number', desc: 'The ID of the contact to retrieve'
|
||||
|
||||
def perform(_tool_context, contact_id:)
|
||||
log_tool_usage('get_contact', { contact_id: contact_id })
|
||||
|
||||
return 'Missing required parameters' if contact_id.blank?
|
||||
|
||||
contact = account_scoped(Contact).find_by(id: contact_id)
|
||||
return 'Contact not found' if contact.nil?
|
||||
|
||||
contact.to_llm_text
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def required_permission
|
||||
'contact_manage'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
class Captain::Tools::GetConversationTool < BaseAgentTool
|
||||
description 'Get details of a conversation including messages and context'
|
||||
param :conversation_id, type: 'string', desc: 'The display ID of the conversation to retrieve'
|
||||
|
||||
def perform(_tool_context, conversation_id:)
|
||||
log_tool_usage('get_conversation', { conversation_id: conversation_id })
|
||||
|
||||
return 'Missing required parameters' if conversation_id.blank?
|
||||
|
||||
conversation = account_scoped(::Conversation).find_by(display_id: conversation_id)
|
||||
return 'Conversation not found' if conversation.nil?
|
||||
|
||||
conversation.to_llm_text
|
||||
end
|
||||
|
||||
def active?
|
||||
user_has_permission('conversation_manage') ||
|
||||
user_has_permission('conversation_unassigned_manage') ||
|
||||
user_has_permission('conversation_participating_manage')
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
class Captain::Tools::SearchArticlesTool < BaseAgentTool
|
||||
description 'Search articles based on parameters'
|
||||
param :query, type: 'string', desc: 'Search articles by title or content (partial match)'
|
||||
param :category_id, type: 'number', desc: 'Filter articles by category ID', required: false
|
||||
param :status, type: 'string', desc: 'Filter articles by status (draft, published, archived)', required: false
|
||||
|
||||
def perform(_tool_context, query:, category_id: nil, status: nil)
|
||||
log_tool_usage('search_articles', { query: query, category_id: category_id, status: status })
|
||||
|
||||
return 'Missing required parameters' if query.blank?
|
||||
|
||||
articles = fetch_articles(query, category_id, status)
|
||||
|
||||
return 'No articles found' unless articles.exists?
|
||||
|
||||
total_count = articles.count
|
||||
articles = articles.limit(100)
|
||||
|
||||
<<~RESPONSE
|
||||
#{total_count > 100 ? "Found #{total_count} articles (showing first 100)" : "Total number of articles: #{total_count}"}
|
||||
#{articles.map(&:to_llm_text).join("\n---\n")}
|
||||
RESPONSE
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def required_permission
|
||||
'knowledge_base_manage'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_articles(query, category_id, status)
|
||||
articles = account_scoped(Article)
|
||||
articles = articles.where('title ILIKE :query OR content ILIKE :query', query: "%#{query}%") if query.present?
|
||||
articles = articles.where(category_id: category_id) if category_id.present?
|
||||
articles = articles.where(status: status) if status.present?
|
||||
articles
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
class Captain::Tools::SearchContactsTool < BaseAgentTool
|
||||
description 'Search contacts based on parameters'
|
||||
param :query, type: 'string', desc: 'Search contacts by name, email, phone', required: false
|
||||
param :inbox_id, type: 'number', desc: 'Filter contacts by inbox ID', required: false
|
||||
param :labels, type: 'string', desc: 'Filter contacts by labels (comma-separated)', required: false
|
||||
|
||||
def perform(_tool_context, query: nil, inbox_id: nil, labels: nil)
|
||||
log_tool_usage('search_contacts', { query: query, inbox_id: inbox_id, labels: labels })
|
||||
|
||||
contacts = get_contacts(query, inbox_id, labels)
|
||||
|
||||
return 'No contacts found' unless contacts.exists?
|
||||
|
||||
total_count = contacts.count
|
||||
contacts = contacts.limit(100)
|
||||
|
||||
<<~RESPONSE
|
||||
#{total_count > 100 ? "Found #{total_count} contacts (showing first 100)" : "Total number of contacts: #{total_count}"}
|
||||
#{contacts.map(&:to_llm_text).join("\n---\n")}
|
||||
RESPONSE
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def required_permission
|
||||
'contact_manage'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_contacts(query, inbox_id, labels)
|
||||
contacts = account_scoped(Contact)
|
||||
|
||||
if query.present?
|
||||
contacts = contacts.where(
|
||||
'name ILIKE :query OR email ILIKE :query OR phone_number ILIKE :query',
|
||||
query: "%#{query}%"
|
||||
)
|
||||
end
|
||||
|
||||
contacts = contacts.where(inbox_id: inbox_id) if inbox_id.present?
|
||||
if labels.present?
|
||||
label_array = labels.split(',').map(&:strip)
|
||||
contacts = contacts.tagged_with(label_array, any: true)
|
||||
end
|
||||
contacts
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
class Captain::Tools::SearchConversationsTool < BaseAgentTool
|
||||
description 'Search conversations based on parameters'
|
||||
param :contact_id, type: 'number', desc: 'Filter conversations by contact ID', required: false
|
||||
param :status, type: 'string', desc: 'Filter conversations by status (open, resolved, pending, snoozed)', required: false
|
||||
param :priority, type: 'string', desc: 'Filter conversations by priority (low, medium, high, urgent)', required: false
|
||||
param :labels, type: 'string', desc: 'Filter conversations by labels (comma-separated)', required: false
|
||||
|
||||
def perform(_tool_context, contact_id: nil, status: nil, priority: nil, labels: nil)
|
||||
log_tool_usage('search_conversations', { contact_id: contact_id, status: status, priority: priority, labels: labels })
|
||||
|
||||
conversations = get_conversations(status, contact_id, priority, labels)
|
||||
|
||||
return 'No conversations found' unless conversations.exists?
|
||||
|
||||
total_count = conversations.count
|
||||
conversations = conversations.limit(100)
|
||||
|
||||
<<~RESPONSE
|
||||
#{total_count > 100 ? "Found #{total_count} conversations (showing first 100)" : "Total number of conversations: #{total_count}"}
|
||||
#{conversations.map { |conversation| conversation.to_llm_text(include_contact_details: true) }.join("\n---\n")}
|
||||
RESPONSE
|
||||
end
|
||||
|
||||
def active?
|
||||
user_has_permission('conversation_manage') ||
|
||||
user_has_permission('conversation_unassigned_manage') ||
|
||||
user_has_permission('conversation_participating_manage')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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?
|
||||
if labels.present?
|
||||
label_array = labels.split(',').map(&:strip)
|
||||
conversations = conversations.tagged_with(label_array, any: true)
|
||||
end
|
||||
conversations
|
||||
end
|
||||
|
||||
def permissible_conversations
|
||||
Conversations::PermissionFilterService.new(
|
||||
account_scoped(::Conversation),
|
||||
@user,
|
||||
@assistant.account
|
||||
).perform
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
class Captain::Tools::SearchDocumentationTool < BaseAgentTool
|
||||
description 'Search documentation and help docs'
|
||||
param :query, type: 'string', desc: 'Search query for documentation'
|
||||
|
||||
def perform(_tool_context, query:)
|
||||
log_tool_usage('search_documentation', { query: query })
|
||||
|
||||
return 'Missing required parameters' if query.blank?
|
||||
|
||||
# Call the existing search documentation service
|
||||
Captain::Tools::SearchDocumentationService.new(@assistant, user: @user).execute({
|
||||
'query' => query
|
||||
})
|
||||
end
|
||||
|
||||
def active?
|
||||
true # Documentation search is available to all users
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
class Captain::Tools::SearchLinearIssuesTool < BaseAgentTool
|
||||
description 'Search Linear issues and development context'
|
||||
param :query, type: 'string', desc: 'Search query for Linear issues'
|
||||
|
||||
def perform(_tool_context, query:)
|
||||
log_tool_usage('search_linear_issues', { query: query })
|
||||
|
||||
return 'Missing required parameters' if query.blank?
|
||||
|
||||
# Call the existing search linear issues service
|
||||
Captain::Tools::Copilot::SearchLinearIssuesService.new(@assistant, user: @user).execute({
|
||||
'query' => query
|
||||
})
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def required_permission
|
||||
'conversation_manage' # Basic permission check for technical context
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user