diff --git a/enterprise/app/models/concerns/agentable.rb b/enterprise/app/models/concerns/agentable.rb index c12e15f52..810cf2556 100644 --- a/enterprise/app/models/concerns/agentable.rb +++ b/enterprise/app/models/concerns/agentable.rb @@ -24,7 +24,8 @@ module Concerns::Agentable current_time: format_current_time(state[:timezone]), conversation: state[:conversation] || {}, contact: config['feature_contact_attributes'].present? ? state[:contact] : nil, - campaign: state[:campaign] || {} + campaign: state[:campaign] || {}, + message_length_limit: state[:message_length_limit] ) end diff --git a/enterprise/app/services/captain/assistant/agent_runner_service.rb b/enterprise/app/services/captain/assistant/agent_runner_service.rb index 197b2e39b..059b11fc1 100644 --- a/enterprise/app/services/captain/assistant/agent_runner_service.rb +++ b/enterprise/app/services/captain/assistant/agent_runner_service.rb @@ -20,6 +20,9 @@ class Captain::Assistant::AgentRunnerService def generate_response(message_history: []) message_to_process, context = run_payload(message_history) @last_run_result = runner.run(message_to_process, context: context, max_turns: 10) + @last_run_result = rewrite_oversized_response(@last_run_result) if response_too_long?(@last_run_result) + + raise "Captain response exceeds the channel limit of #{message_length_limit} characters" if response_too_long?(@last_run_result) process_agent_result(@last_run_result) rescue StandardError => e @@ -93,6 +96,28 @@ class Captain::Assistant::AgentRunnerService response end + def rewrite_oversized_response(result) + runner.run( + "Your previous response was #{response_text(result).length} characters, but this channel allows a maximum of " \ + "#{message_length_limit} characters. Rewrite it within the limit while preserving the essential information. " \ + 'Do not call tools or perform new actions.', + context: result.context, + max_turns: 10 + ) + end + + def response_too_long?(result) + message_length_limit && response_text(result).length > message_length_limit + end + + def response_text(result) + extract_text_from_content(result.output).to_s + end + + def message_length_limit + @message_length_limit ||= Captain::MessageLengthLimit.for(@conversation&.inbox) + end + def error_response(error_message) { 'response' => 'conversation_handoff', diff --git a/enterprise/app/services/captain/assistant/runner_state_helper.rb b/enterprise/app/services/captain/assistant/runner_state_helper.rb index d2d6146f8..3b43e5ecc 100644 --- a/enterprise/app/services/captain/assistant/runner_state_helper.rb +++ b/enterprise/app/services/captain/assistant/runner_state_helper.rb @@ -31,6 +31,7 @@ module Captain::Assistant::RunnerStateHelper def build_conversation_state(state) state[:conversation] = slice_attrs(@conversation, CONVERSATION_STATE_ATTRIBUTES) state[:channel_type] = @conversation.inbox&.channel_type + state[:message_length_limit] = Captain::MessageLengthLimit.for(@conversation.inbox) state[:contact] = slice_attrs(@conversation.contact, CONTACT_STATE_ATTRIBUTES) if @conversation.contact state[:campaign] = slice_attrs(@conversation.campaign, CAMPAIGN_STATE_ATTRIBUTES) if @conversation.campaign state[:contact_inbox] = slice_attrs(@conversation.contact_inbox, CONTACT_INBOX_STATE_ATTRIBUTES) if @conversation.contact_inbox diff --git a/enterprise/lib/captain/message_length_limit.rb b/enterprise/lib/captain/message_length_limit.rb new file mode 100644 index 000000000..0d5918269 --- /dev/null +++ b/enterprise/lib/captain/message_length_limit.rb @@ -0,0 +1,23 @@ +class Captain::MessageLengthLimit + DEFAULT = 10_000 + CHANNEL_LIMITS = { + 'Channel::FacebookPage' => 2_000, + 'Channel::Instagram' => 1_000, + 'Channel::Line' => 2_000, + 'Channel::Sms' => 320, + 'Channel::Telegram' => 4_096, + 'Channel::Tiktok' => 6_000, + 'Channel::Whatsapp' => 4_096 + }.freeze + TWILIO_LIMITS = { + 'sms' => 320, + 'whatsapp' => 1_600 + }.freeze + + def self.for(inbox) + return unless inbox + return TWILIO_LIMITS.fetch(inbox.channel.medium) if inbox.twilio? + + CHANNEL_LIMITS.fetch(inbox.channel_type, DEFAULT) + end +end diff --git a/enterprise/lib/captain/prompts/assistant.liquid b/enterprise/lib/captain/prompts/assistant.liquid index a8f1dada3..722ca0631 100644 --- a/enterprise/lib/captain/prompts/assistant.liquid +++ b/enterprise/lib/captain/prompts/assistant.liquid @@ -50,6 +50,11 @@ Always respect these boundaries: When a Response Guideline or Guardrail explicitly requires transfer for a matched condition, follow it instead of the generic consent-first handoff defaults below. +{% if message_length_limit -%} +# Channel Requirements +Keep your response at or under {{ message_length_limit }} characters so it can be delivered through this channel. +{% endif -%} + # Decision Framework ## 1. Analyze the Request diff --git a/enterprise/lib/captain/prompts/scenario.liquid b/enterprise/lib/captain/prompts/scenario.liquid index afa2cd420..667c009dc 100644 --- a/enterprise/lib/captain/prompts/scenario.liquid +++ b/enterprise/lib/captain/prompts/scenario.liquid @@ -47,6 +47,11 @@ Always respect these boundaries: {% endfor %} {% endif -%} +{% if message_length_limit -%} +# Channel Requirements +Keep your response at or under {{ message_length_limit }} characters so it can be delivered through this channel. +{% endif -%} + {% if tools.size > 0 -%} # Available Tools You have access to these tools: