feat(articles): use Responses API for search terms

This commit is contained in:
aakashb95
2026-07-10 23:35:50 +05:30
parent 96d1320aa5
commit 3171312cdd
2 changed files with 93 additions and 9 deletions
@@ -1,6 +1,23 @@
module Enterprise::Concerns::Article
extend ActiveSupport::Concern
SEARCH_TERMS_FEATURE = 'help_center_article_generation'
SEARCH_TERMS_SCHEMA = {
name: 'article_search_terms',
schema: {
type: 'object',
properties: {
search_terms: {
type: 'array',
items: { type: 'string' }
}
},
required: %w[search_terms],
additionalProperties: false
},
strict: true
}.freeze
included do
after_save :add_article_embedding, if: -> { saved_change_to_title? || saved_change_to_description? || saved_change_to_content? }
@@ -67,23 +84,38 @@ module Enterprise::Concerns::Article
{ role: 'system', content: article_to_search_terms_prompt },
{ role: 'user', content: "title: #{title} \n description: #{description} \n content: #{content}" }
]
headers = { 'Content-Type' => 'application/json', 'Authorization' => "Bearer #{openai_api_key}" }
body = { model: 'gpt-4o', messages: messages, response_format: { type: 'json_object' } }.to_json
Rails.logger.info "Requesting Chat GPT with body: #{body}"
response = HTTParty.post(openai_api_url, headers: headers, body: body)
Rails.logger.info "Chat GPT response: #{response.body}"
JSON.parse(response.parsed_response['choices'][0]['message']['content'])['search_terms']
response = responses_client.create(
model: search_terms_route[:model],
messages: messages,
schema: SEARCH_TERMS_SCHEMA,
reasoning_effort: search_terms_route[:reasoning_effort],
metadata: {
account_id: account_id,
article_id: id,
feature: 'article_search_terms'
}
)
JSON.parse(response[:message])['search_terms']
end
private
def search_terms_route
@search_terms_route ||= Llm::FeatureRouter.resolve(feature: SEARCH_TERMS_FEATURE, account: account)
end
def responses_client
@responses_client ||= Llm::ResponsesClient.new(api_key: openai_api_key, api_base: openai_api_base)
end
def openai_api_key
InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_API_KEY')&.value.presence || raise(I18n.t('captain.api_key_missing'))
end
def openai_api_url
def openai_api_base
endpoint = InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_ENDPOINT')&.value.presence || 'https://api.openai.com/'
endpoint = endpoint.chomp('/')
"#{endpoint}/v1/chat/completions"
endpoint.chomp('/')
end
end
+52
View File
@@ -208,6 +208,58 @@ RSpec.describe Article do
end
end
describe '#generate_article_search_terms' do
let(:responses_client) { instance_double(Llm::ResponsesClient) }
let(:article) do
create(
:article,
account: account,
category: category_1,
content: 'How to configure billing invoices and payment reminders',
description: 'Billing setup guide',
portal: portal_1,
author: user,
title: 'Configure billing'
)
end
before do
InstallationConfig.find_or_initialize_by(name: 'CAPTAIN_OPEN_AI_API_KEY').tap do |config|
config.value = 'sk-test'
config.save!
end
InstallationConfig.find_or_initialize_by(name: 'CAPTAIN_OPEN_AI_ENDPOINT').tap do |config|
config.value = 'https://api.openai.test/v1'
config.save!
end
allow(Llm::ResponsesClient).to receive(:new).and_return(responses_client)
allow(responses_client).to receive(:create).and_return(
{ message: { search_terms: ['billing setup', 'invoice reminders'] }.to_json }
)
end
it 'uses Responses API with the help center article model route' do
expect(article.generate_article_search_terms).to eq(['billing setup', 'invoice reminders'])
expect(Llm::ResponsesClient).to have_received(:new).with(
api_key: 'sk-test',
api_base: 'https://api.openai.test/v1'
)
expect(responses_client).to have_received(:create).with(
hash_including(
model: Llm::Models.default_model_for('help_center_article_generation'),
reasoning_effort: Llm::Models.reasoning_effort_for('help_center_article_generation'),
schema: Enterprise::Concerns::Article::SEARCH_TERMS_SCHEMA,
metadata: hash_including(
account_id: account.id,
article_id: article.id,
feature: 'article_search_terms'
)
)
)
end
end
describe '.update_positions' do
let!(:article_a) { create(:article, portal: portal_1, category: category_1, author: user, position: 10) }
let!(:article_b) { create(:article, portal: portal_1, category: category_1, author: user, position: 11) }