Files
PlantAI-rag-new/sdk/app/routers/paper.py
T

110 lines
4.3 KiB
Python

"""routers/paper.py —— 论文撰写路由(对话大脑)。
条件注册(PAPER_WRITER_ENABLED 为假不注册,同 bioflow_proxy 模式)。
端点:health(探活反代)/ turns(一轮状态机)/ confirm / resume / progress / citations。
"""
from __future__ import annotations
import asyncio
from typing import Any
import httpx
from fastapi import APIRouter, Depends, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from ..config import get_settings
from ..db import Database, get_db
from ..services.paper import agent
from ..services.paper.mcp_client import PaperMcpClient
from ..upstream import envelope
router = APIRouter(prefix="/api/paper", tags=["paper"])
class PaperTurnRequest(BaseModel):
session_id: str
text: str
paper_id: str | None = None
template_key: str = ""
class PaperConfirmRequest(BaseModel):
session_id: str
def _client() -> PaperMcpClient:
s = get_settings()
return PaperMcpClient(s.paper_writer_url, s.paper_writer_read_timeout)
async def _session_owned(db: Database, session_id: str, owner_id: str | None) -> bool:
if owner_id is None:
return True
row = await db.fetchone("SELECT owner_id FROM sessions WHERE id = ?", (session_id,))
return row is not None and row["owner_id"] == owner_id
@router.get("/health")
async def paper_health() -> dict[str, Any]:
"""探活反代 8097 /health;离线返回 offline 标记(前端据此隐藏 paper 入口)。"""
s = get_settings()
try:
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.get(f"{s.paper_writer_url.rstrip('/')}/health")
resp.raise_for_status()
return resp.json()
except Exception: # noqa: BLE001
return {"ok": False, "status": "offline"}
@router.post("/turns")
async def paper_turn(payload: PaperTurnRequest, request: Request, db: Database = Depends(get_db)) -> dict[str, Any]:
user = getattr(request.state, "user", None)
if user and not await _session_owned(db, payload.session_id, user["id"]):
return JSONResponse(status_code=404, content=envelope("NOT_FOUND", message="会话不存在", status=404)[0])
blocks = await agent.handle_turn(db, payload.session_id, payload.text, payload.paper_id, payload.template_key)
return {"ok": True, "blocks": blocks}
@router.post("/papers/{paper_id}/confirm")
async def paper_confirm(paper_id: str, payload: PaperConfirmRequest, request: Request, db: Database = Depends(get_db)) -> dict[str, Any]:
user = getattr(request.state, "user", None)
if user and not await _session_owned(db, payload.session_id, user["id"]):
return JSONResponse(status_code=404, content=envelope("NOT_FOUND", message="会话不存在", status=404)[0])
asyncio.create_task(agent.run_writing_loop(db, paper_id, payload.session_id))
return {"ok": True, "paper_id": paper_id}
@router.post("/papers/{paper_id}/resume")
async def paper_resume(paper_id: str, payload: PaperConfirmRequest, request: Request, db: Database = Depends(get_db)) -> dict[str, Any]:
user = getattr(request.state, "user", None)
if user and not await _session_owned(db, payload.session_id, user["id"]):
return JSONResponse(status_code=404, content=envelope("NOT_FOUND", message="会话不存在", status=404)[0])
asyncio.create_task(agent.run_writing_loop(db, paper_id, payload.session_id))
return {"ok": True, "paper_id": paper_id}
@router.get("/papers/{paper_id}")
async def paper_progress(paper_id: str) -> dict[str, Any]:
client = _client()
try:
return await client.call_tool("get_paper_progress", {"paper_id": paper_id})
except Exception as exc: # noqa: BLE001
return {"ok": False, "code": "UPSTREAM_DOWN", "message": str(exc)}
@router.get("/papers/{paper_id}/citations/{n}")
async def paper_citation(paper_id: str, n: int) -> dict[str, Any]:
client = _client()
try:
paper = await client.call_tool("get_paper", {"paper_id": paper_id})
except Exception as exc: # noqa: BLE001
return {"ok": False, "code": "UPSTREAM_DOWN", "message": str(exc)}
if not paper.get("ok"):
return paper
for ref in paper.get("references") or []:
if ref.get("n") == n:
return {"ok": True, "citation": ref}
return {"ok": False, "code": "NOT_FOUND", "message": f"引用 [{n}] 不存在"}