cardserver / app /api /v1 /endpoints /generate.py
GitHub Actions
🚀 Auto-deploy from GitHub
1788936
from fastapi import APIRouter, HTTPException, Depends, Request
from dotenv import load_dotenv
from supabase import Client
import uuid
import logging
from typing import Optional, Dict, Any
from ..schemas.card_schemas import CardGenerateRequest, CardGenerateResponse
from ....core.generator import build_prompt # get_constellation wird hier nicht direkt verwendet
from ....core.card_renderer import generate_card as render_card_sync # Umbenennen für Klarheit
from ....utils.qr_utils import generate_qr_code_sync # Umbenennen für Klarheit
from ....services.database import get_supabase_client, save_card
from ....core.config import settings
from ....core.model_loader import get_generator
from ....core.constraints import generate_with_retry, check_constraints
from ....core.auth import authenticate_request, get_current_user
from ....services.request_logger import RequestEventLogger
from ....services.auth_logger import get_client_info
from fastapi.concurrency import run_in_threadpool # Importieren
# Configure logger for this module
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# Create console handler if not already configured
if not logger.handlers:
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
load_dotenv()
router = APIRouter()
# Asynchrone Wrapper für blockierende Funktionen
async def render_card_async(*args, **kwargs):
return await run_in_threadpool(render_card_sync, *args, **kwargs)
async def generate_qr_code_async(*args, **kwargs):
return await run_in_threadpool(generate_qr_code_sync, *args, **kwargs)
@router.post("/generate", response_model=CardGenerateResponse)
async def generate_endpoint(
request: CardGenerateRequest,
fastapi_request: Request,
supabase: Client = Depends(get_supabase_client),
authenticated: bool = Depends(authenticate_request),
current_user: Optional[Dict[str, Any]] = Depends(get_current_user)
):
# IMMEDIATE DEBUG LOG - Should appear as soon as endpoint is hit
logger.info(f"🎯 GENERATE ENDPOINT HIT! Terms: {request.terms}")
# Generate unique request ID for tracking
request_id = str(uuid.uuid4())
# Log to console for HuggingFace visibility
logger.info(f"🚀 [REQUEST START] ID: {request_id}")
logger.info(f"📝 Terms: {request.terms}")
logger.info(f"📅 Date: {request.card_date}")
logger.info(f"🌐 Language: {request.lang}")
logger.info(f"👤 User: {current_user.get('username', 'anonymous') if current_user else 'anonymous'}")
try:
# Extract client information
client_ip, user_agent = get_client_info(fastapi_request)
lang = request.lang or "de"
input_date_str = request.card_date.isoformat()
logger.info(f"🔄 [PROCESSING] Generating card for date: {input_date_str}, lang: {lang}")
# Log user context if available
user_id = current_user["id"] if current_user else None
username = current_user["username"] if current_user else "anonymous"
# Log incoming request
RequestEventLogger.log_card_generation_request(
user_id=user_id,
username=username,
client_ip=client_ip,
user_agent=user_agent,
terms=request.terms,
card_date=input_date_str,
lang=lang,
card_design_id=request.card_design_id_override,
symbol_ids=request.symbol_ids_override,
request_id=request_id
)
logger.info(f"📝 [PROMPT] Building prompt for LLM...")
card_prompt = build_prompt(
lang=lang,
card_date=input_date_str,
terms=request.terms,
request_id=request_id
)
logger.info(f"✅ [PROMPT] Built prompt with {len(card_prompt)} characters")
logger.info(f"🤖 [LLM] Loading model and preparing generation...")
llm_pipeline = get_generator()
generation_params = {
"max_new_tokens": settings.GENERATION_MAX_NEW_TOKENS,
"temperature": settings.GENERATION_TEMPERATURE,
"do_sample": settings.GENERATION_DO_SAMPLE,
"top_k": settings.GENERATION_TOP_K,
"top_p": settings.GENERATION_TOP_P,
"return_full_text": False
}
logger.info(f"🎯 [LLM] Starting text generation with {settings.GENERATION_MAX_RETRIES} max retries...")
card_text = await generate_with_retry(
prompt=card_prompt,
generator=llm_pipeline,
terms=request.terms,
max_retries=settings.GENERATION_MAX_RETRIES,
generation_params=generation_params,
request_id=request_id
)
logger.info(f"📝 [LLM] Generated text: {card_text[:100]}{'...' if len(card_text) > 100 else ''}")
if card_text == "Leider konnte kein gültiger Text erzeugt werden." or not check_constraints(card_text, request.terms):
error_msg = "Kartentext konnte nicht generiert werden oder erfüllt nicht die Bedingungen."
logger.error(f"❌ [ERROR] {error_msg}")
# Log failed card generation
RequestEventLogger.log_card_generation_result(
request_id=request_id,
success=False,
error_message=error_msg
)
raise HTTPException(
status_code=500,
detail=error_msg
)
logger.info(f"🎨 [QR] Generating QR code...")
card_id_for_url = str(uuid.uuid4())
qr_content_url = f"{settings.FRONTEND_BASE_URL}/card/{card_id_for_url}"
logger.info(f"🔗 [QR] QR Code URL: {qr_content_url}")
qr_code_file_id = await generate_qr_code_async(
data=qr_content_url,
output_path=settings.resolved_qr_code_path,
size=settings.QR_CODE_SIZE
)
qr_code_url = f"{settings.API_PREFIX}/static/images/qr/{qr_code_file_id}.png"
logger.info(f"✅ [QR] QR code generated: {qr_code_file_id}")
logger.info(f"🖼️ [CARD] Rendering card image...")
card_design_id_to_render = request.card_design_id_override or 1
symbol_ids_to_render = request.symbol_ids_override or [1, 2]
card_file_id = await render_card_async(
card_design_id=card_design_id_to_render,
symbol_ids=symbol_ids_to_render,
text=card_text,
base_images_path=settings.resolved_base_path,
symbols_images_path=settings.resolved_symbols_path,
font_path=settings.resolved_default_font_path,
output_path=settings.resolved_generated_path,
request_id=request_id
)
logger.info(f"✅ [CARD] Card image generated: {card_file_id}")
logger.info(f"💾 [DB] Saving card to database...")
card_data_for_db = {
"terms": request.terms,
"card_date": input_date_str,
"card_text": card_text,
"image_filename": f"{card_file_id}.png",
"qr_code_filename": f"{qr_code_file_id}.png",
"qr_code_link": qr_content_url,
"session_id": uuid.UUID(card_id_for_url),
"lang": lang,
"prompt_text": card_prompt,
"ml_model_info": llm_pipeline.model.config.to_dict() if hasattr(llm_pipeline, 'model') and hasattr(llm_pipeline.model, 'config') else {"name": str(type(llm_pipeline.model).__name__)},
"generation_params": generation_params
}
db_response = await save_card(supabase, card_data_for_db)
db_id = None
if db_response and hasattr(db_response, 'data') and db_response.data and len(db_response.data) > 0:
db_id = str(db_response.data[0].get('id'))
elif isinstance(db_response, list) and db_response and isinstance(db_response[0], dict):
db_id = str(db_response[0].get('id'))
# Log successful card generation
RequestEventLogger.log_card_generation_result(
request_id=request_id,
success=True,
card_id=db_id if db_id else card_id_for_url,
card_file_id=card_file_id,
qr_code_file_id=qr_code_file_id,
qr_content_url=qr_content_url
)
logger.info(f"🎉 [SUCCESS] Card generation completed!")
logger.info(f"📋 Card ID: {db_id if db_id else card_id_for_url}")
logger.info(f"🖼️ Image: {card_file_id}.png")
logger.info(f"📱 QR Code: {qr_code_file_id}.png")
return CardGenerateResponse(
message="Karte erfolgreich generiert.",
card_id=db_id if db_id else card_id_for_url,
qr_code_image_url=qr_code_url
)
except FileNotFoundError as e:
error_msg = f"Ein benötigtes Template oder eine Datei wurde nicht gefunden: {e.filename}"
logger.error(f"FileNotFoundError in generate_endpoint: {e}")
# Log failed card generation
RequestEventLogger.log_card_generation_result(
request_id=request_id,
success=False,
error_message=error_msg
)
raise HTTPException(status_code=500, detail=error_msg)
except HTTPException as e:
# Log failed card generation for HTTP exceptions
RequestEventLogger.log_card_generation_result(
request_id=request_id if 'request_id' in locals() else None,
success=False,
error_message=str(e.detail)
)
raise e
except Exception as e:
error_msg = f"Ein interner Fehler ist aufgetreten: {str(e)}"
import traceback
logger.error(f"Unerwarteter Fehler in generate_endpoint: {type(e).__name__} - {str(e)}")
logger.error(traceback.format_exc())
# Log failed card generation
RequestEventLogger.log_card_generation_result(
request_id=request_id if 'request_id' in locals() else None,
success=False,
error_message=error_msg
)
raise HTTPException(status_code=500, detail=error_msg)