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

195 lines
5.6 KiB
Python

from datetime import datetime
import json
from typing import Optional
from models.llm_message import LLMSystemMessage, LLMUserMessage
from templates.presentation_layout import SlideLayoutModel
from models.presentation_outline_model import SlideOutlineModel
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.schema_utils import add_field_in_schema, remove_fields_from_schema
SLIDE_CONTENT_SYSTEM_PROMPT = """
You will be given slide content and response schema.
You need to generate structured content json based on the schema.
# Steps
1. Analyze the content.
2. Analyze the response schema.
3. Generate structured content json based on the schema.
4. Generate speaker note if required.
5. Provide structured content json as output.
# General Rules
- Make sure to follow language guidelines.
- Speaker note should be normal text, not markdown.
- Never ever go over the max character limit but don't clip the sentence to satisfy character limit instead rephrase it.
- Do not add emoji in the content.
- Don't provide $schema field in content json.
{markdown_emphasis_rules}
{user_instructions}
{tone_instructions}
{verbosity_instructions}
{output_fields_instructions}
"""
SLIDE_CONTENT_USER_PROMPT = """
# Current Date and Time:
{current_date_time}
# Icon Query And Image Prompt Language:
English
# Slide Language:
{language}
# SLIDE CONTENT: START
{content}
# SLIDE CONTENT: END
"""
def _resolve_prompt_language(language: Optional[str]) -> str:
if language is None:
return "auto-detect"
s = str(language).strip()
if not s:
return "auto-detect"
if s.lower() in {"auto", "auto-detect"}:
return "auto-detect"
return s
def _get_schema_markdown(response_schema: Optional[dict]) -> str:
if not response_schema:
return "- Follow the provided response schema strictly."
try:
schema_text = json.dumps(response_schema, ensure_ascii=False)
except Exception:
return "- Follow the provided response schema strictly."
return f"- Follow this response schema exactly: {schema_text}"
def get_system_prompt(
tone: Optional[str] = None,
verbosity: Optional[str] = None,
instructions: Optional[str] = None,
response_schema: Optional[dict] = None,
):
markdown_emphasis_rules = (
"- Strictly use markdown to emphasize important points, by bolding or "
"italicizing the part of text."
)
user_instructions = f"# User Instructions:\n{instructions}" if instructions else ""
tone_instructions = (
f"# Tone Instructions:\nMake slide as {tone} as possible." if tone else ""
)
verbosity_instructions = ""
if verbosity:
verbosity_instructions = "# Verbosity Instructions:\n"
if verbosity == "concise":
verbosity_instructions += "Make slide as concise as possible."
elif verbosity == "standard":
verbosity_instructions += "Make slide as standard as possible."
elif verbosity == "text-heavy":
verbosity_instructions += "Make slide as text-heavy as possible."
output_fields_instructions = "# Output Fields:\n" + _get_schema_markdown(
response_schema
)
return SLIDE_CONTENT_SYSTEM_PROMPT.format(
markdown_emphasis_rules=markdown_emphasis_rules,
user_instructions=user_instructions,
tone_instructions=tone_instructions,
verbosity_instructions=verbosity_instructions,
output_fields_instructions=output_fields_instructions,
)
def get_user_prompt(outline: str, language: Optional[str]):
return SLIDE_CONTENT_USER_PROMPT.format(
current_date_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
language=_resolve_prompt_language(language),
content=outline,
)
def get_messages(
outline: str,
language: Optional[str],
tone: Optional[str] = None,
verbosity: Optional[str] = None,
instructions: Optional[str] = None,
response_schema: Optional[dict] = None,
):
return [
LLMSystemMessage(
content=get_system_prompt(
tone,
verbosity,
instructions,
response_schema,
),
),
LLMUserMessage(
content=get_user_prompt(outline, language),
),
]
async def get_slide_content_from_type_and_outline(
slide_layout: SlideLayoutModel,
outline: SlideOutlineModel,
language: Optional[str],
tone: Optional[str] = None,
verbosity: Optional[str] = None,
instructions: Optional[str] = None,
):
client = LLMClient()
model = get_model()
response_schema = remove_fields_from_schema(
slide_layout.json_schema, ["__image_url__", "__icon_url__"]
)
response_schema = add_field_in_schema(
response_schema,
{
"__speaker_note__": {
"type": "string",
"minLength": 100,
"maxLength": 500,
"description": "Speaker note for the slide",
}
},
True,
)
try:
response = await client.generate_structured(
model=model,
messages=get_messages(
outline.content,
language,
tone,
verbosity,
instructions,
response_schema,
),
response_format=response_schema,
strict=False,
validate_schema=True,
)
return response
except Exception as e:
raise handle_llm_client_exceptions(e)