From 01c187340f244e496c1e7e188e3f1437593ba975 Mon Sep 17 00:00:00 2001 From: Color2333 <1552429809@qq.com> Date: Mon, 20 Jul 2026 02:05:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(tools):=20=E7=9F=AD=E5=89=8D=E7=BC=80=20pap?= =?UTF-8?q?er=5Fid=20=E5=AF=BC=E8=87=B4=20skim/deep=5Fread/embed/figures/r?= =?UTF-8?q?easoning=20=E5=B4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:_require_paper 支持短前缀(≥8 hex 自动模糊匹配完整 UUID),但 handler 里的 `UUID(paper_id)` 直接解析原始短前缀(如 "b7e07388"), 报 "badly formed hexadecimal UUID string"。 system prompt 示例 F 明确教用户"直接传 b7e07388",但 handler 崩了, agent 可用性受损(LLM 按指令调工具却失败)。 修复:handler 用 `paper.id`(_require_paper 已 resolve 成完整 UUID), 不再用原始 paper_id 调 UUID()。涉及: - read.py: _skim_paper / _deep_read_paper / _embed_paper(3 处 pid = UUID(paper.id)) - figures.py: _analyze_figures 改用 _require_paper 统一解析 + UUID(paper.id) - reasoning.py: _reasoning_analysis 改用 _require_paper + UUID(paper.id) figures.py/reasoning.py 此前自己管理 session 且 UUID(paper_id) 对短前缀崩, 改用 _require_paper 后统一走 detached-but-loaded 模式,与 read.py 一致。 测试:69 passed / 2 skipped(无回归)。 --- packages/ai/tools/handlers/figures.py | 28 ++++++++++++------------- packages/ai/tools/handlers/read.py | 12 ++++++----- packages/ai/tools/handlers/reasoning.py | 20 ++++++++---------- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/packages/ai/tools/handlers/figures.py b/packages/ai/tools/handlers/figures.py index 0bf3a3e..cd6da0c 100644 --- a/packages/ai/tools/handlers/figures.py +++ b/packages/ai/tools/handlers/figures.py @@ -6,8 +6,6 @@ from uuid import UUID from packages.ai.tools.types import ToolProgress, ToolResult -from packages.storage.db import session_scope -from packages.storage.repositories import PaperRepository if TYPE_CHECKING: from collections.abc import Iterator @@ -16,25 +14,25 @@ def _analyze_figures(paper_id: str, max_figures: int = 10) -> Iterator[ToolProgress | ToolResult]: """提取并解读论文图表""" from packages.ai.figure_service import FigureService + from packages.ai.tools.base import _require_paper - with session_scope() as session: - repo = PaperRepository(session) - try: - paper = repo.get_by_id(UUID(paper_id)) - except (ValueError, Exception) as exc: - yield ToolResult(success=False, summary=f"论文不存在: {exc}") - return - if not paper.pdf_path: - yield ToolResult(success=False, summary="论文没有 PDF 文件,无法提取图表") - return - pdf_path = paper.pdf_path - title = paper.title + # 用 _require_paper 统一解析(支持短前缀,返回 detached 但属性已加载的 paper) + paper, err = _require_paper(paper_id) + if err: + yield err + return + if not paper.pdf_path: + yield ToolResult(success=False, summary="论文没有 PDF 文件,无法提取图表") + return + pdf_path = paper.pdf_path + title = paper.title + pid = UUID(paper.id) # 用完整 UUID,不用原始短前缀 yield ToolProgress(message=f"正在提取「{(title or '')[:30]}」中的图表...", current=1, total=3) svc = FigureService() try: results = svc.analyze_paper_figures( - UUID(paper_id), + pid, pdf_path, max_figures=max_figures, ) diff --git a/packages/ai/tools/handlers/read.py b/packages/ai/tools/handlers/read.py index a3ed786..937475e 100644 --- a/packages/ai/tools/handlers/read.py +++ b/packages/ai/tools/handlers/read.py @@ -24,7 +24,9 @@ def _skim_paper(paper_id: str) -> Iterator[ToolProgress | ToolResult]: if not paper.abstract: yield ToolResult(success=False, summary="该论文缺少摘要,无法执行粗读") return - pid = UUID(paper_id) + # 用 paper.id(_require_paper 已 resolve 成完整 UUID),不用原始 paper_id + # (原始可能是短前缀,UUID(paper_id) 会崩 "badly formed hexadecimal UUID") + pid = UUID(paper.id) title = (paper.title or "")[:40] yield ToolProgress(message=f"正在粗读「{title}」...", current=1, total=2) try: @@ -48,7 +50,7 @@ def _deep_read_paper(paper_id: str) -> Iterator[ToolProgress | ToolResult]: if not paper.arxiv_id and not paper.pdf_path: yield ToolResult(success=False, summary="该论文无 arXiv ID 且无 PDF,无法精读") return - pid = UUID(paper_id) + pid = UUID(paper.id) title = (paper.title or "")[:40] yield ToolProgress(message=f"正在精读「{title}」,预计 30-60 秒...", current=1, total=3) try: @@ -68,11 +70,11 @@ def _embed_paper(paper_id: str) -> Iterator[ToolProgress | ToolResult]: if err: yield err return - pid = UUID(paper_id) + pid = UUID(paper.id) if paper.embedding: yield ToolResult( success=True, - data={"paper_id": paper_id, "status": "already_embedded"}, + data={"paper_id": paper.id, "status": "already_embedded"}, summary="该论文已有向量,跳过", ) return @@ -87,7 +89,7 @@ def _embed_paper(paper_id: str) -> Iterator[ToolProgress | ToolResult]: PaperPipelines().embed_paper(pid) yield ToolResult( success=True, - data={"paper_id": paper_id, "status": "embedded"}, + data={"paper_id": paper.id, "status": "embedded"}, summary="向量化完成", ) except Exception as exc: diff --git a/packages/ai/tools/handlers/reasoning.py b/packages/ai/tools/handlers/reasoning.py index ced908f..4cb7ee2 100644 --- a/packages/ai/tools/handlers/reasoning.py +++ b/packages/ai/tools/handlers/reasoning.py @@ -6,8 +6,6 @@ from uuid import UUID from packages.ai.tools.types import ToolProgress, ToolResult -from packages.storage.db import session_scope -from packages.storage.repositories import PaperRepository if TYPE_CHECKING: from collections.abc import Iterator @@ -16,20 +14,20 @@ def _reasoning_analysis(paper_id: str) -> Iterator[ToolProgress | ToolResult]: """推理链深度分析""" from packages.ai.reasoning_service import ReasoningService + from packages.ai.tools.base import _require_paper - with session_scope() as session: - repo = PaperRepository(session) - try: - paper = repo.get_by_id(UUID(paper_id)) - except (ValueError, Exception) as exc: - yield ToolResult(success=False, summary=f"论文不存在: {exc}") - return - title = paper.title + # 用 _require_paper 统一解析(支持短前缀,返回 detached 但属性已加载的 paper) + paper, err = _require_paper(paper_id) + if err: + yield err + return + title = paper.title + pid = UUID(paper.id) # 用完整 UUID,不用原始短前缀 yield ToolProgress(message=f"正在分析「{(title or '')[:30]}」的推理链...", current=1, total=2) svc = ReasoningService() try: - result = svc.analyze(UUID(paper_id)) + result = svc.analyze(pid) except Exception as exc: yield ToolResult(success=False, summary=f"推理链分析失败: {exc}") return