feat: add async AI description generation on file upload
Adds _generate_ai_description background task that fires after a successful upload: images get a one-sentence vision description via Claude, text/log/config files get extracted_content + AI summary when >2000 chars. Runs as asyncio.create_task so it never blocks the upload response. Errors are logged and swallowed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,60 @@ def _check_storage_configured() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _generate_ai_description(upload_id: UUID, file_data: bytes, content_type: str) -> None:
|
||||||
|
"""Background task: generate AI description for uploaded file."""
|
||||||
|
try:
|
||||||
|
from app.core.database import async_session_maker
|
||||||
|
from app.services.assistant_chat_service import _call_ai
|
||||||
|
import base64
|
||||||
|
|
||||||
|
async with async_session_maker() as db:
|
||||||
|
result = await db.execute(
|
||||||
|
select(FileUpload).where(FileUpload.id == upload_id)
|
||||||
|
)
|
||||||
|
upload = result.scalar_one_or_none()
|
||||||
|
if not upload:
|
||||||
|
return
|
||||||
|
|
||||||
|
if content_type.startswith("image/"):
|
||||||
|
b64_data = base64.b64encode(file_data).decode("utf-8")
|
||||||
|
description, _, _ = await _call_ai(
|
||||||
|
system_base="You are a technical image analyst for IT troubleshooting.",
|
||||||
|
rag_context="",
|
||||||
|
history=[],
|
||||||
|
new_message="Describe this image in one sentence for a troubleshooting context log.",
|
||||||
|
images=[{"media_type": content_type, "data": b64_data}],
|
||||||
|
max_tokens=100,
|
||||||
|
)
|
||||||
|
upload.ai_description = description
|
||||||
|
elif content_type.startswith("text/") or content_type in (
|
||||||
|
"application/json", "application/xml", "application/yaml",
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
text_content = file_data.decode("utf-8")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
text_content = file_data.decode("latin-1")
|
||||||
|
|
||||||
|
upload.extracted_content = text_content[:10000]
|
||||||
|
|
||||||
|
if len(text_content) > 2000:
|
||||||
|
summary, _, _ = await _call_ai(
|
||||||
|
system_base="You are a technical log/config analyst.",
|
||||||
|
rag_context="",
|
||||||
|
history=[],
|
||||||
|
new_message=f"Summarize this file content in 2-3 sentences:\n\n{text_content[:5000]}",
|
||||||
|
max_tokens=200,
|
||||||
|
)
|
||||||
|
upload.content_summary = summary
|
||||||
|
upload.ai_description = summary
|
||||||
|
else:
|
||||||
|
upload.ai_description = f"Text file: {upload.filename}"
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
except Exception:
|
||||||
|
logger.exception(f"Failed to generate AI description for upload {upload_id}")
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_model=FileUploadResponse, status_code=status.HTTP_201_CREATED)
|
@router.post("", response_model=FileUploadResponse, status_code=status.HTTP_201_CREATED)
|
||||||
@limiter.limit("10/minute")
|
@limiter.limit("10/minute")
|
||||||
async def upload_file(
|
async def upload_file(
|
||||||
@@ -113,6 +167,11 @@ async def upload_file(
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(upload)
|
await db.refresh(upload)
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
asyncio.create_task(
|
||||||
|
_generate_ai_description(upload.id, file_data, content_type)
|
||||||
|
)
|
||||||
|
|
||||||
presigned_url = storage_service.get_presigned_url(upload.storage_key)
|
presigned_url = storage_service.get_presigned_url(upload.storage_key)
|
||||||
|
|
||||||
return FileUploadResponse(
|
return FileUploadResponse(
|
||||||
|
|||||||
Reference in New Issue
Block a user