Skip to content

fix(agent/arxiv): 修复 arXiv 检索/状态机/错误处理等核心体验问题#17

Merged
Color2333 merged 40 commits intomainfrom
dev
Apr 24, 2026
Merged

fix(agent/arxiv): 修复 arXiv 检索/状态机/错误处理等核心体验问题#17
Color2333 merged 40 commits intomainfrom
dev

Conversation

@Color2333
Copy link
Copy Markdown
Owner

@Color2333 Color2333 commented Apr 24, 2026

Summary

从一次真实使用记录定位到全链路多处核心 bug,本次一并修复:arXiv 搜不到经典论文、入库状态机「成功+过期」矛盾、Cloudflare 504 HTML 甩用户脸上、入库前看不到论文标题等。11 个文件,+364/-112。

问题与修复

后端 - arXiv 检索

  • arxiv_client.pyimport time(重试分支原本会 NameError 崩)
  • fetch_latest 默认 days_back=0 —— 原 7 天过滤把 OpenShape/Uni3D/PointContrast 等经典论文全筛掉
  • _build_arxiv_query 拆词上限 3→6;新增引号精确短语搜索
  • /ingest/arxiv 路由暴露 days_back 参数(默认 0),前端 ingestApi.arxiv 同步
  • search_arxiv Agent 工具暴露 days_back / sort_by(默认 relevance)

后端 - Agent 状态机

  • 新增 _HANDLED_ACTION_CACHE(TTL 1h 内存缓存)做 confirm/reject 幂等保护
  • 找不到 action 时区分文案:「已处理过」vs「真的过期」,消除矛盾提示
  • SYSTEM_PROMPT 强化:ingest_arxiv 前必须列出候选标题/作者/年份,禁止盲 ID 确认

前端 - 错误处理

  • services/api.ts 新增 friendlyStatusMessage + extractErrorMessage
  • request / fetchSSE 响应非 JSON 或以 < / <!doctype 开头一律按状态码降级文案
  • 504/524/408/502/503 全部有对应中文提示,Cloudflare HTML 错误页不再甩用户
  • errorHandler.ts 补 504/524/408 识别 + HTML 污染兜底

前端 - Agent 交互

  • AgentSessionContext 新增 handledActionsRef,防双击/StrictMode 重复提交
  • ActionConfirmCard 特化 ingest_arxiv:反查 search_arxiv 候选,显示标题+作者卡片

顺带修复

  • PaperDetail.tsx 精读结果错塞 savedDeep(真 bug,导致精读后界面空白)
  • AggregationPanel / Collect 多源搜索字段不匹配(sources vs source
  • 清理 agent_tools.py Iterator TYPE_CHECKING / set comprehension / 重复字典 key
  • 清理多处 unused import

真实端到端验证

查询 修前 修后 证据
OpenShape 0 2 篇 2305.10764 OpenShape (NeurIPS'23)
Uni3D 0 5 篇全命中 2310.06773 Uni3D (ICLR'24)
ULIP-2 0 1 篇 2305.08275 ULIP-2 原版
PointContrast 0 3 篇 2007.10985 PointContrast 原版
Point-MAE 0 4 篇 2203.06604 Point-MAE
ShapeNet 0 5 篇 1512.03012 ShapeNet 经典原版

Test plan

  • npx tsc --noEmit 前端 0 错误
  • npx vite build 生产构建通过(2.5s)
  • python -m py_compile 后端 3 个改动文件通过
  • pre-commit hooks(ruff / ruff-format / 各通用检查)全绿
  • 后端模块导入 + 状态机幂等缓存单元测试通过
  • 真实 HTTP 端到端调用 /ingest/arxiv?query=X 对 6 个经典查询全部命中
  • 浏览器端 UX 验收(504 错误降级 / 入库卡片新展示 / 精读分模块显示 / 双击 confirm 不再"已过期")— 建议 merge 前人肉过一遍

🤖 Generated with Claude Code

参考 learn-claude-code (s01-s12) 体系重构 Agent 基础设施。

Phase 1 - Agent Loop + 工具层:
- packages/agent_core/loop.py: 显式 AgentLoop 类,while + stop_reason 循环
- packages/agent_core/dispatcher.py: ToolDispatcher,工具注册表 + dispatch map
- packages/agent_core/tools/: bash / filesystem handlers

Phase 2 - 持久化 + Context:
- packages/agent_core/tasks.py: TaskManager,JSON 文件持久化 + blockedBy 依赖图
- packages/agent_core/background.py: BackgroundTaskRunner,daemon 线程池

Phase 3 - Team 协作:
- packages/agent_core/message_bus.py: MessageBus,JSONL 邮箱异步通信
- packages/agent_core/teammates.py: TeammateManager,持久 agent 线程管理
- packages/agent_core/protocols.py: TeamProtocols,shutdown/plan_approval FSM

Skills (packages/agent_core/skills/):
- agent-loop: AgentLoop 使用指南
- tool-dispatcher: 工具注册机制
- task-persistence: TaskManager 文件格式
- context-compaction: 3层压缩策略
- agent-teams: 多 agent 协作
- team-protocols: 通信协议 FSM

README 引用 learn-claude-code 项目致谢。
- 添加 StreamingAgentLoop: 支持流式 LLM + SSE + 确认机制
- 添加 ConfirmationMixin: 接管 _CONFIRM_TOOLS pending 逻辑
- 添加 StreamingToolDispatcher: 适配生成器式 tool handlers
- 添加 GlobalTrackerAdapter: global_tracker 兼容层
- 重构 agent_service.py: 使用新架构,保持原有接口不变
- 所有文件通过 py_compile 和 ruff check
- Context Compact (s06): 3层压缩策略,支持无限长度会话
  - Layer1: 消息摘要 (LLM生成)
  - Layer2: 关键决策提取
  - Layer3: 元信息压缩
  - CompactingStreamingAgentLoop 混入类

- TodoWrite (s03): 统一任务计划接口
  - TodoManager: JSON持久化,支持层级结构(parent_id)
  - PlannerMixin: 计划生成混入类
  - 格式规范: [WHERE] [HOW] to [WHY] — expect [RESULT]

- Subagents (s04): 增强型子Agent并行调度
  - SubagentRunner: 独立线程执行,支持sync/async
  - SubagentPool: 并发控制(max_concurrent)
  - 全局单例 get_subagent_pool()

- agent_service.py: 导出所有新模块供consumer使用
- 所有文件通过 py_compile 和 ruff check
修复的问题:
1. tool消息丢失:每个tool_result事件立即保存到DB
2. 消息累积逻辑错误:现在保存所有user/assistant/tool消息
3. SSE正则bug:修复 (.+?) DOTALL模式下可能匹配异常
4. 历史消息恢复:API层从DB加载消息传给stream_chat
5. stream_chat返回类型:改为 tuple[Iterator, conversation]
6. schemas: AgentMessage添加meta字段支持

涉及的变更:
- agent.py: 重写消息保存逻辑,修复SSE解析
- agent_service.py: stream_chat/confirm_action/reject_action返回conversation
- schemas.py: AgentMessage.meta字段
在 session_scope 外部访问 PipelineRun ORM 对象导致 DetachedInstanceError。
将 recent_runs 列表推导式移到 with scope 内部,确保对象 attached 时访问属性。
问题原因:
- agent_core.loop.PaperMindToolResult 和 agent_tools.ToolResult 是两个同名不同模块的类
- _execute_and_emit/execute_confirmed_action/execute_and_continue 中的 isinstance 检查只匹配 PaperMindToolResult
- execute_tool_stream 返回的是 agent_tools.ToolResult,导致 isinstance 返回 False
- 结果:result 一直是默认值 "无结果",工具看起来"调用失败"

修复方案:
- 增加 hasattr duck-typing 检查,兼容两种 ToolResult 类
- 新增后端数据模型: UserSchema, SensemakingSession, SchemaPaperInteraction
- 新增 Sensemaking API: Act1/2/3 认知重构流程端点
- 新增翻译 API: 划词翻译和段落对照翻译
- 新增论文分段 API: /papers/{id}/segments
- ToolPanel 重构: Tab 化 (翻译/AI聚合/Canvas)
- CanvasPanel: 完整的 Act1/2/3 引导流程 UI
- TranslationPanel: 划词翻译 + 全文对照模式
- PdfReader 集成 ToolPanel 替换旧 AI Panel
- main.py: 注册 sensemaking 和 translate 路由
- papers.py: 新增 /papers/{id}/segments 论文分段 API
- PdfReader.tsx: 集成 ToolPanel 组件
- config.py: 修复 ieee_api_key 缺失问题
- storage/models.py: 添加 UserSchema/SensemakingSession/SchemaPaperInteraction 表
- 左半屏显示原文 PDF
- 右半屏固定显示 ToolPanel (翻译/AI聚合/Canvas)
- 移除浮窗切换按钮
- 进度条自适应左半屏宽度
- 检测到 PDF 未下载时显示下载按钮
- 用户可直接在面板内下载 PDF
- 改进错误提示
- 添加 paperPdfPath 属性
- 优先使用本地 PDF,其次 arXiv 在线代理
- PaperDetail 传入 pdf_path
- TranslationPanel 重构为左右分栏:左侧 PDF 原文,右侧翻译对照
- PdfReader 传递 paperArxivId 和 paperPdfPath 给 TranslationPanel
- 清理未使用的状态变量(pdfDoc, currentPage, pageInput, goToPage)
- PyMuPDF 逐页提取文本,跟踪每段所属页码
- 每个分段 now includes pageNumber (1-indexed)
- 为前端双语对照滚动同步提供基础
- PDF 滚动时自动滚动翻译面板到对应页码段落
- 翻译段落添加点击跳转功能,点击跳转到 PDF 对应页面
- 移除双向滚动同步(干扰页码同步逻辑)
- 清理未使用的变量和多余注释
后端实现:
- translate.py: 新增 extract_segments_from_pdf() 快速提取分段
- translate.py: 并发翻译控制(ThreadPoolExecutor, max_workers=5)
- routers/translate.py: 新增 /bilingual-pdf 端点,支持两种模式
  - fast: PyMuPDF 分段 + 并发翻译(1-2 分钟)
  - layout: PDFMathTranslate 布局保留(3-5 分钟)
- 异步任务处理,不阻塞请求
- TranslationPanel 新增翻译模式下拉框
- 快速翻译:⚡ 1-2 分钟,文本对照
- 布局保留:📐 3-5 分钟,完整排版
- 仅在全屏对照模式显示模式选择
- 添加混合翻译模式说明
- 快速翻译:PyMuPDF 分段 + 并发(1-2 分钟)
- 布局保留:PDFMathTranslate 完整排版(3-5 分钟)
模型配置更新:
- config.py: 默认模型统一为 glm-4.7 / glm-4.6v
- model_tier.py: 预设模板只保留智谱 GLM-4.7 系列
- LLMSettings.tsx: 前端预设只保留统一配置

配置说明:
- 粗读/简单任务:glm-4.7
- 精读/复杂任务:glm-4.7
- 视觉任务:glm-4.6v
- 降级备用:glm-4.7

简化模型管理,避免配置混乱。
Color2333 and others added 9 commits March 23, 2026 19:41
界面改进:
- 顶部添加当前激活配置概览卡片,一目了然
- 配置列表卡片样式优化,图标 + 模型标签更直观
- 表单分组展示(基础信息 / 模型配置),层次清晰
- 预设配置卡片带图标和描述,更有引导性
- 整体布局更紧凑、更专业

交互优化:
- 文本模型输入联动(修改一个自动填其他)
- 悬停效果和过渡动画
- 删除按钮仅在非激活状态可点击
- 删除旧的独立LLMSettings对话框页面
- 新Settings页面采用左侧导航+右侧内容布局
- 四个设置标签页:LLM配置/邮箱报告/Pipeline/运维
- 修复form label关联和button type等lint问题
- 路由/settings现在指向新的Settings组件
- 移除 showSettings state 和 SettingsDialog 弹窗
- 点击侧边栏设置改为导航到 /settings 路由
- 删除 SettingsDialog.tsx 组件
- 清理不再使用的 lazy/Suspense 导入
- 修复 Settings.tsx 中 useLocation 未定义错误
LLMTab:
- 添加ProviderBadge彩色服务商标签
- 添加deactivate切回默认配置功能
- 添加OpenAI/Anthropic预设配置

EmailTab:
- 添加send_email_report开关
- 添加recipient_emails收件人配置
- 添加cron_expression定时任务配置
- 添加include_paper_details/include_graph_insights选项
- 添加AddEmailConfigModal/EditEmailConfigModal完整表单
- 添加QQ邮箱/Gmail/163邮箱快捷预设
- 添加test邮件功能

PipelineTab:
- 添加StatusDot状态指示器
- 添加paper_id显示
- 添加running/pending状态过滤

OpsTab:
- 添加dailyJob每日任务
- 添加weeklyJob每周图维护
- 添加loading状态管理
- EmailTab: 添加 auto_deep_read 自动精读开关
- EmailTab: 添加 deep_read_limit 每日精读上限配置
- LLMTab: 添加 handleActivate/handleDelete 专用处理函数
- LLMTab: activate/delete/编辑按钮在操作中禁用防止竞态
- 新增"多源搜索"tab,支持 ArXiv/OpenAlex/Semantic Scholar 等多渠道
- 添加 paperApi.multiSourceSearch() 和 suggestChannels() API
- 添加 MultiSourceSearchResult 和 ChannelSuggestion 类型
- 多源搜索区域包含:搜索框、渠道推荐、渠道选择按钮组
- 搜索结果展示论文标题、作者、来源和日期
P0 修复:
- api.ts: 端点从 /papers/multi-source-search 改为 /papers/search-multi
- api.ts: 修复响应字段映射 (papers→results, channel_stats→channelStats)
- 类型定义: 更新 MultiSourcePaper 匹配后端返回结构
- arxiv_channel.py: 确保 source/source_id 正确设置

P1 修复:
- arxiv_channel.py: days_back=0 移除默认7天限制
- ChannelContext.tsx: 使用 resolveApiBase() 替代硬编码 /api
- AggregationPanel.tsx: 使用 paperApi.multiSourceSearch() 替代原生 fetch
- MultiSourceSearchBar.tsx: 使用 paperApi.suggestChannels() 替代原生 fetch
从一次真实使用记录定位到全链路多处核心 bug,本次一并修复:

后端 - arXiv 检索:
- arxiv_client.py 补 import time(重试分支原本会 NameError)
- fetch_latest 默认 days_back=0(原 7 天过滤把 OpenShape/Uni3D 等经典论文全筛掉)
- _build_arxiv_query 拆词上限 3→6,新增带引号精确短语搜索
- /ingest/arxiv 路由暴露 days_back 参数(默认 0),前端 ingestApi.arxiv 同步
- search_arxiv Agent 工具暴露 days_back / sort_by(默认 relevance)

后端 - Agent 状态机:
- 新增 _HANDLED_ACTION_CACHE TTL 内存缓存,confirm/reject 幂等保护
- 找不到 action 时区分文案:「已处理过」vs「真的过期」,消除矛盾提示
- SYSTEM_PROMPT 强化:ingest_arxiv 前必须列出候选标题/作者/年份,禁止盲 ID 确认

前端 - 错误处理:
- services/api.ts 新增 friendlyStatusMessage + extractErrorMessage
- request/fetchSSE 响应非 JSON 或以 < / <!doctype 开头一律按状态码降级文案
- 504/524/408/502/503 等全部有对应中文提示,Cloudflare HTML 错误页不再甩用户
- errorHandler.ts 补 504/524/408 识别 + HTML 污染兜底

前端 - Agent 交互:
- AgentSessionContext 新增 handledActionsRef,防双击/StrictMode 重复提交
- ActionConfirmCard 特化 ingest_arxiv:反查 search_arxiv 候选,显示标题+作者卡片

顺带修复:
- PaperDetail.tsx 精读结果错塞到 savedDeep(真 bug,导致精读后空白)
- AggregationPanel/Collect 多源搜索类型不匹配(sources vs source 字段)
- 清理 agent_tools.py 的 Iterator TYPE_CHECKING / set comprehension / 重复字典 key
- 清理 Collect 及 PaperDetail 多处 unused import

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


This is an automated reminder from PR Review Gate.

@Color2333
Copy link
Copy Markdown
Owner Author

/oc review

@opencode-agent
Copy link
Copy Markdown
Contributor

Good! The TypeScript deprecation warning is not a real error. Now let me provide my review:

Code Review Summary

这是一个高质量的 bug fix PR,修复了从真实使用中发现的 11 个核心问题。以下是详细评审:

✅ 主要修复

1. arXiv 检索问题 (packages/integrations/arxiv_client.py)

  • ✅ 补充 import time(重试分支原本会 NameError)
  • fetch_latest 默认 days_back=0,解决经典论文被过滤问题
  • _build_arxiv_query 拆词上限 3→6,新增引号精确短语搜索
  • /ingest/arxiv 路由和前端 API 同步暴露 days_back 参数
  • search_arxiv Agent 工具暴露 days_back / sort_by

代码质量: 逻辑清晰,日期过滤回退机制处理得当(500 错误时自动去掉日期重试)

2. Agent 状态机幂等保护 (packages/ai/agent_service.py)

  • ✅ 新增 _HANDLED_ACTION_CACHE(TTL 1h)防止重复 confirm/reject
  • ✅ 区分「已处理过」vs「真过期」文案,消除矛盾提示
  • SYSTEM_PROMPT 强化:ingest_arxiv 前必须列出候选标题/作者/年份

代码质量: 缓存清理机制完善(超过 256 条目自动清理过期项)

3. 前端错误处理 (frontend/src/services/api.ts, errorHandler.ts)

  • ✅ 新增 friendlyStatusMessage + extractErrorMessage
  • ✅ 非 JSON/HTML 响应降级处理(解决 Cloudflare 504 HTML 错误页)
  • ✅ 504/524/408/502/503 全部有对应中文提示

代码质量: 错误提取逻辑健壮,考虑了 JSON 解析失败/HTML 污染/超长文本等边界情况

4. Agent 交互优化 (frontend/src/contexts/AgentSessionContext.tsx)

  • ✅ 新增 handledActionsRef 防双击/StrictMode 重复提交
  • ActionConfirmCard 特化 ingest_arxiv:反查 search_arxiv 候选,显示标题 + 作者卡片

代码质量: lookupArxivCandidates 函数设计巧妙,从对话历史反向查找元信息

5. PaperDetail 修复 (frontend/src/pages/PaperDetail.tsx)

  • ✅ 精读结果正确存入 savedDeep(原 bug 错塞 savedDeep 导致界面空白)

⚠️ 潜在问题

1. agent_tools.py:1017 并发处理

def _process_one(pid_str: str) -> tuple[bool, bool]:
    """单篇论文:embed ∥ skim 并行"""
    pid = UUID(pid_str)
    e_ok, s_ok = False, False
    with ThreadPoolExecutor(max_workers=2) as inner:  # ⚠️ 每次创建新线程池
        fe = inner.submit(pipelines.embed_paper, pid)
        fs = inner.submit(pipelines.skim, pid)
        ...

建议: 外层已创建 ThreadPoolExecutor(max_workers=PAPER_CONCURRENCY),内层无需再创建线程池,可直接顺序调用或使用 asyncio.gather

2. arxiv_client.py:205 重复字典 key

{"code": "cs.CL", "name": "Computational Linguistics", "description": ""},
# 与上方的 cs.CL (Computation and Language) 重复

影响: 后者覆盖前者,实际不影响功能(都是 cs.CL 分类)

3. agent_service.py:328 竞态条件

if not action:
    already_handled = _is_action_handled(confirmed_action_id)
    err_msg = (...)

风险: 多线程环境下 _HANDLED_ACTION_CACHE 可能并发访问,但考虑 Python GIL 和 TTL 设计,实际风险较低

📊 测试覆盖

  • npx tsc --noEmit 前端 0 错误
  • python -m py_compile 后端 3 个文件通过
  • ✅ 端到端验证:6 个经典查询(OpenShape/Uni3D/ULIP-2/PointContrast/Point-MAE/ShapeNet)全部命中

🎯 关键改进验证

问题 修复前 修复后 证据
OpenShape 检索 0 篇 2 篇 2305.10764 NeurIPS'23
Uni3D 检索 0 篇 5 篇 2310.06773 ICLR'24
Cloudflare 504 甩 HTML 页 中文提示 friendlyStatusMessage
重复 confirm "已过期"矛盾 "已处理过" _HANDLED_ACTION_CACHE

📝 建议

  1. 高优先级: 修复 agent_tools.py:1017 嵌套线程池问题(性能优化)
  2. 中优先级: 清理 arxiv_client.py 重复的 cs.CL 分类
  3. 低优先级: 考虑为 _HANDLED_ACTION_CACHE 添加线程锁(可选)

Review 结论: LGTM (Look Good To Merge)

核心 bug 修复到位,错误处理健壮,端到端验证充分。建议合并后人肉过一遍前端 UX(504 降级/入库卡片/精读显示)。

New%20session%20-%202026-04-24T04%3A14%3A04.771Z
opencode session  |  github run

回应 PR #17 opencode review 建议(中优先级):
fetch_categories 的 fallback 列表里 cs.CL 被错写了两次
(Computation and Language / Computational Linguistics),
官方 arxiv 分类只有一个 cs.CL = Computation and Language,删除重复项。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


This is an automated reminder from PR Review Gate.

@Color2333
Copy link
Copy Markdown
Owner Author

感谢 review!逐条回应:

✅ 已处理

  • [中优] arxiv_client.py cs.CL 重复分类 → 已在 commit fcc241a 删除重复项(官方 arxiv 只有一个 cs.CL = Computation and Language

⏸ 保留现状(带理由)

[高优] agent_tools.py:1017 嵌套线程池:review 建议"直接顺序调用或 asyncio.gather",但这里 embed_paperskim 都是 IO-bound 的 LLM 长调用(每个几秒到几十秒),内层并行能让单篇论文快 ~2×;改顺序会让整体流程变慢。ThreadPoolExecutor(max_workers=2) 创建/销毁开销 < 50ms,占比可忽略。且此代码不在本 PR 变更范围(_process_one 函数未改动),按 KISS/YAGNI 不在这次动它。

[低优] _HANDLED_ACTION_CACHE 线程锁:review 自己也指出"Python GIL 和 TTL 设计,实际风险较低"。Python dict 的 get / __setitem__ / pop 对单 key 都是 GIL-atomic,TTL 是 "stale 即惰性清理" 的最终一致模型,竞态窗口内最坏结果只是多一次误判(复查一次 DB)而已,无数据损坏风险。加锁会增加每次 Agent 对话的锁竞争开销,不值得。

合并前再本地跑了一次 smoke 确认删分类没破坏 fetch_categories() 逻辑。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant