feat: include contact verified status with each tool call (#13663)
Co-authored-by: aakashb95 <aakashbakhle@gmail.com>
This commit is contained in:
@@ -71,6 +71,7 @@ module Concerns::Toolable
|
|||||||
add_base_headers(headers, state)
|
add_base_headers(headers, state)
|
||||||
add_conversation_headers(headers, state[:conversation]) if state[:conversation]
|
add_conversation_headers(headers, state[:conversation]) if state[:conversation]
|
||||||
add_contact_headers(headers, state[:contact]) if state[:contact]
|
add_contact_headers(headers, state[:contact]) if state[:contact]
|
||||||
|
add_contact_inbox_headers(headers, state[:contact_inbox])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -91,6 +92,11 @@ module Concerns::Toolable
|
|||||||
headers['X-Chatwoot-Contact-Phone'] = contact[:phone_number].to_s if contact[:phone_number].present?
|
headers['X-Chatwoot-Contact-Phone'] = contact[:phone_number].to_s if contact[:phone_number].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_contact_inbox_headers(headers, contact_inbox)
|
||||||
|
headers['X-Chatwoot-Contact-Inbox-Id'] = contact_inbox[:id].to_s if contact_inbox&.[](:id)
|
||||||
|
headers['X-Chatwoot-Contact-Inbox-Verified'] = (contact_inbox&.[](:hmac_verified) || false).to_s
|
||||||
|
end
|
||||||
|
|
||||||
def format_response(raw_response_body)
|
def format_response(raw_response_body)
|
||||||
return raw_response_body if response_template.blank?
|
return raw_response_body if response_template.blank?
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class Captain::Assistant::AgentRunnerService
|
|||||||
custom_attributes additional_attributes
|
custom_attributes additional_attributes
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
|
CONTACT_INBOX_STATE_ATTRIBUTES = %i[id hmac_verified].freeze
|
||||||
|
|
||||||
CAMPAIGN_STATE_ATTRIBUTES = %i[id title message campaign_type description].freeze
|
CAMPAIGN_STATE_ATTRIBUTES = %i[id title message campaign_type description].freeze
|
||||||
|
|
||||||
def initialize(assistant:, conversation: nil, callbacks: {})
|
def initialize(assistant:, conversation: nil, callbacks: {})
|
||||||
@@ -127,16 +129,21 @@ class Captain::Assistant::AgentRunnerService
|
|||||||
assistant_config: @assistant.config
|
assistant_config: @assistant.config
|
||||||
}
|
}
|
||||||
|
|
||||||
if @conversation
|
build_conversation_state(state) if @conversation
|
||||||
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
|
state
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_conversation_state(state)
|
||||||
|
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
|
||||||
|
return unless @conversation.contact_inbox
|
||||||
|
|
||||||
|
state[:contact_inbox] =
|
||||||
|
@conversation.contact_inbox.attributes.symbolize_keys.slice(*CONTACT_INBOX_STATE_ATTRIBUTES)
|
||||||
|
end
|
||||||
|
|
||||||
def build_and_wire_agents
|
def build_and_wire_agents
|
||||||
assistant_agent = @assistant.agent
|
assistant_agent = @assistant.agent
|
||||||
scenario_agents = @assistant.scenarios.enabled.map(&:agent)
|
scenario_agents = @assistant.scenarios.enabled.map(&:agent)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Here's the metadata we have about the current conversation and the contact assoc
|
|||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
||||||
{% if campaign.id -%}
|
{% if campaign.id -%}
|
||||||
{% render 'campaign' %}
|
{% render 'campaign', campaign: campaign %}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Here's the metadata we have about the current conversation and the contact assoc
|
|||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
||||||
{% if campaign.id -%}
|
{% if campaign.id -%}
|
||||||
{% render 'campaign' %}
|
{% render 'campaign', campaign: campaign %}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
||||||
|
|||||||
@@ -249,6 +249,10 @@ RSpec.describe Captain::Tools::HttpTool, type: :model do
|
|||||||
id: conversation.id,
|
id: conversation.id,
|
||||||
display_id: conversation.display_id
|
display_id: conversation.display_id
|
||||||
},
|
},
|
||||||
|
contact_inbox: {
|
||||||
|
id: conversation.contact_inbox.id,
|
||||||
|
hmac_verified: conversation.contact_inbox.hmac_verified
|
||||||
|
},
|
||||||
contact: {
|
contact: {
|
||||||
id: contact.id,
|
id: contact.id,
|
||||||
email: contact.email,
|
email: contact.email,
|
||||||
@@ -272,6 +276,8 @@ RSpec.describe Captain::Tools::HttpTool, type: :model do
|
|||||||
'X-Chatwoot-Tool-Slug' => custom_tool.slug,
|
'X-Chatwoot-Tool-Slug' => custom_tool.slug,
|
||||||
'X-Chatwoot-Conversation-Id' => conversation.id.to_s,
|
'X-Chatwoot-Conversation-Id' => conversation.id.to_s,
|
||||||
'X-Chatwoot-Conversation-Display-Id' => conversation.display_id.to_s,
|
'X-Chatwoot-Conversation-Display-Id' => conversation.display_id.to_s,
|
||||||
|
'X-Chatwoot-Contact-Inbox-Id' => conversation.contact_inbox.id.to_s,
|
||||||
|
'X-Chatwoot-Contact-Inbox-Verified' => conversation.contact_inbox.hmac_verified.to_s,
|
||||||
'X-Chatwoot-Contact-Id' => contact.id.to_s,
|
'X-Chatwoot-Contact-Id' => contact.id.to_s,
|
||||||
'X-Chatwoot-Contact-Email' => contact.email
|
'X-Chatwoot-Contact-Email' => contact.email
|
||||||
})
|
})
|
||||||
@@ -282,6 +288,7 @@ RSpec.describe Captain::Tools::HttpTool, type: :model do
|
|||||||
expect(WebMock).to have_requested(:get, 'https://example.com/api/data')
|
expect(WebMock).to have_requested(:get, 'https://example.com/api/data')
|
||||||
.with(headers: {
|
.with(headers: {
|
||||||
'X-Chatwoot-Account-Id' => account.id.to_s,
|
'X-Chatwoot-Account-Id' => account.id.to_s,
|
||||||
|
'X-Chatwoot-Contact-Inbox-Verified' => conversation.contact_inbox.hmac_verified.to_s,
|
||||||
'X-Chatwoot-Contact-Email' => contact.email
|
'X-Chatwoot-Contact-Email' => contact.email
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
@@ -296,6 +303,7 @@ RSpec.describe Captain::Tools::HttpTool, type: :model do
|
|||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'X-Chatwoot-Account-Id' => account.id.to_s,
|
'X-Chatwoot-Account-Id' => account.id.to_s,
|
||||||
'X-Chatwoot-Tool-Slug' => custom_tool.slug,
|
'X-Chatwoot-Tool-Slug' => custom_tool.slug,
|
||||||
|
'X-Chatwoot-Contact-Inbox-Verified' => conversation.contact_inbox.hmac_verified.to_s,
|
||||||
'X-Chatwoot-Contact-Email' => contact.email
|
'X-Chatwoot-Contact-Email' => contact.email
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -316,6 +324,7 @@ RSpec.describe Captain::Tools::HttpTool, type: :model do
|
|||||||
.with(headers: {
|
.with(headers: {
|
||||||
'Authorization' => 'Bearer test_token',
|
'Authorization' => 'Bearer test_token',
|
||||||
'X-Chatwoot-Account-Id' => account.id.to_s,
|
'X-Chatwoot-Account-Id' => account.id.to_s,
|
||||||
|
'X-Chatwoot-Contact-Inbox-Verified' => conversation.contact_inbox.hmac_verified.to_s,
|
||||||
'X-Chatwoot-Contact-Id' => contact.id.to_s
|
'X-Chatwoot-Contact-Id' => contact.id.to_s
|
||||||
})
|
})
|
||||||
.to_return(status: 200, body: '{"success": true}')
|
.to_return(status: 200, body: '{"success": true}')
|
||||||
@@ -336,13 +345,18 @@ RSpec.describe Captain::Tools::HttpTool, type: :model do
|
|||||||
conversation: {
|
conversation: {
|
||||||
id: conversation.id,
|
id: conversation.id,
|
||||||
display_id: conversation.display_id
|
display_id: conversation.display_id
|
||||||
|
},
|
||||||
|
contact_inbox: {
|
||||||
|
id: conversation.contact_inbox.id,
|
||||||
|
hmac_verified: conversation.contact_inbox.hmac_verified
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
stub_request(:get, 'https://example.com/api/data')
|
stub_request(:get, 'https://example.com/api/data')
|
||||||
.with(headers: {
|
.with(headers: {
|
||||||
'X-Chatwoot-Account-Id' => account.id.to_s,
|
'X-Chatwoot-Account-Id' => account.id.to_s,
|
||||||
'X-Chatwoot-Conversation-Id' => conversation.id.to_s
|
'X-Chatwoot-Conversation-Id' => conversation.id.to_s,
|
||||||
|
'X-Chatwoot-Contact-Inbox-Verified' => conversation.contact_inbox.hmac_verified.to_s
|
||||||
})
|
})
|
||||||
.to_return(status: 200, body: '{"success": true}')
|
.to_return(status: 200, body: '{"success": true}')
|
||||||
|
|
||||||
@@ -351,6 +365,32 @@ RSpec.describe Captain::Tools::HttpTool, type: :model do
|
|||||||
expect(WebMock).to have_requested(:get, 'https://example.com/api/data')
|
expect(WebMock).to have_requested(:get, 'https://example.com/api/data')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'defaults contact inbox verified header to false when contact inbox is missing' do
|
||||||
|
tool_context_without_contact_inbox = Struct.new(:state).new({
|
||||||
|
account_id: account.id,
|
||||||
|
assistant_id: assistant.id,
|
||||||
|
conversation: {
|
||||||
|
id: conversation.id,
|
||||||
|
display_id: conversation.display_id
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
id: contact.id,
|
||||||
|
email: contact.email
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
stub_request(:get, 'https://example.com/api/data')
|
||||||
|
.with(headers: {
|
||||||
|
'X-Chatwoot-Contact-Inbox-Verified' => 'false'
|
||||||
|
})
|
||||||
|
.to_return(status: 200, body: '{"success": true}')
|
||||||
|
|
||||||
|
tool.perform(tool_context_without_contact_inbox)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:get, 'https://example.com/api/data')
|
||||||
|
.with(headers: { 'X-Chatwoot-Contact-Inbox-Verified' => 'false' })
|
||||||
|
end
|
||||||
|
|
||||||
it 'includes contact phone when present' do
|
it 'includes contact phone when present' do
|
||||||
contact.update!(phone_number: '+1234567890')
|
contact.update!(phone_number: '+1234567890')
|
||||||
tool_context_with_state.state[:contact][:phone_number] = '+1234567890'
|
tool_context_with_state.state[:contact][:phone_number] = '+1234567890'
|
||||||
@@ -366,6 +406,22 @@ RSpec.describe Captain::Tools::HttpTool, type: :model do
|
|||||||
expect(WebMock).to have_requested(:get, 'https://example.com/api/data')
|
expect(WebMock).to have_requested(:get, 'https://example.com/api/data')
|
||||||
.with(headers: { 'X-Chatwoot-Contact-Phone' => '+1234567890' })
|
.with(headers: { 'X-Chatwoot-Contact-Phone' => '+1234567890' })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'includes unverified contact inbox status explicitly as false' do
|
||||||
|
conversation.contact_inbox.update!(hmac_verified: false)
|
||||||
|
tool_context_with_state.state[:contact_inbox][:hmac_verified] = false
|
||||||
|
|
||||||
|
stub_request(:get, 'https://example.com/api/data')
|
||||||
|
.with(headers: {
|
||||||
|
'X-Chatwoot-Contact-Inbox-Verified' => 'false'
|
||||||
|
})
|
||||||
|
.to_return(status: 200, body: '{"success": true}')
|
||||||
|
|
||||||
|
tool.perform(tool_context_with_state)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:get, 'https://example.com/api/data')
|
||||||
|
.with(headers: { 'X-Chatwoot-Contact-Inbox-Verified' => 'false' })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -341,6 +341,10 @@ RSpec.describe Captain::CustomTool, type: :model do
|
|||||||
id: conversation.id,
|
id: conversation.id,
|
||||||
display_id: conversation.display_id
|
display_id: conversation.display_id
|
||||||
},
|
},
|
||||||
|
contact_inbox: {
|
||||||
|
id: conversation.contact_inbox.id,
|
||||||
|
hmac_verified: conversation.contact_inbox.hmac_verified
|
||||||
|
},
|
||||||
contact: {
|
contact: {
|
||||||
id: contact.id,
|
id: contact.id,
|
||||||
email: contact.email,
|
email: contact.email,
|
||||||
@@ -376,6 +380,13 @@ RSpec.describe Captain::CustomTool, type: :model do
|
|||||||
expect(headers['X-Chatwoot-Contact-Email']).to eq(contact.email)
|
expect(headers['X-Chatwoot-Contact-Email']).to eq(contact.email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'includes contact inbox verification metadata when present' do
|
||||||
|
headers = tool.build_metadata_headers(state)
|
||||||
|
|
||||||
|
expect(headers['X-Chatwoot-Contact-Inbox-Id']).to eq(conversation.contact_inbox.id.to_s)
|
||||||
|
expect(headers['X-Chatwoot-Contact-Inbox-Verified']).to eq(conversation.contact_inbox.hmac_verified.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
it 'handles missing conversation gracefully' do
|
it 'handles missing conversation gracefully' do
|
||||||
state[:conversation] = nil
|
state[:conversation] = nil
|
||||||
|
|
||||||
@@ -396,11 +407,21 @@ RSpec.describe Captain::CustomTool, type: :model do
|
|||||||
expect(headers['X-Chatwoot-Account-Id']).to eq(account.id.to_s)
|
expect(headers['X-Chatwoot-Account-Id']).to eq(account.id.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'handles missing contact inbox gracefully' do
|
||||||
|
state[:contact_inbox] = nil
|
||||||
|
|
||||||
|
headers = tool.build_metadata_headers(state)
|
||||||
|
|
||||||
|
expect(headers['X-Chatwoot-Contact-Inbox-Id']).to be_nil
|
||||||
|
expect(headers['X-Chatwoot-Contact-Inbox-Verified']).to eq('false')
|
||||||
|
end
|
||||||
|
|
||||||
it 'handles empty state' do
|
it 'handles empty state' do
|
||||||
headers = tool.build_metadata_headers({})
|
headers = tool.build_metadata_headers({})
|
||||||
|
|
||||||
expect(headers).to be_a(Hash)
|
expect(headers).to be_a(Hash)
|
||||||
expect(headers['X-Chatwoot-Tool-Slug']).to eq('custom_test_tool')
|
expect(headers['X-Chatwoot-Tool-Slug']).to eq('custom_test_tool')
|
||||||
|
expect(headers['X-Chatwoot-Contact-Inbox-Verified']).to eq('false')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'omits contact email header when email is blank' do
|
it 'omits contact email header when email is blank' do
|
||||||
@@ -418,6 +439,22 @@ RSpec.describe Captain::CustomTool, type: :model do
|
|||||||
|
|
||||||
expect(headers).not_to have_key('X-Chatwoot-Contact-Phone')
|
expect(headers).not_to have_key('X-Chatwoot-Contact-Phone')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'includes contact inbox verified header when false' do
|
||||||
|
state[:contact_inbox][:hmac_verified] = false
|
||||||
|
|
||||||
|
headers = tool.build_metadata_headers(state)
|
||||||
|
|
||||||
|
expect(headers['X-Chatwoot-Contact-Inbox-Verified']).to eq('false')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'defaults contact inbox verified header to false when value is nil' do
|
||||||
|
state[:contact_inbox][:hmac_verified] = nil
|
||||||
|
|
||||||
|
headers = tool.build_metadata_headers(state)
|
||||||
|
|
||||||
|
expect(headers['X-Chatwoot-Contact-Inbox-Verified']).to eq('false')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#to_tool_metadata' do
|
describe '#to_tool_metadata' do
|
||||||
|
|||||||
@@ -384,6 +384,15 @@ RSpec.describe Captain::Assistant::AgentRunnerService do
|
|||||||
expect(state[:channel_type]).to eq(inbox.channel_type)
|
expect(state[:channel_type]).to eq(inbox.channel_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'includes contact inbox attributes when conversation is present' do
|
||||||
|
state = service.send(:build_state)
|
||||||
|
|
||||||
|
expect(state[:contact_inbox]).to include(
|
||||||
|
id: conversation.contact_inbox.id,
|
||||||
|
hmac_verified: conversation.contact_inbox.hmac_verified
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it 'includes contact attributes when contact is present' do
|
it 'includes contact attributes when contact is present' do
|
||||||
state = service.send(:build_state)
|
state = service.send(:build_state)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user