presenton/servers/fastapi/utils/llm_calls/edit_slide.py

171 lines
5.1 KiB
Python

import asyncio
from datetime import datetime
from typing import Optional
from fastapi import HTTPException
from llmai import get_client
from llmai.shared import JSONSchemaResponse, Message, SystemMessage, UserMessage
from models.presentation_layout import SlideLayoutModel
from models.sql.slide import SlideModel
from utils.llm_config import get_llm_config
from utils.llm_client_error_handler import handle_llm_client_exceptions
from utils.llm_utils import extract_structured_content, get_generate_kwargs
from utils.llm_provider import get_model
from utils.schema_utils import add_field_in_schema, remove_fields_from_schema
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_system_prompt(
tone: Optional[str] = None,
verbosity: Optional[str] = None,
instructions: Optional[str] = None,
memory_context: Optional[str] = None,
):
memory_block = (
"\n # Retrieved Presentation Memory Context\n"
f" {memory_context}\n"
" - Use this context only if it is relevant to the user prompt.\n"
" - Prefer this context over assumptions when resolving ambiguity.\n"
if memory_context
else ""
)
return f"""
Edit Slide data and speaker note based on provided prompt, follow mentioned steps and notes and provide structured output.
{"# User Instruction:" if instructions else ""}
{instructions or ""}
{"# Tone:" if tone else ""}
{tone or ""}
{"# Verbosity:" if verbosity else ""}
{verbosity or ""}
# Notes
- Provide output in language mentioned in **Input**.
- The goal is to change Slide data based on the provided prompt.
- Do not change **Image prompts** and **Icon queries** if not asked for in prompt.
- Generate **Image prompts** and **Icon queries** if asked to generate or change in prompt.
- Make sure to follow language guidelines.
- Speaker note should be normal text, not markdown.
- Speaker note should be simple, clear, concise and to the point.
{memory_block}
**Go through all notes and steps and make sure they are followed, including mentioned constraints**
"""
def get_user_prompt(prompt: str, slide_data: dict, language: str):
display_language = _resolve_prompt_language(language)
return f"""
## Icon Query And Image Prompt Language
English
## Current Date and Time
{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
## Slide Content Language
{display_language}
## Prompt
{prompt}
## Slide data
{slide_data}
"""
def get_messages(
prompt: str,
slide_data: dict,
language: Optional[str],
tone: Optional[str] = None,
verbosity: Optional[str] = None,
instructions: Optional[str] = None,
memory_context: Optional[str] = None,
) -> list[Message]:
return [
SystemMessage(
content=get_system_prompt(tone, verbosity, instructions, memory_context),
),
UserMessage(
content=get_user_prompt(prompt, slide_data, language),
),
]
async def get_edited_slide_content(
prompt: str,
slide: SlideModel,
language: Optional[str],
slide_layout: SlideLayoutModel,
tone: Optional[str] = None,
verbosity: Optional[str] = None,
instructions: Optional[str] = None,
memory_context: Optional[str] = None,
):
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": 250,
"description": "Speaker note for the slide",
}
},
True,
)
client = get_client(config=get_llm_config())
try:
response_format = JSONSchemaResponse(
name="response",
json_schema=response_schema,
strict=False,
)
messages = get_messages(
prompt,
slide.content,
language,
tone,
verbosity,
instructions,
memory_context,
)
for attempt in range(3):
response = await asyncio.to_thread(
client.generate,
**get_generate_kwargs(
model=model,
messages=messages,
response_format=response_format,
),
)
content = extract_structured_content(response.content)
if content is not None:
return content
if attempt < 2:
await asyncio.sleep(0.5 * (attempt + 1))
raise HTTPException(status_code=400, detail="LLM did not return any content")
except Exception as e:
raise handle_llm_client_exceptions(e)