后端核心变更: - API 层: 新增 AppError 枚举统一错误类型,替代散落的 (StatusCode, String) - Agent 工具域: 重组为 astro/system/ 和 astro/research/ 两级域,新增 ProcessPaperTool 流水线工具 - 安全: 新增 SSRF 双层防护 (同步字符串级 + 异步 DNS 解析级),覆盖 IPv4/IPv6 私网段 - 弱密码检测: 扩展弱密码列表并增加最小长度检查 - LLM 客户端: 新增 ChatCompleter/Embedder trait,支持依赖注入与批量向量化 embed_batch - 批量处理: AssetBatch 从串行改为 Semaphore 并发池 (BATCH_CONCURRENCY=3) - 分块器: 重写为三阶段结构化管线 (章节解析→短节合并→带标题路径子块) - RAG: embedding 计算移出事务,RetrievalResult 新增 headings/section_index 字段 - 检索: ADS/arXiv 并行检索 (tokio::join!),去重改用 HashSet,本地库回填批量 IN 查询 - 天体查询: Sesame API 升级到 v4,新增视差误差/自行/视向速度/多波段测光字段 - 迁移: 14 个增量文件合并为单一 init.sql,支持 sqlx::migrate! 内存库集成测试 - 测试: circuit_breaker/hooks/task_board/session/memory/streaming_executor 新增修正 15+ 测试 前端架构重构: - 目录重组: features/ → pages/ + components/ + hooks/ 三层分离 - App.tsx 从 1181 行压缩至 ~174 行 (逻辑抽入 9 个自定义 Hook) - Agent 面板拆分为 AgentSessionSidebar/AgentMessageList/AgentInputArea 子组件 - 新增 GlobalDialog/PaperDetailModal/UncachedPaperModal 通用对话框组件 - 工具函数抽取: celestial.ts (天体坐标格式), paper.tsx (文献信息渲染)
327 lines
14 KiB
Markdown
327 lines
14 KiB
Markdown
# AstroResearch Database Schema / 数据库设计
|
||
|
||
AstroResearch 使用轻量级、零配置的 **SQLite** 数据库作为持久化存储。数据库文件默认保存在项目根目录下的 `astro_research.db`(可通过 `.env` 中的 `DATABASE_URL` 配置),由 Rust 中的 `sqlx` 驱动管理并自动执行迁移。
|
||
|
||
---
|
||
|
||
## 1. 实体关系图 (Entity-Relationship Diagram)
|
||
|
||
```mermaid
|
||
erDiagram
|
||
PAPERS {
|
||
text bibcode PK
|
||
text title
|
||
text authors "JSON Array"
|
||
text year
|
||
text pub "Journal/Publisher"
|
||
text keywords "JSON Array"
|
||
text abstract
|
||
text doi
|
||
text arxiv_id
|
||
integer citation_count
|
||
integer reference_count
|
||
text doctype "文献类型"
|
||
text pdf_path "PDF 物理路径 或 error:诊断"
|
||
text html_path "HTML 物理路径 或 error:诊断"
|
||
text markdown_path "Markdown 物理路径"
|
||
text translation_path "翻译文件物理路径"
|
||
datetime created_at
|
||
}
|
||
|
||
NOTES {
|
||
integer id PK
|
||
text bibcode FK
|
||
integer paragraph_index
|
||
text note_text
|
||
text highlight_color
|
||
text selected_text
|
||
datetime created_at
|
||
}
|
||
|
||
CITATIONS_REFERENCES {
|
||
text source_bibcode PK
|
||
text target_bibcode PK
|
||
}
|
||
|
||
SYNC_QUERIES {
|
||
integer id PK
|
||
text query "检索关键词"
|
||
text source "数据源"
|
||
integer limit_count "拉取上限"
|
||
datetime last_run "最近运行时间"
|
||
datetime created_at "创建时间"
|
||
}
|
||
|
||
PAPER_CHUNKS_CONTENT {
|
||
integer rowid PK
|
||
text bibcode FK
|
||
integer paragraph_index
|
||
text content
|
||
text headings "章节路径"
|
||
integer section_index "章节编号"
|
||
}
|
||
|
||
PAPER_TARGETS {
|
||
integer id PK
|
||
text bibcode FK
|
||
text target_name "标准化天体名称"
|
||
text ra "赤经"
|
||
text dec "赤纬"
|
||
real parallax "视差 mas"
|
||
text spectral_type "光谱分类"
|
||
real v_magnitude "视星等"
|
||
text aliases "JSON Array"
|
||
text otype "天体类型"
|
||
text oname "SIMBAD 主名称"
|
||
real pm_ra "自行 RA"
|
||
real pm_de "自行 Dec"
|
||
real radial_velocity "视向速度"
|
||
real parallax_err "视差误差"
|
||
text photometry "测光数据 JSON"
|
||
}
|
||
|
||
AGENT_SESSIONS {
|
||
text session_id PK
|
||
text title
|
||
text model
|
||
integer turn_count
|
||
text last_error
|
||
text summary
|
||
text metadata
|
||
datetime deleted_at
|
||
integer rewind_count "回退计数"
|
||
text parent_session_id FK "分支来源"
|
||
text branch_metadata "分支元数据"
|
||
text mode "模式标识符"
|
||
}
|
||
|
||
AGENT_MESSAGES {
|
||
integer id PK
|
||
text session_id FK
|
||
integer turn_index
|
||
integer step_index
|
||
text role "system/user/assistant/tool"
|
||
text content
|
||
text thought "思考链"
|
||
text tool_calls "工具调用 JSON"
|
||
text tool_call_id "工具调用 ID"
|
||
integer token_count
|
||
text metadata
|
||
text raw_json
|
||
text agent_name "消息归属"
|
||
integer active "软删除标记"
|
||
}
|
||
|
||
AGENT_TASKS {
|
||
integer id PK
|
||
text session_id FK
|
||
text task_id
|
||
text content
|
||
text status "pending/in_progress/completed"
|
||
text blocked_by "JSON Array — DAG 依赖"
|
||
text owner "分配目标 agent 名称"
|
||
}
|
||
|
||
AGENT_AUDIT_LOG {
|
||
integer id PK
|
||
text session_id FK
|
||
integer step
|
||
text tool_name
|
||
text status "OK / FAIL / SESSION_STOP"
|
||
integer elapsed_ms
|
||
text output_preview
|
||
text agent_name
|
||
}
|
||
|
||
AGENT_TEAM_MEMBERS {
|
||
integer id PK
|
||
text session_id FK
|
||
text agent_name
|
||
text agent_role
|
||
text status "spawning / active / idle / shutdown"
|
||
}
|
||
|
||
PAPERS ||--o{ NOTES : "has"
|
||
PAPERS ||--o{ CITATIONS_REFERENCES : "cites / cited_by"
|
||
PAPERS ||--o{ PAPER_CHUNKS_CONTENT : "chunked into"
|
||
PAPERS ||--o{ PAPER_TARGETS : "contains"
|
||
AGENT_SESSIONS ||--o{ AGENT_MESSAGES : "contains"
|
||
AGENT_SESSIONS ||--o{ AGENT_TASKS : "owns"
|
||
AGENT_SESSIONS ||--o{ AGENT_AUDIT_LOG : "records"
|
||
AGENT_SESSIONS ||--o{ AGENT_TEAM_MEMBERS : "members"
|
||
AGENT_SESSIONS ||--o| AGENT_SESSIONS : "branches from"
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 数据表结构详述 (Table Schema Details)
|
||
|
||
### 2.1 papers 表 (文献元数据)
|
||
存储文献的核心元数据和本地物理存储路径。
|
||
- **特殊字段说明**:
|
||
- `pdf_path` / `html_path`:正常情况下存储相对路径(如 `library/PDF/2024arXiv.pdf`)。当下载失败时,会以 `error:` 前缀存储诊断信息(如 `error:Cloudflare 拦截`)。特殊值 `error:no_resource` 表示用户手动标记了"无有效全文资源"。
|
||
- `doctype`:文献类型标识,如 `article`、`eprint`、`proceedings`、`phdthesis`、`catalog`、`software`、`circular`、`book` 等。
|
||
- **索引**:
|
||
- `idx_papers_doi` -> 基于 `doi`
|
||
- `idx_papers_arxiv_id` -> 基于 `arxiv_id`
|
||
|
||
### 2.2 citations_references 表 (引文与参考文献拓扑)
|
||
多对多关联表,存储文献之间的引用网络(即拓扑星系图的基础数据)。
|
||
- **复合主键**:`(source_bibcode, target_bibcode)`
|
||
- **索引**:
|
||
- `idx_citations_ref_source` -> 优化以 `source_bibcode` 查询参考文献
|
||
- `idx_citations_ref_target` -> 优化以 `target_bibcode` 查询被引文献
|
||
|
||
### 2.3 notes 表 (高亮与阅读笔记)
|
||
存储学者在阅读器中对特定段落创建的高亮和笔记。
|
||
- **外键**:`bibcode` 级联删除 (`ON DELETE CASCADE`)。
|
||
- **索引**:
|
||
- `idx_notes_bibcode` -> 优化单篇文献的笔记列表查询。
|
||
|
||
### 2.4 sync_queries 表 (同步检索条件)
|
||
存储用户保存的批量同步检索条件,支持快速重新同步。
|
||
- **唯一约束**:`UNIQUE(query, source, limit_count)` 确保相同条件的检索不会重复保存。
|
||
|
||
### 2.5 paper_chunks_content 表 (RAG 文本切片)
|
||
存储文献 Markdown 段落的文本切片,用于向量嵌入和混合检索。
|
||
- `rowid`:自增主键,与 `vec_paper_chunks` 虚拟表的 rowid 保持 1:1 对齐。
|
||
- `headings`:章节路径,如 `"Introduction > Background"`,用于结构化检索。
|
||
- `section_index`:章节内的段落编号。
|
||
- **外键**:`bibcode` 级联删除 (`ON DELETE CASCADE`)。
|
||
- **关联 FTS5**:`paper_chunks_fts` 虚拟表通过触发器自动同步 `content` 字段。
|
||
|
||
### 2.6 paper_targets 表 (天体目标缓存)
|
||
存储从文献中提取并通过 CDS Sesame 解析的天体物理参数。
|
||
- `target_name`:标准化天体名称(如 `GD 358`)。
|
||
- `ra` / `dec`:赤道坐标(赤经/赤纬)。
|
||
- `parallax`:视差(mas,毫角秒)。
|
||
- `spectral_type`:光谱分类(如 `DA1`)。
|
||
- `otype` / `oname`:Sesame v4 返回的天体类型和 SIMBAD 官方主名称。
|
||
- `pm_ra` / `pm_de`:自行分量(mas/yr)。
|
||
- `radial_velocity`:视向速度。
|
||
- `photometry`:多波段测光数据(JSON 文本)。
|
||
- **唯一约束**:`UNIQUE(bibcode, target_name)`。
|
||
- **索引**:`idx_paper_targets_name`。
|
||
|
||
### 2.7 agent_sessions 表 (Agent 会话)
|
||
存储 ReAct 智能体对话会话的元数据。
|
||
- `deleted_at`:软删除时间戳(NULL 表示活跃会话)。
|
||
- `rewind_count`:会话回退(undo)次数,用于追踪重做历史。
|
||
- `parent_session_id`:分支来源会话 ID,支持会话分叉(branch)。
|
||
- `branch_metadata`:分支元数据(JSON 文本)。
|
||
- `mode`:会话模式标识符(`default`、`deep-research`、`literature-reader`),控制 Agent 的行为配置。
|
||
- **索引**:`idx_agent_sessions_updated`(WHERE deleted_at IS NULL,仅索引活跃会话)。
|
||
|
||
### 2.8 agent_messages 表 (Agent 消息)
|
||
存储 Agent 会话的完整对话历史,包括思考链和工具调用。
|
||
- `role`:`system` / `user` / `assistant` / `tool`。
|
||
- `thought`:LLM 思考链内容(CoT)。
|
||
- `tool_calls`:工具调用 JSON(函数名 + 参数)。
|
||
- `tool_call_id`:工具调用唯一标识(用于 SSE 流中 tool_call ↔ tool_result 配对)。
|
||
- `agent_name`:消息归属(`lead` / 子代理名 / teammate 名),支持身份隔离。
|
||
- `active`:软删除标记(`0` = 已回退隐藏,`1` = 正常可见),用于 session rewind。
|
||
- **索引**:`idx_agent_messages_session`、`idx_agent_messages_turn`、`idx_agent_messages_agent`、`idx_agent_messages_active`。
|
||
- **关联 FTS5**:`agent_messages_fts` 虚拟表通过触发器自动同步 `content`、`thought`、`tool_calls` 字段。
|
||
|
||
### 2.9 agent_tasks 表 (Agent 任务追踪)
|
||
持久化智能体的结构化待办任务,支持 DAG 依赖模式。
|
||
- `blocked_by`:JSON 数组,前置任务 ID 列表。
|
||
- `status` 生命周期:`pending` → `in_progress` → `completed`。
|
||
- `owner`:分配目标 agent 名称(多 Agent 团队协作路由)。
|
||
- **索引**:`idx_agent_tasks_session`、`idx_agent_tasks_status`、`idx_agent_tasks_session_task` (UNIQUE)。
|
||
|
||
### 2.10 agent_audit_log 表 (Agent 工具审计)
|
||
记录所有工具调用的审计信息:工具名称、执行状态、耗时 (ms)、输出预览。
|
||
- `status`:`OK` / `FAIL` / `SESSION_STOP`。
|
||
- `agent_name`:区分 lead/子代理/teammate 的调用来源。
|
||
- **用途**:`GET /api/chat/metrics` 聚合指标、会话审计回溯。
|
||
|
||
### 2.11 agent_team_members 表 (多 Agent 团队)
|
||
管理多智能体团队中每个成员的生命周期。
|
||
- `status`:`spawning` → `active` → `idle` → `shutdown`。
|
||
- `agent_role`:区分 team lead / teammate 等角色。
|
||
- **唯一约束**:`UNIQUE(session_id, agent_name)`。
|
||
|
||
### 2.12 全文搜索 (FTS5)
|
||
|
||
系统在以下实体表上建立了 FTS5 全文索引,通过触发器自动保持同步:
|
||
|
||
| FTS5 虚拟表 | 实体表 | 索引字段 | 用途 |
|
||
|:---|:---|:---|:---|
|
||
| `papers_fts` | `papers` | title, authors, keywords, abstract, pub | 本地文献 BM25 全文检索 |
|
||
| `paper_chunks_fts` | `paper_chunks_content` | content | 段落级稀疏检索,配合 `vec_paper_chunks` 向量做混合搜索 |
|
||
| `agent_sessions_fts` | `agent_sessions` | session_id, title, summary, metadata | 会话历史搜索 |
|
||
| `agent_messages_fts` | `agent_messages` | session_id, role, content, thought, tool_calls | 对话内容搜索 |
|
||
|
||
每个 FTS5 虚拟表通过 `content='<实体表>'` 声明为外部内容表,并配套 `_insert`、`_update`、`_delete` 三个触发器保持数据同步。
|
||
|
||
---
|
||
|
||
## 3. 数据库迁移说明
|
||
|
||
### 3.1 迁移机制
|
||
|
||
迁移脚本存放在 `migrations/` 目录下,使用 `sqlx` 的 **change-based 迁移** 模型:
|
||
|
||
- **编译期嵌入**:`sqlx::migrate!("./migrations")` 宏在编译时将 `.sql` 文件嵌入二进制
|
||
- **启动时自动执行**:`src/main.rs`、`src/bin/cli.rs` 以及所有测试的 in-memory 数据库在启动时调用 `.run(&pool)`,对比 `_sqlx_migrations` 追踪表,按版本号顺序执行未应用的迁移
|
||
- **版本号规则**:文件名格式 `YYYYMMDDHHMMSS_description.sql`,时间戳前缀保证全局唯一排序
|
||
- **幂等性**:所有 DDL 必须使用 `IF NOT EXISTS` / `IF EXISTS`,确保可重复执行
|
||
- **checksum 校验**:sqlx 对每个迁移文件计算 SHA-256 校验和并存入 `_sqlx_migrations`;若文件内容在已应用后被修改,启动时将拒绝执行(防止 schema 漂移)
|
||
|
||
### 3.2 当前迁移
|
||
|
||
| 迁移文件 | 说明 |
|
||
|:---|:---|
|
||
| `00000000000000_init.sql` | **完整初始化**:包含所有表、索引、FTS5 虚拟表及同步触发器,详见第 2 节各表结构。 |
|
||
|
||
> **注意**:`vec_paper_chunks` 向量虚拟表由 `src/main.rs` 在迁移之后动态创建,维度由 `EMBEDDING_DIM` 环境变量控制(默认 1536),不在迁移文件中管理。
|
||
|
||
### 3.3 新增迁移
|
||
|
||
开发新功能需要变更数据库 schema 时:
|
||
|
||
1. 在 `migrations/` 下新建文件,命名 `YYYYMMDDHHMMSS_<简短描述>.sql`(取当前时间)
|
||
2. 编写增量 SQL(`ALTER TABLE ADD COLUMN`、`CREATE TABLE IF NOT EXISTS` 等)
|
||
3. 运行 `cargo build && cargo test` 验证
|
||
4. 更新本文档的 3.2 节和对应的表结构说明
|
||
|
||
**SQLite 约束**:SQLite 不支持事务性 DDL 的全部语义,且 `ALTER TABLE` 能力有限(不支持 `DROP COLUMN`、`RENAME COLUMN` 等旧版本的常见操作)。新增字段时务必使用 `DEFAULT` 值以保证向后兼容。
|
||
|
||
### 3.4 迁移 Squash
|
||
|
||
开发期产生的增量迁移应在 **首次正式发布前** squash 为单一 `init.sql`,避免迁移文件线性膨胀。Squash 流程:
|
||
|
||
1. **导出完整 schema**:`sqlite3 library/astro_research.db .schema`
|
||
2. **移除以下内容**:
|
||
- `_sqlx_migrations` 表(sqlx 自动管理)
|
||
- `sqlite_sequence` 表(SQLite 内部表)
|
||
- `vec_paper_chunks` 及其关联内部表(由 main.rs 运行时创建)
|
||
- 所有 FTS5 内部表(`_data`、`_idx`、`_docsize`、`_config`,由 FTS5 自动管理)
|
||
3. **写入 `migrations/00000000000000_init.sql`**:确保所有 DDL 带 `IF NOT EXISTS` / `IF EXISTS`
|
||
4. **删除所有旧迁移文件**
|
||
5. **重建二进制**:`cargo build`(新迁移文件需嵌入)
|
||
6. **更新已有数据库**(保留数据):
|
||
```sql
|
||
DELETE FROM _sqlx_migrations;
|
||
-- 重启应用后 sqlx 自动应用新的 init 迁移(所有 IF NOT EXISTS 均为 no-op)
|
||
```
|
||
7. **验证**:`cargo test --lib && cargo run`
|
||
|
||
> 本项目在 2026-06-25 执行了一次 squash,将 18 个开发期增量迁移合并为 1 个 `00000000000000_init.sql`,同时保留了已有的 1341 条文献记录和 6477 条引用关系。
|
||
|
||
---
|
||
|
||
## 4. 错误诊断存储约定
|
||
|
||
系统使用 `papers` 表的 `pdf_path` 和 `html_path` 字段的双重语义来同时存储正常路径和错误诊断:
|
||
|
||
| 字段值模式 | 含义 | 前端展示 |
|
||
|:---|:---|:---|
|
||
| `NULL` | 尚未尝试下载 | 琥珀色"未下载"角标 |
|
||
| `library/PDF/xxx.pdf` | 下载成功,正常物理路径 | 蓝色"已下载"角标 |
|
||
| `error:具体原因` | 下载失败,原因为前缀后的文本 | 红色"下载失败"角标,悬浮显示原因 |
|
||
| `error:no_resource` | 用户手动标记为无有效全文资源 | 灰色"无资源"角标 |
|
||
|
||
`health_check` 工具在 `--fix` 模式下会清理损坏文件并将路径重置为 `NULL`,但**不会**清除 `error:` 前缀的记录(以保留诊断线索)。
|