presenton/electron/servers/fastapi/utils/llm_calls/generate_presentation_structure.py

173 lines
5.9 KiB
Python

from typing import Optional, Dict
from models.llm_message import LLMSystemMessage, LLMUserMessage
from models.presentation_layout import PresentationLayoutModel
from models.presentation_outline_model import PresentationOutlineModel
from services.llm_client import LLMClient
from utils.llm_client_error_handler import handle_llm_client_exceptions
from utils.llm_provider import get_model
from utils.get_dynamic_models import get_presentation_structure_model_with_n_slides
from models.presentation_structure_model import PresentationStructureModel
STRUCTURE_FROM_SLIDES_MARKDOWN_SYSTEM_PROMPT = """
You will be given available slide layouts and content for each slide.
You need to select a layout for each slide based on the mentioned guidelines.
# Steps
1. Analyze all available slide layouts.
2. Analyze content for each slide.
3. Select a layout for each slide one by one by following the selection rules.
# Analyzing Slide Layouts
- Identify what each layout contains based on provided schema markdown.
# Analyzing Content
- Identify how the content is structured.
- Identify if the content contains tables.
# Selection Rules
- If content contains table, then select either table layout or graph layout.
- Don't select layout with image unless content contains image.
- Don't select table layout if content does not contain table.
- You are allowed to select same layout for multiple slides.
# Table Layout Selection Rules
- Must select table layout if the content contains table with text data.
- Must only select a layout with table if the table only contains text data.
# Graph Layout Selection Rules
- Must only select a layout with chart if the content contains table with numeric data.
- Identify how many columns are present in the table.
- Must select a layout that supports n-1 charts for n columns.
- Must prioritize layouts that support multiple charts.
- Don't select metrics layout for content containing table with numeric data.
- For example, if content contains table with 3 columns, then select a layout that supports 2 charts.
{user_instructions}
# Output Rules:
- One layout index for each slide.
- Example: [0, 1, 2, 3, 4]
{presentation_layout}
"""
GET_MESSAGES_SYSTEM_PROMPT = """
You're a professional presentation designer with creative freedom to design engaging presentations.
# DESIGN PHILOSOPHY
- Create visually compelling and varied presentations
- Match layout to content purpose and audience needs
# Layout Selection Guidelines
1. **Content-driven choices**: Let the slide's purpose guide layout selection
- Opening/closing → Title layouts
- Processes/workflows → Visual process layouts
- Comparisons/contrasts → Side-by-side layouts
- Data/metrics → Chart/graph layouts
- Concepts/ideas → Image + text layouts
- Key insights → Emphasis layouts
2. **Visual variety**: Aim for diverse slide layouts across the presentation.
- Don't use same layout for multiple slides unless necessary.
- Mix text-heavy and visual-heavy slides naturally
- Use your judgment on when repetition serves the content
- Balance information density across slides
- Adjacent slide layouts should be different unless instructed/necessary otherwise.
3. **Audience experience**: Consider how slides work together
- Create natural transitions between topics
4. **Table of contents**:
- Must only use table of contents layout if slide content contains table of contents.
{user_instruction_header}
User instruction should be taken into account while creating the presentation structure, except for number of slides.
Select layout index for each of the {n_slides} slides based on what will best serve the presentation's goals.
"""
def get_messages(
presentation_layout: PresentationLayoutModel,
n_slides: int,
data: str,
instructions: Optional[str] = None,
):
system_prompt = GET_MESSAGES_SYSTEM_PROMPT.format(
user_instruction_header="# User Instruction:" if instructions else "",
n_slides=n_slides,
)
return [
LLMSystemMessage(content=system_prompt),
LLMUserMessage(content=(
f"{presentation_layout.to_string()}\n\n"
"--------------------------------------\n\n"
f"{data}"
)),
]
def get_messages_for_slides_markdown(
presentation_layout: PresentationLayoutModel,
n_slides: int,
data: str,
instructions: Optional[str] = None,
):
system_prompt = STRUCTURE_FROM_SLIDES_MARKDOWN_SYSTEM_PROMPT.format(
user_instructions=instructions or "",
presentation_layout=presentation_layout.to_string(with_schema=True),
)
return [
LLMSystemMessage(
content=system_prompt
),
LLMUserMessage(
content=data
)
]
async def generate_presentation_structure(
presentation_outline: PresentationOutlineModel,
presentation_layout: PresentationLayoutModel,
instructions: Optional[str] = None,
using_slides_markdown: bool = False,
) -> PresentationStructureModel:
client = LLMClient()
model = get_model()
response_model = get_presentation_structure_model_with_n_slides(
len(presentation_outline.slides)
)
try:
response = await client.generate_structured(
model=model,
messages=(
get_messages_for_slides_markdown(
presentation_layout,
len(presentation_outline.slides),
presentation_outline.to_string(),
instructions,
)
if using_slides_markdown
else get_messages(
presentation_layout,
len(presentation_outline.slides),
presentation_outline.to_string(),
instructions,
)
),
response_format=response_model.model_json_schema(),
strict=True,
)
return PresentationStructureModel(**response)
except Exception as e:
raise handle_llm_client_exceptions(e)