feat(backend): 重构 HTTP 错误处理为 RFC7807 标准并优化多个 service
This commit is contained in:
@@ -9,6 +9,7 @@ from datetime import date
|
||||
from typing import Annotated
|
||||
|
||||
from ag_ui.core import RunAgentInput
|
||||
from core.http.errors import problem_payload
|
||||
from core.agentscope.events import to_sse_event
|
||||
from core.agentscope.schemas.agui_input import (
|
||||
parse_run_input,
|
||||
@@ -131,11 +132,17 @@ async def enqueue_run(
|
||||
try:
|
||||
request = parse_run_input(request.model_dump(by_alias=True, exclude_none=True))
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=problem_payload(code="AGENT_RUN_INPUT_INVALID", detail=str(exc)),
|
||||
) from exc
|
||||
try:
|
||||
validate_run_request_messages_contract(request)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=problem_payload(code="AGENT_RUN_MESSAGES_INVALID", detail=str(exc)),
|
||||
) from exc
|
||||
task = await service.enqueue_run(
|
||||
run_input=request,
|
||||
current_user=current_user,
|
||||
@@ -188,11 +195,23 @@ async def stream_events(
|
||||
if last_event_id is not None and (
|
||||
len(last_event_id) > 32 or _LAST_EVENT_ID_RE.fullmatch(last_event_id) is None
|
||||
):
|
||||
raise HTTPException(status_code=422, detail="Invalid Last-Event-ID")
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=problem_payload(
|
||||
code="AGENT_INVALID_LAST_EVENT_ID",
|
||||
detail="Invalid Last-Event-ID",
|
||||
),
|
||||
)
|
||||
|
||||
sse_slot_acquired = await _acquire_sse_slot(user_id=str(current_user.id))
|
||||
if not sse_slot_acquired:
|
||||
raise HTTPException(status_code=429, detail="Too many SSE connections")
|
||||
raise HTTPException(
|
||||
status_code=429,
|
||||
detail=problem_payload(
|
||||
code="AGENT_SSE_CONNECTION_LIMIT",
|
||||
detail="Too many SSE connections",
|
||||
),
|
||||
)
|
||||
|
||||
async def _event_iter() -> AsyncIterator[str]:
|
||||
cursor = last_event_id
|
||||
@@ -283,9 +302,22 @@ async def upload_attachment(
|
||||
) -> AttachmentUploadResponse:
|
||||
payload = await file.read()
|
||||
if not payload:
|
||||
raise HTTPException(status_code=422, detail="Empty attachment")
|
||||
raise HTTPException(
|
||||
status_code=422,
|
||||
detail=problem_payload(
|
||||
code="AGENT_ATTACHMENT_EMPTY",
|
||||
detail="Empty attachment",
|
||||
),
|
||||
)
|
||||
if len(payload) > _MAX_ATTACHMENT_UPLOAD_BYTES:
|
||||
raise HTTPException(status_code=413, detail="Attachment too large")
|
||||
raise HTTPException(
|
||||
status_code=413,
|
||||
detail=problem_payload(
|
||||
code="AGENT_ATTACHMENT_TOO_LARGE",
|
||||
detail="Attachment too large",
|
||||
params={"maxBytes": _MAX_ATTACHMENT_UPLOAD_BYTES},
|
||||
),
|
||||
)
|
||||
attachment = await service.upload_attachment(
|
||||
thread_id=thread_id,
|
||||
filename=file.filename,
|
||||
@@ -330,7 +362,13 @@ async def transcribe(
|
||||
temp_path: str | None = None
|
||||
try:
|
||||
if audio.content_type not in _ALLOWED_AUDIO_CONTENT_TYPES:
|
||||
raise HTTPException(status_code=400, detail="Unsupported audio format")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=problem_payload(
|
||||
code="AGENT_AUDIO_UNSUPPORTED_FORMAT",
|
||||
detail="Unsupported audio format",
|
||||
),
|
||||
)
|
||||
|
||||
content_length = request.headers.get("content-length")
|
||||
if content_length is not None:
|
||||
@@ -343,7 +381,14 @@ async def transcribe(
|
||||
and declared_length
|
||||
> _MAX_TRANSCRIBE_AUDIO_BYTES + _MULTIPART_OVERHEAD_BYTES
|
||||
):
|
||||
raise HTTPException(status_code=400, detail="Audio file too large")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=problem_payload(
|
||||
code="AGENT_AUDIO_TOO_LARGE",
|
||||
detail="Audio file too large",
|
||||
params={"maxBytes": _MAX_TRANSCRIBE_AUDIO_BYTES},
|
||||
),
|
||||
)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp_file:
|
||||
temp_path = tmp_file.name
|
||||
@@ -356,16 +401,35 @@ async def transcribe(
|
||||
break
|
||||
total_bytes += len(chunk)
|
||||
if total_bytes > _MAX_TRANSCRIBE_AUDIO_BYTES:
|
||||
raise HTTPException(status_code=400, detail="Audio file too large")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=problem_payload(
|
||||
code="AGENT_AUDIO_TOO_LARGE",
|
||||
detail="Audio file too large",
|
||||
params={"maxBytes": _MAX_TRANSCRIBE_AUDIO_BYTES},
|
||||
),
|
||||
)
|
||||
if len(header) < _WAV_HEADER_MIN_BYTES:
|
||||
required = _WAV_HEADER_MIN_BYTES - len(header)
|
||||
header.extend(chunk[:required])
|
||||
tmp_file.write(chunk)
|
||||
|
||||
if total_bytes == 0:
|
||||
raise HTTPException(status_code=400, detail="Empty audio file")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=problem_payload(
|
||||
code="AGENT_AUDIO_EMPTY",
|
||||
detail="Empty audio file",
|
||||
),
|
||||
)
|
||||
if not _looks_like_wav_header(bytes(header)):
|
||||
raise HTTPException(status_code=400, detail="Unsupported audio format")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=problem_payload(
|
||||
code="AGENT_AUDIO_UNSUPPORTED_FORMAT",
|
||||
detail="Unsupported audio format",
|
||||
),
|
||||
)
|
||||
|
||||
transcript = await asr_service.transcribe_file(
|
||||
temp_path, audio.filename or "unknown"
|
||||
@@ -376,7 +440,13 @@ async def transcribe(
|
||||
except HTTPException:
|
||||
raise
|
||||
except RuntimeError:
|
||||
raise HTTPException(status_code=502, detail="ASR service unavailable")
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
detail=problem_payload(
|
||||
code="AGENT_ASR_UNAVAILABLE",
|
||||
detail="ASR service unavailable",
|
||||
),
|
||||
)
|
||||
finally:
|
||||
await audio.close()
|
||||
if temp_path:
|
||||
|
||||
Reference in New Issue
Block a user