Merge pull request #260 from presenton/feat/llm-client-error-handling
feat(fastapi): adds better error handling for LLMClient
This commit is contained in:
commit
3ad1e59a46
9 changed files with 145 additions and 83 deletions
|
|
@ -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}),
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
16
servers/fastapi/utils/llm_client_error_handler.py
Normal file
16
servers/fastapi/utils/llm_client_error_handler.py
Normal 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}")
|
||||
Loading…
Add table
Reference in a new issue