모듈 레벨 변수를 직접 import하면 init_queue() 이후에도 None 참조가 유지됨. 모듈 자체를 import하여 접근. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
81 lines
2.5 KiB
Python
81 lines
2.5 KiB
Python
"""Chat router — POST /chat, GET /chat/{job_id}/stream, GET /chat/{job_id}/status, POST /chat/{job_id}/cancel."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
from models.schemas import CancelResponse, ChatRequest, ChatResponse, JobStatusResponse
|
|
from services.job_manager import job_manager
|
|
from services import job_queue as jq_module
|
|
from services.state_stream import state_stream
|
|
|
|
router = APIRouter(tags=["chat"])
|
|
|
|
|
|
@router.post("/chat", response_model=ChatResponse)
|
|
async def create_chat(body: ChatRequest):
|
|
"""job_id 즉시 반환 (ACK). 백그라운드에서 파이프라인 처리 시작."""
|
|
job = job_manager.create(body.message)
|
|
state_stream.create(job.id)
|
|
await jq_module.job_queue.submit(job)
|
|
return ChatResponse(job_id=job.id)
|
|
|
|
|
|
@router.get("/chat/{job_id}/stream")
|
|
async def stream_chat(job_id: str):
|
|
"""SSE 스트림으로 상태 + 결과 전달."""
|
|
job = job_manager.get(job_id)
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="Job not found")
|
|
|
|
return StreamingResponse(
|
|
_stream_with_cleanup(job_id),
|
|
media_type="text/event-stream",
|
|
headers={
|
|
"Cache-Control": "no-cache",
|
|
"X-Accel-Buffering": "no",
|
|
},
|
|
)
|
|
|
|
|
|
async def _stream_with_cleanup(job_id: str):
|
|
try:
|
|
async for chunk in state_stream.subscribe(job_id):
|
|
yield chunk
|
|
finally:
|
|
state_stream.cleanup(job_id)
|
|
|
|
|
|
@router.get("/chat/{job_id}/status", response_model=JobStatusResponse)
|
|
async def job_status(job_id: str):
|
|
"""job 상태 조회 (SSE 없이)."""
|
|
job = job_manager.get(job_id)
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="Job not found")
|
|
|
|
return JobStatusResponse(
|
|
job_id=job.id,
|
|
status=job.status,
|
|
created_at=job.created_at,
|
|
pipeline=job.pipeline,
|
|
queue_position=jq_module.job_queue.position(job.id) if jq_module.job_queue else None,
|
|
)
|
|
|
|
|
|
@router.post("/chat/{job_id}/cancel", response_model=CancelResponse)
|
|
async def cancel_chat(job_id: str):
|
|
"""진행 중인 job 취소."""
|
|
success = job_manager.cancel(job_id)
|
|
if not success:
|
|
raise HTTPException(status_code=404, detail="Job not found or already finished")
|
|
return CancelResponse(status="cancelled")
|
|
|
|
|
|
@router.get("/queue/stats")
|
|
async def queue_stats():
|
|
"""큐 통계."""
|
|
if jq_module.job_queue:
|
|
return jq_module.job_queue.stats
|
|
return {"pending": 0, "active": 0}
|