diff --git a/lib/captain/summary_service.rb b/lib/captain/summary_service.rb index 1e1e24f76..eb5309cb6 100644 --- a/lib/captain/summary_service.rb +++ b/lib/captain/summary_service.rb @@ -26,11 +26,12 @@ class Captain::SummaryService < Captain::BaseTaskService end def generate_and_cache_summary + msg_count = conversation_message_count result = make_api_call( - model: GPT_MODEL, + model: summary_model(msg_count), messages: [ { role: 'system', content: prompt_from_file('summary') }, - { role: 'user', content: conversation.to_llm_text(include_contact_details: false) } + { role: 'user', content: build_summary_content(msg_count) } ] ) @@ -39,6 +40,41 @@ class Captain::SummaryService < Captain::BaseTaskService result end + def conversation_message_count + conversation.messages.where(message_type: [:incoming, :outgoing]).count + end + + def summary_model(msg_count) + msg_count < 7 ? GPT_MODEL : 'gpt-4.1' + end + + def build_summary_content(msg_count) + llm_text = conversation.to_llm_text(include_contact_details: false) + context = build_conversation_context(msg_count) + context.present? ? "#{llm_text}\n\n#{context}" : llm_text + end + + def build_conversation_context(msg_count) + ['Conversation Stats:', *context_fields(msg_count).compact].join("\n") + end + + def context_fields(msg_count) + [ + "Message count: #{msg_count}", + ("Status: #{conversation.status}" if conversation.status.present?), + ("Priority: #{conversation.priority}" if conversation.priority.present?), + ("Labels: #{conversation.cached_label_list}" if conversation.cached_label_list.present?), + *account_context_fields + ] + end + + def account_context_fields + [ + ("Account industry: #{account.custom_attributes['industry']}" if account.custom_attributes&.dig('industry').present?), + ("Account language: #{account.locale}" if account.locale.present?) + ] + end + def cache_summary(summary) conversation.update( cached_summary: summary, diff --git a/lib/integrations/openai/openai_prompts/summary.liquid b/lib/integrations/openai/openai_prompts/summary.liquid index f14f73cc5..9b8778070 100644 --- a/lib/integrations/openai/openai_prompts/summary.liquid +++ b/lib/integrations/openai/openai_prompts/summary.liquid @@ -2,19 +2,29 @@ AI support conversation summarizer - Produce a high-signal summary that lets a new agent understand the CURRENT STATE in under 10 seconds. + Produce a high-signal bullet summary that lets a new agent understand the CURRENT STATE and know what to do next — in under 10 seconds. + Earlier bullets convey what happened and what the current state is. The final bullet is always the actionable next step. bullet_list true - 10 + + Count the messages in the conversation to determine bullet limits. + 1-3 messages → 1 bullet max + 4-10 messages → 2-3 bullets max + 11-30 messages → 3-4 bullets max + 31+ messages → 5-6 bullets max + true true true + Do not prefix bullets with labels like "Next step:" or "Issue:" — ordering alone conveys structure. - same_as_user + + If an agent locale is provided in the context, write the summary in that language. + true true @@ -27,27 +37,35 @@ What is the customer trying to achieve right now? What is the current blocker/problem? What has already been done that changed the state? - What is the explicitly stated next step (who does what)? + What is the actionable next step (who does what)? + If a priority item has no evidence in the conversation, skip it entirely — do not invent or guess. + The final bullet must always be an actionable next step derived from the conversation. + + If account industry or business context is provided, adapt the summary focus accordingly (e.g., e-commerce → order/payment/shipping; SaaS → features/bugs/subscriptions; healthcare → appointments/records). + If conversation labels are provided, use them to understand the issue category and focus the summary on what matters for that category. + Only use context that is explicitly provided. Do not assume industry or category if not stated. + + A bullet must describe a blocker, a confirmed action taken, a decision, or a required next step. - If a detail does not help an agent act or understand the blocker, omit it. + Would removing this bullet prevent the agent from acting correctly? If no, omit it. Greetings, thanks, apologies, pleasantries, offers of help. Repeated statements that do not add new information. - Meta commentary like “the current blocker is…” if it repeats an earlier bullet. + Meta commentary like "the current blocker is…" if it repeats an earlier bullet. Use ONLY information explicitly present in the conversation. - Do NOT compute or derive facts (no time math like “15 minutes from now”, no inferred causes). - Do NOT invent resolution, closure, or “no further steps” statements. + Do NOT compute or derive facts (no time math like "15 minutes from now", no inferred causes). + Do NOT invent resolution, closure, or "no further steps" statements. Do NOT restate the same fact in multiple bullets; merge into one. @@ -61,7 +79,7 @@ - Prefer **Customer** and **Agent** over “user” and “support agent”. + Prefer **Customer** and **Agent** over "user" and "support agent". @@ -71,7 +89,9 @@ Every bullet contains at least one **bold** phrase. No bullet contains inferred/derived info. No two bullets repeat the same meaning. - Bullets reflect current state and next action (if explicitly stated). + Last bullet is an actionable next step. + Summary language matches agent locale (if provided) or conversation language. + Bullet count respects the scaling rules for the message count. true