Merge pull request #260 from presenton/feat/llm-client-error-handling

feat(fastapi): adds better error handling for LLMClient
This commit is contained in:
Saurav Niraula 2025-08-30 16:39:13 +05:45 committed by GitHub
commit 3ad1e59a46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 145 additions and 83 deletions

View file

@ -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}),

View file

@ -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:

View file

@ -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)

View file

@ -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]:

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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}")