From 49e3cb6dc48a87577a4c3812c3c5900e9f039b23 Mon Sep 17 00:00:00 2001 From: Yaojia Wang Date: Wed, 8 Jul 2026 12:46:15 +0200 Subject: [PATCH] =?UTF-8?q?docs(backend):=20SSE=20=E7=AB=AF=E7=82=B9?= =?UTF-8?q?=E5=9C=A8=20OpenAPI=20=E5=A6=82=E5=AE=9E=E5=A3=B0=E6=98=8E=20te?= =?UTF-8?q?xt/event-stream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit draft/rewrite/review 三个流式端点返回 StreamingResponse,OpenAPI 原按默认 application/json 标注 200,schema 不诚实。新增共享 _SSE_RESPONSE 声明 200 的 content 为 text/event-stream 并挂到三个装饰器。export_openapi 通过,schema 现 出现 3 处 text/event-stream。未跑前端 gen:api(按要求)。 --- apps/api/ww_api/routers/projects.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/api/ww_api/routers/projects.py b/apps/api/ww_api/routers/projects.py index 30daa56..3258833 100644 --- a/apps/api/ww_api/routers/projects.py +++ b/apps/api/ww_api/routers/projects.py @@ -105,6 +105,13 @@ _ACCEPT_ERRORS: dict[int | str, dict[str, Any]] = { 409: {"model": ErrorEnvelope, "description": "存在未裁决冲突"}, 503: {"model": ErrorEnvelope, "description": "LLM 不可用"}, } +# SSE 端点:如实声明 200 的 media type 为 text/event-stream(否则 OpenAPI 误标 json)。 +_SSE_RESPONSE: dict[int | str, dict[str, Any]] = { + 200: { + "description": "SSE 事件流(`event: \\ndata: `,见 memory/contracts.md)", + "content": {"text/event-stream": {}}, + } +} ProjectRepoDep = Annotated[ProjectRepo, Depends(get_project_repo)] ChapterRepoDep = Annotated[ChapterRepo, Depends(get_chapter_repo)] @@ -256,7 +263,7 @@ def _encode_sse(event: SseEvent) -> str: return f"event: {event.event}\ndata: {payload}\n\n" -@router.post("/{project_id}/chapters/{chapter_no}/draft") +@router.post("/{project_id}/chapters/{chapter_no}/draft", responses=_SSE_RESPONSE) async def stream_draft( project_id: uuid.UUID, chapter_no: int, @@ -319,7 +326,7 @@ async def stream_draft( ) -@router.post("/{project_id}/chapters/{chapter_no}/rewrite") +@router.post("/{project_id}/chapters/{chapter_no}/rewrite", responses=_SSE_RESPONSE) async def stream_rewrite( project_id: uuid.UUID, chapter_no: int, @@ -532,7 +539,7 @@ async def _resolve_review_draft( return saved.content -@router.post("/{project_id}/chapters/{chapter_no}/review") +@router.post("/{project_id}/chapters/{chapter_no}/review", responses=_SSE_RESPONSE) async def review_chapter( project_id: uuid.UUID, chapter_no: int,