diff --git a/servers/fastapi/api/v1/ppt/endpoints/outlines.py b/servers/fastapi/api/v1/ppt/endpoints/outlines.py index 7310d48a..13b94380 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/outlines.py +++ b/servers/fastapi/api/v1/ppt/endpoints/outlines.py @@ -7,7 +7,12 @@ from sqlalchemy.ext.asyncio import AsyncSession from models.presentation_outline_model import PresentationOutlineModel from models.sql.presentation import PresentationModel -from models.sse_response import SSECompleteResponse, SSEResponse, SSEStatusResponse +from models.sse_response import ( + SSECompleteResponse, + SSEErrorResponse, + SSEResponse, + SSEStatusResponse, +) from services.temp_file_service import TEMP_FILE_SERVICE from services.database import get_async_session from services.documents_loader import DocumentsLoader @@ -72,6 +77,10 @@ async def stream_outlines( # Give control to the event loop await asyncio.sleep(0) + if isinstance(chunk, HTTPException): + yield SSEErrorResponse(detail=chunk.detail).to_string() + return + yield SSEResponse( event="response", data=json.dumps({"type": "chunk", "chunk": chunk}), diff --git a/servers/fastapi/api/v1/ppt/endpoints/presentation.py b/servers/fastapi/api/v1/ppt/endpoints/presentation.py index 003894e4..b4063b9e 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/presentation.py +++ b/servers/fastapi/api/v1/ppt/endpoints/presentation.py @@ -30,7 +30,7 @@ from utils.dict_utils import deep_update from utils.export_utils import export_presentation from utils.llm_calls.generate_presentation_outlines import generate_ppt_outline from models.sql.slide import SlideModel -from models.sse_response import SSECompleteResponse, SSEResponse +from models.sse_response import SSECompleteResponse, SSEErrorResponse, SSEResponse from services.database import get_async_session from services.temp_file_service import TEMP_FILE_SERVICE @@ -225,14 +225,18 @@ async def stream_presentation( for i, slide_layout_index in enumerate(structure.slides): slide_layout = layout.slides[slide_layout_index] - slide_content = await get_slide_content_from_type_and_outline( - slide_layout, - outline.slides[i], - presentation.language, - presentation.tone, - presentation.verbosity, - presentation.instructions, - ) + try: + slide_content = await get_slide_content_from_type_and_outline( + slide_layout, + outline.slides[i], + presentation.language, + presentation.tone, + presentation.verbosity, + presentation.instructions, + ) + except HTTPException as e: + yield SSEErrorResponse(detail=e.detail).to_string() + return slide = SlideModel( presentation=presentation_id, @@ -373,6 +377,10 @@ async def generate_presentation_api( request.instructions, request.web_search, ): + + if isinstance(chunk, HTTPException): + raise chunk + presentation_outlines_text += chunk try: diff --git a/servers/fastapi/utils/llm_calls/edit_slide.py b/servers/fastapi/utils/llm_calls/edit_slide.py index 6123e443..8664d229 100644 --- a/servers/fastapi/utils/llm_calls/edit_slide.py +++ b/servers/fastapi/utils/llm_calls/edit_slide.py @@ -4,6 +4,7 @@ from models.llm_message import LLMSystemMessage, LLMUserMessage from models.presentation_layout import SlideLayoutModel from models.sql.slide import SlideModel 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 @@ -103,12 +104,16 @@ async def get_edited_slide_content( ) client = LLMClient() - response = await client.generate_structured( - model=model, - messages=get_messages( - prompt, slide.content, language, tone, verbosity, instructions - ), - response_format=response_schema, - strict=False, - ) - return response + try: + response = await client.generate_structured( + model=model, + messages=get_messages( + prompt, slide.content, language, tone, verbosity, instructions + ), + response_format=response_schema, + strict=False, + ) + return response + + except Exception as e: + raise handle_llm_client_exceptions(e) diff --git a/servers/fastapi/utils/llm_calls/edit_slide_html.py b/servers/fastapi/utils/llm_calls/edit_slide_html.py index cf58d185..195c2fbd 100644 --- a/servers/fastapi/utils/llm_calls/edit_slide_html.py +++ b/servers/fastapi/utils/llm_calls/edit_slide_html.py @@ -1,6 +1,7 @@ from typing import Optional from models.llm_message import LLMSystemMessage, LLMUserMessage from services.llm_client import LLMClient +from utils.llm_client_error_handler import handle_llm_client_exceptions from utils.llm_provider import get_model system_prompt = """ @@ -50,14 +51,17 @@ async def get_edited_slide_html(prompt: str, html: str): model = get_model() client = LLMClient() - response = await client.generate( - model=model, - messages=[ - LLMSystemMessage(content=system_prompt), - LLMUserMessage(content=get_user_prompt(prompt, html)), - ], - ) - return extract_html_from_response(response) or html + try: + response = await client.generate( + model=model, + messages=[ + LLMSystemMessage(content=system_prompt), + LLMUserMessage(content=get_user_prompt(prompt, html)), + ], + ) + return extract_html_from_response(response) or html + except Exception as e: + raise handle_llm_client_exceptions(e) def extract_html_from_response(response_text: str) -> Optional[str]: diff --git a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py index d70a05d4..cdc3138c 100644 --- a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py +++ b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py @@ -5,6 +5,7 @@ from models.llm_message import LLMSystemMessage, LLMUserMessage from models.llm_tools import SearchWebTool from services.llm_client import LLMClient from utils.get_dynamic_models import get_presentation_outline_model_with_n_slides +from utils.llm_client_error_handler import handle_llm_client_exceptions from utils.llm_provider import get_model @@ -86,21 +87,26 @@ async def generate_ppt_outline( client = LLMClient() - async for chunk in client.stream_structured( - model, - get_messages( - content, - n_slides, - language, - additional_context, - tone, - verbosity, - instructions, - ), - response_model.model_json_schema(), - strict=True, - tools=( - [SearchWebTool] if (client.enable_web_grounding() and web_search) else None - ), - ): - yield chunk + try: + async for chunk in client.stream_structured( + model, + get_messages( + content, + n_slides, + language, + additional_context, + tone, + verbosity, + instructions, + ), + response_model.model_json_schema(), + strict=True, + tools=( + [SearchWebTool] + if (client.enable_web_grounding() and web_search) + else None + ), + ): + yield chunk + except Exception as e: + yield handle_llm_client_exceptions(e) diff --git a/servers/fastapi/utils/llm_calls/generate_presentation_structure.py b/servers/fastapi/utils/llm_calls/generate_presentation_structure.py index 33464b4e..4d6bd101 100644 --- a/servers/fastapi/utils/llm_calls/generate_presentation_structure.py +++ b/servers/fastapi/utils/llm_calls/generate_presentation_structure.py @@ -3,6 +3,7 @@ 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 @@ -73,15 +74,18 @@ async def generate_presentation_structure( len(presentation_outline.slides) ) - response = await client.generate_structured( - model=model, - messages=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) + try: + response = await client.generate_structured( + model=model, + messages=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) diff --git a/servers/fastapi/utils/llm_calls/generate_slide_content.py b/servers/fastapi/utils/llm_calls/generate_slide_content.py index 3f702161..2674fbd6 100644 --- a/servers/fastapi/utils/llm_calls/generate_slide_content.py +++ b/servers/fastapi/utils/llm_calls/generate_slide_content.py @@ -4,6 +4,7 @@ from models.llm_message import LLMSystemMessage, LLMUserMessage from models.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 @@ -112,16 +113,20 @@ async def get_slide_content_from_type_and_outline( True, ) - response = await client.generate_structured( - model=model, - messages=get_messages( - outline.content, - language, - tone, - verbosity, - instructions, - ), - response_format=response_schema, - strict=False, - ) - return response + try: + response = await client.generate_structured( + model=model, + messages=get_messages( + outline.content, + language, + tone, + verbosity, + instructions, + ), + response_format=response_schema, + strict=False, + ) + return response + + except Exception as e: + raise handle_llm_client_exceptions(e) diff --git a/servers/fastapi/utils/llm_calls/select_slide_type_on_edit.py b/servers/fastapi/utils/llm_calls/select_slide_type_on_edit.py index 7235e558..d0e52379 100644 --- a/servers/fastapi/utils/llm_calls/select_slide_type_on_edit.py +++ b/servers/fastapi/utils/llm_calls/select_slide_type_on_edit.py @@ -3,6 +3,7 @@ from models.presentation_layout import PresentationLayoutModel, SlideLayoutModel from models.slide_layout_index import SlideLayoutIndex from models.sql.slide import SlideModel from services.llm_client import LLMClient +from utils.llm_client_error_handler import handle_llm_client_exceptions from utils.llm_provider import get_model @@ -46,16 +47,20 @@ async def get_slide_layout_from_prompt( slide_layout_index = layout.get_slide_layout_index(slide.layout) - response = await client.generate_structured( - model=model, - messages=get_messages( - prompt, - slide.content, - layout, - slide_layout_index, - ), - response_format=SlideLayoutIndex.model_json_schema(), - strict=True, - ) - index = SlideLayoutIndex(**response).index - return layout.slides[index] + try: + response = await client.generate_structured( + model=model, + messages=get_messages( + prompt, + slide.content, + layout, + slide_layout_index, + ), + response_format=SlideLayoutIndex.model_json_schema(), + strict=True, + ) + index = SlideLayoutIndex(**response).index + return layout.slides[index] + + except Exception as e: + raise handle_llm_client_exceptions(e) diff --git a/servers/fastapi/utils/llm_client_error_handler.py b/servers/fastapi/utils/llm_client_error_handler.py new file mode 100644 index 00000000..a4371e8c --- /dev/null +++ b/servers/fastapi/utils/llm_client_error_handler.py @@ -0,0 +1,16 @@ +from fastapi import HTTPException +from anthropic import APIError as AnthropicAPIError +from openai import APIError as OpenAIAPIError +from google.genai.errors import APIError as GoogleAPIError + + +def handle_llm_client_exceptions(e: Exception) -> HTTPException: + if isinstance(e, OpenAIAPIError): + return HTTPException(status_code=500, detail=f"OpenAI API error: {e.message}") + if isinstance(e, GoogleAPIError): + return HTTPException(status_code=500, detail=f"Google API error: {e.message}") + if isinstance(e, AnthropicAPIError): + return HTTPException( + status_code=500, detail=f"Anthropic API error: {e.message}" + ) + return HTTPException(status_code=500, detail=f"LLM API error: {e}")