semblance/backend/app/services/focus_group_summary_service.py
2025-12-19 19:26:16 +00:00

125 lines
5.1 KiB
Python
Executable file

"""
Focus Group Summary Service
This service generates LLM-powered one-line summaries for focus groups,
designed for display in the focus group list view to help users quickly
understand what each session is about.
"""
from typing import Dict, Any, Optional
import logging
from app.services.llm_service import LLMService, LLMServiceError
from app.utils.prompt_loader import load_prompt, PromptLoaderError
# Set up logger for this module
logger = logging.getLogger(__name__)
class FocusGroupSummaryError(Exception):
"""Custom exception for focus group summary generation errors."""
pass
async def generate_focus_group_summary(
focus_group_data: Dict[str, Any],
temperature: float = 0.7,
llm_model: Optional[str] = None
) -> Optional[str]:
"""
Generate a one-line summary of a focus group for display in list views.
Args:
focus_group_data: Dictionary containing:
- name: Focus group name
- topic: Discussion topic
- duration: Session duration in minutes
- description: Research brief/objective
- discussionGuide: (optional) The generated discussion guide
temperature: LLM temperature setting (default 0.7 for balanced creativity)
llm_model: Optional LLM model override
Returns:
A concise one-line summary string (max ~100 characters), or None on failure
"""
try:
# Extract data from focus group
name = focus_group_data.get('name', 'Unnamed Focus Group')
topic = focus_group_data.get('topic', 'General Research')
duration = focus_group_data.get('duration', 60)
description = focus_group_data.get('description') or focus_group_data.get('objective', '')
discussion_guide = focus_group_data.get('discussionGuide', '')
# Build discussion guide section for prompt
discussion_guide_section = ""
if discussion_guide:
# Handle both string and dict formats for discussion guide
if isinstance(discussion_guide, dict):
guide_title = discussion_guide.get('title', '')
guide_sections = discussion_guide.get('sections', [])
guide_text = f"Discussion Guide Title: {guide_title}\n"
for section in guide_sections[:3]: # Limit to first 3 sections to avoid too much context
section_title = section.get('title', '')
section_content = section.get('content', '')[:200] # Truncate content
guide_text += f"- {section_title}: {section_content}...\n"
discussion_guide_section = f"Discussion Guide Overview:\n{guide_text}"
elif isinstance(discussion_guide, str) and len(discussion_guide) > 0:
# Truncate long guides
truncated_guide = discussion_guide[:500] + "..." if len(discussion_guide) > 500 else discussion_guide
discussion_guide_section = f"Discussion Guide Overview:\n{truncated_guide}"
# Load and format the prompt
try:
final_prompt = load_prompt('focus-group-summary-generation', {
'name': name,
'topic': topic,
'duration': str(duration),
'description': description or 'Not specified',
'discussion_guide_section': discussion_guide_section
})
except PromptLoaderError as e:
logger.error(f"Error loading focus group summary prompt: {e}")
raise FocusGroupSummaryError(f"Error loading summary prompt: {str(e)}")
# Log the LLM API call
logger.info(f"Generating summary for focus group: {name}")
print(f"🤖 Backend: Generating one-line summary for focus group '{name}' using {llm_model or 'default model'}")
try:
raw_response = await LLMService.generate_content(
prompt=final_prompt,
temperature=temperature,
model_name=llm_model
)
# Clean up the response
summary = raw_response.strip()
# Remove any quotes that might wrap the response
if summary.startswith('"') and summary.endswith('"'):
summary = summary[1:-1]
if summary.startswith("'") and summary.endswith("'"):
summary = summary[1:-1]
# Remove any markdown formatting
if summary.startswith("```"):
summary = summary.strip("```").strip()
# Truncate if too long (enforce max 150 characters for safety)
if len(summary) > 150:
summary = summary[:147] + "..."
logger.info(f"Generated summary for '{name}': {summary}")
print(f"✅ Generated summary: {summary}")
return summary
except LLMServiceError as e:
logger.error(f"LLM service error generating summary: {e}")
raise FocusGroupSummaryError(f"Error from LLM service: {str(e)}")
except FocusGroupSummaryError:
raise
except Exception as e:
logger.error(f"Unexpected error generating focus group summary: {e}")
raise FocusGroupSummaryError(f"Error generating focus group summary: {str(e)}")