Files
admin 91f6a1c242 feat: 新增论文撰写功能 paper-writer(独立 MCP 服务 + BFF 大脑 + 前端三卡)
- paper_writer_service(8097):FastAPI + MCP streamable-http 单端点,9 工具
  (propose_paper_outline/write_paper_section/get_paper_progress/search_refs/
  revise_paper_section/revise_paper/get_paper/complete_paper/cancel_paper),
  SQLite 三表(papers/paper_sections/citations,WAL),GB/T 7714 引用协议
- BFF:services/paper/{mcp_client,agent}.py(裸 JSON-RPC 客户端 + 规则状态机)、
  routers/paper.py、schemas.py 4 个 Paper*Block + ArtifactBlock 加 paper_id/citation_count、
  config.py 开关、main.py 条件注册、docgen.py 抽 insert_artifact 公共 helper
- 前端:intent.js paperIntent 纯函数、Composer docgen 分支 sub-route、sendPaper/轮询、
  PaperBriefCard/PaperOutlineCard/PaperProgressCard、DocPane cite-chip
- deploy/paper_restart.sh + toggle_paper.sh;compile_sdk.sh 白名单追加 paper 文件
- tests/paper 26 条(工具逻辑 + MCP 协议 + BFF 状态机 + 块 schema + 前端 intent 单测)
2026-09-30 22:29:22 +08:00

81 lines
2.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""tests/paper/conftest.py —— 论文服务测试共享夹具(tmp db + 假 LLM + 假检索)。
全部 mock LLM/vstore,不依赖生产服务(8090/8094 等绝不探测)。
镜像 bioflow_service/tests/conftest.py 的 _run(新建事件循环)+ 临时库模式。
"""
from __future__ import annotations
import asyncio
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
for _p in (str(REPO_ROOT), str(REPO_ROOT / "sdk")):
if _p not in sys.path:
sys.path.insert(0, _p)
from paper_writer_service.app import db as dbmod # noqa: E402
from paper_writer_service.app.services.paper import llm as llmmod # noqa: E402
from paper_writer_service.app.services.paper import search as searchmod # noqa: E402
def _run(coro):
"""同步跑一个协程(新建 event loop;bioflow/docgen 测试同款可靠模式)。"""
return asyncio.new_event_loop().run_until_complete(coro)
class FakeLLM:
"""可编程假 LLM:按顺序返回预设 dict;或抛 LLMError。记录全部调用。"""
def __init__(self, responses=None, error=None):
self.responses = list(responses or [])
self.error = error
self.calls: list[dict] = []
async def complete_json(self, system, user, **kwargs):
self.calls.append({"system": system, "user": user})
if self.error:
raise llmmod.LLMError(self.error)
return self.responses.pop(0) if self.responses else {}
async def complete_text(self, system, user, **kwargs):
self.calls.append({"system": system, "user": user})
if self.error:
raise llmmod.LLMError(self.error)
return self.responses.pop(0) if self.responses else ""
# ---------------------------------------------------------------------------
# DB:单例改路径重连(服务模块以 from ...db import db 持有单例引用)
# ---------------------------------------------------------------------------
@pytest.fixture
def paper_db(tmp_path):
dbmod.db._path = Path(tmp_path) / "paper.db"
_run(dbmod.db.connect())
yield dbmod.db
_run(dbmod.db.close())
@pytest.fixture
def fake_llm(monkeypatch):
fake = FakeLLM()
monkeypatch.setattr(llmmod, "complete_json", fake.complete_json)
monkeypatch.setattr(llmmod, "complete_text", fake.complete_text)
return fake
@pytest.fixture
def fake_search(monkeypatch):
async def _search_refs(paper_id="", query="", top_k=8):
return {"ok": True, "items": [
{"doc_id": "d1", "title": "水稻SPL基因家族研究进展", "snippet": "SPL 是植物特有转录因子家族。", "score": 0.9, "meta": {}},
{"doc_id": "d2", "title": "植物SPL转录因子功能", "snippet": "SPL 调控开花与株型。", "score": 0.8, "meta": {}},
]}
monkeypatch.setattr(searchmod, "search_refs", _search_refs)
return _search_refs