diff --git a/app/models/article.rb b/app/models/article.rb index 48e0529a2..14450b574 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -33,6 +33,7 @@ # class Article < ApplicationRecord include PgSearch::Model + include LlmFormattable has_many :associated_articles, class_name: :Article, diff --git a/app/services/llm_formatter/article_llm_formatter.rb b/app/services/llm_formatter/article_llm_formatter.rb new file mode 100644 index 000000000..83d2af4e1 --- /dev/null +++ b/app/services/llm_formatter/article_llm_formatter.rb @@ -0,0 +1,22 @@ +class LlmFormatter::ArticleLlmFormatter + attr_reader :article + + def initialize(article) + @article = article + end + + def format(*) + <<~TEXT + Title: #{article.title} + ID: #{article.id} + Status: #{article.status} + Category: #{article.category&.name || 'Uncategorized'} + Author: #{article.author&.name} + Views: #{article.views} + Created At: #{article.created_at} + Updated At: #{article.updated_at} + Content: + #{article.content} + TEXT + end +end diff --git a/enterprise/app/services/captain/copilot/chat_service.rb b/enterprise/app/services/captain/copilot/chat_service.rb index 6e05f0b62..fbb042fcd 100644 --- a/enterprise/app/services/captain/copilot/chat_service.rb +++ b/enterprise/app/services/captain/copilot/chat_service.rb @@ -21,10 +21,12 @@ class Captain::Copilot::ChatService < Llm::BaseOpenAiService end def register_tools - @tool_registry.register_tool(Captain::Tools::Copilot::GetConversationService) - @tool_registry.register_tool(Captain::Tools::Copilot::SearchConversationService) + @tool_registry.register_tool(Captain::Tools::Copilot::GetArticleService) @tool_registry.register_tool(Captain::Tools::Copilot::GetContactService) + @tool_registry.register_tool(Captain::Tools::Copilot::GetConversationService) + @tool_registry.register_tool(Captain::Tools::Copilot::SearchArticleService) @tool_registry.register_tool(Captain::Tools::Copilot::SearchContactService) + @tool_registry.register_tool(Captain::Tools::Copilot::SearchConversationService) end def build_initial_messages(config) diff --git a/enterprise/app/services/captain/tools/copilot/get_article_service.rb b/enterprise/app/services/captain/tools/copilot/get_article_service.rb new file mode 100644 index 000000000..35db12cf6 --- /dev/null +++ b/enterprise/app/services/captain/tools/copilot/get_article_service.rb @@ -0,0 +1,40 @@ +class Captain::Tools::Copilot::GetArticleService < Captain::Tools::BaseService + def name + 'get_article' + end + + def description + 'Get details of an article including its content and metadata' + end + + def parameters + { + type: 'object', + properties: { + article_id: { + type: 'number', + description: 'The ID of the article to retrieve' + }, + account_id: { + type: 'number', + description: 'The ID of the account the article belongs to' + } + }, + required: %w[article_id account_id] + } + end + + def execute(arguments) + article_id = arguments['article_id'] + account_id = arguments['account_id'] + + Rails.logger.info { "[CAPTAIN][GetArticle] #{article_id}, #{account_id}" } + + return 'Missing required parameters' if article_id.blank? || account_id.blank? + + article = Article.find_by(id: article_id, account_id: account_id) + return 'Article not found' if article.nil? + + article.to_llm_text + end +end diff --git a/enterprise/app/services/captain/tools/copilot/search_article_service.rb b/enterprise/app/services/captain/tools/copilot/search_article_service.rb new file mode 100644 index 000000000..84fcc2040 --- /dev/null +++ b/enterprise/app/services/captain/tools/copilot/search_article_service.rb @@ -0,0 +1,60 @@ +class Captain::Tools::Copilot::SearchArticleService < Captain::Tools::BaseService + def name + 'search_article' + end + + def description + 'Search for articles based on query parameters' + end + + def parameters + { + type: 'object', + properties: { + account_id: { + type: 'number', + description: 'The ID of the account to search articles in' + }, + query: { + type: 'string', + description: 'Search articles by title or content (partial match)' + }, + category_id: { + type: 'number', + description: 'Filter articles by category ID' + }, + status: { + type: 'string', + enum: %w[draft published archived], + description: 'Filter articles by status' + } + }, + required: %w[account_id] + } + end + + def execute(arguments) + account_id = arguments['account_id'] + query = arguments['query'] + category_id = arguments['category_id'] + status = arguments['status'] + + Rails.logger.info do + "[CAPTAIN][SearchArticle] account_id: #{account_id}, query: #{query}, category_id: #{category_id}, status: #{status}" + end + + return 'Missing required parameters' if account_id.blank? + + articles = Article.where(account_id: account_id) + 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? + + return 'No articles found' unless articles.exists? + + <<~RESPONSE + Total number of articles: #{articles.count} + #{articles.map(&:to_llm_text).join("\n---\n")} + RESPONSE + end +end diff --git a/enterprise/app/services/captain/tools/copilot/search_contact_service.rb b/enterprise/app/services/captain/tools/copilot/search_contact_service.rb index b5e3de641..94a7d0126 100644 --- a/enterprise/app/services/captain/tools/copilot/search_contact_service.rb +++ b/enterprise/app/services/captain/tools/copilot/search_contact_service.rb @@ -47,17 +47,14 @@ class Captain::Tools::Copilot::SearchContactService < Captain::Tools::BaseServic contacts = Contact.where(account_id: account_id) contacts = contacts.where(email: email) if email.present? contacts = contacts.where(phone_number: phone_number) if phone_number.present? - contacts = contacts.where('name ILIKE ?', "%#{name}%") if name.present? + contacts = contacts.where('LOWER(name) ILIKE ?', "%#{name.downcase}%") if name.present? return 'No contacts found' unless contacts.exists? <<~RESPONSE Total number of contacts: #{contacts.count} - #{ - contacts.map do |contact| - contact.to_llm_text - end.join("\n---\n") - } + + #{contacts.map(&:to_llm_text).join("\n---\n")} RESPONSE end end