feat: add campaign context to Captain v2 prompts (#13644)
Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com>
This commit is contained in:
@@ -19,9 +19,11 @@ module Concerns::Agentable
|
||||
state = context.context[:state] || {}
|
||||
conversation_data = state[:conversation] || {}
|
||||
contact_data = state[:contact] || {}
|
||||
campaign_data = state[:campaign] || {}
|
||||
enhanced_context = enhanced_context.merge(
|
||||
conversation: conversation_data,
|
||||
contact: contact_data
|
||||
contact: contact_data,
|
||||
campaign: campaign_data
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ class Captain::Assistant::AgentRunnerService
|
||||
custom_attributes additional_attributes
|
||||
].freeze
|
||||
|
||||
CAMPAIGN_STATE_ATTRIBUTES = %i[id title message campaign_type description].freeze
|
||||
|
||||
def initialize(assistant:, conversation: nil, callbacks: {})
|
||||
@assistant = assistant
|
||||
@conversation = conversation
|
||||
@@ -129,6 +131,7 @@ class Captain::Assistant::AgentRunnerService
|
||||
state[:conversation] = @conversation.attributes.symbolize_keys.slice(*CONVERSATION_STATE_ATTRIBUTES)
|
||||
state[:channel_type] = @conversation.inbox&.channel_type
|
||||
state[:contact] = @conversation.contact.attributes.symbolize_keys.slice(*CONTACT_STATE_ATTRIBUTES) if @conversation.contact
|
||||
state[:campaign] = @conversation.campaign.attributes.symbolize_keys.slice(*CAMPAIGN_STATE_ATTRIBUTES) if @conversation.campaign
|
||||
end
|
||||
|
||||
state
|
||||
|
||||
@@ -8,7 +8,7 @@ You are {{name}}, a helpful and knowledgeable assistant. Your role is to primari
|
||||
|
||||
Don't digress away from your instructions, and use all the available tools at your disposal for solving customer issues. If you are to state something factual about {{product_name}} ensure you source that information from the FAQs only. Use the `captain--tools--faq_lookup` tool for this.
|
||||
|
||||
{% if conversation || contact -%}
|
||||
{% if conversation || contact || campaign.id -%}
|
||||
# Current Context
|
||||
|
||||
Here's the metadata we have about the current conversation and the contact associated with it:
|
||||
@@ -20,6 +20,10 @@ Here's the metadata we have about the current conversation and the contact assoc
|
||||
{% if contact -%}
|
||||
{% render 'contact' %}
|
||||
{% endif -%}
|
||||
|
||||
{% if campaign.id -%}
|
||||
{% render 'campaign' %}
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
|
||||
{% if response_guidelines.size > 0 -%}
|
||||
|
||||
@@ -8,7 +8,7 @@ You are a specialized agent called "{{ title }}", your task is to handle the fol
|
||||
|
||||
If you believe the user's request is not within the scope of your role, you can assign this conversation back to the orchestrator agent using the `handoff_to_{{ assistant_name }}` tool
|
||||
|
||||
{% if conversation || contact %}
|
||||
{% if conversation || contact || campaign.id %}
|
||||
# Current Context
|
||||
|
||||
Here's the metadata we have about the current conversation and the contact associated with it:
|
||||
@@ -20,6 +20,10 @@ Here's the metadata we have about the current conversation and the contact assoc
|
||||
{% if contact -%}
|
||||
{% render 'contact' %}
|
||||
{% endif -%}
|
||||
|
||||
{% if campaign.id -%}
|
||||
{% render 'campaign' %}
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
|
||||
|
||||
|
||||
8
enterprise/lib/captain/prompts/snippets/campaign.liquid
Normal file
8
enterprise/lib/captain/prompts/snippets/campaign.liquid
Normal file
@@ -0,0 +1,8 @@
|
||||
# Campaign Context
|
||||
This conversation was initiated in response to a campaign message.
|
||||
- Campaign: {{ campaign.title }}
|
||||
- Type: {{ campaign.campaign_type }}
|
||||
{% if campaign.description -%}
|
||||
- Description: {{ campaign.description }}
|
||||
{% endif -%}
|
||||
- Original Message Sent: {{ campaign.message }}
|
||||
@@ -97,7 +97,8 @@ RSpec.describe Concerns::Agentable do
|
||||
expected_context = {
|
||||
base_key: 'base_value',
|
||||
conversation: { id: 123 },
|
||||
contact: { name: 'John' }
|
||||
contact: { name: 'John' },
|
||||
campaign: {}
|
||||
}
|
||||
|
||||
expect(Captain::PromptRenderer).to receive(:render).with(
|
||||
@@ -108,6 +109,26 @@ RSpec.describe Concerns::Agentable do
|
||||
dummy_instance.agent_instructions(context_double)
|
||||
end
|
||||
|
||||
it 'merges campaign data from context state' do
|
||||
context_double = instance_double(Agents::RunContext,
|
||||
context: {
|
||||
state: {
|
||||
conversation: { id: 123 },
|
||||
contact: { name: 'John' },
|
||||
campaign: { id: 10, title: 'Summer Sale', message: 'Check it out' }
|
||||
}
|
||||
})
|
||||
|
||||
expect(Captain::PromptRenderer).to receive(:render).with(
|
||||
'dummy_class',
|
||||
hash_including(
|
||||
campaign: { id: 10, title: 'Summer Sale', message: 'Check it out' }
|
||||
)
|
||||
)
|
||||
|
||||
dummy_instance.agent_instructions(context_double)
|
||||
end
|
||||
|
||||
it 'handles context without state' do
|
||||
context_double = instance_double(Agents::RunContext, context: {})
|
||||
|
||||
@@ -116,7 +137,8 @@ RSpec.describe Concerns::Agentable do
|
||||
hash_including(
|
||||
base_key: 'base_value',
|
||||
conversation: {},
|
||||
contact: {}
|
||||
contact: {},
|
||||
campaign: {}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -394,6 +394,34 @@ RSpec.describe Captain::Assistant::AgentRunnerService do
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not include campaign when conversation has no campaign' do
|
||||
state = service.send(:build_state)
|
||||
|
||||
expect(state).not_to have_key(:campaign)
|
||||
end
|
||||
|
||||
context 'when conversation has a campaign' do
|
||||
let(:campaign) { create(:campaign, account: account, title: 'Summer Sale', message: 'Check out our deals!', description: 'Seasonal promo') }
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox, contact: contact, campaign: campaign) }
|
||||
|
||||
it 'includes campaign attributes in state' do
|
||||
state = service.send(:build_state)
|
||||
|
||||
expect(state[:campaign]).to include(
|
||||
id: campaign.id,
|
||||
title: 'Summer Sale',
|
||||
message: 'Check out our deals!',
|
||||
description: 'Seasonal promo'
|
||||
)
|
||||
end
|
||||
|
||||
it 'only includes attributes defined in CAMPAIGN_STATE_ATTRIBUTES' do
|
||||
state = service.send(:build_state)
|
||||
|
||||
expect(state[:campaign].keys).to match_array(described_class::CAMPAIGN_STATE_ATTRIBUTES)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when conversation is nil' do
|
||||
subject(:service) { described_class.new(assistant: assistant, conversation: nil) }
|
||||
|
||||
@@ -407,6 +435,7 @@ RSpec.describe Captain::Assistant::AgentRunnerService do
|
||||
)
|
||||
expect(state).not_to have_key(:conversation)
|
||||
expect(state).not_to have_key(:contact)
|
||||
expect(state).not_to have_key(:campaign)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -477,5 +506,11 @@ RSpec.describe Captain::Assistant::AgentRunnerService do
|
||||
:id, :name, :email, :phone_number, :identifier, :contact_type
|
||||
)
|
||||
end
|
||||
|
||||
it 'defines campaign state attributes' do
|
||||
expect(described_class::CAMPAIGN_STATE_ATTRIBUTES).to include(
|
||||
:id, :title, :message, :campaign_type, :description
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user