diff --git a/servers/fastapi/api/v1/ppt/endpoints/images.py b/servers/fastapi/api/v1/ppt/endpoints/images.py index 7e8997ec..2eb40589 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/images.py +++ b/servers/fastapi/api/v1/ppt/endpoints/images.py @@ -1,4 +1,3 @@ -from typing import Annotated from fastapi import APIRouter, Body from models.image_prompt import ImagePrompt diff --git a/servers/fastapi/get_test_schema.py b/servers/fastapi/get_test_schema.py index 82c825ca..6ec34f83 100644 --- a/servers/fastapi/get_test_schema.py +++ b/servers/fastapi/get_test_schema.py @@ -2,6 +2,7 @@ from typing import List, Optional from pydantic import BaseModel, Field, HttpUrl, EmailStr from models.presentation_layout import PresentationLayoutModel, SlideLayoutModel +from models.presentation_outline_model import PresentationOutlineModel class ContactInfoModel(BaseModel): @@ -559,4 +560,6 @@ presentation_layout = PresentationLayoutModel( ], ) -print(presentation_layout.model_dump_json()) +# print(presentation_layout.model_dump_json()) + +print(PresentationOutlineModel.model_json_schema()) diff --git a/servers/fastapi/utils/async_iterator.py b/servers/fastapi/utils/async_iterator.py new file mode 100644 index 00000000..210e61d3 --- /dev/null +++ b/servers/fastapi/utils/async_iterator.py @@ -0,0 +1,16 @@ +import asyncio +from typing import AsyncGenerator, Callable, Iterator, TypeVar + +T = TypeVar("T") + + +def iterator_to_async( + func: Callable[..., Iterator[T]], +) -> Callable[..., AsyncGenerator[T, None]]: + async def wrapper(*args, **kwargs) -> AsyncGenerator[T, None]: + iterator = func(*args, **kwargs) + for item in iterator: + yield item + await asyncio.sleep(0) + + return wrapper diff --git a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py index e089e909..2b772bc5 100644 --- a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py +++ b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py @@ -1,10 +1,14 @@ from typing import Optional from openai.lib.streaming.chat._events import ContentDeltaEvent +from google.genai.types import GenerateContentConfig +from utils.async_iterator import iterator_to_async from utils.get_dynamic_models import get_presentation_outline_model_with_n_slides from utils.llm_provider import ( + get_google_llm_client, get_large_model, get_llm_client, + is_google_selected, ) system_prompt = """ @@ -64,18 +68,33 @@ async def generate_ppt_outline( model = get_large_model() response_model = get_presentation_outline_model_with_n_slides(n_slides) - client = get_llm_client() - async with client.beta.chat.completions.stream( - model=model, - messages=get_prompt_template(prompt, n_slides, language, content), - response_format={ - "type": "json_schema", - "json_schema": { - "name": "PresentationOutline", - "schema": response_model.model_json_schema(), + if not is_google_selected(): + client = get_llm_client() + async with client.beta.chat.completions.stream( + model=model, + messages=get_prompt_template(prompt, n_slides, language, content), + response_format={ + "type": "json_schema", + "json_schema": { + "name": "PresentationOutline", + "schema": response_model.model_json_schema(), + }, }, - }, - ) as stream: - async for event in stream: - if isinstance(event, ContentDeltaEvent): - yield event.delta + ) as stream: + async for event in stream: + if isinstance(event, ContentDeltaEvent): + yield event.delta + + else: + client = get_google_llm_client() + generate_stream = iterator_to_async(client.models.generate_content_stream) + async for event in generate_stream( + model=model, + contents=[get_user_prompt(prompt, n_slides, language, content)], + config=GenerateContentConfig( + system_instruction=system_prompt, + response_mime_type="application/json", + response_json_schema=response_model.model_json_schema(), + ), + ): + yield event.text diff --git a/servers/fastapi/utils/llm_calls/generate_slide_content.py b/servers/fastapi/utils/llm_calls/generate_slide_content.py index 817ce581..a5afded2 100644 --- a/servers/fastapi/utils/llm_calls/generate_slide_content.py +++ b/servers/fastapi/utils/llm_calls/generate_slide_content.py @@ -1,36 +1,48 @@ +import asyncio import json -from pydantic import BaseModel, Field +from google.genai.types import GenerateContentConfig from models.presentation_layout import SlideLayoutModel from models.presentation_outline_model import SlideOutlineModel -from utils.llm_provider import get_llm_client, get_small_model +from utils.llm_provider import ( + get_google_llm_client, + get_llm_client, + get_small_model, + is_google_selected, +) + +system_prompt = """ + Generate structured slide based on provided title and outline, follow mentioned steps and notes and provide structured output. + + # Steps + 1. Analyze the outline and title. + 2. Generate structured slide based on the outline and title. + + # Notes + - Slide body should not use words like "This slide", "This presentation". + - Rephrase the slide body to make it flow naturally. + - Do not use markdown formatting in slide body. +""" + + +def get_user_prompt(title: str, outline: str): + return f""" + ## Slide Title + {title} + + ## Slide Outline + {outline} + """ def get_prompt_to_generate_slide_content(title: str, outline: str): return [ { "role": "system", - "content": f""" - Generate structured slide based on provided title and outline, follow mentioned steps and notes and provide structured output. - - # Steps - 1. Analyze the outline and title. - 2. Generate structured slide based on the outline and title. - - # Notes - - Slide body should not use words like "This slide", "This presentation". - - Rephrase the slide body to make it flow naturally. - - Do not use markdown formatting in slide body. - """, + "content": system_prompt, }, { "role": "user", - "content": f""" - ## Slide Title - {title} - - ## Slide Outline - {outline} - """, + "content": get_user_prompt(title, outline), }, ] @@ -38,24 +50,37 @@ def get_prompt_to_generate_slide_content(title: str, outline: str): async def get_slide_content_from_type_and_outline( slide_layout: SlideLayoutModel, outline: SlideOutlineModel ): - response_format = { - "type": "json_schema", - "json_schema": { - "name": slide_layout.name or slide_layout.id, - "schema": slide_layout.json_schema, - }, - } - client = get_llm_client() model = get_small_model() - response = await client.beta.chat.completions.parse( - model=model, - messages=get_prompt_to_generate_slide_content( - outline.title, - outline.body, - ), - response_format=response_format, - ) - - return json.loads(response.choices[0].message.content) + if not is_google_selected(): + client = get_llm_client() + response = await client.beta.chat.completions.parse( + model=model, + messages=get_prompt_to_generate_slide_content( + outline.title, + outline.body, + ), + response_format={ + "type": "json_schema", + "json_schema": { + "name": "SlideContent", + "schema": slide_layout.json_schema, + }, + }, + ) + return json.loads(response.choices[0].message.content) + else: + client = get_google_llm_client() + user_prompt = get_user_prompt(outline.title, outline.body) + response = await asyncio.to_thread( + client.models.generate_content, + model=model, + contents=[user_prompt], + config=GenerateContentConfig( + system_instruction=system_prompt, + response_mime_type="application/json", + response_json_schema=slide_layout.json_schema, + ), + ) + return response.model_dump(mode="json")