Compare commits

...
Author SHA1 Message Date
aakashb95 01c84be757 add improve 2025-12-15 16:15:33 +05:30
aakashb95 5ac32803dc remove redundant checks 2025-12-15 15:11:10 +05:30
Aakash BakhleandGitHub 18e0cfb327 Merge branch 'feature/cw-6117' into feature/cw-6118 2025-12-15 15:07:54 +05:30
Vishnu NarayananandGitHub a70d331813 Merge branch 'develop' into feature/cw-6117 2025-12-12 18:54:13 +05:30
aakashb95 3b4c5bd916 remove redundant logic 2025-12-12 16:43:11 +05:30
aakashb95 491e7d0c6c Merge branch 'feature/cw-6117' into feature/cw-6118 2025-12-12 16:36:58 +05:30
aakashb95 e81b812ac2 ui changes 2025-12-12 16:34:52 +05:30
aakashb95 baec4523d5 add improve prompts 2025-12-12 16:27:43 +05:30
Aakash BakhleandGitHub 65136c90d8 Merge branch 'develop' into feature/cw-6117 2025-12-12 14:45:57 +05:30
aakashb95 24125e2d9a move agent instruction for tone to file 2025-12-12 14:39:58 +05:30
aakashb95 52e3642127 fix rubocop 2025-12-12 14:39:46 +05:30
aakashb95 6e13eb965b fix specs 2025-12-12 14:28:22 +05:30
aakashb95 7d78f67d3b fix ent override 2025-12-12 14:15:04 +05:30
aakashb95 a7420f8fae update specs 2025-12-12 14:00:47 +05:30
aakashb95 847ce98006 add confident tone 2025-12-12 14:00:40 +05:30
aakashb95 8b9da2e409 include keys for tones 2025-12-11 22:49:57 +05:30
aakashb95 d540d885f2 base prompt to improve editor replies 2025-12-11 22:49:46 +05:30
9 changed files with 154 additions and 38 deletions
@@ -102,8 +102,8 @@ const createNonDraftMessageAIAssistActions = (t, replyMode) => {
const createDraftMessageAIAssistActions = t => {
return [
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.REPHRASE'),
key: 'rephrase',
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.CONFIDENT'),
key: 'confident',
icon: ICON_AI_ASSIST,
},
{
@@ -112,13 +112,13 @@ const createDraftMessageAIAssistActions = t => {
icon: ICON_AI_GRAMMAR,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.EXPAND'),
key: 'expand',
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.PROFESSIONAL'),
key: 'professional',
icon: ICON_AI_EXPAND,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SHORTEN'),
key: 'shorten',
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.CASUAL'),
key: 'casual',
icon: ICON_AI_SHORTEN,
},
{
@@ -132,8 +132,8 @@ const createDraftMessageAIAssistActions = t => {
icon: ICON_AI_ASSIST,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SIMPLIFY'),
key: 'simplify',
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.STRAIGHTFORWARD'),
key: 'straightforward',
icon: ICON_AI_ASSIST,
},
];
@@ -145,7 +145,11 @@
"EXPAND": "Expand",
"MAKE_FRIENDLY": "Change message tone to friendly",
"MAKE_FORMAL": "Use formal tone",
"SIMPLIFY": "Simplify"
"SIMPLIFY": "Simplify",
"CONFIDENT": "Use confident tone",
"PROFESSIONAL": "Use professional tone",
"CASUAL": "Use casual tone",
"STRAIGHTFORWARD": "Use straightforward tone"
},
"ASSISTANCE_MODAL": {
"DRAFT_TITLE": "Draft content",
@@ -8,4 +8,8 @@ export const OPEN_AI_OPTIONS = {
SIMPLIFY: 'simplify',
REPLY_SUGGESTION: 'reply_suggestion',
SUMMARIZE: 'summarize',
CASUAL: 'casual',
PROFESSIONAL: 'professional',
STRAIGHTFORWARD: 'straightforward',
CONFIDENT: 'confident',
};
@@ -1,6 +1,6 @@
module Enterprise::Integrations::OpenaiProcessorService
ALLOWED_EVENT_NAMES = %w[rephrase summarize reply_suggestion label_suggestion fix_spelling_grammar shorten expand
make_friendly make_formal simplify].freeze
ALLOWED_EVENT_NAMES = %w[summarize reply_suggestion label_suggestion fix_spelling_grammar improve
make_friendly make_formal casual professional confident straightforward].freeze
CACHEABLE_EVENTS = %w[label_suggestion].freeze
def label_suggestion_message
+2 -1
View File
@@ -7,7 +7,8 @@ class Integrations::LlmBaseService
# 120000 * 4 = 480,000 characters (rounding off downwards to 400,000 to be safe)
TOKEN_LIMIT = 400_000
GPT_MODEL = Llm::Config::DEFAULT_MODEL
ALLOWED_EVENT_NAMES = %w[rephrase summarize reply_suggestion fix_spelling_grammar shorten expand make_friendly make_formal simplify].freeze
ALLOWED_EVENT_NAMES = %w[summarize reply_suggestion fix_spelling_grammar casual professional make_friendly make_formal confident
straightforward improve].freeze
CACHEABLE_EVENTS = %w[].freeze
pattr_initialize [:hook!, :event!]
@@ -0,0 +1,29 @@
You are an AI writing assistant integrated into Chatwoot, an omnichannel customer support platform.
{% if selection -%}
# Task
Improve ONLY the selected portion of the message. Return the COMPLETE message with the improved selection seamlessly integrated. Keep all other parts exactly as they are.
{% endif -%}
{% if tone_instruction -%}
# Tone
Apply the following tone to {% if selection %}the selected portion{% else %}the message{% endif %}:
<tone_instruction>
{{ tone_instruction }}
</tone_instruction>
{% endif -%}
{% unless tone_instruction or selection -%}
# Task
Improve the message for clarity, grammar, spelling, and natural flow while maintaining the original tone and intent.
{% endunless -%}
# Guidelines
- Preserve the core meaning and all important information
- Keep the message concise and appropriate for customer support
- Do not add information that wasn't in the original
- Do not remove critical details or instructions
- Reply in the same language as the original message
Output only the final improved message without any preamble, tags or explanation.
@@ -0,0 +1,19 @@
You are an AI writing assistant integrated into Chatwoot, an omnichannel customer support platform. Your task is to rewrite customer support message to match a specific tone while preserving the original meaning and intent.
Here is the tone to apply to the message you will receive:
<tone_instruction>
%s
</tone_instruction>
Your task is to rewrite the message according to the specified tone instructions.
Important guidelines:
- Preserve the core meaning and all important information from the original message
- Keep the rewritten message concise and appropriate for customer support
- Maintain helpfulness and respect regardless of tone
- Do not add information that wasn't in the original message
- Do not remove critical details or instructions
- Ensure that the reply should be in user language.
Output only the rewritten message without any preamble, tags or explanation.
+67 -18
View File
@@ -1,5 +1,4 @@
class Integrations::Openai::ProcessorService < Integrations::LlmBaseService
AGENT_INSTRUCTION = 'You are a helpful support agent.'.freeze
LANGUAGE_INSTRUCTION = 'Ensure that the reply should be in user language.'.freeze
def reply_suggestion_message
make_api_call(reply_suggestion_body)
@@ -9,39 +8,50 @@ class Integrations::Openai::ProcessorService < Integrations::LlmBaseService
make_api_call(summarize_body)
end
def rephrase_message
make_api_call(build_api_call_body("#{AGENT_INSTRUCTION} Please rephrase the following response. " \
"#{LANGUAGE_INSTRUCTION}"))
def confident_message
tone_instruction = determine_tone_instruction('confident')
make_api_call(build_api_call_body(tone_rewrite_prompt(tone_instruction)))
end
def fix_spelling_grammar_message
make_api_call(build_api_call_body("#{AGENT_INSTRUCTION} Please fix the spelling and grammar of the following response. " \
make_api_call(build_api_call_body('Please fix the spelling and grammar of the following response. ' \
"#{LANGUAGE_INSTRUCTION}"))
end
def shorten_message
make_api_call(build_api_call_body("#{AGENT_INSTRUCTION} Please shorten the following response. " \
"#{LANGUAGE_INSTRUCTION}"))
def straightforward_message
tone_instruction = determine_tone_instruction('straightforward')
make_api_call(build_api_call_body(tone_rewrite_prompt(tone_instruction)))
end
def expand_message
make_api_call(build_api_call_body("#{AGENT_INSTRUCTION} Please expand the following response. " \
"#{LANGUAGE_INSTRUCTION}"))
def casual_message
tone_instruction = determine_tone_instruction('casual')
make_api_call(build_api_call_body(tone_rewrite_prompt(tone_instruction)))
end
def make_friendly_message
make_api_call(build_api_call_body("#{AGENT_INSTRUCTION} Please make the following response more friendly. " \
"#{LANGUAGE_INSTRUCTION}"))
tone_instruction = determine_tone_instruction('friendly')
make_api_call(build_api_call_body(tone_rewrite_prompt(tone_instruction)))
end
def make_formal_message
make_api_call(build_api_call_body("#{AGENT_INSTRUCTION} Please make the following response more formal. " \
"#{LANGUAGE_INSTRUCTION}"))
tone_instruction = determine_tone_instruction('formal')
make_api_call(build_api_call_body(tone_rewrite_prompt(tone_instruction)))
end
def simplify_message
make_api_call(build_api_call_body("#{AGENT_INSTRUCTION} Please simplify the following response. " \
"#{LANGUAGE_INSTRUCTION}"))
def professional_message
tone_instruction = determine_tone_instruction('professional')
make_api_call(build_api_call_body(tone_rewrite_prompt(tone_instruction)))
end
def improve_message
content = event['data']['content']
selection = event['data']['selection'].presence
tone = event['data']['tone'].presence
system_prompt = improve_prompt(selection, tone)
user_content = selection ? improve_user_content(content, selection) : content
make_api_call(build_api_call_body(system_prompt, user_content))
end
private
@@ -51,6 +61,25 @@ class Integrations::Openai::ProcessorService < Integrations::LlmBaseService
Rails.root.join(path, "#{file_name}.txt").read
end
def tone_rewrite_prompt(tone_instruction)
format(prompt_from_file('tone_rewrite'), tone_instruction)
end
def improve_prompt(selection, tone)
tone_instruction = determine_tone_instruction(tone) if tone.present?
render_liquid_prompt('improve', { selection: selection, tone_instruction: tone_instruction })
end
def improve_user_content(content, selection)
"Full message:\n#{content}\n\nSelected portion to improve:\n#{selection}"
end
def render_liquid_prompt(template_name, context = {})
template_path = Rails.root.join('lib/integrations/openai/openai_prompts', "#{template_name}.liquid")
template = Liquid::Template.parse(template_path.read)
template.render(context.deep_stringify_keys)
end
def build_api_call_body(system_content, user_content = event['data']['content'])
{
model: GPT_MODEL,
@@ -133,6 +162,26 @@ class Integrations::Openai::ProcessorService < Integrations::LlmBaseService
].concat(conversation_messages(in_array_format: true))
}.to_json
end
def determine_tone_instruction(tone)
case tone
when 'friendly'
'Warm, approachable, and personable. Use conversational language, positive words, and show empathy. ' \
'May include phrases like \"Happy to help!\" or \"I\'d be glad to...\"'
when 'confident'
'Assertive and assured. Use definitive language, avoid hedging words like \"maybe\" or \"I think\". ' \
'Be direct and authoritative while remaining helpful.'
when 'straightforward'
'Clear, direct, and to-the-point. Remove unnecessary words, get straight to the information or solution. No fluff or extra pleasantries.'
when 'casual'
'Relaxed and informal. Use contractions, simpler words, and a conversational style. Friendly but less formal than professional tone.'
when 'professional'
'Formal, polished, and business-appropriate. Use complete sentences, proper grammar, ' \
'and maintain respectful distance. Avoid slang or overly casual language.'
else
determine_tone_instruction('friendly')
end
end
end
Integrations::Openai::ProcessorService.prepend_mod_with('Integrations::OpenaiProcessorService')
@@ -53,17 +53,23 @@ RSpec.describe Integrations::Openai::ProcessorService do
it 'sets system instructions' do
service.perform
expect(mock_chat).to have_received(:with_instructions).with(a_string_including('You are a helpful support agent'))
if event_name == 'fix_spelling_grammar'
expect(mock_chat).to have_received(:with_instructions)
.with(a_string_including('Please fix the spelling and grammar'))
else
expect(mock_chat).to have_received(:with_instructions)
.with(a_string_including('You are an AI writing assistant integrated into Chatwoot'))
end
end
end
it_behaves_like 'text transformation operation', 'rephrase'
it_behaves_like 'text transformation operation', 'confident'
it_behaves_like 'text transformation operation', 'fix_spelling_grammar'
it_behaves_like 'text transformation operation', 'shorten'
it_behaves_like 'text transformation operation', 'expand'
it_behaves_like 'text transformation operation', 'casual'
it_behaves_like 'text transformation operation', 'professional'
it_behaves_like 'text transformation operation', 'make_friendly'
it_behaves_like 'text transformation operation', 'make_formal'
it_behaves_like 'text transformation operation', 'simplify'
it_behaves_like 'text transformation operation', 'straightforward'
end
describe 'conversation-based operations' do
@@ -125,7 +131,7 @@ RSpec.describe Integrations::Openai::ProcessorService do
end
describe 'response structure' do
let(:event) { { 'name' => 'rephrase', 'data' => { 'content' => 'test message' } } }
let(:event) { { 'name' => 'confident', 'data' => { 'content' => 'test message' } } }
context 'when response includes usage data' do
before do
@@ -166,10 +172,13 @@ RSpec.describe Integrations::Openai::ProcessorService do
end
describe 'endpoint configuration' do
let(:event) { { 'name' => 'rephrase', 'data' => { 'content' => 'test message' } } }
let(:event) { { 'name' => 'confident', 'data' => { 'content' => 'test message' } } }
context 'without CAPTAIN_OPEN_AI_ENDPOINT configured' do
before { InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_ENDPOINT')&.destroy }
before do
InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_ENDPOINT')&.destroy
allow(Llm::Config).to receive(:with_api_key).and_call_original
end
it 'uses default OpenAI endpoint' do
expect(Llm::Config).to receive(:with_api_key).with(
@@ -185,6 +194,7 @@ RSpec.describe Integrations::Openai::ProcessorService do
before do
InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_ENDPOINT')&.destroy
create(:installation_config, name: 'CAPTAIN_OPEN_AI_ENDPOINT', value: 'https://custom.azure.com/')
allow(Llm::Config).to receive(:with_api_key).and_call_original
end
it 'uses custom endpoint' do