Merge branch 'feat/custom_schema_and_layout' of github.com:presenton/presenton into feat/custom_schema_and_layout
This commit is contained in:
commit
aa9ededb2b
13 changed files with 165 additions and 15 deletions
0
servers/fastapi/api/__init__.py
Normal file
0
servers/fastapi/api/__init__.py
Normal file
|
|
@ -4,6 +4,7 @@ from typing import Annotated, List, Optional
|
|||
import uuid
|
||||
from fastapi import APIRouter, Body, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlmodel import select
|
||||
|
||||
from models.presentation_outline_model import SlideOutlineModel
|
||||
from models.presentation_layout import PresentationLayoutModel
|
||||
|
|
@ -144,6 +145,7 @@ async def stream_presentation(presentation_id: str):
|
|||
slide = SlideModel(
|
||||
presentation=presentation_id,
|
||||
layout=slide_layout.id,
|
||||
index=i,
|
||||
content=slide_content,
|
||||
)
|
||||
slides.append(slide)
|
||||
|
|
@ -176,3 +178,17 @@ async def stream_presentation(presentation_id: str):
|
|||
).to_string()
|
||||
|
||||
return StreamingResponse(inner(), media_type="text/event-stream")
|
||||
|
||||
|
||||
@PRESENTATION_ROUTER.get("/", response_model=PresentationWithSlides)
|
||||
def get_presentation(id: str):
|
||||
with get_sql_session() as sql_session:
|
||||
presentation = sql_session.get(PresentationModel, id)
|
||||
slides = sql_session.exec(
|
||||
select(SlideModel).where(SlideModel.presentation == id)
|
||||
)
|
||||
slides = sorted(slides, key=lambda x: x.index)
|
||||
return PresentationWithSlides(
|
||||
**presentation.model_dump(),
|
||||
slides=slides,
|
||||
)
|
||||
|
|
|
|||
0
servers/fastapi/constants/__init__.py
Normal file
0
servers/fastapi/constants/__init__.py
Normal file
0
servers/fastapi/enums/__init__.py
Normal file
0
servers/fastapi/enums/__init__.py
Normal file
0
servers/fastapi/models/__init__.py
Normal file
0
servers/fastapi/models/__init__.py
Normal file
|
|
@ -7,4 +7,5 @@ class SlideModel(SQLModel, table=True):
|
|||
id: str = Field(primary_key=True, default_factory=get_random_uuid)
|
||||
presentation: str
|
||||
layout: str
|
||||
index: int
|
||||
content: dict = Field(sa_column=Column(JSON))
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ httpcore==1.0.9
|
|||
httptools==0.6.4
|
||||
httpx==0.28.1
|
||||
idna==3.10
|
||||
iniconfig==2.1.0
|
||||
Jinja2==3.1.6
|
||||
jiter==0.10.0
|
||||
lxml==6.0.0
|
||||
|
|
@ -34,10 +35,12 @@ MarkupSafe==3.0.2
|
|||
mdurl==0.1.2
|
||||
multidict==6.6.3
|
||||
openai==1.95.1
|
||||
packaging==25.0
|
||||
pathvalidate==3.3.1
|
||||
pdfminer.six==20250506
|
||||
pdfplumber==0.11.7
|
||||
pillow==11.3.0
|
||||
pluggy==1.6.0
|
||||
propcache==0.3.2
|
||||
pyasn1==0.6.1
|
||||
pyasn1_modules==0.4.2
|
||||
|
|
@ -46,6 +49,7 @@ pydantic==2.11.7
|
|||
pydantic_core==2.33.2
|
||||
Pygments==2.19.2
|
||||
pypdfium2==4.30.1
|
||||
pytest==8.4.1
|
||||
python-docx==1.2.0
|
||||
python-dotenv==1.1.1
|
||||
python-multipart==0.0.20
|
||||
|
|
|
|||
0
servers/fastapi/tests/__init__.py
Normal file
0
servers/fastapi/tests/__init__.py
Normal file
70
servers/fastapi/tests/test_gemini_schema_support.py
Normal file
70
servers/fastapi/tests/test_gemini_schema_support.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import json
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from google.genai.types import GenerateContentResponse, GenerateContentConfig
|
||||
|
||||
|
||||
from utils.llm_provider import get_google_llm_client, get_large_model
|
||||
|
||||
|
||||
class HeadingDescription(BaseModel):
|
||||
heading: str = Field(
|
||||
description="Heading of the slide", min_length=10, max_length=20
|
||||
)
|
||||
description: str = Field(
|
||||
description="Description of the slide", min_length=40, max_length=200
|
||||
)
|
||||
|
||||
|
||||
class SlideContentTest(BaseModel):
|
||||
title: str = Field(description="Title of the slide", min_length=10, max_length=20)
|
||||
first_content: HeadingDescription = Field(description="First content of the slide")
|
||||
second_content: HeadingDescription = Field(
|
||||
description="Second content of the slide"
|
||||
)
|
||||
third_content: HeadingDescription = Field(description="Third content of the slide")
|
||||
|
||||
|
||||
class ColumnContentModel(BaseModel):
|
||||
title: str = Field(min_length=3, max_length=100, description="Column title")
|
||||
content: str = Field(min_length=10, max_length=800, description="Column content")
|
||||
|
||||
|
||||
class TwoColumnSlideModel(BaseModel):
|
||||
title: str = Field(
|
||||
min_length=3,
|
||||
max_length=100,
|
||||
description="Title of the slide",
|
||||
)
|
||||
subtitle: Optional[str] = Field(
|
||||
min_length=3,
|
||||
max_length=150,
|
||||
description="Optional subtitle or description",
|
||||
)
|
||||
leftColumn: ColumnContentModel = Field(
|
||||
description="Left column content",
|
||||
)
|
||||
rightColumn: ColumnContentModel = Field(
|
||||
description="Right column content",
|
||||
)
|
||||
backgroundImage: Optional[str] = Field(
|
||||
description="URL to background image for the slide"
|
||||
)
|
||||
|
||||
|
||||
def test_gemini_schema_support():
|
||||
response: GenerateContentResponse = get_google_llm_client().models.generate_content(
|
||||
model=get_large_model(),
|
||||
contents=[
|
||||
"Generate a slide for a presentation",
|
||||
"The slide should have a title and two contents",
|
||||
"The title should be a short title for the slide",
|
||||
"The contents should be a heading and a description",
|
||||
"The heading should be a short heading for the slide",
|
||||
],
|
||||
config=GenerateContentConfig(
|
||||
response_mime_type="application/json",
|
||||
response_schema=TwoColumnSlideModel.model_json_schema(),
|
||||
),
|
||||
)
|
||||
print(response.parsed)
|
||||
66
servers/fastapi/tests/test_openai_schema_support.py
Normal file
66
servers/fastapi/tests/test_openai_schema_support.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import asyncio
|
||||
import json
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
from utils.llm_provider import get_llm_client, get_large_model
|
||||
|
||||
|
||||
class HeadingDescription(BaseModel):
|
||||
heading: str = Field(
|
||||
description="Heading of the slide", min_length=10, max_length=20
|
||||
)
|
||||
description: str = Field(
|
||||
description="Description of the slide", min_length=40, max_length=200
|
||||
)
|
||||
|
||||
|
||||
class SlideContentTest(BaseModel):
|
||||
title: str = Field(description="Title of the slide", min_length=10, max_length=20)
|
||||
first_content: HeadingDescription = Field(description="First content of the slide")
|
||||
second_content: HeadingDescription = Field(
|
||||
description="Second content of the slide"
|
||||
)
|
||||
third_content: HeadingDescription = Field(description="Third content of the slide")
|
||||
|
||||
|
||||
class ColumnContentModel(BaseModel):
|
||||
title: str = Field(min_length=3, max_length=100, description="Column title")
|
||||
content: str = Field(min_length=10, max_length=800, description="Column content")
|
||||
|
||||
|
||||
class TwoColumnSlideModel(BaseModel):
|
||||
title: str = Field(
|
||||
min_length=3,
|
||||
max_length=100,
|
||||
description="Title of the slide",
|
||||
)
|
||||
subtitle: Optional[str] = Field(
|
||||
min_length=3,
|
||||
max_length=150,
|
||||
description="Optional subtitle or description",
|
||||
)
|
||||
leftColumn: ColumnContentModel = Field(
|
||||
description="Left column content",
|
||||
)
|
||||
rightColumn: ColumnContentModel = Field(
|
||||
description="Right column content",
|
||||
)
|
||||
backgroundImage: Optional[str] = Field(
|
||||
description="URL to background image for the slide"
|
||||
)
|
||||
|
||||
|
||||
def test_openai_schema_support():
|
||||
response = asyncio.run(
|
||||
get_llm_client().beta.chat.completions.parse(
|
||||
model=get_large_model(),
|
||||
messages=[
|
||||
{"role": "system", "content": "You are a helpful assistant."},
|
||||
{"role": "user", "content": "Generate a slide for a presentation"},
|
||||
],
|
||||
response_format=TwoColumnSlideModel,
|
||||
)
|
||||
)
|
||||
print(response.choices[0].message.parsed)
|
||||
0
servers/fastapi/utils/__init__.py
Normal file
0
servers/fastapi/utils/__init__.py
Normal file
|
|
@ -13,19 +13,6 @@ def get_prompt(presentation_layout: PresentationLayoutModel, n_slides: int, data
|
|||
return [
|
||||
{
|
||||
"role": "system",
|
||||
# "content": f"""
|
||||
# You're a professional presentation designer with years of experience in designing clear and engaging presentations.
|
||||
# {presentation_layout.to_string()}
|
||||
# # Steps
|
||||
# 1. Analyze provided Number of slides, Presentation title, Slides content and Presentation Layout.
|
||||
# 2. Select appropriate slide layout **index** for each slide.
|
||||
# # Notes
|
||||
# - Slide layout should be selected based on provided content for slide and notes.
|
||||
# - Don't fall into patterns like always using layout 2 and after layout 1.
|
||||
# - Each presentation should have its own unique flow and rhythm.
|
||||
# - Select type for {n_slides} slides.
|
||||
# **Go through notes and steps and make sure they are all followed. Rule breaks are strictly not allowed.**
|
||||
# """,
|
||||
"content": f"""
|
||||
You're a professional presentation designer.
|
||||
{presentation_layout.to_string()}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from http.client import HTTPException
|
||||
import os
|
||||
from fastapi import HTTPException
|
||||
from openai import AsyncOpenAI
|
||||
from google import genai
|
||||
|
||||
|
|
@ -15,7 +15,13 @@ from utils.get_env import (
|
|||
|
||||
|
||||
def get_llm_provider():
|
||||
return LLMProvider(get_llm_provider_env())
|
||||
try:
|
||||
return LLMProvider(get_llm_provider_env())
|
||||
except:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Invalid LLM provider. Please select one of: openai, google, ollama, custom",
|
||||
)
|
||||
|
||||
|
||||
def get_ollama_url():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue