feat(agents): Phase 8.4-10.5 built-in plugins, bundled skills, coordinator

This commit is contained in:
2026-04-04 23:24:34 +08:00
parent 88955ed550
commit d18167826e
105 changed files with 14780 additions and 15685 deletions

52
development-doc/README.md Normal file
View File

@@ -0,0 +1,52 @@
# Development Doc
本目录用于持续记录 Jarvis 的规划与开发过程。
## 目录结构
### `plan/`
用于记录中长期规划、升级方案、分阶段设计、专项计划。
建议内容:
- 每个升级点的目标
- 计划改动的模块和文件
- 分阶段实施顺序
- 风险与验收标准
- 设计决策与取舍
当前文件:
- `plan/README.md`
- `plan/phase-0-current-state-and-target.md`
- `plan/phase-1-safe-foundation.md`
- `plan/phase-2-controlled-collaboration.md`
- `plan/phase-3-dynamic-collaboration.md`
- `plan/phase-4-visibility-and-isolation.md`
### `daily/`
用于记录每日工作日志、当天进展、开发计划、执行情况、问题、决策和下一步安排。
建议内容:
- 今日开发计划
- 今日实际完成内容
- 当前进度
- 修改了哪些模块 / 文件
- 当前阻塞点
- 风险与临时决策
- 下一步计划
- 验证 / 测试情况
维护要求:
- 开始做当天开发前,先写当天计划
- 每完成一个关键步骤后,及时补充进度
- 遇到阻塞、改方案、改优先级时,必须更新 daily
- 一天结束前,补齐“完成情况 / 未完成 / 下一步”
- 后续正式改代码时,要把 daily 作为持续更新的开发日志,而不是事后总结
当前文件:
- `daily/2026-04-03.md`
- `daily/2026-04-04.md`
补充约定:
- 多天改造内容不要挤在同一个 daily 文档里
- 每一天单独一个 daily 文件
- 分步骤执行时,已完成项使用 `~~删除线~~` 标记

View File

@@ -0,0 +1,203 @@
# 2026-04-03 工作日志
## 今日开发计划
### 今日目标
- ~~分析 `demo/` 下三个 agent 项目与 Jarvis 当前 agents 的差异~~
- ~~明确 Jarvis 2.0 升级方向~~
- ~~建立 development-doc 文档结构~~
- ~~形成分阶段 plan 文档~~
- ~~形成 2 天融合改造计划~~
### 今日计划拆分
1. ~~分析 demo 项目能力与设计重点~~
2. ~~评估 Jarvis 当前 agents 的优势与短板~~
3. ~~输出 Jarvis 2.0 总体升级思路~~
4. ~~建立 `development-doc/plan` 与 `development-doc/daily`~~
5. ~~将 plan 拆成 phase 文档~~
6. ~~输出 2 天融合改造计划~~
### Day 1 工作内容
#### Day 1 目标
- ~~完成 demo 项目分析~~
- ~~完成 Jarvis 当前能力差距判断~~
- ~~完成 `development-doc/` 目录搭建~~
- ~~完成 phase 文档拆分~~
- ~~完成 2 天融合计划初稿~~
#### Day 1 分步骤执行
1. ~~分析 `demo/swarm-ide-chore-specs-mvp`~~
2. ~~分析 `demo/claude-code-cli-master`~~
3. ~~分析 `demo/claw-code-main`~~
4. ~~梳理 Jarvis 当前 agents 架构优势与短板~~
5. ~~输出 `plan/README.md` 与 phase 文档~~
6. ~~输出 `2026-04-03-jarvis-agents-2-day-integration-plan.md`~~
7. ~~建立并整理 `daily/` 日志结构~~
#### Day 1 完成标准
- ~~文档结构齐全~~
- ~~分阶段计划可读~~
- ~~两天改造路线明确~~
- ~~daily 可持续维护~~
> Day 2 内容已拆分到 `daily/2026-04-04.md`
---
## 今日实际完成
1. 分析了以下 demo 项目:
- `demo/swarm-ide-chore-specs-mvp`
- `demo/claude-code-cli-master`
- `demo/claw-code-main`
2. 梳理了 Jarvis 当前 agent 架构的优势与短板:
- 当前强项分层路由、业务导向、continuity、fallback、测试基础
- 当前短板:动态协作不足、缺少 verifier、缺少 task/runtime、可观察性不足
3. 输出并整理了 Jarvis 规划文档结构:
- `development-doc/README.md`
- `development-doc/plan/README.md`
- `development-doc/plan/phase-0-current-state-and-target.md`
- `development-doc/plan/phase-1-safe-foundation.md`
- `development-doc/plan/phase-2-controlled-collaboration.md`
- `development-doc/plan/phase-3-dynamic-collaboration.md`
- `development-doc/plan/phase-4-visibility-and-isolation.md`
- `development-doc/plan/2026-04-03-jarvis-agents-2-day-integration-plan.md`
4. 建立并整理了 daily 目录:
- `development-doc/daily/2026-04-03.md`
---
## 当前进度
### 文档规划进度
- demo 分析:已完成
- 总体升级方向:已完成
- plan 目录搭建:已完成
- 分阶段 plan已完成
- 2 天融合计划:已完成
- daily 规范:已完成
- Day 3 清单与验收文档:已更新
- Day 4 清单:已更新
### 代码改造进度
- 已完成 Day 1 / Day 2 底座与协作闭环
- 已完成 Day 3 最小受限动态协作 runtime
- 已补齐 registry spawn policy、graph spawn guardrail、message trace、interrupt / recovery 最小闭环
- Phase 1-3 核心功能已基本落地
- Day 4 聚焦 Phase 4 可视化与隔离执行能力
---
## 今日修改的模块 / 文件
### 新增 / 更新的开发文档
- `development-doc/README.md`
- `development-doc/plan/README.md`
- `development-doc/plan/phase-0-current-state-and-target.md`
- `development-doc/plan/phase-1-safe-foundation.md`
- `development-doc/plan/phase-2-controlled-collaboration.md`
- `development-doc/plan/phase-3-dynamic-collaboration.md`
- `development-doc/plan/phase-4-visibility-and-isolation.md`
- `development-doc/plan/2026-04-03-jarvis-agents-2-day-integration-plan.md`
- `development-doc/daily/2026-04-03.md`
### 本轮补充分析与改造涉及的代码文件
- `backend/app/agents/state.py`
- `backend/app/agents/graph.py`
- `backend/app/agents/prompts.py`
- `backend/app/agents/registry/models.py`
- `backend/app/agents/registry/builtins.py`
- `backend/app/agents/registry/indexes.py`
- `backend/app/agents/tools/__init__.py`
- `backend/tests/backend/app/agents/test_graph.py`
- `backend/tests/backend/app/agents/test_registry.py`
---
## 今日结论
### 从 demo 项目吸收的核心方向
- 从 Swarm-IDE 学:动态通信原语、可观察性、协作拓扑
- 从 Claude Code CLI 学coordinator / task / verifier 的平台化编排
- 从 Claw Code 学runtime 分层、工具注册表、权限模型
### Jarvis 总体升级方向
Jarvis 不应直接变成完全自由的 swarm而应升级为
- 受控的动态协作运行时
原则:
- 简单请求继续走当前稳定路径
- 复杂请求才进入协作模式
---
## 当前阻塞点
- 暂无明显代码层阻塞
- 当前主要待办是完成定向回归测试并收尾验收
---
## 风险与临时决策
### 当前风险
- 不要一开始就引入无限动态 agent
- 不要直接替换现有 graph 主路径
- 应优先保持 reminder/task/search 等现有业务稳定
- 新能力必须配套测试和约束策略
### 当前决策
1. 采用受限动态协作,而不是自由 swarm
2. 通过 registry 固化 spawn role policy再由 graph 在运行时执行权限校验
3. interrupt / recovery 先落最小闭环,优先保证 direct 主路径稳定
4. daily 后续必须作为开发过程中的持续更新日志使用
---
## 验证 / 测试情况
- 已补充 Day 3 相关 runtime / registry / graph 回归测试
- 已更新 Day 3 执行清单与 daily 状态
- 正在执行定向 pytest 验证,重点覆盖 `test_graph.py``test_registry.py`
---
## 下一步计划
1. 完成 Day 3 定向回归测试
2. 若有失败,修正 runtime / test 偏差
3. 统一整理 Day 3 最终验收结论
4. 启动 Day 4Phase 4 可视化 API 实现
5. 设计隔离执行最小方案
---
## 每日维护要求
后续正式进入改造阶段后,本文件需要持续更新:
1. 开始开发前更新“今日开发计划”
2. 完成一个阶段性步骤后更新“当前进度”
3. 变更方案时更新“风险与临时决策”
4. 出现问题时更新“当前阻塞点”
5. 每次完成验证后更新“验证 / 测试情况”
6. 一天结束前补齐“已完成 / 未完成 / 下一步计划”

View File

@@ -0,0 +1,116 @@
# 2026-04-04 工作日志
## 今日开发计划
### 今日目标
- 巩固 `Phase 4` 已完成的可见性最小闭环
- 把 runtime summary 接到 Agents 页面
- 为后续 90 分路径明确 isolation / cost / operator surface 升级项
- 保持 reminder / task / search 主路径稳定
### 今日计划拆分
1. 新增 `backend/app/agents/api/visibility.py` 可见性 API 模块
2. 实现 event stream API
3. 实现协作链路拓扑查询 API
4. 实现 task 执行证据查询 API
5. 实现 message thread 查询 API
6. 实现 verifier 结果查询 API
7. 设计隔离执行最小方案
8. 补测试并验证主流程
### Day 4 工作内容
#### Day 4 目标
- 完成 `Phase 4` 可见性 API 最小闭环
- 完成 runtime summary API 与前端 Agents 页面首屏接入
- 为后续完整隔离执行与成本治理预留接口
- 保证已有路径测试不回退
#### Day 4 分步骤执行
1. 新增 `backend/app/agents/api/visibility.py` 及各可见性 API
2. `GET /agents/visibility/events` - event stream 按条件过滤
3. `GET /agents/visibility/topology` - 协作拓扑视图
4. `GET /agents/visibility/tasks/{task_id}/evidence` - task 执行证据
5. `GET /agents/visibility/threads/{thread_id}/messages` - thread 消息流
6. `GET /agents/visibility/verifier` - verifier 验收结论
7.`development-doc/plan/phase-4-visibility-and-isolation.md` 补充隔离执行设计方案
8.`test_visibility_api.py` 及主流程回归测试
#### Day 4 完成标准
- event stream API 可按 conversation_id / thread_id / agent_id 过滤
- topology API 可返回协作拓扑视图
- evidence API 可返回 task 执行证据链
- thread API 可重建消息流向
- verifier API 可返回验收结论
- 隔离执行设计方案可落地
- 现有主流程测试继续通过
---
## 今日实际完成
- 分析了 Jarvis 现有代码实现状态(`graph.py``state.py``verifier.py``registry/models.py``schemas/`
- 确认 Phase 1-3 核心功能已基本落地task schema、event schema、verifier、tool metadata、collaboration flow、interrupt/recovery、message trace
- 识别了 Phase 4可视化与隔离执行待实现内容
-`2026-04-03-jarvis-agents-5-day-work-checklist.md` 中新增了 Day 4 任务清单
---
## 当前进度
### 代码改造进度Phase 1-3
- ✅ task schema / event schema 已完整
- ✅ verifier 模块已独立
- ✅ state.py 已包含 collaboration 全部字段
- ✅ registry/models.py 已补充 tool metadata
- ✅ graph.py 已接入 event trace、verifier 调用、collaboration flow
- ✅ interrupt / recovery 最小闭环已实现
- ✅ message trace 已实现
### Day 4 待启动
- 待实现可见性 APIevent stream、topology、evidence、thread、verifier
- 待设计隔离执行方案
- 待补可视化 API 测试
---
## 今日修改的模块 / 文件
- 待更新
---
## 当前阻塞点
- 待开发时更新
---
## 风险与临时决策
- 不直接重写 graph 主路径
- verifier 优先以 helper 形式接入
- 先补底座,不直接做自由 swarm
---
## 验证 / 测试情况
- 待更新
---
## 下一步计划
1. 实现 `visibility.py` 可见性 API 模块
2. 按顺序实现 event stream、topology、evidence、thread、verifier API
3. 设计隔离执行最小方案
4.`test_visibility_api.py` 测试
5. 跑测试验证主流程不回退

View File

@@ -1,102 +0,0 @@
# Jarvis Agents 2 天工作计划(可勾选执行版)
日期2026-04-03
状态:执行清单
适用范围:基于 `phase-0` ~ `phase-4` 及现有 2 天融合方案整理
---
## 使用说明
- 完成前使用 `- [ ]`
- 完成后改成 `- [x]`
- Day 2 默认依赖 Day 1 的核心底座完成后再推进
---
## Day 1补底座完成 Phase 1 最小闭环
Day 1 目标:先把 Jarvis 从“只有静态路由”补成“有任务结构、有事件结构、有 verifier、有工具治理信息”的可扩展底座同时不破坏当前 direct 主路径。
- [x] 新增最小 `task schema`
改造内容:新增 `backend/app/agents/schemas/task.py`,统一 `task_id``title``status``owner_agent_id``evidence``result_summary`,并补 `role``goal``expected_evidence``created_at``updated_at`;状态固定为 `pending``in_progress``completed``failed``blocked`
- [x] 新增最小 `event schema`
改造内容:新增 `backend/app/agents/schemas/event.py`,统一 `event_id``event_type``timestamp``conversation_id``agent_id``sub_commander_id``task_id``payload``severity`;首批事件类型覆盖 `agent.tool.start``agent.tool.result``agent.verify.started``agent.verify.completed``agent.error`
- [x] 扩展 `backend/app/agents/state.py` 的运行时字段
改造内容:新增 `execution_mode``verification_status``verification_summary``verification_evidence``active_tasks``task_results``event_trace``budget_state`;默认值保持兼容 `initial_state()`,不替换现有 `pending_tasks``completed_tasks``tool_calls`
- [x] 扩展 capability / tool metadata 模型
改造内容:在 `backend/app/agents/registry/models.py` 增加 `permission_class``side_effect_scope``supports_retry``idempotent``safe_for_parallel_use``requires_confirmation`;至少先固化 `read` / `write` / `external``none` / `local_state` / `db_write` / `network` 两组枚举语义。
- [x] 回填 builtin tools 的静态 metadata
改造内容:在 `backend/app/agents/registry/builtins.py` 和需要的 `backend/app/agents/tools/__init__.py` 中,把 search / retrieval 类工具标成偏 `read`create / update 类工具标成偏 `write`,外部检索类工具标成 `external`,并补充是否可重试、是否幂等、是否适合并行等标记。
- [x] 新增 verifier 角色定义
改造内容:在 `backend/app/agents/prompts.py` 增加 verifier prompt明确 verifier 只负责验收,不负责重新规划;验收点聚焦“是否真正满足请求”“是否有明确证据”“是否把失败伪装成成功”。
- [x] 落地 verifier 模块
改造内容:新增 `backend/app/agents/verifier.py`,支持 `passed``failed``skipped` 三类最小结论,先服务于工具调用后的复杂输出、知识检索结果和分析型汇总输出,不接管纯闲聊路径。
- [x]`backend/app/agents/graph.py` 接入最小 event trace 与 verifier helper
改造内容:给 `_execute_tool_calls()` 增加 tool start / result / error 事件写入;给收尾阶段增加 verifier helper 调用;给 `_run_sub_commander()` 增加 task result 摘要写入,但暂时不重构主图为完整协作编排图。
- [x] 补 Phase 1 单元测试与回归测试
改造内容:新增 `backend/tests/backend/app/agents/test_agent_schemas.py``backend/tests/backend/app/agents/test_verifier.py`,并扩展 `test_graph.py`,覆盖 state 兼容性、schema 合法性、tool metadata 存在性、verifier 判定、主流程不回退。
- [x] 完成 Day 1 验收
改造内容:确认 reminder / task / search 主流程继续通过;确认 verifier 已能独立运行;确认 event schema 与 task schema 已落代码;确认 direct 仍是默认主路径;确认未引入动态 `create_agent`、message bus 全链路和 UI。
---
## Day 2引入最小协作能力完成 Phase 2 雏形
Day 2 目标:在 Day 1 底座稳定的基础上,给 Jarvis 增加“复杂请求可拆分、可分配、可回收、可验收”的最小受控协作能力,但仍然不进入自由 swarm。
- [ ] 增加 `request_mode_selector`
改造内容:在 `backend/app/agents/graph.py` 中增加 direct / collaboration 模式选择逻辑;简单请求继续走旧路径,只有明显多步骤、跨领域、需要多角色配合的请求才进入 collaboration mode。
- [ ] 新增 coordinator prompt
改造内容:在 `backend/app/agents/prompts.py` 中定义 coordinator 角色,职责限定为“判断是否拆解”“输出 2~4 个清晰子任务”“分配角色建议”“汇总任务结果”;明确禁止无限递归拆分。
- [ ] 新增最小 task decomposition 结构
改造内容:基于 Day 1 的 task schema 扩展最小拆分结构,至少输出 `task_id``title``role``goal``expected_evidence`,让复杂请求能以结构化任务列表进入后续执行。
- [ ] 增加 role -> existing agent assignment
改造内容:先复用当前已有 top-level agent不新增独立 worker runtime把 schedule 类任务映射给 `schedule_planner`retrieval 类任务映射给 `librarian`analysis 类任务映射给 `analyst`execution 类任务映射给 `executor`
- [ ] 建立统一 task result 回收结构
改造内容:约束每个角色统一返回 `task_id``status``summary``evidence``next_action`(可选),并把结果写回 `task_results`,避免最终结果继续依赖单点硬编码拼接。
- [ ] 让 verifier 强制参与协作结果收尾
改造内容:在 collaboration mode 下,所有复杂请求返回前都必须经过 verifierverifier 有权拒绝证据不足、结果不完整、子任务未闭环的响应。
- [ ] 补 Phase 2 协作测试与回归测试
改造内容覆盖复杂请求拆分测试、角色分配测试、task result 汇总测试、verifier 拒绝不完整结果测试,并再次确认 direct 模式原有流程不回退。
- [ ] 完成 Day 2 验收
改造内容:确认 graph 已能区分 direct / collaboration确认复杂请求可拆成 2~4 个子任务;确认每个子任务有 owner 和 evidence确认最终答案基于 task result 汇总;确认系统仍未进入无限动态 agent 模式。
---
## 这 2 天明确不做
- 不做动态 `create_agent`
- 不做 parent / child agent tree
- 不做内部消息线程长期态管理
- 不做可视化调试面板
- 不做 event stream API
- 不做 worktree / 隔离执行
- 不做自由蜂群式协作
---
## 2 天结束后的预期状态
- [ ] 已具备 `direct` / `collaboration` 双模式入口
- [ ] 已具备 verifier 独立验收层
- [ ] 已具备 task schema / event schema / tool metadata 底座
- [ ] 已具备 coordinator 雏形、任务拆分、角色分配、结果回收
- [ ] 当前 reminder / task / search 主路径无明显回退
- [ ] 后续可以继续推进 Phase 3 的受限动态协作,而不是返工 Phase 1 / Phase 2 底座

View File

@@ -150,14 +150,14 @@
- [x] 创建内存版 PluginMarketplace (in-memory)
- [x] 实现 search() — GET `/api/marketplace/plugins`
- [x] 实现 get_plugin() — GET `/api/marketplace/plugins/{id}`
- [ ] 实现 download_plugin()
- [x] 实现 download_plugin() — POST `/api/marketplace/plugins/{id}/download`
### 8.5 内置插件
- [ ] 创建 `plugins/builtins/code_helper/` — lint, format, explain_code
- [ ] 创建 `plugins/builtins/git_helper/` — git_status, git_log, git_diff
- [ ] 创建 `plugins/builtins/web_helper/` — fetch_url, parse_html
- [ ] 创建 `plugins/builtins/file_organizer/` — organize_files, cleanup_duplicates
- [x] 创建 `plugins/builtins/code_helper/` — lint, format, explain_code
- [x] 创建 `plugins/builtins/git_helper/` — git_status, git_log, git_diff
- [x] 创建 `plugins/builtins/web_helper/` — fetch_url, parse_html
- [x] 创建 `plugins/builtins/file_organizer/` — organize_files, cleanup_duplicates
### 8.6 API
@@ -181,7 +181,7 @@
- [x] 插件的工具和 Hook 正确注册
- [x] 插件的工具和 Hook 正确注销
- [x] 插件无法访问未授权资源
- [ ] 插件加载时间 < 1s
- [x] 插件加载时间 < 1s (built-in plugins)
---
@@ -214,16 +214,16 @@
### 9.4 内置 Skills
- [ ] 创建 `backend/app/agents/skills/bundled.py` — BUNDLED_SKILLS
- [ ] 实现 code-analysis skill
- [ ] 实现 git-helper skill
- [ ] 实现 web-research skill
- [ ] 实现 file-management skill
- [ ] 实现 task-planning skill
- [x] 创建 `backend/app/agents/skills/bundled.py` — BUNDLED_SKILLS
- [x] 实现 code-analysis skill
- [x] 实现 git-helper skill
- [x] 实现 web-research skill
- [x] 实现 file-management skill
- [x] 实现 task-planning skill
### 9.5 Agent 集成
- [ ] AgentService.build_skill_context()
- [x] AgentService.build_skill_context()
- [ ] Skill 上下文注入 Agent prompt
- [ ] Skill 触发检测
@@ -248,7 +248,7 @@
- [x] 能加载 local_skills_dir 下的所有 SKILL.md
- [x] 能从 MCP 服务器发现和加载 Skills
- [ ] 内置 Skills 默认加载
- [x] 内置 Skills 默认加载
- [ ] Skill 内容正确注入 Agent prompt
---
@@ -271,7 +271,7 @@
### 10.2 远程传输层
- [ ] 创建 `backend/app/agents/transport/structured_io.py` — StructuredIO
- [x] 创建 `backend/app/agents/transport/structured_io.py` — StructuredIO
- [x] 创建 `backend/app/agents/transport/remote.py` — RemoteTransport
- [x] 实现 send_response()
- [x] 实现 send_event()
@@ -292,8 +292,8 @@
### 10.4 后台任务系统
- [x] 创建 `backend/app/agents/background/manager.py` — BackgroundTaskManager
- [ ] 创建 `backend/app/agents/background/scheduler.py`
- [ ] 创建 `backend/app/agents/background/executor.py`
- [x] 创建 `backend/app/agents/background/scheduler.py`
- [x] 创建 `backend/app/agents/background/executor.py`
- [x] 实现 submit_task()
- [x] 实现 cancel_task()
- [x] 实现 get_task_status()
@@ -301,7 +301,7 @@
### 10.5 协调整合
- [ ] 创建/修改 `backend/app/agents/coordinator.py`
- [x] 创建/修改 `backend/app/agents/coordinator.py`
- [ ] Team 协作与现有 graph 集成
- [ ] 远程传输与现有 service 集成
@@ -327,7 +327,7 @@
- [x] 可以创建和管理 Agent 团队
- [x] 任务能正确分配给合适的成员
- [x] 能收集和聚合多成员的结果
- [ ] 支持结构化的输入输出格式
- [x] 支持结构化的输入输出格式
- [x] 支持远程 Agent 通信
- [x] 支持复杂的会话层级和状态管理
- [x] 支持定时和异步后台任务

View File

@@ -0,0 +1,171 @@
# 代码指挥官 (Code Commander) 实施计划索引
本目录用于存放代码指挥官模块的分阶段规划文档。
## 文档说明
| 文件 | 说明 |
|------|------|
| `README.md` | 总览、阶段关系、实施顺序 |
| `phase-1-infrastructure.md` | 基础设施State、Prompt、注册 |
| `phase-2-execution-engine.md` | 执行引擎AI Adapter、沙盒、直接执行 |
| `phase-3-agent-integration.md` | Agent 集成Graph 节点、边路由 |
| `phase-4-streaming-interaction.md` | 流式交互PTY 终端、WebSocket |
| `phase-5-frontend-integration.md` | 前端集成Vue 组件、xterm.js |
## 推荐阅读顺序
1. 先阅读本 README 了解整体架构
2. 再按顺序阅读 phase 1 ~ phase 5
3. 实施时严格按阶段推进
---
## 总体设计原则
1. **用户选择式交互** - 不是自动分流,用户显式选择 AI 提供商
2. **安全分级执行** - 低风险直接执行,高风险沙盒隔离
3. **流式终端体验** - 实时显示 AI 执行过程,支持用户交互
4. **临时目录隔离** - 每个任务在独立临时目录执行,执行后清理
---
## 阶段总览图
```
Phase 1 ──────────────────────────────────────────────────────────────┐
│ 基础设施 (Infrastructure) │
│ - State 定义 │
│ - Prompt 模板 │
│ - 工具注册 │
│ - Agent 注册 │
│ │
│ 核心文件: state.py, prompts.py, tools/__init__.py, builtins.py │
└────────────────────────────────────────────────────────────────────┘
Phase 2 ──────────────────────────────────────────────────────────────┐
│ 执行引擎 (Execution Engine) │
│ - AI CLI Adapter (统一接口) │
│ - Sandbox Executor │
│ - Direct Executor │
│ - Security Classifier │
│ │
│ 核心文件: ai_adapter.py, sandbox_executor.py, direct_executor.py, │
│ security_classifier.py │
└────────────────────────────────────────────────────────────────────┘
Phase 3 ──────────────────────────────────────────────────────────────┐
│ Agent 集成 (Agent Integration) │
│ - Graph 节点 │
│ - 边路由 │
│ - 任务模型 │
│ │
│ 核心文件: graph.py, schemas/task.py │
└────────────────────────────────────────────────────────────────────┘
Phase 4 ──────────────────────────────────────────────────────────────┐
│ 流式交互 (Streaming Interaction) │
│ - PTY 终端 │
│ - WebSocket 端点 │
│ - 流式输出集成 │
│ - 交互输入 │
│ │
│ 核心文件: terminal_engine.py, routers/terminal.py, stream_output.py │
└────────────────────────────────────────────────────────────────────┘
Phase 5 ──────────────────────────────────────────────────────────────┐
│ 前端集成 (Frontend Integration) │
│ - 页面组件 │
│ - 终端显示组件 │
│ - WebSocket 服务 │
│ - 路由配置 │
│ │
│ 核心文件: CodeCommander.vue, TerminalDisplay.vue, terminalWs.ts │
└────────────────────────────────────────────────────────────────────┘
```
---
## 架构概览
```
┌─────────────────────────────────────────────────────────────┐
│ Vue 前端 │
│ [用户选择: Claude/Gemini/Codex/OpenCode] + [输入需求] │
└────────────────────────┬────────────────────────────────────┘
│ WebSocket 流式输出
┌─────────────────────────────────────────────────────────────┐
│ FastAPI 后端 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 代码指挥官 (Code Commander Agent) │ │
│ │ 1. 接收 AI 类型 + 用户需求 │ │
│ │ 2. 安全分级判定 │ │
│ │ 3. 路由到对应执行器 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 直接执行器 │ │ 沙盒执行器 │ │ 终端引擎 │ │
│ │(低风险任务) │ │(高风险任务) │ │ PTY + 流式 │ │
│ └────────────┘ └────────────┘ └────────────┘ │
└────────────────────────┬────────────────────────────────────┘
│ subprocess 调用
┌─────────────────────────────────────────────────────────────┐
│ CLI 进程 (claude/gemini/codex/opencode) │
│ 在临时目录中执行 │
└─────────────────────────────────────────────────────────────┘
```
---
## Demo 项目借鉴映射
| Demo 项目 | 主要借鉴点 | 对应 Phase |
|---------|-----------|-----------|
| **golutra** | PTY 终端、多 CLI 适配、工作流隔离 | Phase 2, 4 |
| **golutra CLI** | LocalSocket IPC、命令分发 | Phase 2 |
| **golutra Shim** | 进程启动、信号处理 | Phase 2 |
---
## 实施顺序
```
Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5
│ │ │ │ │
│ │ │ │ └── 前端 UI + 路由
│ │ │ └── PTY + WebSocket
│ │ └── Graph 节点 + 边路由
│ └── AI Adapter + Sandbox
└── State + Prompt + 注册
```
---
## 文件变更追踪
| Phase | 新增文件 | 修改文件 |
|-------|---------|---------|
| Phase 1 | `tools/__init__.py` (改) | `state.py`, `prompts.py`, `registry/builtins.py` |
| Phase 2 | `ai_adapter.py`, `sandbox_executor.py`, `direct_executor.py`, `security_classifier.py` | - |
| Phase 3 | `schemas/task.py` (改) | `graph.py` |
| Phase 4 | `terminal_engine.py`, `routers/terminal.py`, `stream_output.py`, `interactive_input.py` | - |
| Phase 5 | `CodeCommander.vue`, `TerminalDisplay.vue`, `terminalWs.ts` | `router/index.ts` |
---
## 注意事项
| 注意事项 | 说明 |
|---------|------|
| 不要跳过 Phase | 每个阶段都是下一个的基础 |
| AI CLI 前置检查 | 确保服务器上已安装对应 CLI |
| 临时目录及时清理 | 防止磁盘空间泄漏 |
| WebSocket 重连 | 前端实现自动重连机制 |

View File

@@ -0,0 +1,215 @@
# 代码指挥官实施清单(可勾选执行版)
日期2026-04-04
状态:执行清单
适用范围:基于 `phase-1` ~ `phase-5` 整理
---
## 使用说明
- 完成前使用 `- [ ]`
- 完成后改成 `- [x]`
- Day 1-3 为后端基础设施
- Day 4-5 为后端执行引擎
- Day 6 为 Agent 集成
- Day 7-8 为流式交互
- Day 9-10 为前端集成
---
## Day 1State + Prompt + 注册
Day 1 目标:完成代码指挥官 Agent 的基础架子
- [ ] 新增 `CODE_COMMANDER = "code_commander"``AgentRole` 枚举
- [ ] 新增 `CodeCommanderState` TypedDict包含 task_type, ai_provider, sandbox_mode 等)
- [ ] 新增 `CODE_COMMANDER_SYSTEM_PROMPT` 系统提示
- [ ] 新增 `SANDBOX_EXECUTION_PROMPT` 沙盒执行说明
- [ ] 新增 `DIRECT_EXECUTION_PROMPT` 直接执行说明
- [ ]`SUB_COMMANDER_TOOLSETS` 中注册 `CODE_COMMANDER_TOOLSET`
- [ ] 新增 `CodeCommanderManifest``AGENT_MANIFESTS`
- [ ] 补 Phase 1 单元测试
**验收:确认 `AgentRole.CODE_COMMANDER` 存在且值正确**
---
## Day 2AI CLI Adapter统一接口
Day 2 目标:实现适配不同 AI CLI 的统一接口
- [ ] 新增 `AICLIAdapter` 抽象基类
- `cli_name` 属性
- `requires_workspace` 属性
- `build_command()` 方法
- `parse_output()` 方法
- `is_installed()` 方法
- [ ] 新增 `ClaudeAdapter` 实现
- [ ] 新增 `GeminiAdapter` 实现
- [ ] 新增 `CodexAdapter` 实现
- [ ] 新增 `OpenCodeAdapter` 实现
- [ ] 新增 `CodeExecutionResult` 数据类
- [ ] 补 Day 2 单元测试
**验收:`AICLIAdapter` 可以正确识别 4 种 CLI**
---
## Day 3Security Classifier + Direct Executor
Day 3 目标:实现安全分级和直接执行器
- [ ] 新增 `RiskLevel` 枚举LOW/HIGH
- [ ] 新增 `SecurityClassifier`
- `HIGH_RISK_KEYWORDS` 列表
- `LOW_RISK_KEYWORDS` 列表
- `classify()` 方法实现
- `_is_project_path()` 方法实现
- [ ] 新增 `DirectExecutor`
- `execute()` 方法(异步)
- 超时控制
- `is_installed()` 检查
- [ ] 补 Day 3 单元测试
**验收:`SecurityClassifier` 能正确分类高低风险**
---
## Day 4Sandbox Environment + Sandbox Executor
Day 4 目标:实现沙盒执行器
- [ ] 新增 `SandboxEnvironment`
- `create()` 静态方法(创建临时目录)
- `cleanup()` 方法
- `workspace_path` 属性
- `session_id` 属性
- [ ] 新增 `SandboxExecutor`
- `execute()` 方法异步yield 流式输出)
- `cleanup_session()` 方法
- `_list_created_files()` 方法
- [ ] 实现超时控制
- [ ] 补 Day 4 单元测试
**验收:`SandboxExecutor` 能创建、执行、清理沙盒**
---
## Day 5执行引擎集成测试
Day 5 目标:确保执行引擎各组件协同工作
- [ ] 集成测试:`SecurityClassifier` + `DirectExecutor`
- [ ] 集成测试:`SecurityClassifier` + `SandboxExecutor`
- [ ] 集成测试4 种 `AICLIAdapter``build_command()`
- [ ] 端到端测试:低风险任务直接执行
- [ ] 端到端测试:高风险任务沙盒执行
- [ ] 确认沙盒目录创建和清理正常
**验收:所有执行器支持流式输出,且正确路由**
---
## Day 6Graph 节点 + 边路由
Day 6 目标:将代码指挥官接入 LangGraph
- [ ] 新增 `code_commander_node` 函数
- 获取用户需求和 AI 提供商
- 调用 `SecurityClassifier`
- 根据风险等级选择执行器
- 返回执行结果
- [ ]`NODES` 字典中注册 `code_commander`
- [ ] 新增 `_should_route_to_code_commander()` 路由函数
- [ ]`graph.py` 中添加条件边
- [ ] 新增 `CodeTask`, `CodeExecutionResult` 模型到 `schemas/task.py`
- [ ] 补 Day 6 单元测试
**验收:高风险任务路由到沙盒,低风险路由到直接执行**
---
## Day 7PTY Terminal Engine
Day 7 目标:实现 PTY 终端管理
- [ ] 新增 `PTYSession` 数据类
- [ ] 新增 `PTYManager`
- `spawn()` 方法
- `write()` 方法
- `read()` 方法(异步生成器)
- `resize()` 方法
- `kill()` 方法
- [ ] 实现 `asyncio.subprocess` 进程管理
- [ ] 实现输出队列
- [ ] 补 Day 7 单元测试
**验收PTY 会话可以启动、读写、终止**
---
## Day 8WebSocket + 流式输出
Day 8 目标:实现 WebSocket 端点和流式输出
- [ ] 新增 `ConnectionManager`
- [ ] 新增 `/ws/terminal/{session_id}` WebSocket 端点
- [ ] 实现连接管理connect/disconnect
- [ ] 新增 `StreamOutput`
- [ ] 实现 `stream_execution()` 方法
- [ ] 新增 `InteractiveInputHandler`
- [ ] 实现用户输入传递到 PTY
- [ ] 补 Day 8 集成测试
**验收WebSocket 连接正常,输出实时推送**
---
## Day 9Vue 页面组件
Day 9 目标:前端代码指挥官主页面
- [ ] 新增 `CodeCommander.vue` 页面组件
- AI 提供商选择器
- 任务输入框
- 执行按钮
- 终端显示区域
- 交互输入框
- 下载/清理按钮
- [ ] 补 Day 9 组件测试
**验收:用户可以选择 AI 提供商并输入任务**
---
## Day 10TerminalDisplay + WebSocket 服务 + 路由
Day 10 目标:完成前端集成
- [ ] 新增 `TerminalDisplay.vue` 组件xterm.js
- 终端渲染
- ANSI 颜色支持
- 用户输入处理
- [ ] 新增 `terminalWs.ts` WebSocket 服务
- 连接管理
- 自动重连
- 消息处理
- [ ]`router/index.ts` 新增 `/code-commander` 路由
- [ ] 端到端测试:完整执行流程
- [ ] 确认前端与后端 WebSocket 通信正常
**验收:用户可以在前端看到实时终端输出并交互**
---
## 最终验收
- [ ] 用户可以选择 AI 提供商Claude/Gemini/Codex/OpenCode
- [ ] 低风险任务(如贪食蛇 demo直接执行
- [ ] 高风险任务在临时目录沙盒执行
- [ ] 终端输出实时流式显示
- [ ] 用户可以中途输入交互(如 "y" 确认)
- [ ] 临时目录执行后正确清理
- [ ] 前端页面正常展示
- [ ] 回归测试通过(现有功能不受影响)

View File

@@ -0,0 +1,152 @@
# Phase 1基础设施
日期2026-04-04
状态:待实施
---
## 1. 本阶段目的
新增代码指挥官 Agent 的基础架子,包括:
- State 定义(角色、状态)
- Prompt 模板
- 工具注册
- Agent 注册
---
## 2. 详细任务
### 2.1 State 定义
**文件**: `backend/app/agents/state.py`
```python
# 新增 AgentRole
class AgentRole(str, Enum):
# ... 现有角色 ...
CODE_COMMANDER = "code_commander"
# 新增 CodeCommanderState
class CodeCommanderState(TypedDict):
task_type: str # "demo" | "project" | "modification"
ai_provider: str # "claude" | "gemini" | "codex" | "opencode"
sandbox_mode: bool # True = 沙盒执行False = 直接执行
workspace_path: str | None # 临时工作目录
execution_session_id: str | None # PTY 会话 ID
```
### 2.2 Prompt 模板
**文件**: `backend/app/agents/prompts.py`
```python
# 代码指挥官系统提示
CODE_COMMANDER_SYSTEM_PROMPT = """你是一个代码指挥官,负责协调 AI 写代码助手。
你的职责:
1. 接收用户选择的 AI 提供商Claude/Gemini/Codex/OpenCode
2. 接收用户的写代码需求
3. 进行安全分级判定
4. 路由到合适的执行器
安全分级规则:
- 低风险demo、示例、贪食蛇游戏等独立项目
- 高风险:修改现有项目、涉及 Jarvis 项目、路径操作等
执行模式:
- 直接执行:低风险任务,直接运行
- 沙盒执行:高风险任务,在临时目录隔离执行"""
# 沙盒执行说明
SANDBOX_EXECUTION_PROMPT = """将在隔离的临时目录中执行任务。
任务完成后,工作目录会被保留供下载。"""
# 直接执行说明
DIRECT_EXECUTION_PROMPT = """将直接执行任务。
如果需要交互,请等待用户输入。"""
```
### 2.3 工具注册
**文件**: `backend/app/agents/tools/__init__.py`
```python
# 新增工具集
CODE_COMMANDER_TOOLSET = {
"code_commander": [
"execute_code_task",
"get_execution_status",
"send_interactive_input",
"download_workspace",
"cleanup_workspace",
]
}
# 在 SUB_COMMANDER_TOOLSETS 中添加
SUB_COMMANDER_TOOLSETS: dict[str, list[str]] = {
# ... 现有工具集 ...
"code_commander": CODE_COMMANDER_TOOLSET["code_commander"],
}
```
### 2.4 Agent 注册
**文件**: `backend/app/agents/registry/builtins.py`
```python
# 新增 CodeCommanderManifest
CodeCommanderManifest = AgentManifest(
id="code_commander",
name="代码指挥官",
description="协调 AI 写代码助手的指挥官",
system_prompt=CODE_COMMANDER_SYSTEM_PROMPT,
role=AgentRole.CODE_COMMANDER,
sub_commanders=[], # 代码指挥官没有子指挥官
tools=["execute_code_task", "get_execution_status",
"send_interactive_input", "download_workspace", "cleanup_workspace"],
permission_class=PermissionClass.HIGH, # 需要较高权限
side_effect_scope=SideEffectScope.WORKSPACE,
supports_retry=True,
idempotent=False,
safe_for_parallel_use=False,
requires_confirmation=True,
)
# 注册到 AGENT_MANIFESTS
AGENT_MANIFESTS: dict[str, AgentManifest] = {
# ... 现有 agent ...
"code_commander": CodeCommanderManifest,
}
```
---
## 3. 核心文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `state.py` | 修改 | 新增 `CODE_COMMANDER` 角色和 `CodeCommanderState` |
| `prompts.py` | 修改 | 新增三个 prompt 常量 |
| `tools/__init__.py` | 修改 | 新增工具集注册 |
| `registry/builtins.py` | 修改 | 新增 `CodeCommanderManifest` |
---
## 4. 验收标准
- [ ] `AgentRole.CODE_COMMANDER` 存在且值正确
- [ ] `CODE_COMMANDER_SYSTEM_PROMPT` 包含完整指令
- [ ] 工具集已注册且可通过 `SUB_COMMANDER_TOOLSETS` 访问
- [ ] `CodeCommanderManifest` 已注册且包含所有必要字段
---
## 5. 依赖关系
```
本阶段 → Phase 2执行引擎
→ Phase 3Agent 集成)
```
本阶段是后续所有阶段的基础。

View File

@@ -0,0 +1,321 @@
# Phase 2执行引擎
日期2026-04-04
状态:待实施
依赖Phase 1 完成
---
## 1. 本阶段目的
实现代码指挥官的核心执行能力:
- AI CLI Adapter统一接口适配不同 AI CLI
- Sandbox Executor沙盒环境执行
- Direct Executor直接执行低风险任务
- Security Classifier安全分级
---
## 2. 详细任务
### 2.1 AI CLI Adapter
**新文件**: `backend/app/agents/tools/ai_adapter.py`
```python
from abc import ABC, abstractmethod
from pathlib import Path
from dataclasses import dataclass
@dataclass
class CodeExecutionResult:
success: bool
message: str
files_created: list[str]
output: str
error: str | None
class AICLIAdapter(ABC):
@property
@abstractmethod
def cli_name(self) -> str:
"""CLI 命令名称,如 'claude', 'gemini'"""
pass
@property
@abstractmethod
def requires_workspace(self) -> bool:
"""是否需要工作目录"""
pass
@abstractmethod
def build_command(self, prompt: str, workspace: Path | None) -> list[str]:
"""构建 CLI 命令"""
pass
@abstractmethod
def parse_output(self, output: str) -> CodeExecutionResult:
"""解析 CLI 输出"""
pass
@abstractmethod
def is_installed(self) -> bool:
"""检查 CLI 是否已安装"""
pass
class ClaudeAdapter(AICLIAdapter):
cli_name = "claude"
requires_workspace = True
def build_command(self, prompt: str, workspace: Path | None) -> list[str]:
return ["claude", "-p", prompt, "--dangerously-skip-permissions"]
# ... 其他方法实现
class GeminiAdapter(AICLIAdapter):
cli_name = "gemini"
requires_workspace = False
# ...
class CodexAdapter(AICLIAdapter):
cli_name = "codex"
# ...
class OpenCodeAdapter(AICLIAdapter):
cli_name = "opencode"
# ...
```
### 2.2 Security Classifier
**新文件**: `backend/app/agents/tools/security_classifier.py`
```python
from enum import Enum
class RiskLevel(Enum):
LOW = "low" # 直接执行
HIGH = "high" # 沙盒执行
class SecurityClassifier:
HIGH_RISK_KEYWORDS = [
"修改", "编辑", "删除", "移动",
"Jarvis", "backend", "frontend",
"git", "config", ".env",
]
LOW_RISK_KEYWORDS = [
"demo", "示例", "贪食蛇", "俄罗斯方块",
"小游戏", "独立项目", "新项目",
"创建一个", "写一个",
]
def classify(self, task_description: str, target_path: str | None = None) -> RiskLevel:
# 1. 检查高风险关键词
if any(kw in task_description for kw in self.HIGH_RISK_KEYWORDS):
return RiskLevel.HIGH
# 2. 检查目标路径
if target_path and self._is_project_path(target_path):
return RiskLevel.HIGH
# 3. 检查低风险关键词
if any(kw in task_description for kw in self.LOW_RISK_KEYWORDS):
return RiskLevel.LOW
# 4. 默认高风险
return RiskLevel.HIGH
def _is_project_path(self, path: str) -> bool:
# 检查是否指向 Jarvis 项目路径
return "Jarvis" in path or "backend/app" in path
```
### 2.3 Sandbox Executor
**新文件**: `backend/app/agents/tools/sandbox_executor.py`
```python
import tempfile
import shutil
import asyncio
from pathlib import Path
from dataclasses import dataclass, field
from typing import AsyncGenerator
@dataclass
class SandboxEnvironment:
workspace_path: Path
session_id: str
@staticmethod
async def create() -> "SandboxEnvironment":
"""创建新的沙盒环境"""
temp_dir = tempfile.mkdtemp(prefix="jarvis_code_")
session_id = Path(temp_dir).name
return SandboxEnvironment(
workspace_path=Path(temp_dir),
session_id=session_id,
)
async def cleanup(self):
"""清理沙盒环境"""
if self.workspace_path.exists():
shutil.rmtree(self.workspace_path)
@dataclass
class ExecutionResult:
success: bool
exit_code: int
stdout: str
stderr: str
files_created: list[str] = field(default_factory=list)
class SandboxExecutor:
def __init__(self, adapter: AICLIAdapter, timeout: int = 300):
self.adapter = adapter
self.timeout = timeout
self._sessions: dict[str, SandboxEnvironment] = {}
async def execute(
self,
prompt: str,
session_id: str | None = None
) -> AsyncGenerator[str, None]:
"""执行代码任务yield 实时输出"""
# 1. 创建或复用沙盒环境
if session_id and session_id in self._sessions:
env = self._sessions[session_id]
else:
env = await SandboxEnvironment.create()
self._sessions[env.session_id] = env
session_id = env.session_id
# 2. 构建命令
cmd = self.adapter.build_command(prompt, env.workspace_path)
# 3. 异步执行,实时 yield 输出
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=str(env.workspace_path),
)
# 4. 实时读取输出
while True:
line = await process.stdout.readline()
if not line:
break
yield line.decode()
# 5. 等待完成
await process.wait()
# 6. 收集结果
return ExecutionResult(
success=process.returncode == 0,
exit_code=process.returncode or 0,
stdout=...,
stderr=...,
files_created=self._list_created_files(env.workspace_path),
)
async def cleanup_session(self, session_id: str):
"""清理指定会话"""
if session_id in self._sessions:
await self._sessions[session_id].cleanup()
del self._sessions[session_id]
```
### 2.4 Direct Executor
**新文件**: `backend/app/agents/tools/direct_executor.py`
```python
class DirectExecutor:
def __init__(self, adapter: AICLIAdapter, timeout: int = 60):
self.adapter = adapter
self.timeout = timeout
async def execute(self, prompt: str) -> ExecutionResult:
"""直接执行,不需要沙盒"""
if not self.adapter.is_installed():
return ExecutionResult(
success=False,
exit_code=-1,
stdout="",
stderr=f"{self.adapter.cli_name} is not installed",
)
cmd = self.adapter.build_command(prompt, None)
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
try:
stdout, stderr = await asyncio.wait_for(
process.communicate(),
timeout=self.timeout,
)
return ExecutionResult(
success=process.returncode == 0,
exit_code=process.returncode or 0,
stdout=stdout.decode(),
stderr=stderr.decode(),
)
except asyncio.TimeoutError:
process.kill()
return ExecutionResult(
success=False,
exit_code=-1,
stdout="",
stderr=f"Execution timed out after {self.timeout}s",
)
```
---
## 3. 核心文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `ai_adapter.py` | 新增 | 抽象基类 + 4 个具体实现 |
| `security_classifier.py` | 新增 | 安全分级器 |
| `sandbox_executor.py` | 新增 | 沙盒执行器 |
| `direct_executor.py` | 新增 | 直接执行器 |
---
## 4. 验收标准
- [ ] `AICLIAdapter` 可以正确识别 4 种 CLI
- [ ] `SecurityClassifier` 能正确分类高低风险
- [ ] `SandboxExecutor` 能创建、执行、清理沙盒
- [ ] `DirectExecutor` 能直接执行低风险任务
- [ ] 所有执行器支持流式输出
---
## 5. 风险与缓解
| 风险 | 缓解 |
|------|------|
| AI CLI 未安装 | `is_installed()` 检查 + 友好提示 |
| 执行超时 | `timeout` 参数控制 |
| 沙盒清理遗漏 | 使用 `finally` 块确保清理 |
---
## 6. 依赖关系
```
Phase 1基础设施
本阶段 → Phase 3Agent 集成)
→ Phase 4流式交互
```

View File

@@ -0,0 +1,162 @@
# Phase 3Agent 集成
日期2026-04-04
状态:待实施
依赖Phase 1 + Phase 2 完成
---
## 1. 本阶段目的
将代码指挥官接入 LangGraph
- Graph 节点
- 边路由
- 任务模型
---
## 2. 详细任务
### 2.1 Graph 节点
**文件**: `backend/app/agents/graph.py`
```python
# 新增 code_commander_node
async def code_commander_node(state: AgentState) -> AgentState:
"""代码指挥官节点"""
# 1. 获取用户需求和选择的 AI 提供商
user_message = state.messages[-1].content
ai_provider = state.get("ai_provider", "claude")
# 2. 安全分级
classifier = SecurityClassifier()
risk_level = classifier.classify(user_message)
# 3. 根据风险等级选择执行器
adapter = get_adapter(ai_provider)
if risk_level == RiskLevel.LOW:
executor = DirectExecutor(adapter)
result = await executor.execute(user_message)
else:
sandbox = await SandboxEnvironment.create()
executor = SandboxExecutor(adapter)
result = await executor.execute(user_message, sandbox.session_id)
state["workspace_path"] = str(sandbox.workspace_path)
state["execution_session_id"] = sandbox.session_id
# 4. 更新状态
state.messages.append(AIMessage(content=str(result)))
state["next_step"] = None # 任务完成
return state
# 节点注册到 NODES
NODES: dict[str, NodeCallable] = {
# ... 现有节点 ...
"code_commander": code_commander_node,
}
```
### 2.2 边路由
**文件**: `backend/app/agents/graph.py`
```python
def _should_route_to_code_commander(state: AgentState) -> str:
"""判断是否路由到代码指挥官"""
if state.current_agent == "code_commander":
return "code_commander"
# ... 其他条件
return END
# 边注册
def _build_graph() -> CompiledGraph:
# ... 现有边 ...
# 新增代码指挥官相关边
graph.add_conditional_edges(
"master",
_should_route_to_code_commander,
{
"code_commander": "code_commander",
END: END,
}
)
graph.add_edge("code_commander", END)
return graph.compile()
```
### 2.3 任务模型
**文件**: `backend/app/agents/schemas/task.py`
```python
from pydantic import BaseModel, Field
from typing import Literal
class CodeProviderType(str, Enum):
CLAUDE = "claude"
GEMINI = "gemini"
CODEX = "codex"
OPENCODE = "opencode"
class RiskLevelType(str, Enum):
LOW = "low"
HIGH = "high"
class CodeTask(BaseModel):
"""代码任务"""
id: str = Field(default_factory=lambda: f"code_{uuid.uuid4().hex[:8]}")
provider: CodeProviderType
prompt: str
risk_level: RiskLevelType
sandbox_mode: bool
workspace_path: str | None = None
session_id: str | None = None
status: Literal["pending", "running", "completed", "failed"] = "pending"
created_at: datetime = Field(default_factory=datetime.now)
class CodeExecutionResult(BaseModel):
"""代码执行结果"""
task_id: str
success: bool
exit_code: int
stdout: str
stderr: str
files_created: list[str] = Field(default_factory=list)
workspace_path: str | None = None
completed_at: datetime = Field(default_factory=datetime.now)
```
---
## 3. 核心文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `graph.py` | 修改 | 新增 `code_commander_node` 和边路由 |
| `schemas/task.py` | 修改 | 新增 `CodeTask`, `CodeExecutionResult` 等模型 |
---
## 4. 验收标准
- [ ] `code_commander_node` 正确处理任务
- [ ] `SecurityClassifier` 被正确调用
- [ ] 高低风险任务路由到正确的执行器
- [ ] `CodeTask``CodeExecutionResult` 模型正确
---
## 5. 依赖关系
```
Phase 1 + Phase 2
本阶段 → Phase 4流式交互
→ Phase 5前端集成
```

View File

@@ -0,0 +1,298 @@
# Phase 4流式交互
日期2026-04-04
状态:待实施
依赖Phase 3 完成
---
## 1. 本阶段目的
实现 PTY 终端 + WebSocket 流式输出:
- PTY 终端管理
- WebSocket 端点
- 流式输出集成
- 交互输入
---
## 2. 详细任务
### 2.1 PTY Terminal Engine
**新文件**: `backend/app/agents/tools/terminal_engine.py`
```python
import asyncio
import os
from dataclasses import dataclass, field
from typing import AsyncGenerator
@dataclass
class PTYSession:
session_id: str
process: asyncio.subprocess.Process
workspace_path: str
class PTYManager:
def __init__(self):
self._sessions: dict[str, PTYSession] = {}
self._output_queues: dict[str, asyncio.Queue] = {}
async def spawn(
self,
cli: str,
args: list[str],
cwd: str,
session_id: str | None = None
) -> str:
"""启动 PTY 会话"""
if session_id is None:
session_id = f"pty_{os.urandom(8).hex()}"
# 创建 PTY 进程
process = await asyncio.create_subprocess_exec(
*([cli] + args),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=cwd,
env={**os.environ, "TERM": "xterm-256color"},
)
session = PTYSession(
session_id=session_id,
process=process,
workspace_path=cwd,
)
self._sessions[session_id] = session
self._output_queues[session_id] = asyncio.Queue()
# 启动输出读取协程
asyncio.create_task(self._read_output(session_id))
return session_id
async def _read_output(self, session_id: str):
"""读取 PTY 输出并放入队列"""
session = self._sessions.get(session_id)
if not session:
return
queue = self._output_queues[session_id]
while True:
line = await session.process.stdout.readline()
if not line:
break
await queue.put(line.decode())
# 同时推送给所有订阅者
await self._broadcast(session_id, line.decode())
await queue.put(None) # 结束标记
async def write(self, session_id: str, data: str):
"""写入 PTY用户输入"""
session = self._sessions.get(session_id)
if session and session.process.stdin:
session.process.stdin.write(data)
await session.process.stdin.drain()
async def read(self, session_id: str) -> AsyncGenerator[str, None]:
"""读取 PTY 输出"""
queue = self._output_queues.get(session_id)
if not queue:
return
while True:
line = await queue.get()
if line is None:
break
yield line
async def resize(self, session_id: str, rows: int, cols: int):
"""调整终端大小"""
# TODO: 实现 resize
pass
async def kill(self, session_id: str):
"""终止 PTY 会话"""
if session_id in self._sessions:
session = self._sessions[session_id]
session.process.terminate()
await session.process.wait()
del self._sessions[session_id]
del self._output_queues[session_id]
async def _broadcast(self, session_id: str, data: str):
"""广播输出到 WebSocket"""
# 实际推送由 router 层处理
pass
```
### 2.2 WebSocket 端点
**新文件**: `backend/app/routers/terminal.py`
```python
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from typing import dict
router = APIRouter(prefix="/ws/terminal", tags=["terminal"])
class ConnectionManager:
def __init__(self):
self.active_connections: dict[str, WebSocket] = {}
async def connect(self, session_id: str, websocket: WebSocket):
await websocket.accept()
self.active_connections[session_id] = websocket
def disconnect(self, session_id: str):
if session_id in self.active_connections:
del self.active_connections[session_id]
async def send(self, session_id: str, data: str):
if session_id in self.active_connections:
await self.active_connections[session_id].send_text(data)
manager = ConnectionManager()
@router.websocket("/{session_id}")
async def terminal_websocket(websocket: WebSocket, session_id: str):
await manager.connect(session_id, websocket)
# 获取 PTY Manager 实例
from app.agents.tools.terminal_engine import pty_manager
try:
# 订阅该 session 的输出
queue = pty_manager._output_queues.get(session_id)
if queue:
while True:
data = await websocket.receive_text()
# 接收用户输入
await pty_manager.write(session_id, data + "\n")
except WebSocketDisconnect:
manager.disconnect(session_id)
```
### 2.3 流式输出集成
**新文件**: `backend/app/agents/tools/stream_output.py`
```python
import json
from typing import AsyncGenerator
from dataclasses import dataclass
@dataclass
class StreamEvent:
type: str # "output" | "error" | "status" | "complete"
session_id: str
data: str
timestamp: str
class StreamOutput:
def __init__(self, session_id: str, websocket_sender):
self.session_id = session_id
self.websocket_sender = websocket_sender
async def push(self, event_type: str, data: str):
"""推送事件到 WebSocket"""
event = StreamEvent(
type=event_type,
session_id=self.session_id,
data=data,
timestamp=datetime.now().isoformat(),
)
await self.websocket_sender(self.session_id, json.dumps(event.__dict__))
async def stream_execution(
self,
executor,
prompt: str
) -> AsyncGenerator[str, None]:
"""包装执行器,实现流式输出"""
async for line in executor.execute(prompt):
await self.push("output", line)
yield line
await self.push("complete", "")
```
### 2.4 交互输入
**新文件**: `backend/app/agents/tools/interactive_input.py`
```python
class InteractiveInputHandler:
def __init__(self, pty_manager: PTYManager):
self.pty_manager = pty_manager
self._pending_inputs: dict[str, asyncio.Event] = {}
async def wait_for_input(self, session_id: str, prompt: str) -> str:
"""等待用户输入(如 "y" 确认)"""
event = asyncio.Event()
self._pending_inputs[session_id] = event
# 发送提示
from app.routers.terminal import manager
await manager.send(session_id, f"\n{prompt}\n")
# 等待输入完成
await event.wait()
del self._pending_inputs[session_id]
return self._input_cache.get(session_id, "")
async def send_input(self, session_id: str, data: str):
"""用户发送输入"""
self._input_cache[session_id] = data
if session_id in self._pending_inputs:
self._pending_inputs[session_id].set()
# 同时写入 PTY
await self.pty_manager.write(session_id, data + "\n")
```
---
## 3. 核心文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `terminal_engine.py` | 新增 | PTY 终端管理 |
| `routers/terminal.py` | 新增 | WebSocket 端点 |
| `stream_output.py` | 新增 | 流式输出封装 |
| `interactive_input.py` | 新增 | 交互输入处理 |
---
## 4. 验收标准
- [ ] PTY 会话可以启动、读写、终止
- [ ] WebSocket 可以建立连接并收发消息
- [ ] 执行输出实时推送到前端
- [ ] 用户输入可以传递到 PTY
---
## 5. 依赖关系
```
Phase 3Agent 集成)
本阶段 → Phase 5前端集成
```
---
## 6. 备注
PTY 实现参考了 golutra 的 `src-tauri/src/runtime/pty.rs`
- 使用 `portable-pty`
- Windows 路径兼容处理
- shim 机制用于信号处理

View File

@@ -0,0 +1,364 @@
# Phase 5前端集成
日期2026-04-04
状态:待实施
依赖Phase 4 完成
---
## 1. 本阶段目的
Vue 前端新增代码指挥官 UI
- 页面组件
- 终端显示组件
- WebSocket 服务
- 路由配置
---
## 2. 详细任务
### 2.1 页面组件
**新文件**: `frontend/src/pages/chat/CodeCommander.vue`
```vue
<template>
<div class="code-commander">
<!-- AI 提供商选择器 -->
<div class="provider-selector">
<div class="label">选择 AI 助手</div>
<div class="providers">
<button
v-for="p in providers"
:key="p.id"
:class="{ active: selectedProvider === p.id }"
@click="selectedProvider = p.id"
>
<img :src="p.icon" :alt="p.name" />
{{ p.name }}
</button>
</div>
</div>
<!-- 任务输入 -->
<div class="task-input">
<textarea
v-model="taskPrompt"
placeholder="描述你想让 AI 帮你做什么..."
rows="4"
/>
<button @click="executeTask" :disabled="isExecuting">
{{ isExecuting ? '执行中...' : '开始执行' }}
</button>
</div>
<!-- 终端输出 -->
<TerminalDisplay
ref="terminalRef"
:session-id="currentSessionId"
@input="handleUserInput"
/>
<!-- 交互输入框 -->
<div v-if="isWaitingForInput" class="interactive-input">
<span>{{ inputPrompt }}</span>
<input v-model="userInput" @keyup.enter="sendUserInput" />
</div>
<!-- 操作按钮 -->
<div class="actions">
<button @click="downloadFiles" :disabled="!canDownload">
下载文件
</button>
<button @click="cleanup" :disabled="!canCleanup">
清理
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import TerminalDisplay from '@/components/TerminalDisplay.vue'
import { terminalWsService } from '@/services/terminalWs'
const providers = [
{ id: 'claude', name: 'Claude', icon: '/icons/claude.png' },
{ id: 'gemini', name: 'Gemini', icon: '/icons/gemini.png' },
{ id: 'codex', name: 'Codex', icon: '/icons/codex.png' },
{ id: 'opencode', name: 'OpenCode', icon: '/icons/opencode.png' },
]
const selectedProvider = ref('claude')
const taskPrompt = ref('')
const isExecuting = ref(false)
const currentSessionId = ref<string | null>(null)
const isWaitingForInput = ref(false)
const inputPrompt = ref('')
const userInput = ref('')
const terminalRef = ref<InstanceType<typeof TerminalDisplay> | null>(null)
const canDownload = computed(() => currentSessionId.value !== null)
const canCleanup = computed(() => currentSessionId.value !== null)
async function executeTask() {
if (!taskPrompt.value.trim()) return
isExecuting.value = true
currentSessionId.value = await terminalWsService.connect(selectedProvider.value)
// 订阅消息
terminalWsService.onMessage((msg) => {
if (msg.type === 'output') {
terminalRef.value?.write(msg.data)
} else if (msg.type === 'waiting_input') {
isWaitingForInput.value = true
inputPrompt.value = msg.data
} else if (msg.type === 'complete') {
isExecuting.value = false
}
})
// 发送任务
await terminalWsService.sendTask(currentSessionId.value, taskPrompt.value)
}
function handleUserInput(data: string) {
terminalWsService.sendInput(currentSessionId.value!, data)
}
function sendUserInput() {
terminalWsService.sendInput(currentSessionId.value!, userInput.value)
userInput.value = ''
isWaitingForInput.value = false
}
async function downloadFiles() {
// TODO: 调用下载 API
}
async function cleanup() {
if (currentSessionId.value) {
await terminalWsService.disconnect(currentSessionId.value)
currentSessionId.value = null
}
}
</script>
```
### 2.2 终端显示组件
**新文件**: `frontend/src/components/TerminalDisplay.vue`
```vue
<template>
<div class="terminal-display" ref="containerRef">
<div class="terminal-output" ref="outputRef"></div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import 'xterm/css/xterm.css'
const props = defineProps<{
sessionId: string | null
}>()
const emit = defineEmits<{
input: [data: string]
}>()
const containerRef = ref<HTMLElement | null>(null)
const outputRef = ref<HTMLElement | null>(null)
let terminal: Terminal | null = null
let fitAddon: FitAddon | null = null
onMounted(() => {
terminal = new Terminal({
theme: { background: '#1e1e1e' },
cursorBlink: true,
})
fitAddon = new FitAddon()
terminal.loadAddon(fitAddon)
terminal.open(outputRef.value!)
fitAddon.fit()
// 用户输入
terminal.onData((data) => {
emit('input', data)
})
})
onUnmounted(() => {
terminal?.dispose()
})
function write(data: string) {
terminal?.write(data)
}
function clear() {
terminal?.clear()
}
defineExpose({ write, clear })
</script>
<style scoped>
.terminal-display {
background: #1e1e1e;
border-radius: 8px;
overflow: hidden;
}
.terminal-output {
padding: 12px;
min-height: 400px;
}
</style>
```
### 2.3 WebSocket 服务
**新文件**: `frontend/src/services/terminalWs.ts`
```typescript
type MessageHandler = (msg: StreamMessage) => void
interface StreamMessage {
type: 'output' | 'error' | 'status' | 'waiting_input' | 'complete'
session_id: string
data: string
timestamp: string
}
class TerminalWsService {
private ws: WebSocket | null = null
private sessionId: string | null = null
private handlers: MessageHandler[] = []
private reconnectAttempts = 0
private maxReconnectAttempts = 5
async connect(provider: string): Promise<string> {
// 创建会话
const response = await fetch('/api/code-commander/sessions', {
method: 'POST',
body: JSON.stringify({ provider }),
})
const { session_id } = await response.json()
// 建立 WebSocket
this.ws = new WebSocket(`ws://localhost:8000/ws/terminal/${session_id}`)
this.ws.onmessage = (event) => {
const msg: StreamMessage = JSON.parse(event.data)
this.handlers.forEach((h) => h(msg))
}
this.ws.onclose = () => {
this.attemptReconnect()
}
this.sessionId = session_id
return session_id
}
async sendTask(sessionId: string, prompt: string) {
await fetch(`/api/code-commander/sessions/${sessionId}/task`, {
method: 'POST',
body: JSON.stringify({ prompt }),
})
}
sendInput(sessionId: string, input: string) {
this.ws?.send(JSON.stringify({ type: 'input', data: input }))
}
onMessage(handler: MessageHandler) {
this.handlers.push(handler)
}
removeHandler(handler: MessageHandler) {
this.handlers = this.handlers.filter((h) => h !== handler)
}
async disconnect(sessionId: string) {
await fetch(`/api/code-commander/sessions/${sessionId}`, {
method: 'DELETE',
})
this.ws?.close()
this.ws = null
this.sessionId = null
}
private async attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
return
}
this.reconnectAttempts++
await new Promise((r) => setTimeout(r, 1000 * this.reconnectAttempts))
// 重新连接
}
}
export const terminalWsService = new TerminalWsService()
```
### 2.4 路由配置
**文件**: `frontend/src/router/index.ts`
```typescript
{
path: '/code-commander',
name: 'CodeCommander',
component: () => import('@/pages/chat/CodeCommander.vue'),
}
```
---
## 3. 核心文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `CodeCommander.vue` | 新增 | 主页面组件 |
| `TerminalDisplay.vue` | 新增 | 终端显示组件xterm.js |
| `terminalWs.ts` | 新增 | WebSocket 服务 |
| `router/index.ts` | 修改 | 新增路由 |
---
## 4. 验收标准
- [ ] 用户可以选择 AI 提供商
- [ ] 可以输入任务描述并执行
- [ ] 终端实时显示 AI 输出
- [ ] 用户可以输入交互(如 "y"
- [ ] 可以下载和清理文件
---
## 5. 依赖
| 依赖 | 版本 | 用途 |
|------|------|------|
| xterm | ^5.x | 终端渲染 |
| xterm-addon-fit | ^0.8.x | 自适应大小 |
---
## 6. 依赖关系
```
Phase 4流式交互
本阶段(前端集成)→ 端到端测试
```

View File

@@ -0,0 +1,174 @@
# Jarvis Forum 升级计划索引
本目录用于存放 Jarvis 论坛系统的分阶段升级规划文档。
## 文档说明
| 文件 | 说明 |
|------|------|
| `README.md` | 总览、阶段关系、实施顺序 |
| `phase-f-0-current-state.md` | 当前现状、问题、目标架构、VCPToolBox 借鉴 |
| `phase-f-1-data-model.md` | 数据模型升级 |
| `phase-f-2-forum-api.md` | API 增强与安全 |
| `phase-f-3-permissions.md` | 权限系统 |
| `phase-f-4-ai-integration.md` | AI 集成 |
| `checklist.md` | 执行清单 |
## 推荐阅读顺序
1. 先读 `phase-f-0-current-state.md`
2. 再按顺序阅读 phase f-1 ~ f-4
3. 实施时严格按阶段推进
4. 参考 `checklist.md` 进行任务追踪
---
## 总体升级原则
1. **安全第一** - 输入验证、路径安全、并发控制
2. **可扩展性** - 支持多板块、标签、分类
3. **AI 增强** - AI 自动回复、摘要生成、智能分类
4. **测试优先** - 所有升级都要配套测试
5. **向后兼容** - 保持现有 API 兼容性
---
## 阶段总览图
```
F.0 ──────────────────────────────────────────────────────────────┐
│ 现状与目标 │
│ - 当前架构分析 │
│ - 短板识别 │
│ - VCPToolBox VCP论坛 借鉴 │
└────────────────────────────────────────────────────────────────────┘
F.1 ──────────────────────────────────────────────────────────────┐
│ 数据模型升级 │
│ - 板块/分类系统 │
│ - 标签系统 │
│ - 帖子元数据扩展 │
│ │
│ 核心文件: models/forum.py │
│ 工作量: 2 天 │
└────────────────────────────────────────────────────────────────────┘
F.2 ──────────────────────────────────────────────────────────────┐
│ API 增强与安全 │
│ - 文件锁机制 │
│ - 输入验证强化 │
│ - 并发控制 │
│ - API 端点扩展 │
│ │
│ 核心文件: routers/forum.py, services/forum_service.py │
│ 依赖: F.1 │
│ 工作量: 3 天 │
└────────────────────────────────────────────────────────────────────┘
F.3 ──────────────────────────────────────────────────────────────┐
│ 权限系统 │
│ - 用户角色管理 │
│ - 板块权限控制 │
│ - 操作日志 │
│ - 积分/奖励系统 │
│ │
│ 核心文件: models/user.py, services/forum_service.py │
│ 依赖: F.2 │
│ 工作量: 3 天 │
└────────────────────────────────────────────────────────────────────┘
F.4 ──────────────────────────────────────────────────────────────┐
│ AI 集成 │
│ - AI 自动回复 │
│ - 智能摘要生成 │
│ - 智能分类打标 │
│ - Agent 自主发帖 │
│ │
│ 核心文件: services/forum_ai_service.py │
│ 依赖: F.3 │
│ 工作量: 5 天 │
└────────────────────────────────────────────────────────────────────┘
```
---
## VCPToolBox VCP论坛 核心借鉴
| 借鉴点 | 实现位置 | 难度 |
|--------|---------|------|
| 文件锁机制(并发控制) | F.2 | 🟡 中 |
| 严格输入验证 | F.2 | 🟢 低 |
| 安全路径检查 | F.2 | 🟢 低 |
| 板块/分类系统 | F.1 | 🟢 低 |
| 标签系统 | F.1 | 🟢 低 |
| 权限管理 | F.3 | 🟡 中 |
| 积分系统 | F.3 | 🟡 中 |
| AI 自动回复 | F.4 | 🟡 中 |
| Agent 自主发帖 | F.4 | 🟡 中 |
---
## 实施顺序
```
F.0 → F.1 → F.2 → F.3 → F.4
│ │ │ │ │
│ │ │ │ └── AI集成
│ │ │ └── 权限/积分
│ │ └── API增强/安全
│ └── 数据模型
└── 现状与目标
```
**注意:** F.1 是基础,后续阶段都依赖 F.1F.4 需要 Agent 能力成熟。
---
## 文件变更追踪
| Phase | 新增文件 | 修改文件 |
|-------|---------|---------|
| F.1 | - | `models/forum.py`, `schemas/forum.py` |
| F.2 | `services/forum_service.py` | `routers/forum.py` |
| F.3 | `models/user.py` (扩展角色), `services/permission_service.py` | `routers/forum.py`, `services/forum_service.py` |
| F.4 | `services/forum_ai_service.py`, `services/summary_service.py` | `routers/forum.py`, `agents/` |
---
## 与 Agent Phase 1-5 的关系
| Agent Phase | Forum 协作内容 |
|-------------|---------------|
| Phase 1 | Task Schema 追踪 Forum 任务 |
| Phase 2 | Forum 任务可分解给执行 Agent |
| Phase 3 | Agent 可以自主在论坛发帖 |
| Phase 4 | Forum 操作可视化 |
| Phase 5 | 多 Agent 论坛协作 |
| **Phase F** | **Forum 升级路径,与 Phase 1-5 协同** |
---
## 注意事项
| 注意事项 | 说明 |
|---------|------|
| F.1 是基础 | F.2-F.4 都依赖 F.1 的数据模型 |
| 安全第一 | 严格输入验证,防止注入攻击 |
| API 兼容性 | 保持现有 API 兼容 |
| AI 集成 | F.4 需要 Agent 能力成熟后才能实现 |
---
## 总工作量
| Phase | 工作量 |
|-------|--------|
| F.1 | 2 天 |
| F.2 | 3 天 |
| F.3 | 3 天 |
| F.4 | 5 天 |
| **总计** | **13 天** |

View File

@@ -0,0 +1,305 @@
# Forum 升级执行清单
本清单用于追踪 Forum 升级计划的执行进度。
---
## 总进度
| Phase | 名称 | 状态 | 工作量 |
|-------|------|------|--------|
| F.0 | 现状与目标 | ✅ 完成 | - |
| F.1 | 数据模型升级 | ⬜ 待开始 | 2 天 |
| F.2 | API 增强与安全 | ⬜ 待开始 | 3 天 |
| F.3 | 权限系统 | ⬜ 待开始 | 3 天 |
| F.4 | AI 集成 | ⬜ 待开始 | 5 天 |
| **总计** | | | **13 天** |
---
## Phase F.1:数据模型升级
### 目标
升级 Forum 数据模型,支持多板块、标签系统、帖子元数据扩展。
### 任务清单
#### 数据库迁移
- [ ] 创建数据库迁移脚本
- 新增 `forum_boards`
- 新增 `forum_tags`
- 新增 `forum_post_tags`
- 新增 `forum_likes`
- 新增 `forum_stats`
- 扩展 `forum_posts`
- 扩展 `forum_replies`
#### 模型实现
- [ ] 扩展 `models/forum.py`
- [ ] 创建 `ForumBoard` 模型
- [ ] 创建 `ForumTag` 模型
- [ ] 创建 `ForumPostTag` 模型
- [ ] 创建 `ForumLike` 模型
- [ ] 创建 `ForumStats` 模型
- [ ] 扩展 `ForumPost` 字段
- [ ] 扩展 `ForumReply` 字段
#### Schema 实现
- [ ] 扩展 `schemas/forum.py`
- [ ] 创建 `ForumBoardCreate/Out` Schema
- [ ] 扩展 `ForumPostCreate/Out` Schema
- [ ] 扩展 `ForumReplyCreate/Out` Schema
- [ ] 创建 `ForumTagOut` Schema
#### 测试
- [ ] 编写单元测试
- [ ] 测试 ForumBoard CRUD
- [ ] 测试 ForumTag CRUD
- [ ] 测试 ForumLike 功能
- [ ] 测试 ForumStats 更新
### 产出文件
- `models/forum.py`
- `schemas/forum.py`
### 验收
- [ ] 迁移脚本可正常运行
- [ ] 所有新模型可正常创建
- [ ] Schema 验证正常工作
- [ ] 单元测试通过
---
## Phase F.2API 增强与安全
### 目标
增强 Forum API 功能,实现文件锁、输入验证、限流、缓存。
### 任务清单
#### 基础服务
- [ ] 创建 `services/forum_service.py`
- [ ] 实现 `ForumLockManager`
- [ ] 实现 `ForumService`
- [ ] 实现板块 CRUD 方法
- [ ] 实现帖子 CRUD 方法
- [ ] 实现回复 CRUD 方法
- [ ] 实现点赞方法
#### 安全机制
- [ ] 实现输入验证
- [ ] 实现 `sanitize_input` 函数
- [ ] 实现 `validate_post_data` 函数
- [ ] 实现配置常量
- [ ] 实现限流器
- [ ] 实现 `RateLimiter`
- [ ] 集成发帖限流
- [ ] 集成回复限流
- [ ] 实现缓存
- [ ] 实现 `ForumCache`
- [ ] 实现帖子缓存
- [ ] 实现标签缓存
#### API 端点
- [ ] 扩展 `routers/forum.py`
- [ ] GET `/boards` - 列出板块
- [ ] POST `/boards` - 创建板块
- [ ] GET `/posts` - 分页获取帖子
- [ ] POST `/posts` - 创建帖子
- [ ] PATCH `/posts/{id}/pin` - 置顶
- [ ] PATCH `/posts/{id}/lock` - 锁定
- [ ] GET `/tags` - 列出/搜索标签
- [ ] POST `/posts/{id}/tags` - 添加标签
- [ ] POST `/like` - 切换点赞
#### 测试
- [ ] 编写单元测试
- [ ] 测试 ForumLockManager
- [ ] 测试输入验证
- [ ] 测试限流器
- [ ] 测试 API 端点
### 产出文件
- `services/forum_service.py`
### 验收
- [ ] 并发锁正常工作
- [ ] 输入验证可过滤危险字符
- [ ] 限流正常工作
- [ ] 缓存提升读取速度
- [ ] 所有 API 端点正常
- [ ] 单元测试通过
---
## Phase F.3:权限系统
### 目标
实现用户角色管理、板块权限控制、操作日志、积分系统。
### 任务清单
#### 用户扩展
- [ ] 扩展 `models/user.py`
- [ ] 添加 `role` 字段
- [ ] 添加 `forum_score` 字段
- [ ] 添加论坛统计字段
- [ ] 添加禁言相关字段
- [ ] 添加 `moderated_boards` 字段
#### 权限服务
- [ ] 创建 `services/permission_service.py`
- [ ] 实现 `UserRole` 枚举
- [ ] 实现 `Permission` 枚举
- [ ] 实现 `ROLE_PERMISSIONS` 映射
- [ ] 实现 `PermissionService`
- [ ] 实现 `has_permission` 方法
- [ ] 实现 `can_edit_post` 方法
- [ ] 实现 `can_delete_post` 方法
- [ ] 实现 `ban_user` 方法
- [ ] 实现 `unban_user` 方法
#### 日志系统
- [ ] 扩展 `models/forum.py`
- [ ] 创建 `ForumLog` 模型
- [ ] 集成日志记录
- [ ] 记录帖子操作
- [ ] 记录回复操作
- [ ] 记录用户操作
#### 积分服务
- [ ] 创建 `services/score_service.py`
- [ ] 实现 `SCORE_RULES` 配置
- [ ] 实现 `ScoreService`
- [ ] 实现 `add_score` 方法
- [ ] 实现 `get_leaderboard` 方法
- [ ] 实现积分自动增减
#### 管理 API
- [ ] 扩展 `routers/forum.py`
- [ ] POST `/admin/ban/{user_id}` - 禁言
- [ ] POST `/admin/unban/{user_id}` - 解除禁言
- [ ] GET `/admin/logs` - 查看日志
- [ ] GET `/leaderboard` - 积分排行榜
#### 测试
- [ ] 编写单元测试
- [ ] 测试权限检查
- [ ] 测试禁言功能
- [ ] 测试积分计算
- [ ] 测试排行榜
### 产出文件
- `models/user.py`
- `models/forum.py` (ForumLog)
- `services/permission_service.py`
- `services/score_service.py`
### 验收
- [ ] 角色权限正确控制
- [ ] 禁言功能正常
- [ ] 操作日志正确记录
- [ ] 积分正确增减
- [ ] 排行榜正确排序
- [ ] 单元测试通过
---
## Phase F.4AI 集成
### 目标
实现 AI 自动回复、摘要生成、智能打标、Agent 自主发帖。
### 任务清单
#### AI 服务
- [ ] 创建 `services/forum_ai_service.py`
- [ ] 实现 `ForumAIConfig` 配置
- [ ] 实现 `ForumAIService`
- [ ] 实现 `generate_auto_reply` 方法
- [ ] 实现 `_should_auto_reply` 判断
- [ ] 实现 `generate_summary` 方法
- [ ] 实现 `suggest_tags` 方法
- [ ] 实现 `classify_category` 方法
#### 摘要服务
- [ ] 创建 `services/summary_service.py`
- [ ] 实现 `SummaryService`
- [ ] 实现 `get_post_summary` 方法
- [ ] 实现 `get_thread_summary` 方法
- [ ] 实现缓存失效
#### Agent 工具
- [ ] 创建 `agents/tools/forum_tools.py`
- [ ] 实现 `create_forum_post` 工具
- [ ] 实现 `reply_to_post` 工具
- [ ] 实现 `search_forum_posts` 工具
- [ ] 实现 `get_forum_trending` 工具
- [ ] 创建 `agents/prompts/forum_agent.py`
- [ ] 编写 Forum Agent 提示词
- [ ] 配置工具列表
#### 定时任务
- [ ] 创建 `tasks/forum_auto_reply.py`
- [ ] 实现 `auto_reply_task` 函数
- [ ] 实现 `setup_forum_scheduler` 函数
#### API 端点
- [ ] 扩展 `routers/forum.py`
- [ ] POST `/posts/{id}/generate-summary` - 生成摘要
- [ ] POST `/posts/suggest-tags` - 推荐标签
- [ ] POST `/posts/classify` - 分类帖子
- [ ] GET `/posts/{id}/ai-status` - AI 状态
#### 测试
- [ ] 编写单元测试
- [ ] 测试自动回复生成
- [ ] 测试摘要生成
- [ ] 测试智能打标
- [ ] 测试 API 端点
### 产出文件
- `services/forum_ai_service.py`
- `services/summary_service.py`
- `agents/tools/forum_tools.py`
- `agents/prompts/forum_agent.py`
- `tasks/forum_auto_reply.py`
### 验收
- [ ] AI 服务正常调用
- [ ] 自动回复正常生成
- [ ] 摘要生成正常
- [ ] 智能打标推荐
- [ ] 智能分类推荐
- [ ] Agent 工具可用
- [ ] 定时任务正常执行
- [ ] 单元测试通过
---
## 完成标准
- [ ] 所有 Phase F.1-F.4 任务完成
- [ ] 所有单元测试通过
- [ ] API 文档更新完成
- [ ] 用户文档更新完成
- [ ] 部署验证通过
---
## 风险与注意事项
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| F.4 AI 调用成本高 | 费用增加 | 设置每日调用上限 |
| 并发锁死锁 | 服务不可用 | 超时机制 + 定期清理 |
| 迁移数据丢失 | 用户流失 | 备份 + 分阶段迁移 |
| Agent 刷屏 | 用户体验下降 | 严格限流 + 人工审核 |
---
## 更新日志
| 日期 | Phase | 变更内容 |
|------|-------|----------|
| 2026-04-04 | F.0 | 创建文档 |

View File

@@ -0,0 +1,205 @@
# Phase F.0Forum 现状与目标
日期2026-04-04
状态:已完成
借鉴来源VCPToolBox VCP论坛 模块
---
## 1. 本阶段目的
本文件用于统一背景认知,明确:
- Jarvis 当前 Forum 架构处于什么水平
- 主要短板是什么
- 为什么要升级
- 升级后的目标形态是什么
- VCPToolBox VCP论坛 给我们什么启发
---
## 2. 当前 Jarvis Forum 架构
### 2.1 核心流程
```
用户发帖/回复 → ForumRouter → ForumPost/ForumReply 模型 → SQLite
```
### 2.2 核心文件
| 文件 | 职责 |
|------|------|
| `backend/app/routers/forum.py` | 论坛 API 路由 |
| `backend/app/models/forum.py` | ForumPost/ForumReply 数据模型 |
| `backend/app/schemas/forum.py` | Pydantic 请求/响应模型 |
### 2.3 当前数据模型
```python
class ForumPost(BaseModel):
user_id: str
title: str
content: str
category: str # instruction, discussion, question
is_executed: bool
execution_result: Optional[str]
reply_count: int
class ForumReply(BaseModel):
post_id: str
user_id: Optional[str]
agent_id: Optional[str]
content: str
is_ai_reply: bool
```
### 2.4 当前 API 端点
| 方法 | 路径 | 功能 |
|------|------|------|
| GET | `/api/forum/posts` | 列出帖子 |
| POST | `/api/forum/posts` | 创建帖子 |
| GET | `/api/forum/posts/{post_id}` | 获取帖子 |
| DELETE | `/api/forum/posts/{post_id}` | 删除帖子 |
| GET | `/api/forum/posts/{post_id}/replies` | 列出回复 |
| POST | `/api/forum/posts/{post_id}/replies` | 创建回复 |
---
## 3. 当前能力矩阵
| 能力 | 状态 | 说明 |
|------|------|------|
| 基本 CRUD | ✅ | 帖子和回复的增删查 |
| 分类标签 | ✅ | instruction/discussion/question |
| AI 回复标记 | ✅ | `is_ai_reply` 字段 |
| 回复计数 | ✅ | `reply_count` 字段 |
| 执行状态 | ✅ | `is_executed`/`execution_result` |
---
## 4. 当前短板
| 短板 | 严重程度 | 影响 |
|------|----------|------|
| 无板块系统 | 🔴 高 | 所有帖子混在一起 |
| 无标签系统 | 🟡 中 | 无法细粒度分类 |
| 无输入验证 | 🔴 高 | 安全风险 |
| 无并发控制 | 🟡 中 | 多用户操作可能冲突 |
| 无权限管理 | 🟡 中 | 无法控制谁可以做什么 |
| 无积分系统 | 🟡 中 | 无法激励参与 |
| 无 AI 自动回复 | 🟡 中 | 需要人工回复 |
| 无摘要生成 | 🟢 低 | 长帖子难以快速浏览 |
| 无 Agent 自主发帖 | 🟢 低 | Agent 无法主动参与 |
---
## 5. VCPToolBox VCP论坛 核心借鉴
### 5.1 VCP论坛架构
VCPToolBox 的论坛是 Agent 社区交流平台,支持:
- 多 Agent 和用户共同参与
- 超栈追踪和统一 FileAPI
- 文件上传和多媒体支持
- 积分系统和任务版集成
### 5.2 核心安全机制
```javascript
// forumApi.js 中的安全配置
const FORUM_CONFIG = {
MAX_CONTENT_LENGTH: 50000, // 单条内容最大长度 50KB
MAX_FILE_SIZE: 1024 * 1024 * 2, // 单个帖子文件最大 2MB
MAX_MAID_LENGTH: 50, // 用户名最大长度
MAX_TITLE_LENGTH: 100, // 标题最大长度
MAX_FLOORS_PER_POST: 500, // 单帖最大楼层数
UID_PATTERN: /^[a-zA-Z0-9_-]+$/, // UID 允许的字符
LOCK_TIMEOUT: 10000, // 文件锁超时 10秒
MAX_CONCURRENT_WRITES: 5, // 最大并发写入数
};
```
### 5.3 核心安全函数
```javascript
// 文件锁管理器 - 并发控制
class FileLockManager {
async acquireLock(filePath, timeout)
releaseLock(filePath)
}
// 安全路径检查 - 防止路径遍历
function isPathSafe(targetPath, rootPath)
// 输入清理 - 防止注入
function sanitizeInput(input, maxLength)
// 安全的文件名解析
function parsePostFilename(filename)
```
### 5.4 关键设计理念
1. **安全第一** - 严格的输入验证、路径安全、并发控制
2. **可扩展性** - 支持多板块、多楼层、文件附件
3. **Agent 集成** - Agent 可以自主发帖、回帖
4. **积分激励** - Agent 通过发帖获得积分
---
## 6. 目标架构
```
┌─────────────────────────────────────────────────────────────┐
│ User/Agent │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Forum Router Layer │
│ - 输入验证 (sanitize) │
│ - 权限检查 (permission) │
│ - 限流控制 (rate_limit) │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Forum Service Layer │
│ - FileLock (并发控制) │
│ - PermissionService (权限) │
│ - AIService (AI 回复/摘要) │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Data Layer │
│ - ForumPost (板块/标签) │
│ - ForumReply (层级) │
│ - UserRole (权限) │
│ - ForumStats (积分) │
└─────────────────────────────────────────────────────────────┘
```
---
## 7. 借鉴点映射
| VCPToolBox 借鉴点 | Jarvis 实现位置 | 优先级 |
|-------------------|---------------|--------|
| 文件锁机制 | `services/forum_service.py` | 🟢 高 |
| 输入验证强化 | `routers/forum.py` | 🟢 高 |
| 安全路径检查 | `services/forum_service.py` | 🟢 高 |
| 板块/分类系统 | `models/forum.py` | 🟢 高 |
| 标签系统 | `models/forum.py` | 🟡 中 |
| 权限管理 | `services/permission_service.py` | 🟡 中 |
| 积分系统 | `models/user.py` | 🟡 中 |
| AI 自动回复 | `services/forum_ai_service.py` | 🟡 中 |
| 摘要生成 | `services/summary_service.py` | 🟢 低 |
| Agent 自主发帖 | `agents/` | 🟡 中 |
---
## 8. 本阶段产出要求
- [x] 团队对 Jarvis 当前 Forum 问题和目标方向达成一致
- [x] VCPToolBox VCP论坛 借鉴点已映射到具体 Phase
- [x] 后续 phase 文档能够在这个认知基础上展开

View File

@@ -0,0 +1,361 @@
# Phase F.1:数据模型升级
日期2026-04-04
状态:待开始
依赖F.0(已完成)
前置models/forum.py
---
## 1. 本阶段目的
升级 Jarvis Forum 的数据模型,支持:
- 多板块系统(指令/讨论/任务等)
- 标签系统(细粒度分类)
- 帖子元数据扩展(阅读量、点赞、收藏等)
- 层级回复结构
---
## 2. 目标模型
### 2.1 ForumBoard板块
```python
class ForumBoard(BaseModel):
__tablename__ = "forum_boards"
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
name: str # 板块名称,如 "指令广场"
slug: str # URL 友好名称,如 "instruction"
description: Optional[str]
icon: Optional[str] # emoji 或图标名
sort_order: int = 0
is_active: bool = True
min_role: str = "user" # 查看权限guest/user/moderator/admin
parent_id: Optional[str] # 父板块,支持层级
color: Optional[str] # 板块颜色
```
### 2.2 ForumPost帖子扩展
```python
class ForumPost(BaseModel):
__tablename__ = "forum_posts"
# === 现有字段 ===
user_id: str
title: str
content: str
reply_count: int = 0
# === 新增字段 ===
board_id: Optional[str] = None # 所属板块
parent_id: Optional[str] = None # 父帖子(支持回复嵌套)
# 分类与标签
category: Optional[str] # instruction/discussion/question/praise/bug
tags: Optional[str] = "" # JSON 数组 ["python", "api"]
# 状态与权限
is_pinned: bool = False # 置顶
is_locked: bool = False # 锁定(禁止回复)
is_deleted: bool = False # 软删除
is_executed: bool = False # 指令已执行
execution_result: Optional[str]
# 统计与互动
view_count: int = 0
like_count: int = 0
favorite_count: int = 0
share_count: int = 0
# 时间戳
last_reply_at: Optional[datetime] = None # 最后回复时间
last_activity_at: Optional[datetime] = None
# === 关系 ===
board = relationship("ForumBoard", back_populates="posts")
replies = relationship("ForumReply", back_populates="post", cascade="all, delete-orphan")
post_tags = relationship("ForumPostTag", back_populates="post", cascade="all, delete-orphan")
likes = relationship("ForumLike", back_populates="post", cascade="all, delete-orphan")
```
### 2.3 ForumReply回复扩展
```python
class ForumReply(BaseModel):
__tablename__ = "forum_replies"
# === 现有字段 ===
post_id: str
user_id: Optional[str]
agent_id: Optional[str]
content: str
is_ai_reply: bool = False
# === 新增字段 ===
parent_id: Optional[str] = None # 父回复(支持嵌套)
floor: int = 1 # 楼层号
is_pinned: bool = False # 置顶
is_best: bool = False # 最佳回复
is_deleted: bool = False # 软删除
like_count: int = 0
edited_at: Optional[datetime] = None # 编辑时间
# === 关系 ===
post = relationship("ForumPost", back_populates="replies")
parent = relationship("ForumReply", remote_side=[id], backref="children")
likes = relationship("ForumLike", back_populates="reply", cascade="all, delete-orphan")
```
### 2.4 ForumTag标签
```python
class ForumTag(BaseModel):
__tablename__ = "forum_tags"
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
name: str # 标签名,如 "python"
slug: str # URL 友好名
color: str = "#666666" # 标签颜色
post_count: int = 0 # 帖子数量(缓存)
is_official: bool = False # 官方标签
board_id: Optional[str] = None # 限定板块
posts = relationship("ForumPostTag", back_populates="tag")
class ForumPostTag(BaseModel):
"""帖子-标签关联"""
__tablename__ = "forum_post_tags"
post_id: str
tag_id: str
post = relationship("ForumPost", back_populates="post_tags")
tag = relationship("ForumTag", back_populates="posts")
```
### 2.5 ForumLike点赞
```python
class ForumLike(BaseModel):
__tablename__ = "forum_likes"
user_id: str
post_id: Optional[str] = None
reply_id: Optional[str] = None
created_at: datetime = Field(default_factory=datetime.utcnow)
post = relationship("ForumPost", back_populates="likes")
reply = relationship("ForumReply", back_populates="likes")
```
### 2.6 ForumStats用户统计
```python
class ForumStats(BaseModel):
"""用户论坛统计"""
__tablename__ = "forum_stats"
user_id: str = Field(primary_key=True)
post_count: int = 0
reply_count: int = 0
like_received: int = 0 # 收到的赞
best_reply_count: int = 0 # 最佳回复数
score: int = 0 # 积分
last_post_at: Optional[datetime] = None
last_reply_at: Optional[datetime] = None
```
---
## 3. 数据库迁移
### 3.1 新增表
```sql
-- 板块表
CREATE TABLE forum_boards (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
description TEXT,
icon TEXT,
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
min_role TEXT DEFAULT 'user',
parent_id TEXT,
color TEXT
);
-- 帖子-标签关联表
CREATE TABLE forum_post_tags (
post_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
PRIMARY KEY (post_id, tag_id)
);
-- 标签表
CREATE TABLE forum_tags (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
slug TEXT UNIQUE NOT NULL,
color TEXT DEFAULT '#666666',
post_count INTEGER DEFAULT 0,
is_official BOOLEAN DEFAULT FALSE,
board_id TEXT
);
-- 点赞表
CREATE TABLE forum_likes (
user_id TEXT NOT NULL,
post_id TEXT,
reply_id TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, post_id, reply_id)
);
-- 用户论坛统计表
CREATE TABLE forum_stats (
user_id TEXT PRIMARY KEY,
post_count INTEGER DEFAULT 0,
reply_count INTEGER DEFAULT 0,
like_received INTEGER DEFAULT 0,
best_reply_count INTEGER DEFAULT 0,
score INTEGER DEFAULT 0,
last_post_at TIMESTAMP,
last_reply_at TIMESTAMP
);
-- 扩展 forum_posts 表
ALTER TABLE forum_posts ADD COLUMN board_id TEXT;
ALTER TABLE forum_posts ADD COLUMN parent_id TEXT;
ALTER TABLE forum_posts ADD COLUMN tags TEXT DEFAULT '';
ALTER TABLE forum_posts ADD COLUMN is_pinned BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_posts ADD COLUMN is_locked BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_posts ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_posts ADD COLUMN view_count INTEGER DEFAULT 0;
ALTER TABLE forum_posts ADD COLUMN like_count INTEGER DEFAULT 0;
ALTER TABLE forum_posts ADD COLUMN favorite_count INTEGER DEFAULT 0;
ALTER TABLE forum_posts ADD COLUMN share_count INTEGER DEFAULT 0;
ALTER TABLE forum_posts ADD COLUMN last_reply_at TIMESTAMP;
ALTER TABLE forum_posts ADD COLUMN last_activity_at TIMESTAMP;
-- 扩展 forum_replies 表
ALTER TABLE forum_replies ADD COLUMN parent_id TEXT;
ALTER TABLE forum_replies ADD COLUMN floor INTEGER DEFAULT 1;
ALTER TABLE forum_replies ADD COLUMN is_pinned BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_replies ADD COLUMN is_best BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_replies ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE;
ALTER TABLE forum_replies ADD COLUMN like_count INTEGER DEFAULT 0;
ALTER TABLE forum_replies ADD COLUMN edited_at TIMESTAMP;
```
---
## 4. Schema 扩展
### 4.1 ForumBoardSchema
```python
class ForumBoardCreate(BaseModel):
name: str = Field(..., max_length=100)
slug: str = Field(..., max_length=50, pattern=r"^[a-z0-9-]+$")
description: Optional[str] = None
icon: Optional[str] = None
sort_order: int = 0
min_role: str = "user"
parent_id: Optional[str] = None
color: Optional[str] = None
class ForumBoardOut(BaseModel):
id: str
name: str
slug: str
description: Optional[str]
icon: Optional[str]
sort_order: int
post_count: int = 0 # 动态计算
class ForumPostCreate(BaseModel):
title: str = Field(..., max_length=200)
content: str = Field(..., max_length=50000)
board_id: Optional[str] = None
category: Optional[str] = None
tags: List[str] = []
class ForumPostOut(BaseModel):
id: str
user_id: str
title: str
content: str
board_id: Optional[str]
board_name: Optional[str] = None
category: Optional[str]
tags: List[str] = []
reply_count: int
view_count: int
like_count: int
is_pinned: bool
is_locked: bool
created_at: datetime
last_reply_at: Optional[datetime]
user_liked: bool = False # 当前用户是否点赞
```
---
## 5. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 创建数据库迁移脚本 | 🟢 高 |
| 2 | 扩展 models/forum.py | 🟢 高 |
| 3 | 创建 ForumBoard 模型 | 🟢 高 |
| 4 | 创建 ForumTag/ForumPostTag 模型 | 🟡 中 |
| 5 | 创建 ForumLike 模型 | 🟡 中 |
| 6 | 创建 ForumStats 模型 | 🟡 中 |
| 7 | 扩展 schemas/forum.py | 🟢 高 |
| 8 | 编写单元测试 | 🟡 中 |
---
## 6. 核心文件变更
| 文件 | 变更 |
|------|------|
| `models/forum.py` | 新增 ForumBoard, ForumTag, ForumPostTag, ForumLike, ForumStats扩展 ForumPost, ForumReply |
| `schemas/forum.py` | 新增 ForumBoardCreate/Out扩展 ForumPostCreate/Out |
| `database.py` | 注册新模型 |
---
## 7. 工作量估算
| 任务 | 工作量 |
|------|--------|
| 数据库迁移脚本 | 0.5 天 |
| 模型扩展 | 0.5 天 |
| Schema 扩展 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **2 天** |
---
## 8. 验收标准
- [ ] ForumBoard 模型可创建/查询板块
- [ ] ForumPost 可关联板块和标签
- [ ] ForumTag 可创建/查询标签
- [ ] ForumLike 支持点赞功能
- [ ] ForumStats 正确统计用户数据
- [ ] 所有新模型有对应的 Pydantic Schema
- [ ] 迁移脚本可回滚
- [ ] 单元测试覆盖新增功能

View File

@@ -0,0 +1,488 @@
# Phase F.2API 增强与安全
日期2026-04-04
状态:待开始
依赖F.1(待完成)
前置routers/forum.py
---
## 1. 本阶段目的
增强 Jarvis Forum API 的功能和安全:
- 引入文件锁机制(并发控制)
- 强化输入验证
- 扩展 API 端点
- 实现缓存机制
- 添加限流保护
---
## 2. Forum Service 架构
### 2.1 目录结构
```
backend/app/services/
└── forum_service.py # 新增
```
### 2.2 核心服务类
```python
class ForumLockManager:
"""文件锁管理器 - 防止并发写入冲突"""
def __init__(self, max_concurrent: int = 5, timeout: int = 10):
self.locks: Dict[str, LockInfo] = {}
self.max_concurrent = max_concurrent
self.timeout = timeout
async def acquire_lock(self, resource_id: str) -> bool:
"""获取锁"""
def release_lock(self, resource_id: str) -> None:
"""释放锁"""
def cleanup_stale_locks(self) -> None:
"""清理过期锁"""
class ForumService:
"""论坛服务主类"""
def __init__(
self,
db: AsyncSession,
lock_manager: ForumLockManager,
):
self.db = db
self.lock = lock_manager
# === 板块操作 ===
async def create_board(self, data: ForumBoardCreate) -> ForumBoard:
"""创建板块"""
async def list_boards(self) -> List[ForumBoard]:
"""列出板块"""
# === 帖子操作 ===
async def create_post(
self,
user_id: str,
data: ForumPostCreate,
) -> ForumPost:
"""创建帖子(带锁)"""
async with self.lock.acquire_lock(f"post:{user_id}"):
# 创建帖子逻辑
async def get_posts(
self,
board_id: Optional[str] = None,
category: Optional[str] = None,
tags: Optional[List[str]] = None,
page: int = 1,
page_size: int = 20,
) -> PaginatedResult[ForumPostOut]:
"""分页获取帖子"""
async def get_post(self, post_id: str, user_id: Optional[str] = None) -> ForumPostOut:
"""获取帖子详情(增加浏览量)"""
async def update_post(
self,
post_id: str,
user_id: str,
data: ForumPostUpdate,
) -> ForumPost:
"""更新帖子"""
async def delete_post(self, post_id: str, user_id: str) -> None:
"""删除帖子(软删除)"""
async def pin_post(self, post_id: str, is_pinned: bool) -> None:
"""置顶/取消置顶"""
async def lock_post(self, post_id: str, is_locked: bool) -> None:
"""锁定/解锁帖子"""
# === 回复操作 ===
async def create_reply(
self,
post_id: str,
user_id: Optional[str],
data: ForumReplyCreate,
) -> ForumReply:
"""创建回复(带锁)"""
async def get_replies(
self,
post_id: str,
page: int = 1,
page_size: int = 50,
) -> PaginatedResult[ForumReplyOut]:
"""获取回复列表"""
async def update_reply(
self,
reply_id: str,
user_id: str,
content: str,
) -> ForumReply:
"""更新回复"""
async def delete_reply(self, reply_id: str, user_id: str) -> None:
"""删除回复"""
# === 点赞操作 ===
async def toggle_like(
self,
user_id: str,
post_id: Optional[str] = None,
reply_id: Optional[str] = None,
) -> bool:
"""切换点赞状态"""
# === 标签操作 ===
async def create_tag(self, name: str, color: str = "#666666") -> ForumTag:
"""创建标签"""
async def search_tags(self, query: str) -> List[ForumTag]:
"""搜索标签"""
async def add_tags_to_post(
self,
post_id: str,
user_id: str,
tags: List[str],
) -> None:
"""为帖子添加标签"""
```
---
## 3. 安全机制
### 3.1 输入验证
```python
# Forum 安全配置
FORUM_CONFIG = {
"MAX_CONTENT_LENGTH": 50000, # 内容最大 50KB
"MAX_TITLE_LENGTH": 200, # 标题最大 200 字符
"MAX_TAGS_PER_POST": 10, # 每帖最多标签
"MAX_REPLIES_PER_POST": 500, # 每帖最多回复
"MAX_POSTS_PER_USER_PER_DAY": 50, # 每人每天最多发帖
"MAX_REPLIES_PER_USER_PER_DAY": 200, # 每人每天最多回复
}
def sanitize_input(text: str, max_length: int) -> str:
"""清理用户输入"""
if not text or not isinstance(text, str):
return ""
# 移除控制字符
text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', text)
# 限制长度
return text[:max_length]
def validate_post_data(data: ForumPostCreate) -> ForumPostCreate:
"""验证帖子数据"""
# 标题验证
data.title = sanitize_input(data.title, FORUM_CONFIG["MAX_TITLE_LENGTH"])
if len(data.title.strip()) < 3:
raise ValueError("标题至少 3 个字符")
# 内容验证
data.content = sanitize_input(data.content, FORUM_CONFIG["MAX_CONTENT_LENGTH"])
if len(data.content.strip()) < 10:
raise ValueError("内容至少 10 个字符")
# 标签验证
if len(data.tags) > FORUM_CONFIG["MAX_TAGS_PER_POST"]:
raise ValueError(f"最多 {FORUM_CONFIG['MAX_TAGS_PER_POST']} 个标签")
data.tags = [sanitize_input(t, 50) for t in data.tags]
return data
```
### 3.2 并发控制
```python
class RateLimiter:
"""简单的限流器"""
def __init__(self):
self.user_requests: Dict[str, List[datetime]] = defaultdict(list)
def check_rate_limit(
self,
user_id: str,
action: str,
max_requests: int,
window_seconds: int = 86400,
) -> bool:
"""检查是否超过限流"""
now = datetime.utcnow()
cutoff = now - timedelta(seconds=window_seconds)
# 清理过期记录
self.user_requests[user_id] = [
t for t in self.user_requests[user_id] if t > cutoff
]
if len(self.user_requests[user_id]) >= max_requests:
return False
self.user_requests[user_id].append(now)
return True
```
### 3.3 缓存策略
```python
from functools import lru_cache
from typing import Optional
import json
class ForumCache:
"""论坛缓存"""
def __init__(self):
self.cache: Dict[str, CacheEntry] = {}
self.max_size = 1000
self.default_ttl = 300 # 5 分钟
async def get(self, key: str) -> Optional[Any]:
"""获取缓存"""
if key in self.cache:
entry = self.cache[key]
if entry.is_expired():
del self.cache[key]
else:
return entry.value
return None
async def set(self, key: str, value: Any, ttl: int = None) -> None:
"""设置缓存"""
if len(self.cache) >= self.max_size:
# LRU 清理
oldest = min(self.cache.items(), key=lambda x: x[1].created_at)
del self.cache[oldest[0]]
self.cache[key] = CacheEntry(
value=value,
ttl=ttl or self.default_ttl,
)
async def invalidate(self, pattern: str) -> None:
"""清除匹配模式的缓存"""
for key in list(self.cache.keys()):
if pattern.format(key):
del self.cache[key]
```
---
## 4. API 端点扩展
### 4.1 板块 API
```python
@router.get("/boards", response_model=list[ForumBoardOut])
async def list_boards(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""列出所有板块"""
service = ForumService(db, lock_manager)
return await service.list_boards()
@router.post("/boards", response_model=ForumBoardOut, status_code=201)
async def create_board(
data: ForumBoardCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""创建板块(仅管理员)"""
if current_user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
service = ForumService(db, lock_manager)
return await service.create_board(data)
```
### 4.2 帖子 API 扩展
```python
@router.get("/posts", response_model=PaginatedResponse[ForumPostOut])
async def list_posts(
board_id: Optional[str] = None,
category: Optional[str] = None,
tags: Optional[str] = None, # comma-separated
page: int = 1,
page_size: int = 20,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""分页获取帖子"""
service = ForumService(db, lock_manager)
tag_list = tags.split(",") if tags else None
return await service.get_posts(
board_id=board_id,
category=category,
tags=tag_list,
page=page,
page_size=page_size,
)
@router.post("/posts", response_model=ForumPostOut, status_code=201)
async def create_post(
data: ForumPostCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""创建帖子"""
# 限流检查
if not rate_limiter.check_rate_limit(
current_user.id, "post", FORUM_CONFIG["MAX_POSTS_PER_USER_PER_DAY"]
):
raise HTTPException(status_code=429, detail="今日发帖次数已达上限")
# 验证数据
data = validate_post_data(data)
service = ForumService(db, lock_manager)
return await service.create_post(current_user.id, data)
@router.patch("/posts/{post_id}/pin")
async def pin_post(
post_id: str,
is_pinned: bool,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""置顶/取消置顶(仅版主+"""
service = ForumService(db, lock_manager)
await service.pin_post(post_id, is_pinned)
return {"success": True}
@router.patch("/posts/{post_id}/lock")
async def lock_post(
post_id: str,
is_locked: bool,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""锁定/解锁帖子"""
service = ForumService(db, lock_manager)
await service.lock_post(post_id, is_locked)
return {"success": True}
```
### 4.3 标签 API
```python
@router.get("/tags", response_model=list[ForumTagOut])
async def list_tags(
query: Optional[str] = None,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""搜索/列出标签"""
service = ForumService(db, lock_manager)
if query:
return await service.search_tags(query)
return await service.list_tags()
@router.post("/posts/{post_id}/tags")
async def add_tags(
post_id: str,
tags: List[str],
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""为帖子添加标签"""
service = ForumService(db, lock_manager)
await service.add_tags_to_post(post_id, current_user.id, tags)
return {"success": True}
```
### 4.4 点赞 API
```python
@router.post("/like")
async def toggle_like(
post_id: Optional[str] = None,
reply_id: Optional[str] = None,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""切换点赞状态"""
if not post_id and not reply_id:
raise HTTPException(status_code=400, detail="需要指定 post_id 或 reply_id")
service = ForumService(db, lock_manager)
liked = await service.toggle_like(
user_id=current_user.id,
post_id=post_id,
reply_id=reply_id,
)
return {"liked": liked}
```
---
## 5. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 创建 ForumLockManager | 🟢 高 |
| 2 | 创建 ForumService | 🟢 高 |
| 3 | 实现输入验证函数 | 🟢 高 |
| 4 | 实现限流器 | 🟡 中 |
| 5 | 实现缓存 | 🟡 中 |
| 6 | 扩展 Router 端点 | 🟢 高 |
| 7 | 单元测试 | 🟡 中 |
---
## 6. 核心文件变更
| 文件 | 变更 |
|------|------|
| `services/forum_service.py` | 新增 |
| `services/__init__.py` | 添加 export |
| `routers/forum.py` | 扩展端点,集成服务 |
---
## 7. 工作量估算
| 任务 | 工作量 |
|------|--------|
| ForumLockManager | 0.5 天 |
| ForumService 核心 | 1 天 |
| 输入验证/限流/缓存 | 0.5 天 |
| API 端点扩展 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **3 天** |
---
## 8. 验收标准
- [ ] ForumLockManager 可正确管理并发锁
- [ ] 输入验证可过滤危险字符
- [ ] 限流器可防止滥用
- [ ] 缓存可提升热门帖子读取速度
- [ ] 所有新增 API 端点正常工作
- [ ] 现有 API 保持向后兼容
- [ ] 单元测试覆盖核心逻辑

View File

@@ -0,0 +1,540 @@
# Phase F.3:权限系统
日期2026-04-04
状态:待开始
依赖F.2(待完成)
前置models/user.py
---
## 1. 本阶段目的
实现 Jarvis Forum 的权限系统:
- 用户角色管理user/moderator/admin
- 板块权限控制
- 操作日志记录
- 积分/奖励系统
---
## 2. 用户角色系统
### 2.1 角色定义
```python
class UserRole(str, Enum):
"""用户角色枚举"""
GUEST = "guest" # 访客 - 只能浏览
USER = "user" # 普通用户 - 可发帖/回复
MODERATOR = "moderator" # 版主 - 可管理板块
ADMIN = "admin" # 管理员 - 全权限
class Permission(str, Enum):
"""权限枚举"""
# 帖子权限
POST_CREATE = "post:create"
POST_EDIT_OWN = "post:edit:own"
POST_EDIT_ANY = "post:edit:any"
POST_DELETE_OWN = "post:delete:own"
POST_DELETE_ANY = "post:delete:any"
POST_PIN = "post:pin"
POST_LOCK = "post:lock"
POST_VIEW = "post:view"
# 回复权限
REPLY_CREATE = "reply:create"
REPLY_EDIT_OWN = "reply:edit:own"
REPLY_DELETE_OWN = "reply:delete:own"
REPLY_DELETE_ANY = "reply:delete:any"
REPLY_PIN = "reply:pin"
REPLY_BEST = "reply:best"
# 板块权限
BOARD_CREATE = "board:create"
BOARD_EDIT = "board:edit"
BOARD_DELETE = "board:delete"
# 标签权限
TAG_CREATE = "tag:create"
TAG_MANAGE = "tag:manage"
# 用户权限
USER_BAN = "user:ban"
USER_ROLE = "user:role"
# 积分权限
SCORE_VIEW = "score:view"
SCORE_MANAGE = "score:manage"
```
### 2.2 角色权限映射
```python
ROLE_PERMISSIONS: Dict[UserRole, Set[Permission]] = {
UserRole.GUEST: {
Permission.POST_VIEW,
},
UserRole.USER: {
Permission.POST_VIEW,
Permission.POST_CREATE,
Permission.POST_EDIT_OWN,
Permission.POST_DELETE_OWN,
Permission.REPLY_CREATE,
Permission.REPLY_EDIT_OWN,
Permission.REPLY_DELETE_OWN,
Permission.SCORE_VIEW,
},
UserRole.MODERATOR: {
# 用户权限
Permission.POST_VIEW,
Permission.POST_CREATE,
Permission.POST_EDIT_OWN,
Permission.POST_DELETE_OWN,
Permission.REPLY_CREATE,
Permission.REPLY_EDIT_OWN,
Permission.REPLY_DELETE_OWN,
# 版主权限
Permission.POST_PIN,
Permission.POST_LOCK,
Permission.POST_DELETE_ANY,
Permission.REPLY_PIN,
Permission.REPLY_BEST,
Permission.TAG_MANAGE,
Permission.SCORE_VIEW,
},
UserRole.ADMIN: set(Permission), # 所有权限
}
```
---
## 3. User 模型扩展
### 3.1 新增字段
```python
# models/user.py 扩展
class User(BaseModel):
__tablename__ = "users"
# === 现有字段 ===
id: str
username: str
email: str
hashed_password: str
avatar: Optional[str]
created_at: datetime
# === Forum 相关扩展 ===
role: str = "user" # guest/user/moderator/admin
forum_score: int = 0 # 论坛积分
# 论坛统计
post_count: int = 0
reply_count: int = 0
like_received: int = 0
best_reply_count: int = 0
# 状态
is_forum_banned: bool = False # 论坛禁言
forum_ban_until: Optional[datetime] = None # 禁言截止时间
# 板块版主关系
moderated_boards: List[str] = [] # 管理的板块 ID 列表
```
---
## 4. Permission Service
### 4.1 服务实现
```python
class PermissionService:
"""权限服务"""
def __init__(self, db: AsyncSession):
self.db = db
def has_permission(self, user: User, permission: Permission) -> bool:
"""检查用户是否拥有某权限"""
if user.is_forum_banned:
return False
if user.forum_ban_until and user.forum_ban_until > datetime.utcnow():
return False
role = UserRole(user.role)
return permission in ROLE_PERMISSIONS.get(role, set())
def has_board_permission(
self,
user: User,
permission: Permission,
board_id: str,
) -> bool:
"""检查用户在特定板块的权限"""
if not self.has_permission(user, permission):
return False
# 版主只能管理自己板块
if role == UserRole.MODERATOR:
return board_id in user.moderated_boards
return True
async def can_edit_post(self, user: User, post: ForumPost) -> bool:
"""检查用户是否可以编辑帖子"""
if user.role == UserRole.ADMIN:
return True
if user.role == UserRole.MODERATOR:
return post.board_id in user.moderated_boards
return post.user_id == user.id
async def can_delete_post(self, user: User, post: ForumPost) -> bool:
"""检查用户是否可以删除帖子"""
if user.role == UserRole.ADMIN:
return True
if user.role == UserRole.MODERATOR:
return post.board_id in user.moderated_boards
return post.user_id == user.id
async def ban_user(
self,
admin: User,
target_user_id: str,
reason: str,
duration: Optional[timedelta] = None,
) -> None:
"""禁言用户"""
if admin.role != UserRole.ADMIN:
raise PermissionError("需要管理员权限")
result = await self.db.execute(
select(User).where(User.id == target_user_id)
)
target_user = result.scalar_one_or_none()
if not target_user:
raise ValueError("用户不存在")
target_user.is_forum_banned = True
target_user.forum_ban_until = (
datetime.utcnow() + duration if duration else None
)
# 记录操作日志
await self._log_action(
admin=admin,
action="ban_user",
target_id=target_user_id,
details={"reason": reason, "duration": str(duration)},
)
```
### 4.2 依赖注入
```python
# 在 ForumService 中注入
class ForumService:
def __init__(
self,
db: AsyncSession,
lock_manager: ForumLockManager,
permission_service: PermissionService,
):
self.db = db
self.lock = lock_manager
self.perms = permission_service
```
---
## 5. 操作日志
### 5.1 日志模型
```python
class ForumLog(BaseModel):
__tablename__ = "forum_logs"
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
user_id: str # 操作者
action: str # 操作类型
target_type: str # post/reply/board/user/tag
target_id: str # 目标 ID
details: Optional[str] = None # JSON 详情
ip_address: Optional[str] = None
user_agent: Optional[str] = None
created_at: datetime = Field(default_factory=datetime.utcnow)
```
### 5.2 日志记录
```python
async def _log_action(
self,
admin: User,
action: str,
target_id: str,
details: Optional[dict] = None,
request: Optional[Request] = None,
) -> None:
"""记录操作日志"""
log = ForumLog(
user_id=admin.id,
action=action,
target_type=action.split(":")[0],
target_id=target_id,
details=json.dumps(details) if details else None,
ip_address=request.client.host if request else None,
user_agent=request.headers.get("user-agent") if request else None,
)
self.db.add(log)
await self.db.commit()
# 日志操作类型
class ForumAction(str, Enum):
POST_CREATE = "post:create"
POST_UPDATE = "post:update"
POST_DELETE = "post:delete"
POST_PIN = "post:pin"
POST_LOCK = "post:lock"
REPLY_CREATE = "reply:create"
REPLY_UPDATE = "reply:update"
REPLY_DELETE = "reply:delete"
USER_BAN = "user:ban"
USER_UNBAN = "user:unban"
SCORE_CHANGE = "score:change"
BOARD_CREATE = "board:create"
BOARD_UPDATE = "board:update"
BOARD_DELETE = "board:delete"
```
---
## 6. 积分系统
### 6.1 积分规则
```python
SCORE_RULES = {
# 帖子相关
"post_create": 5, # 发帖
"post_delete": -5, # 删除帖子
"post_liked": 2, # 帖子被点赞
"post_best": 10, # 帖子被设为精华
# 回复相关
"reply_create": 2, # 回复
"reply_delete": -2, # 删除回复
"reply_liked": 1, # 回复被点赞
"reply_best": 5, # 回复被设为最佳
"reply_adopted": 10, # 提问被采纳
# 活跃相关
"daily_login": 1, # 每日登录
"daily_post": 3, # 每日首次发帖
"daily_reply": 1, # 每日首次回复
}
class ScoreService:
"""积分服务"""
def __init__(self, db: AsyncSession):
self.db = db
async def add_score(
self,
user_id: str,
action: str,
reason: str,
) -> int:
"""增加积分"""
score_delta = SCORE_RULES.get(action, 0)
result = await self.db.execute(
select(User).where(User.id == user_id)
)
user = result.scalar_one_or_none()
if user:
user.forum_score += score_delta
# 更新统计
if action.startswith("post_"):
user.post_count += 1
elif action.startswith("reply_"):
user.reply_count += 1
if action == "post_liked":
user.like_received += 1
elif action == "reply_liked":
user.like_received += 1
elif action == "reply_best":
user.best_reply_count += 1
await self.db.commit()
return score_delta
async def get_leaderboard(
self,
limit: int = 10,
period: str = "all", # all/month/week
) -> List[dict]:
"""获取积分排行榜"""
query = select(User).where(User.forum_score > 0)
if period == "month":
# 本月排行
pass
elif period == "week":
# 本周排行
pass
query = query.order_by(desc(User.forum_score)).limit(limit)
result = await self.db.execute(query)
return [
{
"rank": i + 1,
"user_id": user.id,
"username": user.username,
"avatar": user.avatar,
"score": user.forum_score,
}
for i, user in enumerate(result.scalars().all())
]
```
---
## 7. API 端点
### 7.1 管理端点
```python
@router.post("/admin/ban/{user_id}")
async def ban_user(
user_id: str,
reason: str,
duration: Optional[int] = None, # 天数
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""禁言用户(仅管理员)"""
if current_user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
perm_service = PermissionService(db)
await perm_service.ban_user(
admin=current_user,
target_user_id=user_id,
reason=reason,
duration=timedelta(days=duration) if duration else None,
)
return {"success": True}
@router.post("/admin/unban/{user_id}")
async def unban_user(
user_id: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""解除禁言"""
if current_user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
perm_service = PermissionService(db)
await perm_service.unban_user(current_user, user_id)
return {"success": True}
@router.get("/admin/logs")
async def get_logs(
target_id: Optional[str] = None,
action: Optional[str] = None,
page: int = 1,
page_size: int = 50,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""查看操作日志(仅管理员/版主)"""
if current_user.role not in ["admin", "moderator"]:
raise HTTPException(status_code=403, detail="权限不足")
# 查询日志...
return {"logs": [], "total": 0}
@router.get("/leaderboard")
async def get_leaderboard(
limit: int = 10,
period: str = "all",
db: AsyncSession = Depends(get_db),
):
"""获取积分排行榜"""
score_service = ScoreService(db)
return await score_service.get_leaderboard(limit, period)
```
---
## 8. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 扩展 User 模型 | 🟢 高 |
| 2 | 创建 PermissionService | 🟢 高 |
| 3 | 实现权限检查装饰器 | 🟢 高 |
| 4 | 创建 ForumLog 模型 | 🟡 中 |
| 5 | 实现操作日志记录 | 🟡 中 |
| 6 | 创建 ScoreService | 🟡 中 |
| 7 | 实现积分规则 | 🟡 中 |
| 8 | 扩展管理 API | 🟡 中 |
| 9 | 单元测试 | 🟡 中 |
---
## 9. 核心文件变更
| 文件 | 变更 |
|------|------|
| `models/user.py` | 扩展角色和统计字段 |
| `models/forum.py` | 新增 ForumLog |
| `services/permission_service.py` | 新增 |
| `services/score_service.py` | 新增 |
| `services/forum_service.py` | 集成权限检查 |
| `routers/forum.py` | 扩展管理端点 |
---
## 10. 工作量估算
| 任务 | 工作量 |
|------|--------|
| User 模型扩展 | 0.5 天 |
| PermissionService | 0.5 天 |
| 操作日志 | 0.5 天 |
| ScoreService | 0.5 天 |
| API 端点 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **3 天** |
---
## 11. 验收标准
- [ ] User 模型正确存储角色和积分
- [ ] PermissionService 可正确检查权限
- [ ] 权限不足时返回 403
- [ ] 所有管理操作记录日志
- [ ] 积分根据规则正确增减
- [ ] 排行榜正确排序
- [ ] 禁言功能正常工作
- [ ] 单元测试覆盖核心逻辑

View File

@@ -0,0 +1,652 @@
# Phase F.4AI 集成
日期2026-04-04
状态:待开始
依赖F.3(待完成)
前置services/forum_ai_service.py
---
## 1. 本阶段目的
为 Jarvis Forum 集成 AI 能力:
- AI 自动回复
- 帖子摘要生成
- 智能分类打标
- Agent 自主发帖
---
## 2. Forum AI Service
### 2.1 服务架构
```
┌─────────────────────────────────────────────────────────────┐
│ ForumAIService │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ AutoReply │ │ Summary │ │ SmartTagging │ │
│ │ Service │ │ Service │ │ Service │ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ └────────────────┼───────────────────┘ │
│ │ │
│ ┌───────────┴───────────┐ │
│ │ LLM Service │ │
│ │ (复用 Agent LLM) │ │
│ └───────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 核心服务
```python
class ForumAIService:
"""论坛 AI 服务"""
def __init__(
self,
llm_service: LLMService,
config: ForumAIConfig,
):
self.llm = llm_service
self.config = config
# === 自动回复 ===
async def generate_auto_reply(
self,
post: ForumPost,
replies: List[ForumReply],
) -> Optional[str]:
"""生成自动回复"""
if not self.config.auto_reply_enabled:
return None
# 检查是否需要回复
if not self._should_auto_reply(post):
return None
# 构建上下文
context = self._build_reply_context(post, replies)
# 生成回复
prompt = self._build_reply_prompt(context)
response = await self.llm.agenerate(prompt)
return response.content if response else None
def _should_auto_reply(self, post: ForumPost) -> bool:
"""判断是否应该自动回复"""
# 指令类帖子不自动回复
if post.category == "instruction":
return False
# 有 AI 回复的不重复回复
if any(r.is_ai_reply for r in post.replies):
return False
# 检查时间窗口
if post.last_reply_at:
hours_since = (datetime.utcnow() - post.last_reply_at).total_seconds() / 3600
if hours_since < self.config.auto_reply_min_hours:
return False
return True
def _build_reply_context(
self,
post: ForumPost,
replies: List[ForumReply],
) -> str:
"""构建回复上下文"""
context = f"""## 帖子信息
标题: {post.title}
内容: {post.content[:500]}...
## 回复列表
"""
for reply in replies[-5:]: # 最近 5 条
context += f"- {reply.user_id}: {reply.content[:200]}...\n"
return context
def _build_reply_prompt(self, context: str) -> str:
"""构建回复提示词"""
return f"""你是一个友好的社区助手。请根据以下帖子内容,生成一条有帮助的回复。
{context}
要求:
1. 回复要友好、专业、有帮助
2. 不要重复已有的观点
3. 如果是问题,尽量给出建设性的建议
4. 回复长度控制在 100-300 字
5. 不要使用 Markdown 格式,直接输出文本
回复:"""
# === 摘要生成 ===
async def generate_summary(
self,
content: str,
max_length: int = 200,
) -> str:
"""生成帖子摘要"""
if len(content) <= max_length:
return content
prompt = f"""请为以下内容生成一个简洁的摘要,不超过 {max_length} 字。
内容:
{content}
摘要:"""
response = await self.llm.agenerate(prompt)
return response.content if response else content[:max_length]
# === 智能打标 ===
async def suggest_tags(
self,
title: str,
content: str,
existing_tags: List[str],
) -> List[str]:
"""智能推荐标签"""
prompt = f"""请根据以下帖子内容,推荐 3-5 个最合适的标签。
标题: {title}
内容: {content[:1000]}...
已有标签: {', '.join(existing_tags) if existing_tags else ''}
要求:
1. 标签要简洁2-4 个字
2. 选择最相关的标签
3. 如果已有标签合适可以保留
4. 只输出标签,用逗号分隔,不要其他内容
标签:"""
response = await self.llm.agenerate(prompt)
if not response:
return existing_tags
# 解析标签
tags = [t.strip() for t in response.content.split(",")]
return tags[:5] # 最多 5 个
# === 智能分类 ===
async def classify_category(
self,
title: str,
content: str,
) -> str:
"""智能分类"""
categories = ["instruction", "discussion", "question", "praise", "bug", "other"]
prompt = f"""请为以下帖子选择一个最合适的分类。
标题: {title}
内容: {content[:500]}...
可选分类:
- instruction: 指令/任务请求
- discussion: 讨论/分享
- question: 问题/求助
- praise: 表扬/感谢
- bug: Bug 反馈
- other: 其他
请直接输出分类名称,不要其他内容。
分类:"""
response = await self.llm.agenerate(prompt)
if not response:
return "other"
category = response.content.strip().lower()
if category not in categories:
return "other"
return category
```
---
## 3. Summary Service
### 3.1 服务实现
```python
class SummaryService:
"""帖子摘要服务"""
def __init__(self, cache: ForumCache):
self.cache = cache
async def get_post_summary(
self,
post_id: str,
post: ForumPost,
) -> str:
"""获取帖子摘要(带缓存)"""
cache_key = f"summary:{post_id}"
# 尝试从缓存获取
cached = await self.cache.get(cache_key)
if cached:
return cached
# 生成摘要
summary = await self._generate_summary(post)
# 存入缓存1 小时)
await self.cache.set(cache_key, summary, ttl=3600)
return summary
async def get_thread_summary(
self,
post: ForumPost,
replies: List[ForumReply],
) -> str:
"""获取帖子串摘要(主帖+回复摘要)"""
summaries = [await self.get_post_summary(post.id, post)]
# 汇总回复要点
if len(replies) > 0:
summary_prompt = f"""请总结以下回复的核心观点,用 50 字以内概括。
回复列表:
{self._format_replies(replies[:10])}
总结:"""
response = await self.llm.agenerate(summary_prompt)
if response:
summaries.append(f"回复要点: {response.content}")
return "\n\n".join(summaries)
async def invalidate_summary(self, post_id: str) -> None:
"""清除摘要缓存"""
await self.cache.invalidate(f"summary:{post_id}")
```
---
## 4. Agent 自主发帖
### 4.1 Agent Forum 工具
```python
# agents/tools/forum_tools.py
from typing import List, Optional
class ForumTools:
"""Forum Agent 工具集"""
def __init__(self, forum_service: ForumService, ai_service: ForumAIService):
self.forum = forum_service
self.ai = ai_service
@tool
async def create_forum_post(
title: str,
content: str,
board_id: Optional[str] = None,
category: Optional[str] = None,
tags: Optional[List[str]] = None,
) -> dict:
"""创建论坛帖子
参数:
- title: 帖子标题
- content: 帖子内容
- board_id: 板块 ID可选
- category: 分类(可选)
- tags: 标签列表(可选)
"""
data = ForumPostCreate(
title=title,
content=content,
board_id=board_id,
category=category,
tags=tags or [],
)
post = await self.forum.create_post(agent_id=AGENT_ID, data=data)
return {"post_id": post.id, "title": post.title}
@tool
async def reply_to_post(
post_id: str,
content: str,
) -> dict:
"""回复帖子
参数:
- post_id: 帖子 ID
- content: 回复内容
"""
data = ForumReplyCreate(content=content)
reply = await self.forum.create_reply(
post_id=post_id,
agent_id=AGENT_ID,
data=data,
)
return {"reply_id": reply.id, "floor": reply.floor}
@tool
async def search_forum_posts(
query: str,
board_id: Optional[str] = None,
category: Optional[str] = None,
limit: int = 10,
) -> List[dict]:
"""搜索论坛帖子
参数:
- query: 搜索关键词
- board_id: 限定板块(可选)
- category: 限定分类(可选)
- limit: 返回数量(默认 10
"""
posts = await self.forum.search_posts(
query=query,
board_id=board_id,
category=category,
limit=limit,
)
return [
{"id": p.id, "title": p.title, "summary": p.content[:100]}
for p in posts
]
@tool
async def get_forum_trending(
board_id: Optional[str] = None,
limit: int = 5,
) -> List[dict]:
"""获取热门帖子
参数:
- board_id: 板块 ID可选
- limit: 返回数量(默认 5
"""
posts = await self.forum.get_trending(
board_id=board_id,
limit=limit,
)
return [
{
"id": p.id,
"title": p.title,
"reply_count": p.reply_count,
"view_count": p.view_count,
}
for p in posts
]
```
### 4.2 Agent 配置
```python
# agents/prompts/forum_agent.py
FORUM_AGENT_PROMPT = """你是一个活跃的社区成员,可以帮助用户解决问题和参与讨论。
## 你的能力
1. 在论坛发帖分享信息或见解
2. 回复其他用户的帖子
3. 搜索论坛内容
4. 查看热门帖子
## 行为规则
1. 只在有帮助时才发帖/回复,不要刷屏
2. 回复要专业、有建设性
3. 如果用户的问题已经解决,不要重复回答
4. 遇到 Bug 或问题可以主动发帖提醒
5. 定期查看论坛,如果有重要帖子可以参与讨论
## 当前时间
{current_time}
## 用户信息
用户名: {username}
积分: {forum_score}
"""
# Agent 可用的 Forum 工具
FORUM_TOOLS = [
create_forum_post,
reply_to_post,
search_forum_posts,
get_forum_trending,
]
```
---
## 5. 定时任务
### 5.1 自动回复任务
```python
# tasks/forum_auto_reply.py
from apscheduler.schedulers.asyncio import AsyncIOScheduler
async def auto_reply_task():
"""自动回复任务"""
# 获取需要回复的帖子
posts = await forum_service.get_pending_posts(
min_age_hours=settings.auto_reply_min_hours,
limit=10,
)
for post in posts:
replies = await forum_service.get_replies(post.id)
# 生成回复
response = await ai_service.generate_auto_reply(post, replies)
if response:
# 发布回复
await forum_service.create_reply(
post_id=post.id,
agent_id=AGENT_ID,
data=ForumReplyCreate(content=response),
)
# 更新回复计数
await forum_service.increment_reply_count(post.id)
def setup_forum_scheduler(scheduler: AsyncIOScheduler):
"""配置论坛定时任务"""
# 每小时检查一次自动回复
scheduler.add_job(
auto_reply_task,
"interval",
hours=1,
id="forum_auto_reply",
)
```
---
## 6. 配置项
### 6.1 AI 配置
```python
class ForumAIConfig:
"""Forum AI 配置"""
# 自动回复
auto_reply_enabled: bool = True
auto_reply_min_hours: int = 24 # 帖子发布 N 小时后才自动回复
auto_reply_max_per_day: int = 10 # 每天最多自动回复 N 条
# 摘要生成
summary_enabled: bool = True
summary_max_length: int = 200
summary_cache_ttl: int = 3600 # 缓存 1 小时
# 智能打标
smart_tagging_enabled: bool = True
smart_tagging_max_tags: int = 5
# 智能分类
smart_classification_enabled: bool = True
# settings.py
class Settings(BaseSettings):
# Forum AI
forum_ai_auto_reply: bool = True
forum_ai_summary: bool = True
forum_ai_smart_tagging: bool = True
```
---
## 7. API 端点
### 7.1 AI 相关端点
```python
@router.post("/posts/{post_id}/generate-summary")
async def generate_post_summary(
post_id: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""生成帖子摘要"""
result = await db.execute(
select(ForumPost).where(ForumPost.id == post_id)
)
post = result.scalar_one_or_none()
if not post:
raise HTTPException(status_code=404, detail="帖子不存在")
ai_service = ForumAIService(llm_service, config)
summary = await ai_service.generate_summary(post.content)
return {"summary": summary}
@router.post("/posts/suggest-tags")
async def suggest_post_tags(
title: str,
content: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""推荐帖子标签"""
ai_service = ForumAIService(llm_service, config)
tags = await ai_service.suggest_tags(title, content, [])
return {"tags": tags}
@router.post("/posts/classify")
async def classify_post(
title: str,
content: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""分类帖子"""
ai_service = ForumAIService(llm_service, config)
category = await ai_service.classify_category(title, content)
return {"category": category}
@router.get("/posts/{post_id}/ai-status")
async def get_ai_status(
post_id: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""获取帖子 AI 状态"""
result = await db.execute(
select(ForumPost).where(ForumPost.id == post_id)
)
post = result.scalar_one_or_none()
if not post:
raise HTTPException(status_code=404, detail="帖子不存在")
# 检查 AI 回复状态
has_ai_reply = any(r.is_ai_reply for r in post.replies)
summary_cached = await cache.get(f"summary:{post_id}")
return {
"has_ai_reply": has_ai_reply,
"summary_available": bool(summary_cached),
}
```
---
## 8. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 创建 ForumAIService | 🟢 高 |
| 2 | 实现自动回复 | 🟢 高 |
| 3 | 实现摘要生成 | 🟡 中 |
| 4 | 实现智能打标 | 🟡 中 |
| 5 | 实现智能分类 | 🟡 中 |
| 6 | 创建 ForumTools | 🟢 高 |
| 7 | 配置定时任务 | 🟡 中 |
| 8 | 扩展 API 端点 | 🟡 中 |
| 9 | 单元测试 | 🟡 中 |
---
## 9. 核心文件变更
| 文件 | 变更 |
|------|------|
| `services/forum_ai_service.py` | 新增 |
| `services/summary_service.py` | 新增 |
| `agents/tools/forum_tools.py` | 新增 |
| `agents/prompts/forum_agent.py` | 新增 |
| `tasks/forum_auto_reply.py` | 新增 |
| `routers/forum.py` | 扩展 AI 端点 |
---
## 10. 工作量估算
| 任务 | 工作量 |
|------|--------|
| ForumAIService | 1 天 |
| 自动回复 | 1 天 |
| 摘要/打标/分类 | 1 天 |
| ForumTools | 1 天 |
| 定时任务 | 0.5 天 |
| API 端点 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **5.5 天** |
---
## 11. 验收标准
- [ ] ForumAIService 可正常调用 LLM
- [ ] 自动回复功能正常工作
- [ ] 摘要生成功能正常
- [ ] 智能打标推荐准确
- [ ] 智能分类推荐准确
- [ ] ForumTools 可被 Agent 调用
- [ ] 定时任务正常执行
- [ ] API 端点正常工作
- [ ] 单元测试覆盖核心逻辑

View File

@@ -0,0 +1,155 @@
# Jarvis Memory 升级计划索引
本目录用于存放 Jarvis 记忆系统的分阶段升级规划文档。
## 文档说明
| 文件 | 说明 |
|------|------|
| `README.md` | 总览、阶段关系、实施顺序 |
| `phase-m-0-current-state.md` | 当前现状、问题、目标架构 |
| `phase-m-1-importance-scoring.md` | 重要性评分系统 |
| `phase-m-2-forgetting-system.md` | 遗忘曲线系统 |
| `phase-m-3-proactive-reminder.md` | 主动提醒系统 |
| `checklist.md` | 执行清单 |
## 推荐阅读顺序
1. 先读 `phase-m-0-current-state.md`
2. 再按顺序阅读 phase m-1 ~ m-3
3. 实施时严格按阶段推进
4. 参考 `checklist.md` 进行任务追踪
---
## 总体升级原则
1. **频率追踪** - 每次交互更新记忆频率
2. **重要性分层** - 高频/情绪/影响面 → 重要记忆
3. **遗忘曲线** - 低频记忆自然衰减
4. **主动关心** - 定期生成提醒,而非被动响应
5. **可独立推进** - Phase M 可与 Agent Phase 1-5 并行
---
## 阶段总览图
```
M.0 ──────────────────────────────────────────────────────────────┐
│ 现状与目标 │
│ - 当前记忆架构分析 │
│ - 短板识别 │
│ - 拟人记忆目标 │
└────────────────────────────────────────────────────────────────────┘
M.1 ──────────────────────────────────────────────────────────────┐
│ 重要性评分系统 │
│ - MemoryFrequencyTracker (频率追踪) │
│ - EmotionAnalyzer (情绪分析) │
│ - ImpactScorer (影响面评估) │
│ │
│ 核心文件: services/memory/importance_scorer.py │
│ 工作量: 4 天 │
└────────────────────────────────────────────────────────────────────┘
M.2 ──────────────────────────────────────────────────────────────┐
│ 遗忘曲线系统 │
│ - ForgettingCurve (遗忘曲线) │
│ - MemoryDecay (记忆衰减) │
│ - ReinforcementTrigger (强化触发) │
│ │
│ 核心文件: services/memory/forgetting_curve.py │
│ 依赖: M.1 │
│ 工作量: 3 天 │
└────────────────────────────────────────────────────────────────────┘
M.3 ──────────────────────────────────────────────────────────────┐
│ 主动提醒系统 │
│ - DailyDigestGenerator (每日摘要) │
│ - ReminderScheduler (提醒调度) │
│ - ProactiveMemoryInformer (主动提醒) │
│ │
│ 核心文件: services/memory/proactive_reminder.py │
│ 依赖: M.1, M.2 │
│ 工作量: 5 天 │
└────────────────────────────────────────────────────────────────────┘
```
---
## 核心借鉴
| 借鉴点 | 来源 | 难度 |
|--------|------|------|
| 频率追踪 | 儿童认知发育模型 | 🟢 低 |
| 艾宾浩斯遗忘曲线 | 心理学研究 | 🟢 低 |
| 重要性评分 | Jarvis 自身需求 | 🟡 中 |
| 主动提醒 | 儿童认知发育模型 | 🟡 中 |
**注:本升级不借鉴 VCPToolBox因为 VCPToolBox 解决的是「检索精度」问题,而本升级解决的是「记忆价值判断」问题。**
---
## 实施顺序
```
M.0 → M.1 → M.2 → M.3
│ │ │
│ │ └── 主动提醒系统
│ └── 遗忘曲线系统
└── 现状与目标
```
**注意:** M.1 是基础M.2 和 M.3 都依赖 M.1。
---
## 文件变更追踪
| Phase | 新增文件 | 修改文件 |
|-------|---------|---------|
| M.1 | `services/memory/importance_scorer.py`, `services/memory/frequency_tracker.py`, `services/memory/emotion_analyzer.py`, `tests/test_importance_scorer.py` | `models/memory.py`, `services/memory_service.py` |
| M.2 | `services/memory/forgetting_curve.py`, `tests/test_forgetting_curve.py` | `models/memory.py`, `services/memory_service.py` |
| M.3 | `services/memory/daily_digest.py`, `services/memory/reminder_scheduler.py`, `tests/test_proactive_reminder.py` | `services/memory_service.py`, `services/scheduler_service.py` |
---
## 与 Agent Phase 1-5 的关系
| Agent Phase | Memory 协作内容 |
|-------------|----------------|
| Phase 1 | Memory 追踪用户交互频率 |
| Phase 2 | Memory 服务被 Librarian Agent 调用 |
| Phase 3 | 支持动态协作时的记忆共享 |
| Phase 4 | Memory 重要性可视化 |
| Phase 5 | 高级记忆关联分析 |
| **Phase M** | **独立 Memory 升级路径,可与 Phase 1-5 并行推进** |
---
## 注意事项
| 注意事项 | 说明 |
|---------|------|
| M.1 是基础 | M.2 和 M.3 都依赖 M.1 的重要性评分 |
| 渐进式遗忘 | 不是删除,是降权和归档 |
| 主动提醒需用户授权 | 提醒推送需要用户明确开启 |
| 不改变现有检索逻辑 | Memory 升级是独立于 RAG 的 |
---
## 目标:拟人化记忆
```
现在的 Jarvis:
用户问什么Jarvis 答什么,不问就不说
升级后的 Jarvis:
- 知道什么对你重要(频率+情绪+影响面)
- 知道什么是你的痛点(反复问的问题)
- 会主动提醒你关心的事(不是等用户问)
- 知道什么可以忘记(低频记忆自然衰减)
```

View File

@@ -0,0 +1,410 @@
# Jarvis Memory 升级执行清单
日期2026-04-04
状态:执行清单
升级方向:拟人化记忆系统
---
## 使用说明
- 完成前使用 `- [ ]`
- 完成后改成 `- [x]`
- Day M.2 默认依赖 Day M.1 的重要性评分完成后再推进
- Day M.3 默认依赖 Day M.1 和 M.2 完成后再推进
---
## Day M.1重要性评分系统4天
Day M.1 目标:让 Jarvis 知道「什么对你重要」。
### Task M.1.1:实现 FrequencyTracker
- [ ] 新增 `backend/app/services/memory/frequency_tracker.py`
- [ ] 实现 `FrequencyTracker`
- [ ] 实现 `increment()` 方法
```python
def increment(self, memory: UserMemory) -> UserMemory:
memory.frequency_count += 1
memory.last_recalled_at = datetime.now()
return memory
```
- [ ] 实现 `get_time_decay()` 方法
### Task M.1.2:实现 EmotionAnalyzer
- [ ] 新增 `backend/app/services/memory/emotion_analyzer.py`
- [ ] 实现 `EmotionAnalyzer` 类
- [ ] 定义 `EMOTION_KEYWORDS` 字典
```python
EMOTION_KEYWORDS = {
"急": 1.0,
"很重要": 0.9,
"困扰": 0.8,
"担心": 0.7,
"想解决": 0.6,
"无所谓": 0.1,
}
```
- [ ] 实现 `extract()` 方法 - 从文本提取情绪关键词
- [ ] 实现 `calculate_score()` 方法 - 计算情绪分数
### Task M.1.3:实现 ImpactEvaluator
- [ ] 新增 `backend/app/services/memory/impact_evaluator.py`
- [ ] 实现 `ImpactEvaluator` 类
- [ ] 实现 `evaluate()` 方法
```python
def evaluate(self, memory: UserMemory) -> float:
# 关联话题越多,影响面越大
return min(1.0, len(memory.associated_topics) / IMPACT_THRESHOLD)
```
### Task M.1.4:实现 ImportanceScorer
- [ ] 新增 `backend/app/services/memory/importance_scorer.py`
- [ ] 实现 `ImportanceScorer` 类
- [ ] 实现 `calculate_score()` 综合评分方法
```python
def calculate_score(self, memory: UserMemory) -> float:
frequency = self.tracker.get_frequency_score(memory) * 0.35
recency = self.tracker.get_recency_score(memory) * 0.20
emotion = self.emotion_analyzer.calculate_score(memory) * 0.25
impact = self.impact_evaluator.evaluate(memory) * 0.20
return frequency + recency + emotion + impact
```
- [ ] 实现 `get_importance_level()` 方法
- [ ] 实现 `should_escalate()` 方法
### Task M.1.5:修改 UserMemory 模型
- [ ] 修改 `backend/app/models/memory.py`
- [ ] 增加字段:
```python
frequency_count: int = 0
last_recalled_at: DateTime = None
emotion_tags: list[str] = []
importance_score: float = 0.5
importance_level: str = "medium"
associated_topics: list[str] = []
```
### Task M.1.6:集成到 MemoryService
- [ ] 修改 `backend/app/services/memory_service.py`
- [ ] 集成 `ImportanceScorer`
- [ ] 修改 `add_memory()` 方法计算重要性
- [ ] 修改 `recall_memories()` 方法按重要性排序
### Task M.1.7:补测试
- [ ] 新增 `backend/tests/services/test_importance_scorer.py`
- [ ] 测试频率追踪
- [ ] 测试情绪分析
- [ ] 测试重要性评分
- [ ] 测试重要性等级划分
### Day M.1 验收
- [ ] 频率追踪正常recall_count 每次 +1
- [ ] 情绪识别准确(「急」「很重要」等能识别)
- [ ] 重要性分数正确(高频+情绪 = importance >= 0.8
- [ ] 评分影响排序(高重要性记忆排在前面)
- [ ] 单元测试覆盖率 > 80%
---
## Day M.2遗忘曲线系统3天
Day M.2 目标:让 Jarvis 知道「什么可以忘记」。
### Task M.2.1:实现 ForgettingCurve
- [ ] 新增 `backend/app/services/memory/forgetting_curve.py`
- [ ] 实现 `ForgettingCurve` 类
- [ ] 实现 `calculate_decay()` 方法
```python
def calculate_decay(self, memory: UserMemory) -> float:
half_life = self.get_half_life(memory)
days = (datetime.now() - memory.last_accessed_at).days
return exp(-days / half_life)
```
- [ ] 实现 `get_half_life()` 方法(重要性影响半衰期)
### Task M.2.2:实现 MemoryDecay
- [ ] 新增 `backend/app/services/memory/memory_decay.py`
- [ ] 实现 `MemoryDecay` 类
- [ ] 实现 `should_archive()` 方法decay < 0.2
- [ ] 实现 `should_deprioritize()` 方法decay < 0.5
- [ ] 实现 `archive_memory()` 方法
- [ ] 实现 `restore_from_archive()` 方法
### Task M.2.3:实现 MemoryReinforcement
- [ ] 新增 `backend/app/services/memory/reinforcement.py`
- [ ] 实现 `MemoryReinforcement` 类
- [ ] 实现 `trigger()` 方法(召回时强化)
- [ ] 实现 `auto_reinforce()` 方法(定期强化 high 级别)
### Task M.2.4:修改 UserMemory 模型
- [ ] 修改 `backend/app/models/memory.py`
- [ ] 增加字段:
```python
decay_score: float = 1.0
is_archived: bool = False
last_accessed_at: DateTime = None
archive_at: DateTime = None
```
### Task M.2.5:集成到 MemoryService
- [ ] 修改 `backend/app/services/memory_service.py`
- [ ] 集成 ForgettingCurve
- [ ] 修改 recall_memories() 更新 last_accessed_at
- [ ] 集成 MemoryReinforcement
### Task M.2.6:添加调度任务
- [ ] 修改 `backend/app/services/scheduler_service.py`
- [ ] 添加每日遗忘检查cron: 03:00
- [ ] 添加每周强化任务cron: 周一 04:00
### Task M.2.7:补测试
- [ ] 新增 `backend/tests/services/test_forgetting_curve.py`
- [ ] 测试遗忘曲线计算
- [ ] 测试高重要性记忆衰减慢
- [ ] 测试归档/恢复
### Day M.2 验收
- [ ] 遗忘曲线正确30 天后 decay ≈ 0.5
- [ ] 高重要性记忆衰减慢high 衰减速度是 low 的 1/6
- [ ] 归档正常decay < 0.2 自动归档)
- [ ] 恢复正常(归档记忆可以恢复)
- [ ] 调度任务正常(每日检查、周强化执行)
- [ ] 单元测试覆盖率 > 80%
---
## Day M.3主动提醒系统6天
Day M.3 目标:让 Jarvis 从「等用户问」变成「主动关心」。
### Task M.3.1:实现 DailyDigestGenerator
- [ ] 新增 `backend/app/services/memory/daily_digest.py`
- [ ] 实现 `DailyDigestGenerator` 类
- [ ] 定义 `DailyDigest` 数据类
- [ ] 实现 `generate()` 方法
```python
async def generate(self, user_id: int, date: date = None) -> DailyDigest:
# 1. 获取今日对话摘要
# 2. 获取高重要性记忆
# 3. 获取待解答问题
# 4. 生成建议
```
- [ ] 实现 `get_recent_digests()` 方法
### Task M.3.2:实现 ReminderScheduler
- [ ] 新增 `backend/app/services/memory/reminder_scheduler.py`
- [ ] 定义 `Reminder` 数据类
- [ ] 实现 `ReminderScheduler` 类
- [ ] 实现 `create_reminder()` 方法
- [ ] 实现 `get_due_reminders()` 方法
- [ ] 实现 `snooze()` 方法
- [ ] 实现 `dismiss()` 方法
### Task M.3.3:实现 ProactiveInformer
- [ ] 新增 `backend/app/services/memory/proactive_informer.py`
- [ ] 实现 `ProactiveInformer` 类
- [ ] 定义 `TRIGGERS` 配置
- [ ] 定义 `INFORM_PROBABILITY` 配置
- [ ] 实现 `should_inform()` 方法
- [ ] 实现 `get_inform_message()` 方法
- [ ] 实现 `check_and_inform()` 方法
### Task M.3.4:创建提醒数据模型
- [ ] 修改数据库支持 `reminders` 表
- [ ] 新增 `backend/app/models/reminder.py`
- [ ] 或在现有模型文件中增加 Reminder 类
### Task M.3.5:集成到 MemoryService
- [ ] 修改 `backend/app/services/memory_service.py`
- [ ] 集成 DailyDigestGenerator
- [ ] 集成 ProactiveInformer
- [ ] 修改 recall_memories() 触发主动告知检查
### Task M.3.6:集成到 SchedulerService
- [ ] 修改 `backend/app/services/scheduler_service.py`
- [ ] 添加每日摘要生成cron: 22:00
- [ ] 添加提醒检查任务cron: 每 15 分钟)
### Task M.3.7:前端 - 每日摘要展示
- [ ] 修改前端对话页面
- [ ] 新增每日摘要卡片组件
- [ ] 获取和展示今日摘要
### Task M.3.8:前端 - 主动提醒推送
- [ ] 新增主动提醒 Toast 组件
- [ ] 实现稍后/知道了按钮
- [ ] 推送 WebSocket 集成
### Task M.3.9:补测试
- [ ] 新增 `backend/tests/services/test_proactive_reminder.py`
- [ ] 测试每日摘要生成
- [ ] 测试提醒创建和调度
- [ ] 测试主动告知概率
### Day M.3 验收
- [ ] 每日摘要生成正常22:00 自动生成)
- [ ] 提醒创建正常(用户可创建提醒)
- [ ] 提醒到期触发(定时推送)
- [ ] 主动告知概率正确(按配置的概率触发)
- [ ] 告知消息自然(像人说话,不生硬)
- [ ] 用户可控制(可以关闭主动提醒)
- [ ] 单元测试覆盖率 > 80%
---
## 总验收清单
### Phase M.1-M.3 必须完成
- [ ] 重要性评分系统正常工作
- [ ] 遗忘曲线系统正常工作
- [ ] 主动提醒系统正常工作
- [ ] 单元测试覆盖率 > 80%
- [ ] 集成测试通过
- [ ] 原有记忆功能无回退
---
## 总工作量估算
| Phase | 工作量 |
|-------|--------|
| M.1 重要性评分 | 4 天 |
| M.2 遗忘曲线 | 3 天 |
| M.3 主动提醒 | 6 天 |
| **合计** | **13 天** |
---
## 产出清单
| 产出 | 对应 Phase |
|------|-----------|
| `services/memory/frequency_tracker.py` | M.1 |
| `services/memory/emotion_analyzer.py` | M.1 |
| `services/memory/impact_evaluator.py` | M.1 |
| `services/memory/importance_scorer.py` | M.1 |
| `services/memory/forgetting_curve.py` | M.2 |
| `services/memory/memory_decay.py` | M.2 |
| `services/memory/reinforcement.py` | M.2 |
| `services/memory/daily_digest.py` | M.3 |
| `services/memory/reminder_scheduler.py` | M.3 |
| `services/memory/proactive_informer.py` | M.3 |
| `models/memory.py` 更新 | M.1, M.2 |
| `models/reminder.py` 新增 | M.3 |
| 前端摘要卡片 | M.3 |
| 前端提醒 Toast | M.3 |
| 单元测试 > 80% | M.1, M.2, M.3 |
| 集成测试通过 | M.1, M.2, M.3 |
---
## 与 Agent Phase 关系
| Agent Phase | Memory 协作内容 |
|-------------|----------------|
| Phase 1 | Memory 追踪用户交互频率 |
| Phase 2 | Memory 服务被 Librarian Agent 调用 |
| Phase 3 | 支持动态协作时的记忆共享 |
| Phase 4 | Memory 重要性可视化 |
| Phase 5 | 高级记忆关联分析 |
**Phase M 可与 Agent Phase 1-5 并行推进。**

View File

@@ -0,0 +1,125 @@
# Phase M.0Memory 现状与目标
日期2026-04-04
状态:已完成
升级方向:拟人化记忆系统
---
## 1. 本阶段目的
本文件用于统一背景认知,明确:
- Jarvis 当前记忆系统处于什么水平
- 主要短板是什么
- 为什么要升级
- 升级后的目标形态是什么
---
## 2. 当前 Jarvis Memory 架构
### 2.1 核心流程
```
用户对话 → MemoryService → Mem0 (facts/preferences/goals)
→ BrainService → BrainMemory
→ SQLite (memory_summaries)
```
### 2.2 核心文件
| 文件 | 职责 |
|------|------|
| `backend/app/services/memory_service.py` | 三层记忆管理 |
| `backend/app/services/brain_service.py` | Brain 学习与回忆 |
| `backend/app/models/memory.py` | MemorySummary, UserMemory 模型 |
| `backend/app/models/brain.py` | BrainEvent, BrainMemory, BrainTag 模型 |
---
## 3. 当前能力矩阵
| 能力 | 状态 | 说明 |
|------|------|------|
| 短期记忆 | ✅ | messages 表存储当前会话 |
| 中期记忆 | ✅ | memory_summaries 跨会话摘要 |
| 长期记忆 | ✅ | Mem0 facts/preferences/goals |
| 记忆检索 | ✅ | Mem0 语义搜索 + SQLite LIKE |
| 记忆重要性 | ❌ | 无重要性评分 |
| 频率追踪 | ❌ | 无频率统计 |
| 遗忘机制 | ❌ | 存了就不删 |
| 主动提醒 | ❌ | 完全被动响应 |
---
## 4. 当前短板
| 短板 | 严重程度 | 影响 |
|------|----------|------|
| 无重要性评分 | 🔴 高 | 无法区分重要/不重要记忆 |
| 无频率追踪 | 🔴 高 | 反复出现的痛点无法识别 |
| 无遗忘机制 | 🟡 中 | 存储无限增长,冷门知识占空间 |
| 无主动提醒 | 🔴 高 | 用户不问就不说,不像助理 |
| 无情绪感知 | 🟡 中 | 无法区分用户急/重要的表达 |
---
## 5. 目标架构
```
┌─────────────────────────────────────────────────────────────┐
│ 用户对话输入 │
└─────────────────────────┬───────────────────────────────────┘
┌───────────┴───────────┐
│ Importance Scorer │ ← M.1 新增
│ - 频率 (你提几次了) │
│ - 情绪 (急/重要/困扰) │
│ - 影响面 (关联多少) │
│ - 时间 (最近/当时) │
└───────────┬───────────┘
┌────────────────┼────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 高优先级 │ │ 中优先级 │ │ 低优先级 │
│ (主动提醒)│ │ (记住) │ │ (归档) │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
▼ ▼ ▼
形成观点 选择性回忆 自然遗忘
主动提醒 需要时召回 (降权归档)
```
---
## 6. 与 VCPToolBox 的关系
| VCPToolBox 能力 | 本升级是否借鉴 | 理由 |
|-----------------|---------------|------|
| TagMemo 向量检索 | ❌ 不借鉴 | 你需要的是「记住什么」,不是「检索更准」 |
| LIF 脉冲扩散 | ❌ 不借鉴 | 同上 |
| EPA 语义分析 | ❌ 不借鉴 | 同上 |
| 频率追踪 | ✅ 参考 | 这是你核心需要的VCPToolBox 没有,自己设计 |
**结论VCPToolBox 解决的是「知识管理」,本升级解决的是「记忆价值判断」,是两个不同的问题。**
---
## 7. 升级后的直观改变
| 现在 | 升级后 |
|------|--------|
| 问 3 次同一个问题Jarvis 每次都当新问题 | 第 2 次就记住,第 3 次能说"你之前问过..." |
| 很少用的知识自动沉底 | 低频知识自动归档,需要时能恢复 |
| 你问"明天干嘛" → 只能看日程 | 主动说"你昨天说想换工作,提醒你查一下 JD" |
| 告诉 Jarvis 的事可能下次就忘了 | 重要的事主动记,冷门的事知道就行 |
---
## 8. 本阶段产出要求
- [x] 团队对 Jarvis 当前记忆问题和目标方向达成一致
- [x] 明确了与 VCPToolBox 的关系(不借鉴 VCPToolBox
- [x] 后续 phase 文档能够在这个认知基础上展开

View File

@@ -0,0 +1,219 @@
# Phase M.1:重要性评分系统
日期2026-04-04
状态:规划中
依赖:无
工作量4 天
---
## 1. 本阶段目的
建立记忆重要性评分体系,让 Jarvis 知道「什么对你重要」。
核心问题:
- 你提了 3 次同一个问题 → 这是你的痛点,应该深入解决
- 你说「急」「很重要」「困扰我」 → 情绪标记,应该优先级高
- 这个话题关联了多少其他话题 → 影响面越大越重要
---
## 2. 核心架构
```
┌─────────────────────────────────────────────────────────────┐
│ ImportanceScorer │
├─────────────────────────────────────────────────────────────┤
│ calculate_score(memory) → importance_score (0.0-1.0) │
│ │
│ 评分维度: │
│ - frequency_score (频率) × 0.35 │
│ - recency_score (时效) × 0.20 │
│ - emotion_score (情绪) × 0.25 │
│ - impact_score (影响面) × 0.20 │
└─────────────────────────────────────────────────────────────┘
```
---
## 3. 评分维度详解
### 3.1 Frequency Score (频率)
```python
frequency_score = min(1.0, recall_count / FREQUENCY_THRESHOLD)
# FREQUENCY_THRESHOLD = 3 # 提 3 次算高频
# 同时考虑时间衰减
time_decay = exp(-days_since_last_recall / HALF_LIFE_DAYS)
# HALF_LIFE_DAYS = 7 # 7 天减半
```
### 3.2 Recency Score (时效性)
```python
recency_score = exp(-days_since_creation / RECENCY_HALF_LIFE)
# RECENCY_HALF_LIFE = 30 # 30 天减半
# 但重要事件例外:即使久远也保持高时效
if is_emotion_tagged:
recency_score = max(recency_score, 0.7)
```
### 3.3 Emotion Score (情绪)
```python
EMOTION_KEYWORDS = {
"": 1.0,
"很重要": 0.9,
"困扰": 0.8,
"担心": 0.7,
"想解决": 0.6,
"无所谓": 0.1,
}
emotion_score = max([EMOTION_KEYWORDS.get(kw, 0)
for kw in extracted_emotions], default=0.0)
```
### 3.4 Impact Score (影响面)
```python
# 关联多少其他记忆/话题
impact_score = min(1.0, associated_memory_count / IMPACT_THRESHOLD)
# IMPACT_THRESHOLD = 5 # 关联 5 个算满
```
---
## 4. 核心文件
### 4.1 新增文件
| 文件 | 职责 |
|------|------|
| `services/memory/frequency_tracker.py` | 频率追踪器 |
| `services/memory/emotion_analyzer.py` | 情绪分析器 |
| `services/memory/impact_evaluator.py` | 影响面评估器 |
| `services/memory/importance_scorer.py` | 综合评分器 |
### 4.2 修改文件
| 文件 | 修改内容 |
|------|---------|
| `models/memory.py` | 增加 frequency_count, last_recalled_at, emotion_tags 字段 |
| `services/memory_service.py` | 集成 ImportanceScorer |
---
## 5. API 设计
### 5.1 ImportanceScorer
```python
class ImportanceScorer:
def calculate_score(self, memory: UserMemory) -> float:
"""返回 0.0-1.0 的重要性分数"""
def get_importance_level(self, memory: UserMemory) -> Literal["high", "medium", "low"]:
"""返回重要性等级"""
def should_escalate(self, memory: UserMemory) -> bool:
"""是否应该升级为高优先级记忆"""
```
### 5.2 集成到 MemoryService
```python
class MemoryService:
async def add_memory(self, user_id: int, content: str, emotion_tags: list[str] = None):
# 添加记忆时计算初始重要性
importance = self.scorer.calculate_score(new_memory)
async def recall_memories(self, query: str, user_id: int, top_k: int = 5):
# 检索时考虑重要性排序
# 高重要性记忆排在前面
```
---
## 6. 数据模型变更
### 6.1 UserMemory 扩展
```python
class UserMemory:
# 现有字段...
frequency_count: int = 0 # 被回忆次数
last_recalled_at: DateTime = None # 上次被回忆时间
emotion_tags: list[str] = [] # 情绪标签
importance_score: float = 0.5 # 重要性分数
importance_level: str = "medium" # high/medium/low
associated_topics: list[str] = [] # 关联话题
```
---
## 7. 测试设计
### 7.1 频率追踪测试
```python
def test_frequency_increase():
memory = create_memory(frequency_count=0)
memory = tracker.increment(memory)
assert memory.frequency_count == 1
def test_frequency_decay_over_time():
# 7 天后频率应该衰减
pass
```
### 7.2 情绪分析测试
```python
def test_emotion_extraction():
text = "这个问题很急,急需解决"
emotions = analyzer.extract(text)
assert "" in emotions
def test_emotion_scoring():
score = scorer.calculate_emotion_score(["", "很重要"])
assert score >= 0.9
```
### 7.3 综合评分测试
```python
def test_importance_ranking():
memories = [low_freq, high_freq, emotional]
ranked = sorter.rank_by_importance(memories)
assert ranked[0] == emotional # 情绪标签最重
```
---
## 8. 验收标准
| 标准 | 说明 |
|------|------|
| 频率追踪正常 | recall_count 每次召回 +1 |
| 情绪识别准确 | 「急」「很重要」等能识别 |
| 重要性分数正确 | 高频+情绪 = importance >= 0.8 |
| 评分影响排序 | 高重要性记忆排在检索结果前面 |
| 单元测试覆盖率 | > 80% |
---
## 9. 工作量估算
| 任务 | 工作量 |
|------|--------|
| FrequencyTracker | 0.5 天 |
| EmotionAnalyzer | 0.5 天 |
| ImpactEvaluator | 0.5 天 |
| ImportanceScorer | 1 天 |
| 模型变更 | 0.5 天 |
| 集成到 MemoryService | 0.5 天 |
| 测试 | 1 天 |
| **合计** | **4 天** |

View File

@@ -0,0 +1,228 @@
# Phase M.2:遗忘曲线系统
日期2026-04-04
状态:规划中
依赖M.1 (重要性评分)
工作量3 天
---
## 1. 本阶段目的
实现「选择性遗忘」机制,让 Jarvis 知道「什么可以忘记」。
核心问题:
- 冷门知识不应该一直占存储
- 但不能直接删,要有归档机制
- 重要记忆应该被强化,不容易忘
---
## 2. 核心架构
```
┌─────────────────────────────────────────────────────────────┐
│ ForgettingCurve │
├─────────────────────────────────────────────────────────────┤
│ decay_score = exp(-time_since_access / half_life) │
│ │
│ 遗忘策略: │
│ - decay_score < 0.2 → 归档到 cold_storage │
│ - decay_score < 0.5 → 降权,不参与主动提醒 │
│ - importance_level=high → 半衰期延长 3x │
└─────────────────────────────────────────────────────────────┘
```
---
## 3. 艾宾浩斯遗忘曲线模型
### 3.1 基础遗忘曲线
```
保留率
100% |████████████
80% |██████████
60% |███████
40% |█████
20% |██
0% |________________________ 时间
1天 7天 30天 90天
```
### 3.2 Jarvis 遗忘策略
```python
# 基础半衰期30 天
BASE_HALF_LIFE_DAYS = 30
# 重要性影响半衰期
if importance_level == "high":
half_life = BASE_HALF_LIFE_DAYS * 3 # 90 天
elif importance_level == "medium":
half_life = BASE_HALF_LIFE_DAYS * 1 # 30 天
else:
half_life = BASE_HALF_LIFE_DAYS * 0.5 # 15 天
# 遗忘分数
decay_score = exp(-days_since_access / half_life)
```
---
## 4. 核心文件
### 4.1 新增文件
| 文件 | 职责 |
|------|------|
| `services/memory/forgetting_curve.py` | 遗忘曲线计算 |
| `services/memory/memory_decay.py` | 记忆衰减处理 |
| `services/memory/reinforcement.py` | 记忆强化触发 |
### 4.2 修改文件
| 文件 | 修改内容 |
|------|---------|
| `models/memory.py` | 增加 decay_score, is_archived, last_accessed_at 字段 |
| `services/memory_service.py` | 集成遗忘逻辑 |
---
## 5. API 设计
### 5.1 ForgettingCurve
```python
class ForgettingCurve:
def calculate_decay(self, memory: UserMemory) -> float:
"""返回 0.0-1.0 的保留分数"""
def should_archive(self, memory: UserMemory) -> bool:
"""是否应该归档"""
def should_deprioritize(self, memory: UserMemory) -> bool:
"""是否应该降权(不参与主动提醒)"""
```
### 5.2 Reinforcement
```python
class MemoryReinforcement:
def trigger(self, memory_id: int) -> None:
"""被召回时触发强化"""
# 频率 +1
# decay_score 重置
def auto_reinforce(self, user_id: int) -> None:
"""定期自动强化高重要性记忆"""
# 每周检查,对 high 级别记忆做轻量强化
```
---
## 6. 归档机制
### 6.1 热/冷存储分离
```python
# 热存储:活跃记忆,参与检索和主动提醒
HOT_MEMORIES = []
# 冷存储:归档记忆,不参与主动提醒,按需恢复
COLD_MEMORIES = []
```
### 6.2 归档恢复
```python
async def restore_from_archive(self, memory_id: int) -> UserMemory:
"""从归档恢复记忆"""
# 恢复后 decay_score 重置为 0.8
# 重新加入热存储
```
---
## 7. 调度任务
### 7.1 每日遗忘检查
```python
# 每天凌晨执行
@scheduler.scheduled_task("cron", hour=3)
async def daily_forgetting_check():
"""每日遗忘检查"""
# 1. 计算所有记忆的 decay_score
# 2. 归档 decay < 0.2 的记忆
# 3. 降权 decay < 0.5 的记忆
# 4. 强化被召回的记忆
```
### 7.2 每周自动强化
```python
@scheduler.scheduled_task("cron", day_of_week="mon", hour=4)
async def weekly_reinforcement():
"""每周自动强化"""
# 对 high 重要性记忆做轻量强化
# frequency_count *= 1.1 (上限 10)
```
---
## 8. 测试设计
### 8.1 遗忘曲线测试
```python
def test_decay_after_30_days():
memory = create_memory(last_accessed_at=days_ago(30))
decay = curve.calculate_decay(memory)
assert 0.4 < decay < 0.6 # 约 50% 保留
def test_high_importance_slower_decay():
high = create_memory(importance_level="high", last_accessed_at=days_ago(30))
low = create_memory(importance_level="low", last_accessed_at=days_ago(30))
assert curve.calculate_decay(high) > curve.calculate_decay(low)
```
### 8.2 归档测试
```python
def test_archive_low_decay():
memory = create_memory(decay_score=0.15)
assert curve.should_archive(memory) == True
def test_restore_from_archive():
memory = service.restore_from_archive(memory_id)
assert memory.is_archived == False
assert memory.decay_score > 0.5
```
---
## 9. 验收标准
| 标准 | 说明 |
|------|------|
| 遗忘曲线正确 | 30 天后 decay ≈ 0.5 |
| 高重要性记忆衰减慢 | high 级别衰减速度是 low 的 1/6 |
| 归档正常 | decay < 0.2 自动归档 |
| 恢复正常 | 归档记忆可以恢复 |
| 调度任务正常 | 每日检查、周强化执行 |
| 单元测试覆盖率 | > 80% |
---
## 10. 工作量估算
| 任务 | 工作量 |
|------|--------|
| ForgettingCurve | 0.5 天 |
| MemoryDecay | 0.5 天 |
| Reinforcement | 0.5 天 |
| 归档机制 | 0.5 天 |
| 调度任务 | 0.5 天 |
| 测试 | 0.5 天 |
| **合计** | **3 天** |

View File

@@ -0,0 +1,360 @@
# Phase M.3:主动提醒系统
日期2026-04-04
状态:规划中
依赖M.1 (重要性评分), M.2 (遗忘曲线)
工作量5 天
---
## 1. 本阶段目的
让 Jarvis 从「等用户问」变成「主动关心」。
核心问题:
- Jarvis 知道用户关心什么(高重要性记忆)
- Jarvis 知道用户最近做了什么(每日摘要)
- Jarvis 主动提醒,而不是等用户问
---
## 2. 核心架构
```
┌─────────────────────────────────────────────────────────────┐
│ ProactiveReminderSystem │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ DailyDigest │ + │ Reminder │ + │ MemoryInformer│ │
│ │ Generator │ │ Scheduler │ │ │ │
│ │ │ │ │ │ │ │
│ │ 每日摘要生成 │ │ 提醒调度 │ │ 主动提醒推送 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## 3. 每日摘要生成器 (DailyDigestGenerator)
### 3.1 输入来源
```
今日输入:
- 用户今日对话摘要 (memory_summaries)
- 用户今日提到的重点 (high importance memories)
- 用户今日创建的任务 (tasks)
- 用户今日查阅的知识 (knowledge retrieval logs)
```
### 3.2 输出格式
```json
{
"date": "2026-04-04",
"summary": "今天你主要在处理工作问题,提到了换工作的想法",
"key_points": [
{"content": "想换工作", "importance": 0.9, "source": "conversation"},
{"content": "项目 deadline 是周五", "importance": 0.8, "source": "task"}
],
"pending_questions": [
{"content": "量子计算的问题还没完全理解", "importance": 0.6}
],
"suggestions": [
{"text": "明天可以继续聊换工作的话题", "reason": "重要性高且今天没深入"},
{"text": "量子计算的资料可以找找更通俗的解释", "reason": "还没理解"}
]
}
```
### 3.3 生成时机
```python
# 每天晚上 10 点生成
@scheduler.scheduled_task("cron", hour=22)
async def generate_daily_digest():
"""生成每日摘要"""
digest = await generator.generate(user_id)
await storage.save(digest)
```
---
## 4. 提醒调度器 (ReminderScheduler)
### 4.1 提醒类型
| 类型 | 触发条件 | 推送时机 |
|------|---------|---------|
| 后续提醒 | 用户说「回头提醒我」 | 指定时间 |
| 关联提醒 | 提到的话题有关联记忆 | 下次对话时 |
| 周期提醒 | 每周/每月固定事项 | 周期首日 |
| 遗忘提醒 | 归档记忆被重新提到 | 恢复后提醒 |
### 4.2 调度逻辑
```python
class ReminderScheduler:
def schedule_reminder(self, user_id: int, reminder: Reminder):
"""安排提醒"""
def get_due_reminders(self, user_id: int) -> list[Reminder]:
"""获取到期的提醒"""
def snooze_reminder(self, reminder_id: int, minutes: int):
"""推迟提醒"""
```
### 4.3 提醒存储
```python
class Reminder:
id: int
user_id: int
content: str
trigger_type: str # "time" / "context" / "periodic"
trigger_at: DateTime
context: dict # 关联的 memory_id 等
status: str # "pending" / "sent" / "snoozed"
created_at: DateTime
```
---
## 5. 主动记忆告知器 (ProactiveMemoryInformer)
### 5.1 触发条件
```python
TRIGGERS = {
"high_importance_topic": {
"condition": "用户提到高重要性话题",
"action": "提及关联记忆",
"example": "你说想换工作,要不要看看之前收藏的 JD"
},
"repeat_question": {
"condition": "用户重复问某个问题",
"action": "主动说之前回答过",
"example": "你之前问过量子计算,我再给你解释一下?"
},
"forgotten_context": {
"condition": "用户提到已归档的记忆",
"action": "提示可以恢复",
"example": "这个话题你一个月前聊过,要我恢复一下吗?"
},
"pending_goal": {
"condition": "用户设了目标但没进展",
"action": "温和提醒",
"example": "你之前说想学 Python有进展吗"
}
}
```
### 5.2 告知时机
```python
# 不是每次对话都告知,有概率控制
INFORM_PROBABILITY = {
"high_importance_topic": 0.8, # 高重要性话题 80% 主动提
"repeat_question": 1.0, # 重复问题 100% 主动提
"forgotten_context": 0.5, # 已归档话题 50% 提示
"pending_goal": 0.3, # 待办目标 30% 温和提醒
}
```
### 5.3 告知风格
```python
INFORM_STYLE = {
"casual": "对了,你之前提到...",
"gentle": "不知道你有没有注意到...",
"helpful": "我记起你关心这个,要不看看..."
}
```
---
## 6. 核心文件
### 6.1 新增文件
| 文件 | 职责 |
|------|------|
| `services/memory/daily_digest.py` | 每日摘要生成 |
| `services/memory/reminder_scheduler.py` | 提醒调度 |
| `services/memory/proactive_informer.py` | 主动告知 |
| `services/memory/reminder_model.py` | 提醒数据模型 |
### 6.2 修改文件
| 文件 | 修改内容 |
|------|---------|
| `services/scheduler_service.py` | 集成主动提醒调度 |
| `services/memory_service.py` | 集成 ProactiveInformer |
| `routers/conversation.py` | 主动告知触发点 |
---
## 7. API 设计
### 7.1 DailyDigestGenerator
```python
class DailyDigestGenerator:
async def generate(self, user_id: int, date: date = None) -> DailyDigest:
"""生成每日摘要"""
async def get_recent_digests(self, user_id: int, limit: int = 7) -> list[Digest]:
"""获取最近 N 天的摘要"""
```
### 7.2 ReminderScheduler
```python
class ReminderScheduler:
async def create_reminder(self, user_id: int, content: str, trigger_at: datetime):
"""创建提醒"""
async def get_due_reminders(self, user_id: int) -> list[Reminder]:
"""获取到期提醒"""
async def snooze(self, reminder_id: int, minutes: int):
"""推迟提醒"""
```
### 7.3 ProactiveInformer
```python
class ProactiveInformer:
def should_inform(self, user_id: int, trigger_type: str) -> bool:
"""是否应该告知"""
def get_inform_message(self, user_id: int, trigger_type: str, context: dict) -> str:
"""生成告知消息"""
async def check_and_inform(self, conversation_context: dict) -> str | None:
"""检查并返回告知消息,无则返回 None"""
```
---
## 8. 前端集成
### 8.1 每日摘要展示
```vue
<!-- 每日摘要卡片 -->
<div v-if="dailyDigest">
<h3>今日摘要</h3>
<p>{{ dailyDigest.summary }}</p>
<div v-for="point in dailyDigest.keyPoints">
- {{ point.content }}
</div>
<div v-if="dailyDigest.suggestions.length">
<h4>建议</h4>
<p>{{ dailyDigest.suggestions[0].text }}</p>
</div>
</div>
```
### 8.2 主动提醒推送
```vue
<!-- 主动提醒弹窗 -->
<div v-if="activeReminder" class="reminder-toast">
<p>{{ activeReminder.content }}</p>
<button @click="snooze">稍后</button>
<button @click="dismiss">知道了</button>
</div>
```
---
## 9. 测试设计
### 9.1 每日摘要测试
```python
async def test_digest_generation():
digest = await generator.generate(user_id=1)
assert digest.summary is not None
assert len(digest.key_points) > 0
async def test_digest_includes_high_importance():
# 高重要性记忆应该出现在摘要中
pass
```
### 9.2 提醒调度测试
```python
async def test_schedule_reminder():
reminder = await scheduler.create_reminder(
user_id=1,
content="检查邮件",
trigger_at=datetime.now() + timedelta(hours=1)
)
assert reminder.status == "pending"
async def test_get_due_reminders():
due = await scheduler.get_due_reminders(user_id=1)
assert len(due) >= 0
```
### 9.3 主动告知测试
```python
def test_should_inform_probability():
# 高重要性话题 80% 触发
count = sum(informer.should_inform(1, "high_importance_topic")
for _ in range(100))
assert 70 < count < 90 # 允许一点随机波动
def test_repeat_question_always_informs():
# 重复问题 100% 触发
assert informer.should_inform(1, "repeat_question") == True
```
---
## 10. 验收标准
| 标准 | 说明 |
|------|------|
| 每日摘要生成正常 | 22:00 自动生成 |
| 提醒创建正常 | 用户可创建提醒 |
| 提醒到期触发 | 定时推送 |
| 主动告知概率正确 | 按配置的概率触发 |
| 告知消息自然 | 像人说话,不生硬 |
| 用户可控制 | 可以关闭主动提醒 |
| 单元测试覆盖率 | > 80% |
---
## 11. 工作量估算
| 任务 | 工作量 |
|------|--------|
| DailyDigestGenerator | 1.5 天 |
| ReminderScheduler | 1.5 天 |
| ProactiveInformer | 1 天 |
| 前端摘要展示 | 0.5 天 |
| 前端提醒推送 | 0.5 天 |
| 测试 | 1 天 |
| **合计** | **6 天** |
---
## 12. 与现有系统的关系
```
现有 SchedulerService
├── 凌晨任务重建 → 现有功能
├── 每日摘要生成 → M.3 新增
└── 提醒检查 → M.3 新增
```
提醒系统独立于现有任务系统,但可以复用调度基础设施。

View File

@@ -0,0 +1,157 @@
# Jarvis RAG 升级计划索引
本目录用于存放 Jarvis RAG 系统的分阶段升级规划文档。
## 文档说明
| 文件 | 说明 |
|------|------|
| `README.md` | 总览、阶段关系、实施顺序 |
| `phase-r-0-current-state.md` | 当前现状、问题、目标架构、VCPToolBox 借鉴 |
| `phase-r-1-token-chunking.md` | Token 感知分块优化 |
| `phase-r-2-multi-index.md` | 多索引架构 |
| `phase-r-3-dynamic-weight.md` | 动态权重增强 |
| `phase-r-4-advanced.md` | 高级特性(可选) |
| `checklist.md` | 执行清单 |
## 推荐阅读顺序
1. 先读 `phase-r-0-current-state.md`
2. 再按顺序阅读 phase r-1 ~ r-4
3. 实施时严格按阶段推进R.4 为可选
4. 参考 `checklist.md` 进行任务追踪
---
## 总体升级原则
1. **Token 精确控制** - 使用 tiktoken 精确计数
2. **多索引分层** - 按知识类型/重要性分离
3. **动态适配** - 根据查询特性动态调整检索策略
4. **测试优先** - 所有升级都要配套测试
5. **可独立推进** - Phase R 可与 Agent Phase 1-5 并行
---
## 阶段总览图
```
R.0 ──────────────────────────────────────────────────────────────┐
│ 现状与目标 │
│ - 当前架构分析 │
│ - 短板识别 │
│ - VCPToolBox TagMemo V6 借鉴 │
└────────────────────────────────────────────────────────────────────┘
R.1 ──────────────────────────────────────────────────────────────┐
│ Token 感知分块优化 │
│ - tiktoken 集成 │
│ - 智能断句 │
│ - 重叠分块 (10% overlap) │
│ │
│ 核心文件: services/chunker.py │
│ 工作量: 3 天 │
└────────────────────────────────────────────────────────────────────┘
R.2 ──────────────────────────────────────────────────────────────┐
│ 多索引架构 │
│ - Collection 分离策略 │
│ - 懒加载 + LRU TTL │
│ - 重要性感知检索 │
│ │
│ 核心文件: services/multi_index.py │
│ 依赖: R.1 │
│ 工作量: 4 天 │
└────────────────────────────────────────────────────────────────────┘
R.3 ──────────────────────────────────────────────────────────────┐
│ 动态权重增强 │
│ - QueryAnalyzer │
│ - DynamicReranker │
│ - CoreTagAwareSearch │
│ │
│ 核心文件: services/query_analyzer.py, │
│ services/dynamic_reranker.py, │
│ services/core_tag_search.py │
│ 依赖: R.1 │
│ 工作量: 4.5 天 │
└────────────────────────────────────────────────────────────────────┘
R.4 ──────────────────────────────────────────────────────────────┐
│ 高级特性 (可选) │
│ - 语义去重 │
│ - 语义分桶 │
│ - EPA 分析设计 │
│ │
│ 状态: 可选 │
│ 工作量: 4.5 天 │
└────────────────────────────────────────────────────────────────────┘
```
---
## VCPToolBox TagMemo V6 核心借鉴
| 借鉴点 | 实现位置 | 难度 |
|--------|---------|------|
| Token 感知分块85%+10% 重叠) | R.1 | 🟢 低 |
| 多索引架构 | R.2 | 🟡 中 |
| 懒加载 + LRU | R.2 | 🟡 中 |
| TagBoost 动态权重 | R.3 | 🟡 中 |
| 核心标签系统1.33x 加权) | R.3 | 🟡 中 |
| LIF 脉冲扩散 | R.4 | 🔴 高 |
---
## 实施顺序
```
R.0 → R.1 → R.2 → R.3 → (R.4 可选)
│ │ │ │
│ │ │ └── 语义去重/分桶/PCA
│ │ └── 多索引 + 懒加载
│ └── Token感知分块
└── 现状与目标
```
**注意:** R.1 是基础R.2 和 R.3 都依赖 R.1R.4 可选。
---
## 文件变更追踪
| Phase | 新增文件 | 修改文件 |
|-------|---------|---------|
| R.1 | `services/chunker.py`, `tests/test_chunker.py` | `services/document_service.py` |
| R.2 | `services/multi_index.py`, `tests/test_multi_index.py` | `services/knowledge_service.py`, `models/document.py` |
| R.3 | `services/query_analyzer.py`, `services/dynamic_reranker.py`, `services/core_tag_search.py`, `tests/test_dynamic_reranker.py` | `services/knowledge_service.py`, `models/document.py` |
| R.4 | `services/deduplicator.py`, `services/semantic_bucket.py` (可选) | - |
---
## 与 Agent Phase 1-5 的关系
| Agent Phase | RAG 协作内容 |
|-------------|-------------|
| Phase 1 | Task Schema 追踪 RAG 任务 |
| Phase 2 | RAG 任务可分解给 Librarian Agent |
| Phase 3 | 支持多索引动态选择 |
| Phase 4 | RAG 检索过程可视化 |
| Phase 5 | EPA 分析、语义分桶 |
| **Phase R** | **独立 RAG 升级路径,可与 Phase 1-5 并行推进** |
---
## 注意事项
| 注意事项 | 说明 |
|---------|------|
| R.1 是基础 | R.2 和 R.3 都依赖 R.1 的分块优化 |
| Token 精确计数 | 使用 tiktoken多版本验证 |
| 索引分离 | 提供统一检索接口,隐藏内部逻辑 |
| 动态权重 | 提供配置项,允许用户调整 |
| EPA 高复杂度 | Phase R.4 可选,暂不实现 |

View File

@@ -0,0 +1,327 @@
# Jarvis RAG 升级执行清单
日期2026-04-03
状态:执行清单
借鉴来源VCPToolBox TagMemo V6 架构
---
## 使用说明
- 完成前使用 `- [ ]`
- 完成后改成 `- [x]`
- Day R.2 默认依赖 Day R.1 的分块优化完成后再推进
- Day R.3 默认依赖 Day R.1 的分块优化完成后再推进
- Day R.4 为可选特性
---
## Day R.1Token 感知分块优化3天
Day R.1 目标:解决跨块边界信息丢失问题,实现精确的 token 计数和重叠分块。
### Task R.1.1:集成 tiktoken
- [ ] 安装 tiktoken 依赖
```bash
uv add tiktoken
```
- [ ] 新增 `backend/app/services/chunker.py`
实现 `TokenAwareChunker` 类,支持 85% 安全边界
- [ ] 实现 `count_tokens()` 方法
### Task R.1.2:实现智能断句
- [ ] 实现 `find_best_breakpoint()` 函数
在断点处(标点/空白)智能断开
- [ ] 实现 `_force_split_long_text()` 方法
处理超长句子强制分割
### Task R.1.3:实现重叠分块
- [ ] 实现 `chunk_with_overlap()` 方法
10% token 重叠,保证上下文连续性
- [ ] 实现 `_create_overlap()` 方法
创建重叠部分
### Task R.1.4:集成到 DocumentService
- [ ] 修改 `backend/app/services/document_service.py`
集成新的 TokenAwareChunker
- [ ] 替换原有的 `_build_chunks()` 方法
### Task R.1.5:补测试
- [ ] 新增 `backend/tests/services/test_chunker.py`
- [ ] 测试 Token 计数准确性
- [ ] 测试智能断句
- [ ] 测试重叠分块
### Day R.1 验收
- [ ] tiktoken 正确集成token 计数误差 < 1%
- [ ] 超长句子不在词汇中间断开
- [ ] 重叠分块保证上下文连续性
- [ ] 单元测试覆盖率 > 80%
- [ ] 文档上传→分块→检索 集成测试通过
---
## Day R.2多索引架构4天
Day R.2 目标:按知识类型/重要性分层,支持懒加载和 LRU 淘汰。
### Task R.2.1:设计 Collection 分离策略
- [ ] 新增 `backend/app/services/multi_index.py`
- [ ] 定义 `MultiIndexManager` 类
- [ ] 实现 `INDEX_STRATEGIES` 配置
- default: 通用文档
- important: 重要文档
- code: 代码片段
- meeting: 会议记录
- [ ] 实现 `get_collection()` 方法
### Task R.2.2:实现懒加载 + LRU TTL
- [ ] 实现 `LazyIndexLoader` 类
- [ ] 实现 `get_or_load()` 方法
- [ ] 实现 `sweep()` 方法
2小时 TTL 淘汰机制
### Task R.2.3:实现重要性感知检索
- [ ] 实现 `retrieve_with_importance()` 方法
- [ ] important 索引加权 1.2x
### Task R.2.4:修改 Document 模型
- [ ] 修改 `backend/app/models/document.py`
- [ ] 增加 `importance` 字段Float, default=0.5
### Task R.2.5:集成到 KnowledgeService
- [ ] 修改 `backend/app/services/knowledge_service.py`
- [ ] 集成 MultiIndexManager
- [ ] 集成 LazyIndexLoader
- [ ] 根据 importance 选择索引
### Task R.2.6:补测试
- [ ] 新增 `backend/tests/services/test_multi_index.py`
- [ ] 测试多 Collection 创建
- [ ] 测试懒加载
- [ ] 测试 TTL 淘汰
### Day R.2 验收
- [ ] 多 Collection 创建成功
- [ ] 懒加载索引生效
- [ ] TTL 淘汰机制工作
- [ ] 重要性感知检索加权生效
- [ ] 单元测试覆盖率 > 80%
---
## Day R.3动态权重增强4.5天)
Day R.3 目标:根据查询特性动态调整检索策略,支持核心标签加权。
### Task R.3.1:实现查询特性分析
- [ ] 新增 `backend/app/services/query_analyzer.py`
- [ ] 定义 `QueryProfile` 数据类
- [ ] 实现 `QueryAnalyzer` 类
- [ ] 实现查询类型检测
- 代码相关
- 表格相关
- 对话式
- [ ] 实现 `_calc_logic_depth()` 方法
### Task R.3.2:实现动态 Reranker
- [ ] 新增 `backend/app/services/dynamic_reranker.py`
- [ ] 实现 `DynamicReranker` 类
- [ ] 实现 `_get_weights()` 方法
- 代码查询:关键词权重高
- 表格查询:标题权重高
- 对话式:语义权重高
- [ ] 实现 `_calc_beta()` 方法
- [ ] 实现 `rerank()` 方法
### Task R.3.3:实现核心标签系统
- [ ] 新增 `backend/app/services/core_tag_search.py`
- [ ] 实现 `CoreTagAwareSearch` 类
- [ ] 实现 `CORE_BOOST_FACTOR = 1.33`
- [ ] 实现 `search()` 方法
### Task R.3.4:修改 DocumentChunk 模型
- [ ] 修改 `backend/app/models/document.py`
- [ ] 增加 `tags` 字段JSON, default=list
- [ ] 增加 `is_core` 字段Boolean, default=False
### Task R.3.5:集成到 KnowledgeService
- [ ] 修改 `backend/app/services/knowledge_service.py`
- [ ] 集成 QueryAnalyzer
- [ ] 集成 DynamicReranker
- [ ] 集成 CoreTagAwareSearch
- [ ] 修改 `retrieve()` 方法支持动态权重
### Task R.3.6:补测试
- [ ] 新增 `backend/tests/services/test_dynamic_reranker.py`
- [ ] 测试查询特性分析
- [ ] 测试动态权重调整
- [ ] 测试核心标签加权
### Day R.3 验收
- [ ] 查询特性分析准确(代码/表格/对话式识别)
- [ ] 动态权重根据查询类型调整
- [ ] 核心标签检索加权 1.33x
- [ ] Rerank 集成测试通过
---
## Day R.4高级特性可选4.5天)
Day R.4 目标:探索更高级的 RAG 增强技术。
### Task R.4.1:语义去重
- [ ] 新增 `backend/app/services/deduplicator.py`
- [ ] 实现 `SemanticDeduplicator` 类
- [ ] 实现 `_cosine_similarity()` 方法
- [ ] 实现 `deduplicate()` 方法
### Task R.4.2:语义分桶(可选)
- [ ] 新增 `backend/app/services/semantic_bucket.py`
- [ ] 实现 `SemanticBucketing` 类
- [ ] 实现 `bucket_by_topic()` 方法
### Task R.4.3EPA 分析设计(可选探索)
- [ ] 设计 EPA 模块架构
- [ ] 定义 EPA 接口
- [ ] 实现残差金字塔算法(伪代码)
### Day R.4 验收(可选)
- [ ] 语义去重测试通过
- [ ] 语义分桶原型完成(可选)
- [ ] EPA 分析方案设计完成(可选实现)
---
## 总验收清单
### Phase R.1-R.3 必须完成
- [ ] Token 感知分块正常工作
- [ ] 多索引架构正常工作
- [ ] 动态权重增强正常工作
- [ ] 单元测试覆盖率 > 80%
- [ ] 集成测试通过
- [ ] 原有检索功能无回退
### Phase R.4 可选完成
- [ ] 语义去重正常工作
- [ ] 语义分桶正常工作(可选)
- [ ] EPA 设计文档完成(可选)
---
## 总工作量估算
| Phase | 工作量 |
|-------|--------|
| R.1 Token 感知分块 | 3 天 |
| R.2 多索引架构 | 4 天 |
| R.3 动态权重增强 | 4.5 天 |
| R.4 高级特性(可选) | 4.5 天 |
| **R.1-R.3 必须** | **11.5 天** |
| **R.1-R.4 含可选** | **16 天** |
---
## 产出清单
| 产出 | 对应 Phase |
|------|-----------|
| `services/chunker.py` | R.1 |
| `services/multi_index.py` | R.2 |
| `services/query_analyzer.py` | R.3 |
| `services/dynamic_reranker.py` | R.3 |
| `services/core_tag_search.py` | R.3 |
| `services/deduplicator.py` | R.4 |
| `services/semantic_bucket.py` | R.4(可选) |
| `models/document.py` 更新 | R.2, R.3 |
| 单元测试 > 80% | R.1, R.2, R.3 |
| 集成测试通过 | R.1, R.2, R.3 |
---
## 与 Agent Phase 关系
| Agent Phase | RAG 协作内容 |
|-------------|-------------|
| Phase 1 | Task Schema 追踪 RAG 任务 |
| Phase 2 | RAG 任务可分解给 Librarian Agent |
| Phase 3 | 支持多索引动态选择 |
| Phase 4 | RAG 检索过程可视化 |
| Phase 5 | EPA 分析、语义分桶 |
**Phase R 可与 Agent Phase 1-5 并行推进。**

View File

@@ -0,0 +1,156 @@
# Phase R.0RAG 现状与目标
日期2026-04-03
状态:已完成
借鉴来源VCPToolBox TagMemo V6 架构
---
## 1. 本阶段目的
本文件用于统一背景认知,明确:
- Jarvis 当前 RAG 架构处于什么水平
- 主要短板是什么
- 为什么要升级
- 升级后的目标形态是什么
- VCPToolBox 给我们什么启发
---
## 2. 当前 Jarvis RAG 架构
### 2.1 核心流程
```
用户上传文档 → DocumentService (解析/分块) → ChromaDB (向量存储) → KnowledgeService (检索)
```
### 2.2 核心文件
| 文件 | 职责 |
|------|------|
| `backend/app/services/document_service.py` | 文档上传/解析/分块 |
| `backend/app/services/knowledge_service.py` | ChromaDB 向量检索/混合检索 |
| `backend/app/models/document.py` | Document/DocumentChunk 数据模型 |
---
## 3. 当前能力矩阵
| 能力 | 状态 | 说明 |
|------|------|------|
| 多格式文档解析 | ✅ | PDF/MD/TXT/DOCX/CSV/XLSX |
| 结构化分块 | ✅ | 基于标题层级、表格、段落 |
| 向量检索 | ✅ | ChromaDB 语义相似度 |
| 关键词检索 | ✅ | SQL LIKE |
| 混合检索 | ✅ | 向量 + 关键词加权 |
| Rerank | ✅ | 语义分×0.7 + 关键词×0.2 + 标题×0.1 |
| 上下文丰富 | ✅ | 自动获取前/后 chunk |
---
## 4. 当前短板
| 短板 | 严重程度 | 影响 |
|------|----------|------|
| 无重叠分块 | 🟡 中 | 跨块边界信息丢失 |
| 单索引架构 | 🟡 中 | 无法按知识类型/重要性分层 |
| 无动态权重 | 🟡 中 | 检索策略静态,不适配查询类型 |
| 无 Tag/标签系统 | 🟡 中 | 无法利用语义标签增强检索 |
| 无懒加载机制 | 🟢 低 | 大量文档时内存占用高 |
| 无遗忘机制 | 🟢 低 | 存储无限增长 |
---
## 5. VCPToolBox TagMemo V6 核心借鉴
### 5.1 核心架构
```
日记文件变化 → TextChunker(Token感知分块85%+10%重叠)
→ EmbeddingUtils(并发批量向量化)
→ SQLite(元数据) + VexusIndex(Rust HNSW向量索引)
```
### 5.2 TagMemo V6 检索流程
```
Query → EPA分析(逻辑深度L/共振R) → 残差金字塔 → TagBoost(β动态权重)
→ LIF脉冲扩散(2跳) → 向量融合 → VexusIndex搜索
```
### 5.3 核心模块
| 模块 | 功能 |
|------|------|
| TextChunker | Token 感知分块85% 安全边界 + 10% 重叠 |
| EPA | 语义空间投影分析,识别逻辑深度和跨域共振 |
| Residual Pyramid | 残差金字塔,多级剥离捕获微弱信号 |
| TagBoost | 动态权重增强,根据查询特性调整 |
| LIF Spike | 脉冲扩散2跳拓扑联想 |
| VexusIndex | Rust HNSW 向量索引,高性能检索 |
### 5.4 关键设计理念
1. **TagMemo 不是搜索引擎,是记忆联想引擎** - 模拟人类大脑的感知→编码→巩固→检索→重构
2. **动态适配** - 根据查询意图动态调整检索策略
3. **拓扑涌现** - 基于共现矩阵的脉冲扩散,产生非直观联想
---
## 6. 目标架构
```
┌─────────────────────────────────────────────────────────────┐
│ User Query │
└─────────────────────────┬───────────────────────────────────┘
┌───────────┴───────────┐
│ Query Analyzer │ ← R.3 新增
│ (查询特性分析) │
└───────────┬───────────┘
┌────────────────┼────────────────┐
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌──────────────┐
│ Default │ │ Important │ │ Code/Meeting │
│ Collection│ │ Collection │ │ Collections │
└────┬─────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
└──────────────────┼─────────────────┘
┌───────────────────────────┐
│ Dynamic Reranker │ ← R.3 新增
│ (Core Tag Boost + 动态权重)│
└───────────────────────────┘
┌───────────────┐
│ Search Result │
└───────────────┘
```
---
## 7. 借鉴点映射
| VCPToolBox 借鉴点 | Jarvis 实现位置 | 优先级 |
|-------------------|---------------|--------|
| Token 感知分块85%+10% 重叠) | `services/chunker.py` | 🟢 高 |
| 多索引架构 | `services/multi_index.py` | 🟡 中 |
| 懒加载 + LRU TTL | `services/multi_index.py` | 🟡 中 |
| TagBoost 动态权重 | `services/dynamic_reranker.py` | 🟡 中 |
| 核心标签系统1.33x 加权) | `services/core_tag_search.py` | 🟡 中 |
| 语义去重 | `services/deduplicator.py` | 🔴 低 |
| 语义分桶 | `services/semantic_bucket.py` | 🔴 低 |
| EPA 分析 | - | 🔴 探索 |
| LIF 脉冲扩散 | - | 🔴 探索 |
---
## 8. 本阶段产出要求
- [x] 团队对 Jarvis 当前 RAG 问题和目标方向达成一致
- [x] VCPToolBox 借鉴点已映射到具体 Phase
- [x] 后续 phase 文档能够在这个认知基础上展开

View File

@@ -0,0 +1,188 @@
# Phase R.1Token 感知分块优化
日期2026-04-03
状态:已规划
依赖R.0(现状与目标)
工作量3 天
---
## 1. 本阶段目的
解决跨块边界信息丢失问题,实现精确的 token 计数和重叠分块。
---
## 2. 核心任务
### Task R.1.1:集成 tiktoken
**目标:** 使用 tiktoken 精确计算 token 数85% 安全边界
**新增文件:** `backend/app/services/chunker.py`
```python
import tiktoken
class TokenAwareChunker:
"""Token 感知分块器85% 安全边界 + 10% 重叠"""
def __init__(self, max_tokens: int = 8000, overlap_ratio: float = 0.1):
self.encoding = tiktoken.get_encoding("cl100k_base")
self.safe_max = int(max_tokens * 0.85)
self.overlap_tokens = int(self.safe_max * overlap_ratio)
def count_tokens(self, text: str) -> int:
return len(self.encoding.encode(text))
```
---
### Task R.1.2:实现智能断句
**目标:** 在断点处(标点/空白)智能断开,避免在词汇中间断开
```python
BREAK_POINTS = ['\n', '', '', '', '', '', '', ' ', '\t']
def find_best_breakpoint(text: str, max_pos: int) -> int:
"""在 max_pos 附近找到最佳断点(标点/空白处)"""
for i in range(max_pos - 1, max(0, max_pos - 200), -1):
if text[i] in BREAK_POINTS:
return i + 1
return max_pos
```
---
### Task R.1.3:实现重叠分块
**目标:** 10% token 重叠,保证上下文连续性
```python
def chunk_with_overlap(self, text: str) -> list[dict]:
"""带重叠的分块器,上一块末尾作为下一块开头"""
sentences = self._split_sentences(text)
chunks = []
current_chunk = ""
current_tokens = 0
for sentence in sentences:
sentence_tokens = self.count_tokens(sentence)
if sentence_tokens > self.safe_max:
# 超长句子强制分割
forced = self._force_split_long_text(sentence)
chunks.extend(forced)
continue
if current_tokens + sentence_tokens > self.safe_max:
chunks.append({"content": current_chunk.strip()})
# 创建重叠部分
current_chunk = self._create_overlap(sentences, current_tokens)
current_tokens = self.count_tokens(current_chunk)
current_chunk += sentence
current_tokens += sentence_tokens
if current_chunk.strip():
chunks.append({"content": current_chunk.strip()})
return chunks
```
---
## 3. 修改现有文件
### `backend/app/services/document_service.py`
集成新的 TokenAwareChunker
```python
from app.services.chunker import TokenAwareChunker
class DocumentService:
def __init__(self, ...):
# ... existing init
self.chunker = TokenAwareChunker()
def _build_chunks(self, parsed: ParsedDocument) -> list[dict]:
# 原有逻辑替换为重叠分块
chunks = self.chunker.chunk_with_overlap(parsed.summary)
for node in parsed.nodes:
node_chunks = self.chunker.chunk_with_overlap(node.text)
for chunk in node_chunks:
chunks.append(chunk)
return chunks
```
---
## 4. 新增测试
**新增文件:** `backend/tests/services/test_chunker.py`
```python
import pytest
from app.services.chunker import TokenAwareChunker, find_best_breakpoint
class TestTokenAwareChunker:
def test_token_counting(self):
chunker = TokenAwareChunker(max_tokens=100)
text = "Hello, world!"
assert chunker.count_tokens(text) > 0
def test_overlap_ratio(self):
chunker = TokenAwareChunker(max_tokens=100, overlap_ratio=0.1)
assert chunker.overlap_tokens == 10
def test_safe_max(self):
chunker = TokenAwareChunker(max_tokens=100)
assert chunker.safe_max == 85
class TestSmartBreakpoint:
def test_find_breakpoint_at_punctuation(self):
text = "Hello, world! How are you?"
pos = find_best_breakpoint(text, 15)
assert text[pos-1] in [',', '!', '?', '', '', '']
class TestOverlappingChunker:
def test_chunks_have_overlap(self):
chunker = TokenAwareChunker(max_tokens=50, overlap_ratio=0.2)
long_text = "A" * 200 + "." + "B" * 200
chunks = chunker.chunk_with_overlap(long_text)
assert len(chunks) >= 2
```
---
## 5. 验收标准
- [ ] tiktoken 正确集成token 计数误差 < 1%
- [ ] 超长句子不在词汇中间断开
- [ ] 重叠分块保证上下文连续性
- [ ] 单元测试覆盖率 > 80%
- [ ] 集成测试通过(文档上传→分块→检索)
---
## 6. 变更文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `backend/app/services/chunker.py` | 新增 | Token 感知分块器 |
| `backend/app/services/document_service.py` | 修改 | 集成新的分块器 |
| `backend/tests/services/test_chunker.py` | 新增 | 分块器单元测试 |
---
## 7. 工作量估算
| 任务 | 估算 |
|------|------|
| R.1.1 tiktoken 集成 | 0.5 天 |
| R.1.2 智能断句 | 0.5 天 |
| R.1.3 重叠分块 | 1 天 |
| 测试 + 调试 | 1 天 |
| **R.1 总计** | **3 天** |

View File

@@ -0,0 +1,244 @@
# Phase R.2:多索引架构
日期2026-04-03
状态:已规划
依赖R.1Token 感知分块)
工作量4 天
---
## 1. 本阶段目的
按知识类型/重要性分层,支持懒加载和 LRU 淘汰。
---
## 2. 核心任务
### Task R.2.1:设计 Collection 分离策略
**目标:** 按知识类型分离 ChromaDB Collection
**新增文件:** `backend/app/services/multi_index.py`
```python
class MultiIndexManager:
"""多索引管理器,按知识类型分离"""
INDEX_STRATEGIES = {
"default": {
"name": "user_{user_id}_default",
"description": "通用文档"
},
"important": {
"name": "user_{user_id}_important",
"description": "重要文档(1.2x加权)"
},
"code": {
"name": "user_{user_id}_code",
"description": "代码片段"
},
"meeting": {
"name": "user_{user_id}_meeting",
"description": "会议记录"
},
}
def get_collection(self, user_id: str, index_type: str = "default"):
name = self.INDEX_STRATEGIES[index_type]["name"].format(user_id=user_id)
return self.chroma_client.get_or_create_collection(name=name)
```
---
### Task R.2.2:实现懒加载 + LRU TTL
**目标:** 2小时 TTL访问时加载不访问不加载
```python
import time
from threading import Lock
class LazyIndexLoader:
"""懒加载索引,支持 TTL 淘汰"""
def __init__(self, ttl_seconds: int = 7200):
self._cache = {}
self._last_used = {}
self._lock = Lock()
self._ttl = ttl_seconds
def get_or_load(self, key: str, loader_fn) -> Any:
with self._lock:
if key in self._cache:
self._last_used[key] = time.time()
return self._cache[key]
value = loader_fn()
self._cache[key] = value
self._last_used[key] = time.time()
return value
def sweep(self):
"""清理过期索引"""
now = time.time()
expired = [
k for k, t in self._last_used.items()
if now - t > self._ttl
]
for k in expired:
del self._cache[k]
del self._last_used[k]
```
---
### Task R.2.3:实现重要性感知检索
**目标:** important 索引加权 1.2x
```python
async def retrieve_with_importance(
self,
query: str,
user_id: str,
top_k: int = 5,
) -> list[SearchResult]:
"""重要性感知检索,优先返回高重要性文档"""
# 1. 从 default 索引检索
default_results = await self.retrieve(query, user_id, top_k=top_k * 2)
# 2. 从 important 索引检索
important_results = await self.retrieve(
query, user_id,
collection_name=f"user_{user_id}_important",
top_k=top_k
)
# 3. 合并,重要文档加权
scored = []
for r in default_results:
scored.append((r.score * 0.8, r))
for r in important_results:
scored.append((r.score * 1.2, r)) # 重要文档 1.2x
scored.sort(key=lambda x: x[0], reverse=True)
return [r for _, r in scored[:top_k]]
```
---
## 3. 修改现有文件
### `backend/app/models/document.py`
增加 `importance` 字段:
```python
class Document(Base):
# ... existing fields ...
importance = Column(Float, default=0.5) # 0.0 ~ 1.0, >0.8 进入 important 索引
```
---
### `backend/app/services/knowledge_service.py`
集成多索引支持:
```python
from app.services.multi_index import MultiIndexManager, LazyIndexLoader
class KnowledgeService:
def __init__(self, ...):
# ... existing init
self.multi_index = MultiIndexManager(self.chroma_client)
self.lazy_loader = LazyIndexLoader(ttl_seconds=7200)
async def index_document(self, document_id: str, user_id: str, ...):
# 根据 importance 选择索引
doc = await self._get_document(document_id)
if doc.importance >= 0.8:
collection = self.multi_index.get_collection(user_id, "important")
else:
collection = self.multi_index.get_collection(user_id, "default")
# ... rest of indexing
```
---
## 4. 新增测试
**新增文件:** `backend/tests/services/test_multi_index.py`
```python
import pytest
from app.services.multi_index import MultiIndexManager, LazyIndexLoader
class TestMultiIndexManager:
def test_get_collection_creates_if_not_exists(self):
manager = MultiIndexManager(mock_chroma_client)
col = manager.get_collection("user123", "default")
assert col is not None
def test_collection_name_format(self):
manager = MultiIndexManager(mock_chroma_client)
name = manager.INDEX_STRATEGIES["important"]["name"].format(user_id="user123")
assert name == "user_user123_important"
class TestLazyIndexLoader:
def test_get_or_load_caches(self):
loader = LazyIndexLoader()
load_fn = lambda: {"data": "test"}
result1 = loader.get_or_load("key1", load_fn)
result2 = loader.get_or_load("key1", load_fn)
# 第二次调用应该返回缓存的结果,而不是重新加载
assert result1 is result2
def test_sweep_removes_expired(self):
loader = LazyIndexLoader(ttl_seconds=1)
loader.get_or_load("key1", lambda: "value1")
import time
time.sleep(1.1) # 等待过期
loader.sweep()
assert "key1" not in loader._cache
```
---
## 5. 验收标准
- [ ] 多 Collection 创建成功
- [ ] 懒加载索引生效(访问时加载,不访问不加载)
- [ ] TTL 淘汰机制工作2小时无访问自动卸载
- [ ] 重要性感知检索加权生效
- [ ] 单元测试覆盖率 > 80%
---
## 6. 变更文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `backend/app/services/multi_index.py` | 新增 | 多索引管理器 |
| `backend/app/services/knowledge_service.py` | 修改 | 集成多索引支持 |
| `backend/app/models/document.py` | 修改 | 增加 importance 字段 |
| `backend/tests/services/test_multi_index.py` | 新增 | 多索引单元测试 |
---
## 7. 工作量估算
| 任务 | 估算 |
|------|------|
| R.2.1 Collection 分离策略 | 1 天 |
| R.2.2 懒加载 + LRU | 1 天 |
| R.2.3 重要性感知检索 | 0.5 天 |
| 测试 + 调试 | 1.5 天 |
| **R.2 总计** | **4 天** |

View File

@@ -0,0 +1,290 @@
# Phase R.3:动态权重增强
日期2026-04-03
状态:已规划
依赖R.1Token 感知分块)
工作量4.5 天
---
## 1. 本阶段目的
根据查询特性动态调整检索策略,支持核心标签加权。
---
## 2. 核心任务
### Task R.3.1:实现查询特性分析
**目标:** 分析查询类型(代码/表格/对话式)
**新增文件:** `backend/app/services/query_analyzer.py`
```python
import re
from dataclasses import dataclass
@dataclass
class QueryProfile:
logic_depth: float # 逻辑深度 (0-1): 意图明确程度
is_code_related: bool # 是否代码相关
is_table_related: bool # 是否表格相关
keyword_density: float # 关键词密度
is_conversational: bool # 是否对话式查询
class QueryAnalyzer:
CODE_KEYWORDS = {'code', 'function', 'class', 'api', 'python', 'js', 'bug', '函数', '代码'}
TABLE_KEYWORDS = {'table', 'sheet', 'excel', 'csv', 'column', 'row', '数据', '统计', '表格', '', ''}
def analyze(self, query: str) -> QueryProfile:
words = set(re.findall(r'\w+', query.lower()))
return QueryProfile(
logic_depth=self._calc_logic_depth(query),
is_code_related=bool(words & self.CODE_KEYWORDS),
is_table_related=bool(words & self.TABLE_KEYWORDS),
keyword_density=len(words) / max(len(query), 1),
is_conversational=self._is_conversational(query),
)
def _calc_logic_depth(self, query: str) -> float:
"""计算逻辑深度:问句、具体名词越多越聚焦"""
question_markers = ['how', 'why', 'what', 'which', '哪个', '如何', '为什么', '怎么']
has_question = any(q in query.lower() for q in question_markers)
has_specific_terms = len(re.findall(r'\w{5,}', query)) > 3
return 0.8 if (has_question and has_specific_terms) else 0.5
def _is_conversational(self, query: str) -> bool:
"""判断是否为对话式查询"""
conversational_patterns = ['', '我想', '能不能', '可以帮我', 'what do you think']
return any(p in query for p in conversational_patterns)
```
---
### Task R.3.2:实现动态 Reranker
**目标:** 根据查询类型动态调整语义/关键词/标题权重
**新增文件:** `backend/app/services/dynamic_reranker.py`
```python
import json
from dataclasses import dataclass
class DynamicReranker:
"""动态 Reranker根据查询特性调整权重"""
def rerank(
self,
query: str,
results: list[SearchResult],
analyzer: QueryAnalyzer
) -> list[SearchResult]:
profile = analyzer.analyze(query)
weights = self._get_weights(profile)
beta = self._calc_beta(profile)
scored = []
for r in results:
score = r.score * weights["semantic"]
score += self._keyword_score(query, r.content) * weights["keyword"]
score += self._title_score(query, r.document_title) * weights["title"]
# 表格内容加分
if profile.is_table_related:
meta = json.loads(r.metadata_ or "{}")
if meta.get("content_type") == "table_schema":
score += 0.25
elif meta.get("content_type") == "table_rows":
score += 0.15
score *= beta
scored.append((score, r))
scored.sort(key=lambda x: x[0], reverse=True)
return [r for _, r in scored]
def _get_weights(self, profile: QueryProfile) -> dict:
if profile.is_code_related:
return {"semantic": 0.55, "keyword": 0.35, "title": 0.10}
elif profile.is_table_related:
return {"semantic": 0.50, "keyword": 0.30, "title": 0.20}
elif profile.is_conversational:
return {"semantic": 0.85, "keyword": 0.10, "title": 0.05}
else:
return {"semantic": 0.70, "keyword": 0.20, "title": 0.10}
def _calc_beta(self, profile: QueryProfile) -> float:
"""计算动态 Beta逻辑深度高时加大语义权重"""
if profile.logic_depth > 0.7:
return 1.2 # 意图明确,加大权重
elif profile.logic_depth < 0.4:
return 0.8 # 意图模糊,降低权重
return 1.0
```
---
### Task R.3.3:实现核心标签系统
**目标:** 核心标签 1.33x 加权
**新增文件:** `backend/app/services/core_tag_search.py`
```python
class CoreTagAwareSearch:
"""核心标签感知检索"""
CORE_BOOST_FACTOR = 1.33 # 33% 加权
async def search(
self,
query: str,
user_id: str,
core_tags: list[str] = None,
base_search_fn: callable
) -> list[SearchResult]:
results = await base_search_fn(query, user_id)
if core_tags:
for r in results:
meta = json.loads(r.metadata_ or "{}")
chunk_tags = meta.get("tags", [])
if any(tag in chunk_tags for tag in core_tags):
r.score *= self.CORE_BOOST_FACTOR
return sorted(results, key=lambda x: x.score, reverse=True)
```
---
## 3. 修改现有文件
### `backend/app/models/document.py`
增加 `tags``is_core` 字段:
```python
class DocumentChunk(Base):
# ... existing fields ...
tags = Column(JSON, default=list) # ["重要", "代码", "架构"]
is_core = Column(Boolean, default=False) # 是否核心切片
```
---
### `backend/app/services/knowledge_service.py`
集成动态权重:
```python
from app.services.query_analyzer import QueryAnalyzer
from app.services.dynamic_reranker import DynamicReranker
from app.services.core_tag_search import CoreTagAwareSearch
class KnowledgeService:
def __init__(self, ...):
# ... existing init
self.query_analyzer = QueryAnalyzer()
self.dynamic_reranker = DynamicReranker()
self.core_tag_search = CoreTagAwareSearch()
async def retrieve(self, query: str, user_id: str, ..., core_tags: list[str] = None) -> list[SearchResult]:
# ... existing retrieval logic ...
# 动态 Rerank
results = self.dynamic_reranker.rerank(
query, results, self.query_analyzer
)
# 核心标签加权
if core_tags:
results = await self.core_tag_search.search(
query, user_id, core_tags,
lambda q, u: results # 使用已检索的结果
)
return results
```
---
## 4. 新增测试
**新增文件:** `backend/tests/services/test_dynamic_reranker.py`
```python
import pytest
from app.services.query_analyzer import QueryAnalyzer, QueryProfile
from app.services.dynamic_reranker import DynamicReranker
class TestQueryAnalyzer:
def test_code_query_detection(self):
analyzer = QueryAnalyzer()
profile = analyzer.analyze("请解释这段 Python 代码")
assert profile.is_code_related is True
def test_table_query_detection(self):
analyzer = QueryAnalyzer()
profile = analyzer.analyze("统计这个 Excel 表格的总和")
assert profile.is_table_related is True
def test_conversational_detection(self):
analyzer = QueryAnalyzer()
profile = analyzer.analyze("我想了解一下")
assert profile.is_conversational is True
class TestDynamicReranker:
def test_code_query_weights(self):
reranker = DynamicReranker()
analyzer = QueryAnalyzer()
profile = QueryProfile(
logic_depth=0.5,
is_code_related=True,
is_table_related=False,
keyword_density=0.3,
is_conversational=False
)
weights = reranker._get_weights(profile)
assert weights["keyword"] > weights["semantic"] * 0.5 # 代码查询关键词权重较高
```
---
## 5. 验收标准
- [ ] 查询特性分析准确(代码/表格/对话式识别)
- [ ] 动态权重根据查询类型调整
- [ ] 核心标签检索加权 1.33x
- [ ] Rerank 集成测试通过
---
## 6. 变更文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `backend/app/services/query_analyzer.py` | 新增 | 查询特性分析 |
| `backend/app/services/dynamic_reranker.py` | 新增 | 动态 Reranker |
| `backend/app/services/core_tag_search.py` | 新增 | 核心标签检索 |
| `backend/app/services/knowledge_service.py` | 修改 | 集成动态权重 |
| `backend/app/models/document.py` | 修改 | 增加 tags/is_core 字段 |
| `backend/tests/services/test_dynamic_reranker.py` | 新增 | 动态 Reranker 测试 |
---
## 7. 工作量估算
| 任务 | 估算 |
|------|------|
| R.3.1 查询特性分析 | 1 天 |
| R.3.2 动态 Reranker | 1 天 |
| R.3.3 核心标签系统 | 1 天 |
| 测试 + 调试 | 1.5 天 |
| **R.3 总计** | **4.5 天** |

View File

@@ -0,0 +1,255 @@
# Phase R.4:高级特性(可选)
日期2026-04-03
状态:已规划(可选)
工作量4.5 天
---
## 1. 本阶段目的
探索更高级的 RAG 增强技术。
> **注意:** 本阶段为可选特性,不影响核心功能。根据实际需求决定是否实施。
---
## 2. 核心任务
### Task R.4.1:语义去重
**目标:** 消除冗余检索结果
**新增文件:** `backend/app/services/deduplicator.py`
```python
import numpy as np
class SemanticDeduplicator:
"""语义去重,消除冗余检索结果"""
DEDUP_THRESHOLD = 0.88 # 余弦相似度阈值
def deduplicate(
self,
results: list[SearchResult],
embeddings: list[np.ndarray]
) -> list[SearchResult]:
if len(results) <= 1:
return results
# 计算余弦相似度矩阵
n = len(results)
similarity_matrix = np.zeros((n, n))
for i in range(n):
for j in range(i + 1, n):
sim = self._cosine_similarity(embeddings[i], embeddings[j])
similarity_matrix[i][j] = sim
similarity_matrix[j][i] = sim
# 贪心去重
keep = [True] * n
for i in range(n):
if not keep[i]:
continue
for j in range(i + 1, n):
if keep[j] and similarity_matrix[i][j] > self.DEDUP_THRESHOLD:
keep[j] = False
return [r for r, k in zip(results, keep) if k]
def _cosine_similarity(self, a: np.ndarray, b: np.ndarray) -> float:
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
```
---
### Task R.4.2:语义分桶(可选)
**目标:** 按主题自动组织检索结果
**新增文件:** `backend/app/services/semantic_bucket.py`
```python
from collections import defaultdict
import numpy as np
class SemanticBucketing:
"""语义分桶,按主题自动组织检索结果"""
async def bucket_by_topic(
self,
results: list[SearchResult],
embeddings: list[np.ndarray]
) -> dict[str, list[SearchResult]]:
# 使用层次聚类
from sklearn.cluster import AgglomerativeClustering
n_clusters = min(5, len(results))
if n_clusters < 2:
return {"default": results}
clusterer = AgglomerativeClustering(n_clusters=n_clusters)
labels = clusterer.fit_predict(np.array(embeddings))
buckets = defaultdict(list)
for r, label in zip(results, labels):
buckets[f"topic_{label}"].append(r)
# 按每个桶内最高分排序
sorted_buckets = {}
for name, items in buckets.items():
sorted_items = sorted(items, key=lambda x: x.score, reverse=True)
sorted_buckets[name] = sorted_items
return sorted_buckets
```
---
### Task R.4.3EPA 分析设计(探索)
**目标:** 语义空间投影分析方案设计
```python
class EPAModule:
"""
EPA: Embedding Projection Analysis
分析向量在语义空间中的投影,识别:
- 逻辑深度 (Logic Depth): 意图聚焦程度
- 熵 (Entropy): 信息散乱程度
- 共振 (Resonance): 跨域关联程度
注意:此模块为高级特性,复杂度高,建议后续探索。
"""
def project(self, vector: np.ndarray) -> dict:
"""
返回语义投影结果:
- logic_depth: 0~1, 高=意图聚焦
- entropy: 0~1, 高=信息散乱
- resonance: 跨域共振程度
- dominant_axes: 主要语义轴
"""
raise NotImplementedError("EPA 模块探索中")
def detect_cross_domain_resonance(self, vector: np.ndarray) -> dict:
"""
检测跨域共振:
- 当查询同时触及多个正交语义轴时触发
- 返回共振强度和涉及的主要领域
"""
raise NotImplementedError("EPA 模块探索中")
```
---
## 3. 新增测试
```python
# backend/tests/services/test_deduplicator.py
class TestSemanticDeduplicator:
def test_deduplicate_similar_results(self):
dedup = SemanticDeduplicator()
results = [
SearchResult(chunk_id="1", score=0.9, ...),
SearchResult(chunk_id="2", score=0.85, ...),
SearchResult(chunk_id="3", score=0.8, ...),
]
embeddings = [
np.array([0.1, 0.2, 0.3]),
np.array([0.11, 0.21, 0.31]), # 与第一个高度相似
np.array([0.9, 0.8, 0.7]), # 与第一个不相似
]
deduped = dedup.deduplicate(results, embeddings)
assert len(deduped) < len(results) # 应该去掉一些重复结果
```
---
## 4. 验收标准
- [ ] 语义去重测试通过
- [ ] 语义分桶原型完成(可选)
- [ ] EPA 分析方案设计完成(可选实现)
---
## 5. 变更文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `backend/app/services/deduplicator.py` | 新增 | 语义去重 |
| `backend/app/services/semantic_bucket.py` | 新增(可选) | 语义分桶 |
| `backend/tests/services/test_deduplicator.py` | 新增 | 去重测试 |
---
## 6. 工作量估算
| 任务 | 估算 | 状态 |
|------|------|------|
| R.4.1 语义去重 | 1.5 天 | 必须 |
| R.4.2 语义分桶 | 2 天 | 可选 |
| R.4.3 EPA 设计 | 1 天 | 可选 |
| **R.4 总计(必须)** | **1.5 天** | |
| **R.4 总计(含可选)** | **4.5 天** | |
---
## 7. EPA 分析详细设计(供后续参考)
### 7.1 核心概念
EPA (Embedding Projection Analysis) 受 VCPToolBox TagMemo V6 启发,用于分析查询向量在语义空间中的投影特征。
### 7.2 关键指标
| 指标 | 定义 | 计算方式 |
|------|------|----------|
| Logic Depth | 意图聚焦程度 | 通过计算投影熵值判断 |
| Entropy | 信息散乱程度 | 向量分布的熵 |
| Resonance | 跨域共振 | 查询跨越多个语义轴的程度 |
### 7.3 动态 Beta 公式
```
β = σ(L · log(1 + R) - S · noise_penalty)
```
- L: Logic Depth
- R: Resonance
- S: 噪音程度
- σ: 归一化函数
### 7.4 残差金字塔
对查询向量进行多级剥离:
1. 首轮匹配 → 获取主要语义
2. 计算残差 → 提取被掩盖的微弱信号
3. 递归搜索 → 直到 90% 能量被解释
### 7.5 LIF 脉冲扩散
模拟神经元的脉冲传导:
1. 种子节点激活
2. 沿共现矩阵向外扩散2跳限制
3. 阈值过滤噪音
4. 涌现拓扑关联
---
## 8. 风险与注意事项
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| EPA 实现复杂度高 | 高 | Phase R.4 可选,暂不实现 |
| 聚类计算开销 | 中 | 限制聚类数量,使用高效算法 |
| 去重阈值调参 | 中 | 提供配置项,允许用户调整 |

View File

@@ -0,0 +1,601 @@
# Phase RRAG 系统升级专项
日期2026-04-03
状态:已规划
借鉴来源VCPToolBox TagMemo V6 架构
---
## R.0 当前现状与目标
### R.0.1 当前 Jarvis RAG 架构
```
用户上传文档 → DocumentService (解析/分块) → ChromaDB (向量存储) → KnowledgeService (检索)
```
**核心文件:**
- `backend/app/services/document_service.py` - 文档上传/解析/分块
- `backend/app/services/knowledge_service.py` - ChromaDB 向量检索/混合检索
- `backend/app/models/document.py` - Document/DocumentChunk 数据模型
### R.0.2 当前能力矩阵
| 能力 | 状态 | 说明 |
|------|------|------|
| 多格式文档解析 | ✅ | PDF/MD/TXT/DOCX/CSV/XLSX |
| 结构化分块 | ✅ | 基于标题层级、表格、段落 |
| 向量检索 | ✅ | ChromaDB 语义相似度 |
| 关键词检索 | ✅ | SQL LIKE |
| 混合检索 | ✅ | 向量 + 关键词加权 |
| Rerank | ✅ | 语义分*0.7 + 关键词*0.2 + 标题*0.1 |
| 上下文丰富 | ✅ | 自动获取前/后 chunk |
### R.0.3 当前短板
| 短板 | 严重程度 | 影响 |
|------|----------|------|
| 无重叠分块 | 🟡 中 | 跨块边界信息丢失 |
| 单索引架构 | 🟡 中 | 无法按知识类型/重要性分层 |
| 无动态权重 | 🟡 中 | 检索策略静态,不适配查询类型 |
| 无 Tag/标签系统 | 🟡 中 | 无法利用语义标签增强检索 |
| 无懒加载机制 | 🟢 低 | 大量文档时内存占用高 |
| 无遗忘机制 | 🟢 低 | 存储无限增长 |
### R.0.4 VCPToolBox TagMemo 核心借鉴
```
日记文件变化 → TextChunker(Token感知分块85%+10%重叠)
→ EmbeddingUtils(并发批量向量化)
→ SQLite(元数据) + VexusIndex(Rust HNSW向量索引)
```
**TagMemo V6 检索流程:**
```
Query → EPA分析(逻辑深度L/共振R) → 残差金字塔 → TagBoost(β动态权重)
→ LIF脉冲扩散(2跳) → 向量融合 → VexusIndex搜索
```
### R.0.5 目标架构
```
┌─────────────────────────────────────────────────────────────┐
│ User Query │
└─────────────────────────┬───────────────────────────────────┘
┌───────────┴───────────┐
│ Query Analyzer │ ← R.3 新增
│ (查询特性分析) │
└───────────┬───────────┘
┌────────────────┼────────────────┐
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌──────────────┐
│ Default │ │ Important │ │ Code/Meeting │
│ Collection│ │ Collection │ │ Collections │
└────┬─────┘ └─────┬─────┘ └──────┬───────┘
│ │ │
└──────────────────┼─────────────────┘
┌───────────────────────────┐
│ Dynamic Reranker │ ← R.3 新增
│ (Core Tag Boost + 动态权重)│
└───────────────────────────┘
┌───────────────┐
│ Search Result │
└───────────────┘
```
---
## R.1 Token 感知分块优化
**目标:** 解决跨块边界信息丢失问题,实现精确的 token 计数和重叠分块
### R.1.1 核心任务
#### Task R.1.1.1:集成 tiktoken
```python
# services/chunker.py (新增)
import tiktoken
class TokenAwareChunker:
"""Token 感知分块器85% 安全边界 + 10% 重叠"""
def __init__(self, max_tokens: int = 8000, overlap_ratio: float = 0.1):
self.encoding = tiktoken.get_encoding("cl100k_base")
self.safe_max = int(max_tokens * 0.85)
self.overlap_tokens = int(self.safe_max * overlap_ratio)
def count_tokens(self, text: str) -> int:
return len(self.encoding.encode(text))
```
#### Task R.1.1.2:实现智能断句
```python
BREAK_POINTS = ['\n', '', '', '', '', '', '', ' ', '\t']
def find_best_breakpoint(text: str, max_pos: int) -> int:
"""在 max_pos 附近找到最佳断点(标点/空白处)"""
for i in range(max_pos - 1, max(0, max_pos - 200), -1):
if text[i] in BREAK_POINTS:
return i + 1
return max_pos
```
#### Task R.1.1.3:实现重叠分块
```python
def chunk_with_overlap(self, text: str) -> list[dict]:
"""带重叠的分块器,上一块末尾作为下一块开头"""
sentences = self._split_sentences(text)
chunks = []
current_chunk = ""
current_tokens = 0
for sentence in sentences:
sentence_tokens = self.count_tokens(sentence)
if sentence_tokens > self.safe_max:
# 超长句子强制分割
forced = self._force_split_long_text(sentence)
chunks.extend(forced)
continue
if current_tokens + sentence_tokens > self.safe_max:
chunks.append({"content": current_chunk.strip()})
# 创建重叠部分
current_chunk = self._create_overlap(sentences, current_tokens)
current_tokens = self.count_tokens(current_chunk)
current_chunk += sentence
current_tokens += sentence_tokens
if current_chunk.strip():
chunks.append({"content": current_chunk.strip()})
return chunks
```
### R.1.2 验收标准
- [ ] tiktoken 正确集成token 计数误差 < 1%
- [ ] 超长句子不在词汇中间断开
- [ ] 重叠分块保证上下文连续性
- [ ] 单元测试覆盖率 > 80%
### R.1.3 变更文件
| 文件 | 操作 | 说明 |
|------|------|------|
| `services/chunker.py` | 新增 | Token 感知分块器 |
| `services/document_service.py` | 修改 | 集成新的分块器 |
| `tests/test_chunker.py` | 新增 | 分块器单元测试 |
### R.1.4 工作量估算
| 任务 | 估算 |
|------|------|
| R.1.1.1 tiktoken 集成 | 0.5 天 |
| R.1.1.2 智能断句 | 0.5 天 |
| R.1.1.3 重叠分块 | 1 天 |
| 测试 + 调试 | 1 天 |
| **R.1 总计** | **3 天** |
---
## R.2 多索引架构
**目标:** 按知识类型/重要性分层,支持懒加载和 LRU 淘汰
### R.2.1 核心任务
#### Task R.2.1.1:设计 Collection 分离策略
```python
# services/multi_index.py (新增)
class MultiIndexManager:
"""多索引管理器,按知识类型分离"""
INDEX_STRATEGIES = {
"default": {"name": "user_{user_id}_default", "description": "通用文档"},
"important": {"name": "user_{user_id}_important", "description": "重要文档(1.2x加权)"},
"code": {"name": "user_{user_id}_code", "description": "代码片段"},
"meeting": {"name": "user_{user_id}_meeting", "description": "会议记录"},
}
def get_collection(self, user_id: str, index_type: str = "default"):
name = self.INDEX_STRATEGIES[index_type]["name"].format(user_id=user_id)
return self.chroma_client.get_or_create_collection(name=name)
```
#### Task R.2.1.2:实现懒加载 + LRU TTL
```python
import time
from threading import Lock
class LazyIndexLoader:
"""懒加载索引,支持 TTL 淘汰"""
def __init__(self, ttl_seconds: int = 7200):
self._cache = {}
self._last_used = {}
self._lock = Lock()
self._ttl = ttl_seconds
def get_or_load(self, key: str, loader_fn) -> Any:
with self._lock:
if key in self._cache:
self._last_used[key] = time.time()
return self._cache[key]
value = loader_fn()
self._cache[key] = value
self._last_used[key] = time.time()
return value
def sweep(self):
"""清理过期索引"""
now = time.time()
expired = [k for k, t in self._last_used.items() if now - t > self._ttl]
for k in expired:
del self._cache[k]
del self._last_used[k]
```
#### Task R.2.1.3:实现重要性感知检索
```python
async def retrieve_with_importance(
self,
query: str,
user_id: str,
importance_threshold: float = 0.0,
top_k: int = 5,
) -> list[SearchResult]:
"""重要性感知检索,优先返回高重要性文档"""
# 1. 从 default 索引检索
default_results = await self.retrieve(query, user_id, top_k=top_k * 2)
# 2. 从 important 索引检索
important_results = await self.retrieve(
query, user_id,
collection_name=f"user_{user_id}_important",
top_k=top_k
)
# 3. 合并,重要文档加权
scored = []
for r in default_results:
scored.append((r.score * 0.8, r))
for r in important_results:
scored.append((r.score * 1.2, r)) # 重要文档 1.2x
scored.sort(key=lambda x: x[0], reverse=True)
return [r for _, r in scored[:top_k]]
```
### R.2.2 验收标准
- [ ] 多 Collection 创建成功
- [ ] 懒加载索引生效(访问时加载,不访问不加载)
- [ ] TTL 淘汰机制工作2小时无访问自动卸载
- [ ] 重要性感知检索加权生效
### R.2.3 变更文件
| 文件 | 操作 | 说明 |
|------|------|------|
| `services/multi_index.py` | 新增 | 多索引管理器 |
| `services/knowledge_service.py` | 修改 | 集成多索引支持 |
| `models/document.py` | 修改 | 增加 importance 字段 |
| `tests/test_multi_index.py` | 新增 | 多索引单元测试 |
### R.2.4 工作量估算
| 任务 | 估算 |
|------|------|
| R.2.1.1 Collection 分离策略 | 1 天 |
| R.2.1.2 懒加载 + LRU | 1 天 |
| R.2.1.3 重要性感知检索 | 0.5 天 |
| 测试 + 调试 | 1.5 天 |
| **R.2 总计** | **4 天** |
---
## R.3 动态权重增强
**目标:** 根据查询特性动态调整检索策略,支持核心标签加权
### R.3.1 核心任务
#### Task R.3.1.1:实现查询特性分析
```python
# services/query_analyzer.py (新增)
import re
from dataclasses import dataclass
@dataclass
class QueryProfile:
logic_depth: float # 逻辑深度 (0-1): 意图明确程度
is_code_related: bool # 是否代码相关
is_table_related: bool # 是否表格相关
keyword_density: float # 关键词密度
is_conversational: bool # 是否对话式查询
class QueryAnalyzer:
CODE_KEYWORDS = {'code', 'function', 'class', 'api', 'python', 'js', 'bug'}
TABLE_KEYWORDS = {'table', 'sheet', 'excel', 'csv', 'column', 'row', '数据', '统计'}
def analyze(self, query: str) -> QueryProfile:
words = set(re.findall(r'\w+', query.lower()))
return QueryProfile(
logic_depth=self._calc_logic_depth(query),
is_code_related=bool(words & self.CODE_KEYWORDS),
is_table_related=bool(words & self.TABLE_KEYWORDS),
keyword_density=len(words) / max(len(query), 1),
is_conversational=self._is_conversational(query),
)
```
#### Task R.3.1.2:实现动态 Reranker
```python
# services/dynamic_reranker.py (新增)
class DynamicReranker:
def rerank(self, query: str, results: list[SearchResult]) -> list[SearchResult]:
profile = QueryAnalyzer().analyze(query)
# 根据查询类型调整权重
weights = self._get_weights(profile)
beta = self._calc_beta(profile)
scored = []
for r in results:
score = r.score * weights["semantic"]
score += self._keyword_score(query, r.content) * weights["keyword"]
score += self._title_score(query, r.document_title) * weights["title"]
# 表格内容加分
if profile.is_table_related:
meta = json.loads(r.metadata_ or "{}")
if meta.get("content_type") == "table_schema":
score += 0.25
score *= beta
scored.append((score, r))
scored.sort(key=lambda x: x[0], reverse=True)
return [r for _, r in scored]
def _get_weights(self, profile: QueryProfile) -> dict:
if profile.is_code_related:
return {"semantic": 0.55, "keyword": 0.35, "title": 0.10}
elif profile.is_table_related:
return {"semantic": 0.50, "keyword": 0.30, "title": 0.20}
elif profile.is_conversational:
return {"semantic": 0.85, "keyword": 0.10, "title": 0.05}
else:
return {"semantic": 0.70, "keyword": 0.20, "title": 0.10}
```
#### Task R.3.1.3:实现核心标签系统
```python
# 在 models/document.py 中增加 tags 字段
class DocumentChunk(Base):
tags = Column(JSON, default=list) # ["重要", "代码", "架构"]
is_core = Column(Boolean, default=False) # 是否核心切片
# services/core_tag_search.py (新增)
class CoreTagAwareSearch:
CORE_BOOST_FACTOR = 1.33 # 33% 加权
async def search(self, query: str, user_id: str,
core_tags: list[str] = None) -> list[SearchResult]:
results = await self.base_search(query, user_id)
if core_tags:
for r in results:
meta = json.loads(r.metadata_ or "{}")
chunk_tags = meta.get("tags", [])
if any(tag in chunk_tags for tag in core_tags):
r.score *= self.CORE_BOOST_FACTOR
return sorted(results, key=lambda x: x.score, reverse=True)
```
### R.3.2 验收标准
- [ ] 查询特性分析准确(代码/表格/对话式识别)
- [ ] 动态权重根据查询类型调整
- [ ] 核心标签检索加权 1.33x
- [ ] Rerank 集成测试通过
### R.3.3 变更文件
| 文件 | 操作 | 说明 |
|------|------|------|
| `services/query_analyzer.py` | 新增 | 查询特性分析 |
| `services/dynamic_reranker.py` | 新增 | 动态 Reranker |
| `services/core_tag_search.py` | 新增 | 核心标签检索 |
| `services/knowledge_service.py` | 修改 | 集成动态权重 |
| `models/document.py` | 修改 | 增加 tags/is_core 字段 |
| `tests/test_dynamic_reranker.py` | 新增 | 动态 Reranker 测试 |
### R.3.4 工作量估算
| 任务 | 估算 |
|------|------|
| R.3.1.1 查询特性分析 | 1 天 |
| R.3.1.2 动态 Reranker | 1 天 |
| R.3.1.3 核心标签系统 | 1 天 |
| 测试 + 调试 | 1.5 天 |
| **R.3 总计** | **4.5 天** |
---
## R.4 高级特性(可选)
**目标:** 探索更高级的 RAG 增强技术
### R.4.1 Task R.4.1.1:语义去重
```python
class SemanticDeduplicator:
DEDUP_THRESHOLD = 0.88
def deduplicate(self, results, embeddings) -> list:
"""消除冗余检索结果"""
# 计算余弦相似度矩阵
# 贪心去重
...
```
### R.4.2 Task R.4.2.1:语义分桶
```python
class SemanticBucketing:
async def bucket_by_topic(self, results, embeddings) -> dict:
"""按主题自动组织检索结果"""
# 使用聚类算法
...
```
### R.4.3 Task R.4.3.1EPA 分析(探索)
```python
class EPAModule:
"""
EPA: Embedding Projection Analysis
高复杂度Phase R.4 探索
"""
pass # 暂不实现
```
### R.4.4 验收标准
- [ ] 语义去重测试通过
- [ ] 语义分桶原型完成
- [ ] EPA 分析方案设计完成(可选实现)
### R.4.5 工作量估算
| 任务 | 估算 |
|------|------|
| R.4.1.1 语义去重 | 1.5 天 |
| R.4.2.1 语义分桶 | 2 天 |
| R.4.3.1 EPA 设计 | 1 天 |
| **R.4 总计(可选)** | **4.5 天** |
---
## R.5 阶段总结与产出
### R.5.1 完整实施路径
```
R.0 ──────────────────────────────────────────────────────────────┐
│ 现状与目标 │
│ - 当前架构分析 │
│ - 短板识别 │
│ - VCPToolBox 借鉴点 │
└────────────────────────────────────────────────────────────────────┘
R.1 ──────────────────────────────────────────────────────────────┐
│ Token 感知分块优化 │
│ - tiktoken 集成 │
│ - 智能断句 │
│ - 重叠分块 │
│ │
│ 工作量: 3 天 │
└────────────────────────────────────────────────────────────────────┘
R.2 ──────────────────────────────────────────────────────────────┐
│ 多索引架构 │
│ - Collection 分离策略 │
│ - 懒加载 + LRU TTL │
│ - 重要性感知检索 │
│ │
│ 依赖: R.1 │
│ 工作量: 4 天 │
└────────────────────────────────────────────────────────────────────┘
R.3 ──────────────────────────────────────────────────────────────┐
│ 动态权重增强 │
│ - QueryAnalyzer │
│ - DynamicReranker │
│ - CoreTagAwareSearch │
│ │
│ 依赖: R.1 │
│ 工作量: 4.5 天 │
└────────────────────────────────────────────────────────────────────┘
R.4 ──────────────────────────────────────────────────────────────┐
│ 高级特性 (可选) │
│ - 语义去重 │
│ - 语义分桶 │
│ - EPA 分析设计 │
│ │
│ 工作量: 4.5 天(可选) │
└────────────────────────────────────────────────────────────────────┘
```
### R.5.2 总工作量估算
| Phase | 工作量 |
|-------|--------|
| R.1 | 3 天 |
| R.2 | 4 天 |
| R.3 | 4.5 天 |
| R.4(可选) | 4.5 天 |
| **R.1-R.3 必须** | **11.5 天** |
| **R.1-R.4 含可选** | **16 天** |
### R.5.3 产出清单
| 产出 | 对应 Phase |
|------|-----------|
| `services/chunker.py` | R.1 |
| `services/multi_index.py` | R.2 |
| `services/query_analyzer.py` | R.3 |
| `services/dynamic_reranker.py` | R.3 |
| `services/core_tag_search.py` | R.3 |
| `models/document.py` 更新 | R.2, R.3 |
| 单元测试 > 80% | R.1, R.2, R.3 |
| 集成测试通过 | R.1, R.2, R.3 |
### R.5.4 与 Phase 1-5 的关系
| Phase | RAG 协作内容 |
|-------|-------------|
| Phase 1 | 基础加固Task Schema 追踪 RAG 任务 |
| Phase 2 | 协作RAG 任务可分解给 Librarian Agent |
| Phase 3 | 动态:支持多索引动态选择 |
| Phase 4 | 可视化RAG 检索过程可视化 |
| Phase 5 | 高级EPA 分析、语义分桶 |
| **Phase R** | **独立 RAG 升级路径,可与 Phase 1-5 并行推进** |
---
## R.6 风险与注意事项
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| Token 计数不准确 | 低 | 使用 tiktoken 精确计数,多次验证 |
| 索引分离后检索复杂 | 中 | 提供统一检索接口,隐藏内部逻辑 |
| 动态权重调参困难 | 中 | 提供配置项,允许用户调整 |
| EPA 实现复杂度高 | 高 | Phase R.4 可选,暂不实现 |

View File

@@ -0,0 +1,173 @@
# Jarvis Tools 升级计划索引
本目录用于存放 Jarvis 工具系统的分阶段升级规划文档。
## 文档说明
| 文件 | 说明 |
|------|------|
| `README.md` | 总览、阶段关系、实施顺序 |
| `phase-t-0-current-state.md` | 当前现状、问题、目标架构、VCPToolBox 借鉴 |
| `phase-t-1-manifest-system.md` | Manifest 驱动系统 |
| `phase-t-2-tool-registry.md` | 工具注册中心 |
| `phase-t-3-tool-implementation.md` | 核心工具实现 |
| `phase-t-4-advanced.md` | 高级特性(多运行时/Agent协作 |
| `checklist.md` | 执行清单 |
## 推荐阅读顺序
1. 先读 `phase-t-0-current-state.md`
2. 再按顺序阅读 phase t-1 ~ t-4
3. 实施时严格按阶段推进
4. 参考 `checklist.md` 进行任务追踪
---
## 总体升级原则
1. **Manifest 驱动** - 声明式工具定义,热插拔
2. **标准契约** - 统一的调用格式和返回结构
3. **多运行时** - 支持 Python/JS/原生
4. **类型安全** - Pydantic Schema 验证
5. **可观测性** - 调用日志、耗时统计
---
## 阶段总览图
```
T.0 ──────────────────────────────────────────────────────────────┐
│ 现状与目标 │
│ - 当前工具系统分析 │
│ - 短板识别 │
│ - VCPToolBox 工具系统借鉴 │
└────────────────────────────────────────────────────────────────────┘
T.1 ──────────────────────────────────────────────────────────────┐
│ Manifest 驱动系统 │
│ - 工具 manifest 定义 │
│ - 标准化契约 │
│ - Schema 验证 │
│ │
│ 核心文件: tools/manifests/, tools/schemas/ │
│ 工作量: 3 天 │
└────────────────────────────────────────────────────────────────────┘
T.2 ──────────────────────────────────────────────────────────────┐
│ 工具注册中心 │
│ - 工具发现机制 │
│ - 动态注册 │
│ - 工具描述生成 │
│ │
│ 核心文件: tools/registry.py │
│ 依赖: T.1 │
│ 工作量: 2 天 │
└────────────────────────────────────────────────────────────────────┘
T.3 ──────────────────────────────────────────────────────────────┐
│ 核心工具实现 │
│ - 文件操作工具 │
│ - 搜索工具 │
│ - 网页抓取工具 │
│ - 任务管理工具 │
│ │
│ 核心文件: tools/implementations/ │
│ 依赖: T.2 │
│ 工作量: 5 天 │
└────────────────────────────────────────────────────────────────────┘
T.4 ──────────────────────────────────────────────────────────────┐
│ 高级特性 │
│ - 多运行时支持 │
│ - Agent 间协作 │
│ - 定时任务 │
│ │
│ 核心文件: tools/runtime/, agents/tools/ │
│ 依赖: T.3 │
│ 工作量: 4 天 │
└────────────────────────────────────────────────────────────────────┘
```
---
## VCPToolBox 工具系统核心借鉴
| 借鉴点 | 实现位置 | 难度 |
|--------|---------|------|
| Manifest 驱动 | T.1 | 🟢 低 |
| 标准化契约 | T.1 | 🟢 低 |
| configSchema 配置 | T.1 | 🟢 低 |
| 工具注册中心 | T.2 | 🟡 中 |
| 动态发现 | T.2 | 🟡 中 |
| 文件操作工具 | T.3 | 🟢 低 |
| 搜索工具 | T.3 | 🟡 中 |
| 网页抓取 | T.3 | 🟡 中 |
| 多运行时支持 | T.4 | 🟡 中 |
| Agent 间协作 | T.4 | 🟡 中 |
---
## 实施顺序
```
T.0 → T.1 → T.2 → T.3 → T.4
│ │ │ │ │
│ │ │ │ └── 多运行时/Agent协作
│ │ │ └── 核心工具
│ │ └── 注册中心
│ └── Manifest系统
└── 现状与目标
```
**注意:** T.1 是基础,后续阶段都依赖 T.1。
---
## 文件变更追踪
| Phase | 新增文件 | 修改文件 |
|-------|---------|---------|
| T.1 | `tools/manifests/*.yaml`, `tools/schemas/` | `pyproject.toml` |
| T.2 | `tools/registry.py`, `tools/base.py` | `services/agent_service.py` |
| T.3 | `tools/implementations/*.py` | `tools/registry.py` |
| T.4 | `tools/runtime/`, `agents/tools/` | `agents/graph.py` |
---
## 与 Agent Phase 1-5 的关系
| Agent Phase | Tools 协作内容 |
|-------------|---------------|
| Phase 1 | Task Schema 追踪工具调用 |
| Phase 2 | 工具可委托给执行 Agent |
| Phase 3 | 动态选择最优工具 |
| Phase 4 | 工具调用可视化 |
| Phase 5 | 多 Agent 工具协作 |
| **Phase T** | **工具系统升级,与 Phase 1-5 协同** |
---
## 注意事项
| 注意事项 | 说明 |
|---------|------|
| T.1 是基础 | T.2-T.4 都依赖 T.1 的 Manifest 系统 |
| 兼容性优先 | 保持现有 Agent 工具调用方式 |
| 安全第一 | 严格权限控制,防止滥用 |
| 测试优先 | 每个工具都要配套测试 |
---
## 总工作量
| Phase | 工作量 |
|-------|--------|
| T.1 | 3 天 |
| T.2 | 2 天 |
| T.3 | 5 天 |
| T.4 | 4 天 |
| **总计** | **14 天** |

View File

@@ -0,0 +1,251 @@
# Tools 升级执行清单
本清单用于追踪 Tools 升级计划的执行进度。
---
## 总进度
| Phase | 名称 | 状态 | 工作量 |
|-------|------|------|--------|
| T.0 | 现状与目标 | ✅ 完成 | - |
| T.1 | Manifest 驱动系统 | ⬜ 待开始 | 3 天 |
| T.2 | 工具注册中心 | ⬜ 待开始 | 2 天 |
| T.3 | 核心工具实现 | ⬜ 待开始 | 5 天 |
| T.4 | 高级特性 | ⬜ 待开始 | 4 天 |
| **总计** | | | **14 天** |
---
## Phase T.1Manifest 驱动系统
### 目标
建立 Jarvis 的 Manifest 驱动工具系统,定义标准化工件声明。
### 任务清单
#### Schema 定义
- [ ] 创建 `tools/schemas/manifest.py`
- [ ] 定义 `ToolManifest` Schema
- [ ] 定义 `ToolType` 枚举
- [ ] 定义 `RuntimeType` 枚举
- [ ] 定义 `InvocationCommand` Schema
- [ ] 创建 `tools/schemas/tool_call.py`
- [ ] 定义 `ToolCallRequest` Schema
- [ ] 定义 `ToolCallResponse` Schema
- [ ] 定义 `ToolExecutionLog` Schema
#### 验证器
- [ ] 创建 `tools/schemas/validator.py`
- [ ] 实现 `validate_manifest` 函数
- [ ] 实现 `validate_tool_call` 函数
- [ ] 实现错误类
#### 配置系统
- [ ] 创建 `tools/configs/loader.py`
- [ ] 实现 `ConfigLoader`
- [ ] 实现配置缓存
- [ ] 实现配置重载
#### Manifest 文件
- [ ] 创建 `tools/manifests/file_operator.yaml`
- [ ] 创建 `tools/manifests/web_search.yaml`
- [ ] 创建其他工具 Manifest
#### 测试
- [ ] 单元测试
### 产出文件
- `tools/schemas/manifest.py`
- `tools/schemas/tool_call.py`
- `tools/schemas/validator.py`
- `tools/configs/loader.py`
- `tools/manifests/*.yaml`
### 验收
- [ ] Schema 验证正常工作
- [ ] 配置加载器正常工作
- [ ] Manifest 文件格式正确
- [ ] 单元测试通过
---
## Phase T.2:工具注册中心
### 目标
实现工具的动态注册、发现和管理。
### 任务清单
#### 注册中心
- [ ] 创建 `tools/registry.py`
- [ ] 实现 `ToolMetadata` dataclass
- [ ] 实现 `ToolRegistry`
- [ ] 实现注册/注销方法
- [ ] 实现查询方法
- [ ] 实现统计方法
#### 工具发现
- [ ] 创建 `tools/discovery.py`
- [ ] 实现 `ToolDiscovery`
- [ ] 实现自动发现
- [ ] 实现热重载
#### 描述生成
- [ ] 创建 `tools/description.py`
- [ ] 实现 AI 友好描述生成
- [ ] 实现工具列表生成
#### 权限控制
- [ ] 创建 `tools/permissions.py`
- [ ] 实现 `ToolPermission` 枚举
- [ ] 实现 `ToolPermissionChecker`
#### LangChain 集成
- [ ] 创建 `tools/langchain_adapter.py`
- [ ] 实现适配器
- [ ] 集成到 Agent
#### 测试
- [ ] 单元测试
### 产出文件
- `tools/registry.py`
- `tools/discovery.py`
- `tools/description.py`
- `tools/permissions.py`
- `tools/langchain_adapter.py`
### 验收
- [ ] 注册中心正常工作
- [ ] 工具发现正常工作
- [ ] 权限检查正常工作
- [ ] LangChain 适配器正常工作
- [ ] 单元测试通过
---
## Phase T.3:核心工具实现
### 目标
实现文件操作、搜索、网页抓取等核心工具。
### 任务清单
#### 文件操作工具
- [ ] 创建 `tools/implementations/file_operator.py`
- [ ] 实现 `FileOperator`
- [ ] 实现 read_file
- [ ] 实现 write_file
- [ ] 实现 list_directory
- [ ] 实现 search_files
- [ ] 实现路径安全检查
- [ ] 实现多格式支持PDF/DOCX/XLSX
#### 搜索工具
- [ ] 创建 `tools/implementations/web_search.py`
- [ ] 实现 `WebSearch`
- [ ] 实现 search 方法
- [ ] 实现 deep_search 方法
#### 网页抓取工具
- [ ] 创建 `tools/implementations/web_fetch.py`
- [ ] 实现 `WebFetch`
- [ ] 实现 fetch 方法
- [ ] 实现 screenshot 方法
#### 任务管理工具
- [ ] 创建 `tools/implementations/task_manager.py`
- [ ] 实现 `TaskManager`
- [ ] 实现任务 CRUD
#### Manifest 绑定
- [ ] 更新 Manifest 文件
- [ ] 注册到工具中心
#### 测试
- [ ] 单元测试
### 产出文件
- `tools/implementations/file_operator.py`
- `tools/implementations/web_search.py`
- `tools/implementations/web_fetch.py`
- `tools/implementations/task_manager.py`
### 验收
- [ ] 文件操作工具正常工作
- [ ] 搜索工具正常工作
- [ ] 网页抓取工具正常工作
- [ ] 任务管理工具正常工作
- [ ] 单元测试通过
---
## Phase T.4:高级特性
### 目标
实现多运行时支持、Agent 协作和定时任务。
### 任务清单
#### 运行时系统
- [ ] 创建 `tools/runtime/base.py`
- [ ] 定义 `BaseRuntime` 抽象基类
- [ ] 创建 `tools/runtime/python_runtime.py`
- [ ] 创建 `tools/runtime/js_runtime.py`
- [ ] 创建 `tools/runtime/native_runtime.py`
- [ ] 创建 `tools/runtime/manager.py`
#### Agent 协作
- [ ] 创建 `agents/tools/collaboration.py`
- [ ] 定义 `CollaborationMessage`
- [ ] 实现 `CollaborationProtocol`
- [ ] 实现请求/响应机制
#### 定时任务
- [ ] 创建 `tools/scheduler.py`
- [ ] 实现 `ScheduledTask`
- [ ] 实现 `ToolScheduler`
- [ ] 实现多种调度类型
#### 测试
- [ ] 单元测试
### 产出文件
- `tools/runtime/*.py`
- `agents/tools/collaboration.py`
- `tools/scheduler.py`
### 验收
- [ ] 多运行时正常工作
- [ ] Agent 协作正常工作
- [ ] 定时任务正常工作
- [ ] 单元测试通过
---
## 完成标准
- [ ] 所有 Phase T.1-T.4 任务完成
- [ ] 所有单元测试通过
- [ ] API 文档更新完成
- [ ] 部署验证通过
---
## 风险与注意事项
| 风险 | 影响 | 缓解措施 |
|------|------|----------|
| 多运行时复杂性 | 开发成本增加 | 优先实现 Python 运行时 |
| JS 运行时依赖 Node | 部署环境要求 | 提供降级方案 |
| 定时任务精度 | 任务延迟 | 使用专业调度库 |
| 安全漏洞 | 系统风险 | 严格权限控制 |
---
## 更新日志
| 日期 | Phase | 变更内容 |
|------|-------|----------|
| 2026-04-04 | T.0 | 创建文档 |

View File

@@ -0,0 +1,192 @@
# Phase T.0Tools 现状与目标
日期2026-04-04
状态:已完成
借鉴来源VCPToolBox Plugin 系统
---
## 1. 本阶段目的
本文件用于统一背景认知,明确:
- Jarvis 当前工具系统处于什么水平
- 主要短板是什么
- 为什么要升级
- 升级后的目标形态是什么
- VCPToolBox 给我们什么启发
---
## 2. 当前 Jarvis Tools 架构
### 2.1 核心流程
```
Agent 决策 → 工具调用 → LLM API → 返回结果
```
### 2.2 当前工具实现
Jarvis 当前的工具以 LangChain Tools 形式存在:
```python
# agents/tools/
base.py # 工具基类
file_tools.py # 文件操作
search_tools.py # 搜索
web_tools.py # 网页相关
```
### 2.3 当前工具列表
| 工具 | 功能 | 实现方式 |
|------|------|---------|
| `read_file` | 读取文件 | Python |
| `write_file` | 写入文件 | Python |
| `run_python` | 执行代码 | Python |
| `search_knowledge` | 知识库检索 | LangChain |
| `search_web` | 联网搜索 | API |
### 2.4 当前问题
| 问题 | 影响 |
|------|------|
| 硬编码工具 | 新增工具需改代码 |
| 无 manifest | 无法热插拔 |
| 无标准化契约 | 调用格式不统一 |
| 无配置分离 | 敏感信息易泄露 |
| 无类型安全 | 验证缺失 |
| 无权限控制 | 安全隐患 |
---
## 3. VCPToolBox 工具系统分析
### 3.1 六大插件类型
```javascript
const PLUGIN_TYPES = {
static: "静态占位符,自动注入系统提示词",
synchronous: "同步执行stdio 协议",
asynchronous: "异步执行,后台处理",
service: "持续运行服务",
hybridservice: "混合服务Agent间通讯",
messagePreprocessor: "消息预处理"
};
```
### 3.2 Manifest 标准契约
```javascript
{
"manifestVersion": "1.0.0",
"name": "PluginName",
"displayName": "中文显示名",
"description": "功能描述",
"pluginType": "synchronous",
"version": "1.0.0",
"entryPoint": {
"type": "nodejs",
"command": "node Plugin.js",
"timeout": 300000
},
"communication": {
"protocol": "stdio",
"timeout": 300000
},
"configSchema": {
"API_KEY": {
"type": "string",
"description": "API 密钥"
}
},
"capabilities": {
"invocationCommands": [
{
"commandIdentifier": "CommandName",
"description": "详细描述+调用格式示例",
"example": "调用示例"
}
]
}
}
```
### 3.3 核心设计理念
1. **声明式** - 工具通过 manifest 声明,框架自动发现
2. **标准化** - 统一的调用协议stdio/websocket
3. **可配置** - configSchema 声明配置项
4. **可观测** - 调用日志、超时控制
5. **安全隔离** - 沙箱执行、权限控制
### 3.4 关键文件
| 文件 | 作用 |
|------|------|
| `Plugin.js` | 插件加载与执行引擎 |
| `plugin-manifest.json` | 插件声明契约 |
| `config.env` | 插件配置 |
| `routes/` | API 路由层 |
---
## 4. 目标架构
```
┌─────────────────────────────────────────────────────────────┐
│ Tool Manifests │
│ - YAML/JSON 声明式定义 │
│ - 版本管理 │
│ - Schema 验证 │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Tool Registry │
│ - 动态发现 │
│ - 权限控制 │
│ - 调用统计 │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Tool Executor │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Python RT │ │ JS RT │ │ Native RT │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────┴───────────────────────────────────┐
│ Tool Output │
│ - 类型化返回 │
│ - 错误处理 │
│ - 调用日志 │
└─────────────────────────────────────────────────────────────┘
```
---
## 5. 借鉴点映射
| VCPToolBox 借鉴点 | Jarvis 实现位置 | 优先级 |
|-------------------|---------------|--------|
| Manifest 驱动 | `tools/manifests/` | 🟢 高 |
| 标准化契约 | `tools/schemas/` | 🟢 高 |
| configSchema | `tools/base.py` | 🟢 高 |
| 工具注册中心 | `tools/registry.py` | 🟡 中 |
| 动态发现 | `tools/discovery.py` | 🟡 中 |
| 调用日志 | `tools/logging.py` | 🟡 中 |
| 超时控制 | `tools/executor.py` | 🟡 中 |
| 多运行时 | `tools/runtime/` | 🟡 中 |
---
## 6. 本阶段产出要求
- [x] 团队对 Jarvis 当前工具系统和目标方向达成一致
- [x] VCPToolBox 工具系统借鉴点已映射到具体 Phase
- [x] 后续 phase 文档能够在这个认知基础上展开

View File

@@ -0,0 +1,484 @@
# Phase T.1Manifest 驱动系统
日期2026-04-04
状态:待开始
依赖T.0(已完成)
---
## 1. 本阶段目的
建立 Jarvis 的 Manifest 驱动工具系统:
- 定义工具 Manifest Schema
- 实现 Schema 验证
- 创建核心工具的 Manifest 文件
- 实现配置分离
---
## 2. Manifest Schema 设计
### 2.1 目录结构
```
backend/app/tools/
├── manifests/ # 工具 manifest
│ ├── file_operator.yaml
│ ├── search.yaml
│ └── web_fetch.yaml
├── schemas/ # Schema 定义
│ ├── __init__.py
│ ├── manifest.py # Manifest Schema
│ ├── tool_call.py # 工具调用 Schema
│ └── config.py # 配置 Schema
└── configs/ # 配置分离
└── .tool.example # 工具配置模板
```
### 2.2 ToolManifest Schema
```python
# tools/schemas/manifest.py
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from enum import Enum
class ToolType(str, Enum):
"""工具类型"""
SYNC = "sync" # 同步执行
ASYNC = "async" # 异步执行
SERVICE = "service" # 持续服务
class RuntimeType(str, Enum):
"""运行时类型"""
PYTHON = "python"
JAVASCRIPT = "javascript"
NATIVE = "native"
class InvocationCommand(BaseModel):
"""调用命令定义"""
name: str = Field(..., description="命令名称")
description: str = Field(..., description="命令描述(给 AI 看)")
parameters: Optional[Dict[str, Any]] = Field(
default=None,
description="参数 JSON Schema"
)
required: Optional[List[str]] = Field(
default=None,
description="必需参数列表"
)
example: Optional[str] = Field(
default=None,
description="调用示例"
)
class ToolManifest(BaseModel):
"""工具 Manifest"""
manifest_version: str = Field(
default="1.0.0",
description="Manifest 版本"
)
name: str = Field(..., description="工具名称(英文,唯一)")
display_name: str = Field(..., description="显示名称(中文)")
description: str = Field(..., description="工具描述")
author: Optional[str] = Field(default=None, description="作者")
version: str = Field(default="1.0.0", description="版本号")
# 执行配置
type: ToolType = Field(default=ToolType.SYNC, description="工具类型")
runtime: RuntimeType = Field(default=RuntimeType.PYTHON, description="运行时")
entry: str = Field(..., description="执行入口(文件路径或命令)")
timeout: int = Field(default=30000, description="超时时间(毫秒)")
# 配置
config_schema: Optional[Dict[str, Any]] = Field(
default=None,
description="配置项 Schema"
)
# 能力
commands: List[InvocationCommand] = Field(
default_factory=list,
description="可用命令列表"
)
# 元数据
tags: Optional[List[str]] = Field(default=None, description="标签")
dependencies: Optional[List[str]] = Field(default=None, description="依赖工具")
enabled: bool = Field(default=True, description="是否启用")
class Config:
use_enum_values = True
```
### 2.3 ToolCall Schema
```python
# tools/schemas/tool_call.py
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any, List
from datetime import datetime
class ToolCallRequest(BaseModel):
"""工具调用请求"""
tool_name: str = Field(..., description="工具名称")
command: str = Field(..., description="命令名称")
parameters: Dict[str, Any] = Field(default_factory=dict, description="参数")
timeout: Optional[int] = Field(default=None, description="超时时间")
context: Optional[Dict[str, Any]] = Field(
default=None,
description="上下文信息"
)
class ToolCallResponse(BaseModel):
"""工具调用响应"""
status: str = Field(..., description="状态: success/error")
result: Optional[Any] = Field(default=None, description="执行结果")
error: Optional[str] = Field(default=None, description="错误信息")
message: Optional[str] = Field(default=None, description="AI 友好消息")
base64: Optional[str] = Field(default=None, description="Base64 数据")
duration_ms: Optional[int] = Field(default=None, description="执行耗时")
timestamp: datetime = Field(default_factory=datetime.utcnow)
class ToolExecutionLog(BaseModel):
"""工具执行日志"""
id: str
tool_name: str
command: str
parameters: Dict[str, Any]
status: str
duration_ms: int
error: Optional[str]
user_id: Optional[str]
agent_id: Optional[str]
created_at: datetime
```
---
## 3. Manifest 示例
### 3.1 file_operator.yaml
```yaml
manifest_version: "1.0.0"
name: file_operator
display_name: 文件操作器
description: 强大的文件系统操作工具,支持读写、搜索、下载等功能
author: Jarvis
version: "1.0.0"
type: sync
runtime: python
entry: tools/implementations/file_operator.py
timeout: 30000
config_schema:
allowed_directories:
type: string
description: 允许操作的目录列表,逗号分隔
default: ""
max_file_size:
type: integer
description: 最大文件大小(字节)
default: 10485760
commands:
- name: read_file
description: |
读取指定路径文件的内容。支持 PDF、DOCX、XLSX 等格式自动解析。
参数:
- filePath (必需): 文件绝对路径
- encoding (可选): 编码格式,默认 utf8
parameters:
type: object
properties:
filePath:
type: string
description: 文件绝对路径
encoding:
type: string
default: utf8
required: [filePath]
- name: write_file
description: |
将内容写入文件。如果文件存在,自动创建新文件避免覆盖。
参数:
- filePath (必需): 文件绝对路径
- content (必需): 文件内容
parameters:
type: object
properties:
filePath:
type: string
content:
type: string
required: [filePath, content]
- name: list_directory
description: |
列出目录内容。
参数:
- directoryPath (必需): 目录绝对路径
- showHidden (可选): 是否显示隐藏文件
parameters:
type: object
properties:
directoryPath:
type: string
showHidden:
type: boolean
default: false
required: [directoryPath]
- name: search_files
description: |
递归搜索匹配模式的文件。
参数:
- searchPath (必需): 搜索起始目录
- pattern (必需): 文件模式,如 *.txt
parameters:
type: object
properties:
searchPath:
type: string
pattern:
type: string
required: [searchPath, pattern]
tags: [file, system, essential]
enabled: true
```
### 3.2 search.yaml
```yaml
manifest_version: "1.0.0"
name: web_search
display_name: 联网搜索
description: 语义级并发搜索引擎,支持多源搜索和结果聚合
author: Jarvis
version: "1.0.0"
type: sync
runtime: python
entry: tools/implementations/web_search.py
timeout: 60000
config_schema:
api_key:
type: string
description: 搜索引擎 API 密钥
required: true
max_results:
type: integer
description: 最大返回结果数
default: 10
commands:
- name: search
description: |
执行语义级搜索。
参数:
- query (必需): 搜索关键词
- max_results (可选): 最大结果数
- sources (可选): 搜索源列表
parameters:
type: object
properties:
query:
type: string
max_results:
type: integer
default: 10
sources:
type: array
items:
type: string
required: [query]
- name: deep_search
description: |
深度搜索,带摘要生成。
参数:
- query (必需): 研究主题
- keywords (必需): 关键词列表
parameters:
type: object
properties:
query:
type: string
keywords:
type: array
items:
type: string
required: [query, keywords]
tags: [search, web, research]
enabled: true
```
---
## 4. Schema 验证
### 4.1 验证器
```python
# tools/schemas/validator.py
from pydantic import ValidationError
from tools.schemas.manifest import ToolManifest
def validate_manifest(data: dict) -> ToolManifest:
"""验证 Manifest 数据"""
try:
return ToolManifest(**data)
except ValidationError as e:
raise ManifestValidationError(str(e))
def validate_tool_call(data: dict) -> ToolCallRequest:
"""验证工具调用请求"""
from tools.schemas.tool_call import ToolCallRequest
try:
return ToolCallRequest(**data)
except ValidationError as e:
raise ToolCallValidationError(str(e))
class ManifestValidationError(Exception):
"""Manifest 验证错误"""
pass
class ToolCallValidationError(Exception):
"""工具调用验证错误"""
pass
```
---
## 5. 配置分离
### 5.1 工具配置模板
```yaml
# tools/configs/.tool.example
# 文件操作器
file_operator:
allowed_directories: ""
max_file_size: 10485760
# 联网搜索
web_search:
api_key: ""
max_results: 10
```
### 5.2 配置加载器
```python
# tools/configs/loader.py
import yaml
from pathlib import Path
from typing import Dict, Any
class ConfigLoader:
"""工具配置加载器"""
def __init__(self, config_dir: Path):
self.config_dir = config_dir
self._cache: Dict[str, Any] = {}
def load(self, tool_name: str) -> Dict[str, Any]:
"""加载指定工具的配置"""
if tool_name in self._cache:
return self._cache[tool_name]
config_file = self.config_dir / f"{tool_name}.yaml"
if not config_file.exists():
return {}
with open(config_file) as f:
config = yaml.safe_load(f) or {}
self._cache[tool_name] = config
return config
def reload(self, tool_name: str) -> Dict[str, Any]:
"""重新加载配置"""
if tool_name in self._cache:
del self._cache[tool_name]
return self.load(tool_name)
def get(self, tool_name: str, key: str, default: Any = None) -> Any:
"""获取配置项"""
config = self.load(tool_name)
return config.get(key, default)
```
---
## 6. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 创建目录结构 | 🟢 高 |
| 2 | 实现 ToolManifest Schema | 🟢 高 |
| 3 | 实现 ToolCall Schema | 🟢 高 |
| 4 | 实现 Schema 验证器 | 🟢 高 |
| 5 | 创建配置加载器 | 🟢 高 |
| 6 | 创建 file_operator.yaml | 🟢 高 |
| 7 | 创建 search.yaml | 🟡 中 |
| 8 | 创建其他工具 Manifest | 🟡 中 |
| 9 | 单元测试 | 🟡 中 |
---
## 7. 核心文件变更
| 文件 | 变更 |
|------|------|
| `tools/__init__.py` | 模块初始化 |
| `tools/schemas/__init__.py` | Schema 导出 |
| `tools/schemas/manifest.py` | 新增 |
| `tools/schemas/tool_call.py` | 新增 |
| `tools/schemas/validator.py` | 新增 |
| `tools/configs/loader.py` | 新增 |
| `tools/manifests/file_operator.yaml` | 新增 |
| `tools/manifests/search.yaml` | 新增 |
---
## 8. 工作量估算
| 任务 | 工作量 |
|------|--------|
| Schema 定义 | 1 天 |
| 验证器 | 0.5 天 |
| 配置加载器 | 0.5 天 |
| Manifest 文件 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **3 天** |
---
## 9. 验收标准
- [ ] ToolManifest Schema 可正确验证 Manifest
- [ ] ToolCall Schema 可正确验证调用请求
- [ ] 配置加载器可正确加载配置
- [ ] Manifest 文件格式正确
- [ ] Schema 验证器可捕获错误
- [ ] 单元测试覆盖核心逻辑

View File

@@ -0,0 +1,476 @@
# Phase T.2:工具注册中心
日期2026-04-04
状态:待开始
依赖T.1(待完成)
---
## 1. 本阶段目的
建立 Jarvis 的工具注册中心:
- 工具动态发现
- 工具注册与管理
- 工具描述生成
- 调用统计与监控
---
## 2. 工具注册中心架构
### 2.1 核心类
```python
# tools/registry.py
from typing import Dict, List, Optional, Callable
from dataclasses import dataclass, field
from datetime import datetime
import asyncio
@dataclass
class ToolMetadata:
"""工具元数据"""
name: str
display_name: str
description: str
version: str
author: Optional[str] = None
tags: List[str] = field(default_factory=list)
dependencies: List[str] = field(default_factory=list)
enabled: bool = True
registered_at: datetime = field(default_factory=datetime.utcnow)
# 统计
call_count: int = 0
error_count: int = 0
total_duration_ms: int = 0
@property
def avg_duration_ms(self) -> int:
if self.call_count == 0:
return 0
return self.total_duration_ms // self.call_count
@property
def error_rate(self) -> float:
if self.call_count == 0:
return 0.0
return self.error_count / self.call_count
class ToolRegistry:
"""工具注册中心"""
def __init__(self):
self._tools: Dict[str, ToolMetadata] = {}
self._executors: Dict[str, Callable] = {}
self._configs: Dict[str, dict] = {}
self._lock = asyncio.Lock()
# === 注册方法 ===
async def register(
self,
manifest_path: str,
executor: Callable,
config: Optional[dict] = None,
) -> ToolMetadata:
"""注册工具"""
from tools.schemas.validator import validate_manifest
import yaml
with open(manifest_path) as f:
data = yaml.safe_load(f)
manifest = validate_manifest(data)
metadata = ToolMetadata(
name=manifest.name,
display_name=manifest.display_name,
description=manifest.description,
version=manifest.version,
author=manifest.author,
tags=manifest.tags or [],
dependencies=manifest.dependencies or [],
enabled=manifest.enabled,
)
async with self._lock:
self._tools[manifest.name] = metadata
self._executors[manifest.name] = executor
if config:
self._configs[manifest.name] = config
return metadata
async def unregister(self, name: str) -> bool:
"""注销工具"""
async with self._lock:
if name in self._tools:
del self._tools[name]
del self._executors[name]
self._configs.pop(name, None)
return True
return False
async def enable(self, name: str) -> None:
"""启用工具"""
async with self._lock:
if name in self._tools:
self._tools[name].enabled = True
async def disable(self, name: str) -> None:
"""禁用工具"""
async with self._lock:
if name in self._tools:
self._tools[name].enabled = False
# === 查询方法 ===
async def get(self, name: str) -> Optional[ToolMetadata]:
"""获取工具元数据"""
return self._tools.get(name)
async def get_executor(self, name: str) -> Optional[Callable]:
"""获取工具执行器"""
return self._executors.get(name)
async def get_config(self, name: str) -> dict:
"""获取工具配置"""
return self._configs.get(name, {})
async def list_all(self) -> List[ToolMetadata]:
"""列出所有工具"""
return list(self._tools.values())
async def list_enabled(self) -> List[ToolMetadata]:
"""列出已启用的工具"""
return [t for t in self._tools.values() if t.enabled]
async def list_by_tag(self, tag: str) -> List[ToolMetadata]:
"""按标签筛选工具"""
return [t for t in self._tools.values() if tag in t.tags]
async def search(self, query: str) -> List[ToolMetadata]:
"""搜索工具"""
query = query.lower()
return [
t for t in self._tools.values()
if query in t.name.lower()
or query in t.description.lower()
or query in t.display_name.lower()
]
# === 统计方法 ===
async def record_call(
self,
name: str,
duration_ms: int,
error: bool = False,
) -> None:
"""记录调用"""
async with self._lock:
if name in self._tools:
tool = self._tools[name]
tool.call_count += 1
tool.total_duration_ms += duration_ms
if error:
tool.error_count += 1
async def get_stats(self) -> dict:
"""获取统计信息"""
tools = list(self._tools.values())
return {
"total_tools": len(tools),
"enabled_tools": sum(1 for t in tools if t.enabled),
"total_calls": sum(t.call_count for t in tools),
"total_errors": sum(t.error_count for t in tools),
"avg_error_rate": sum(t.error_rate for t in tools) / len(tools) if tools else 0,
}
```
---
## 3. 工具发现机制
### 3.1 自动发现
```python
# tools/discovery.py
from pathlib import Path
from typing import List
class ToolDiscovery:
"""工具自动发现"""
def __init__(self, manifest_dir: Path):
self.manifest_dir = manifest_dir
def discover(self) -> List[Path]:
"""发现所有 Manifest 文件"""
manifests = list(self.manifest_dir.glob("**/*.yaml"))
manifests.extend(self.manifest_dir.glob("**/*.yml"))
manifests.extend(self.manifest_dir.glob("**/*.json"))
return manifests
def discover_by_tag(self, tag: str) -> List[Path]:
"""按标签发现"""
# 读取所有 manifest筛选标签
pass
async def hot_reload(self, registry: ToolRegistry) -> None:
"""热重载工具"""
for manifest_path in self.discover():
# 重新注册
pass
```
### 3.2 启动时注册
```python
# tools/loader.py
from pathlib import Path
async def load_all_tools(registry: ToolRegistry) -> None:
"""加载所有工具"""
manifest_dir = Path(__file__).parent / "manifests"
discovery = ToolDiscovery(manifest_dir)
for manifest_path in discovery.discover():
tool_name = manifest_path.stem
# 加载 executor
executor = load_executor(manifest_path)
# 加载配置
config = load_config(tool_name)
# 注册
await registry.register(manifest_path, executor, config)
def load_executor(manifest_path: Path) -> Callable:
"""加载工具执行器"""
import yaml
with open(manifest_path) as f:
manifest = yaml.safe_load(f)
# 根据运行时类型加载
runtime = manifest.get("runtime", "python")
if runtime == "python":
return load_python_executor(manifest)
elif runtime == "javascript":
return load_js_executor(manifest)
else:
return load_native_executor(manifest)
```
---
## 4. 工具描述生成
### 4.1 AI 友好的工具描述
```python
# tools/description.py
from typing import Dict, List
def generate_tool_description(manifest: dict) -> str:
"""生成 AI 友好的工具描述"""
lines = [
f"## {manifest['display_name']}",
f"{manifest['description']}",
"",
"### 可用命令:",
]
for cmd in manifest.get("commands", []):
lines.append(f"#### {cmd['name']}")
lines.append(cmd["description"])
lines.append("")
if cmd.get("example"):
lines.append("**示例:**")
lines.append(f"```\n{cmd['example']}\n```")
lines.append("")
return "\n".join(lines)
def generate_tools_for_llm(registry: ToolRegistry) -> str:
"""生成给 LLM 的工具列表"""
tools = registry.list_enabled()
sections = ["## 可用工具\n"]
for tool in tools:
manifest = load_manifest(tool.name)
sections.append(generate_tool_description(manifest))
sections.append("\n---\n")
return "\n".join(sections)
```
---
## 5. 权限控制
### 5.1 工具权限
```python
# tools/permissions.py
from enum import Enum
from typing import Set
class ToolPermission(str, Enum):
"""工具权限"""
EXECUTE = "tool:execute"
CONFIGURE = "tool:configure"
ENABLE = "tool:enable"
DISABLE = "tool:disable"
class ToolPermissionChecker:
"""工具权限检查"""
def __init__(self):
self._user_permissions: Dict[str, Set[ToolPermission]] = {}
self._tool_roles: Dict[str, Set[str]] = {} # tool_name -> required_roles
def set_user_permissions(
self,
user_id: str,
permissions: Set[ToolPermission],
) -> None:
"""设置用户权限"""
self._user_permissions[user_id] = permissions
def set_tool_roles(
self,
tool_name: str,
required_roles: Set[str],
) -> None:
"""设置工具所需角色"""
self._tool_roles[tool_name] = required_roles
def can_execute(self, user_id: str, tool_name: str) -> bool:
"""检查用户是否可以执行工具"""
# 检查全局权限
if ToolPermission.EXECUTE in self._user_permissions.get(user_id, set()):
return True
# 检查工具特定角色
required_roles = self._tool_roles.get(tool_name, set())
if not required_roles:
return True
# TODO: 检查用户是否有所需角色
return False
```
---
## 6. 集成到 Agent
### 6.1 LangChain 集成
```python
# tools/langchain_adapter.py
from typing import List, Optional
from langchain.tools import BaseTool
from pydantic import BaseModel
class LangChainToolAdapter:
"""LangChain 工具适配器"""
def __init__(self, registry: ToolRegistry):
self.registry = registry
def to_langchain_tools(self) -> List[BaseTool]:
"""转换为 LangChain 工具"""
tools = []
for metadata in self.registry.list_enabled():
executor = self.registry.get_executor(metadata.name)
config = self.registry.get_config(metadata.name)
tool = self._create_tool(metadata, executor, config)
tools.append(tool)
return tools
def _create_tool(
self,
metadata: ToolMetadata,
executor: Callable,
config: dict,
) -> BaseTool:
"""创建单个 LangChain 工具"""
# 根据 manifest 创建工具
pass
```
---
## 7. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 实现 ToolRegistry 类 | 🟢 高 |
| 2 | 实现 ToolDiscovery | 🟢 高 |
| 3 | 实现工具描述生成 | 🟡 中 |
| 4 | 实现权限检查 | 🟡 中 |
| 5 | 实现 LangChain 适配器 | 🟡 中 |
| 6 | 集成到 Agent | 🟢 高 |
| 7 | 单元测试 | 🟡 中 |
---
## 8. 核心文件变更
| 文件 | 变更 |
|------|------|
| `tools/registry.py` | 新增 |
| `tools/discovery.py` | 新增 |
| `tools/description.py` | 新增 |
| `tools/permissions.py` | 新增 |
| `tools/langchain_adapter.py` | 新增 |
| `tools/__init__.py` | 更新导出 |
---
## 9. 工作量估算
| 任务 | 工作量 |
|------|--------|
| ToolRegistry | 0.5 天 |
| ToolDiscovery | 0.5 天 |
| 描述生成 | 0.3 天 |
| 权限检查 | 0.3 天 |
| LangChain 适配 | 0.3 天 |
| 集成 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **2.5 天** |
---
## 10. 验收标准
- [ ] ToolRegistry 可正确注册/注销工具
- [ ] ToolDiscovery 可发现所有 Manifest
- [ ] 工具描述生成正确
- [ ] 权限检查正常工作
- [ ] LangChain 工具可正常转换
- [ ] Agent 可使用注册的工具
- [ ] 单元测试通过

View File

@@ -0,0 +1,586 @@
# Phase T.3:核心工具实现
日期2026-04-04
状态:待开始
依赖T.2(待完成)
---
## 1. 本阶段目的
实现 Jarvis 的核心工具:
- 文件操作工具
- 搜索工具
- 网页抓取工具
- 任务管理工具
---
## 2. 文件操作工具
### 2.1 实现
```python
# tools/implementations/file_operator.py
import os
import shutil
import asyncio
from pathlib import Path
from typing import Optional, List, Dict, Any
class FileOperator:
"""文件操作工具"""
def __init__(self, config: dict):
self.allowed_dirs = self._parse_allowed_dirs(
config.get("allowed_directories", "")
)
self.max_file_size = config.get("max_file_size", 10 * 1024 * 1024)
def _parse_allowed_dirs(self, dirs_str: str) -> Optional[List[str]]:
"""解析允许目录"""
if not dirs_str:
return None
return [d.strip() for d in dirs_str.split(",") if d.strip()]
def _check_path(self, path: str) -> bool:
"""检查路径是否允许"""
if not self.allowed_dirs:
return True
resolved = Path(path).resolve()
return any(
str(resolved).startswith(allowed)
for allowed in self.allowed_dirs
)
async def read_file(
self,
filePath: str,
encoding: str = "utf-8",
) -> Dict[str, Any]:
"""读取文件"""
if not self._check_path(filePath):
return {"status": "error", "error": "路径不在允许范围内"}
path = Path(filePath)
if not path.exists():
return {"status": "error", "error": "文件不存在"}
if path.stat().st_size > self.max_file_size:
return {"status": "error", "error": "文件过大"}
# 根据扩展名处理
suffix = path.suffix.lower()
if suffix in [".pdf", ".docx", ".xlsx", ".xls", ".csv"]:
return await self._read_binary_file(path)
try:
content = path.read_text(encoding=encoding)
return {"status": "success", "result": content}
except Exception as e:
return {"status": "error", "error": str(e)}
async def _read_binary_file(self, path: Path) -> Dict[str, Any]:
"""读取二进制文件"""
suffix = path.suffix.lower()
if suffix == ".pdf":
return await self._read_pdf(path)
elif suffix in [".docx", ".doc"]:
return await self._read_docx(path)
elif suffix in [".xlsx", ".xls"]:
return await self._read_xlsx(path)
elif suffix == ".csv":
return await self._read_csv(path)
return {"status": "error", "error": "不支持的文件格式"}
async def write_file(
self,
filePath: str,
content: str,
) -> Dict[str, Any]:
"""写入文件"""
if not self._check_path(filePath):
return {"status": "error", "error": "路径不在允许范围内"}
path = Path(filePath)
# 如果文件存在,自动创建新文件名
if path.exists():
path = self._get_unique_path(path)
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
return {
"status": "success",
"result": f"文件已保存: {path.name}",
"path": str(path),
}
except Exception as e:
return {"status": "error", "error": str(e)}
def _get_unique_path(self, path: Path) -> Path:
"""获取唯一路径"""
if not path.exists():
return path
stem = path.stem
suffix = path.suffix
parent = path.parent
counter = 1
while True:
new_path = parent / f"{stem}({counter}){suffix}"
if not new_path.exists():
return new_path
counter += 1
async def list_directory(
self,
directoryPath: str,
showHidden: bool = False,
) -> Dict[str, Any]:
"""列出目录"""
if not self._check_path(directoryPath):
return {"status": "error", "error": "路径不在允许范围内"}
path = Path(directoryPath)
if not path.exists():
return {"status": "error", "error": "目录不存在"}
if not path.is_dir():
return {"status": "error", "error": "不是目录"}
items = []
for item in path.iterdir():
if not showHidden and item.name.startswith("."):
continue
items.append({
"name": item.name,
"type": "directory" if item.is_dir() else "file",
"size": item.stat().st_size if item.is_file() else None,
})
return {"status": "success", "result": items}
async def search_files(
self,
searchPath: str,
pattern: str,
**options,
) -> Dict[str, Any]:
"""搜索文件"""
import fnmatch
if not self._check_path(searchPath):
return {"status": "error", "error": "路径不在允许范围内"}
path = Path(searchPath)
if not path.exists():
return {"status": "error", "error": "路径不存在"}
case_sensitive = options.get("caseSensitive", False)
file_type = options.get("fileType", "all")
include_hidden = options.get("includeHidden", False)
results = []
for item in path.rglob("*"):
if not include_hidden and item.name.startswith("."):
continue
if not fnmatch.fnmatch(item.name, pattern):
continue
if file_type == "file" and item.is_dir():
continue
if file_type == "directory" and item.is_file():
continue
results.append(str(item))
return {"status": "success", "result": results[:100]} # 限制结果数
```
### 2.2 Manifest 绑定
```python
# tools/implementations/__init__.py
from tools.implementations.file_operator import FileOperator
def create_file_operator_executor(config: dict):
"""创建文件操作执行器"""
operator = FileOperator(config)
async def execute(command: str, parameters: dict) -> dict:
if command == "read_file":
return await operator.read_file(**parameters)
elif command == "write_file":
return await operator.write_file(**parameters)
elif command == "list_directory":
return await operator.list_directory(**parameters)
elif command == "search_files":
return await operator.search_files(**parameters)
else:
return {"status": "error", "error": f"未知命令: {command}"}
return execute
```
---
## 3. 搜索工具
### 3.1 实现
```python
# tools/implementations/web_search.py
import asyncio
from typing import Dict, Any, List, Optional
class WebSearch:
"""联网搜索工具"""
def __init__(self, config: dict):
self.api_key = config.get("api_key")
self.max_results = config.get("max_results", 10)
async def search(
self,
query: str,
max_results: Optional[int] = None,
) -> Dict[str, Any]:
"""执行搜索"""
try:
# 实现搜索逻辑
results = await self._do_search(
query,
max_results or self.max_results,
)
return {"status": "success", "result": results}
except Exception as e:
return {"status": "error", "error": str(e)}
async def _do_search(self, query: str, limit: int) -> List[dict]:
"""实际搜索"""
# TODO: 接入搜索 API
return []
async def deep_search(
self,
query: str,
keywords: List[str],
) -> Dict[str, Any]:
"""深度搜索"""
try:
# 并发执行多个搜索
tasks = [
self._do_search(kw, 5)
for kw in [query] + keywords
]
results = await asyncio.gather(*tasks)
# 聚合结果
aggregated = self._aggregate_results(results)
return {"status": "success", "result": aggregated}
except Exception as e:
return {"status": "error", "error": str(e)}
def _aggregate_results(self, results: List[List[dict]]) -> dict:
"""聚合搜索结果"""
# TODO: 实现结果聚合
return {"summary": "聚合结果", "sources": []}
```
---
## 4. 网页抓取工具
### 4.1 实现
```python
# tools/implementations/web_fetch.py
import asyncio
from typing import Dict, Any, Optional
from dataclasses import dataclass
@dataclass
class FetchResult:
"""抓取结果"""
url: str
title: Optional[str]
content: str
images: List[str]
links: List[str]
status: int
class WebFetch:
"""网页抓取工具"""
def __init__(self, config: dict):
self.timeout = config.get("timeout", 30)
self.user_agent = config.get(
"user_agent",
"Mozilla/5.0 (compatible; Jarvis/1.0)"
)
async def fetch(
self,
url: str,
include_images: bool = True,
) -> Dict[str, Any]:
"""抓取网页"""
try:
result = await self._do_fetch(url, include_images)
return {
"status": "success",
"result": {
"url": result.url,
"title": result.title,
"content": result.content,
"images": result.images,
"status": result.status,
}
}
except Exception as e:
return {"status": "error", "error": str(e)}
async def _do_fetch(
self,
url: str,
include_images: bool,
) -> FetchResult:
"""实际抓取"""
import httpx
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
url,
headers={"User-Agent": self.user_agent},
)
response.raise_for_status()
# TODO: 解析 HTML 提取内容
return FetchResult(
url=url,
title=None,
content=response.text,
images=[],
links=[],
status=response.status_code,
)
async def screenshot(
self,
url: str,
) -> Dict[str, Any]:
"""截取网页截图"""
# TODO: 接入截图服务
return {"status": "error", "error": "未实现"}
```
---
## 5. 任务管理工具
### 5.1 实现
```python
# tools/implementations/task_manager.py
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
class TaskStatus(str, Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
@dataclass
class Task:
"""任务"""
id: str
name: str
description: str
status: TaskStatus = TaskStatus.PENDING
created_at: datetime = field(default_factory=datetime.utcnow)
scheduled_at: Optional[datetime] = None
result: Optional[Any] = None
error: Optional[str] = None
class TaskManager:
"""任务管理工具"""
def __init__(self, config: dict):
self._tasks: Dict[str, Task] = {}
async def create_task(
self,
name: str,
description: str,
scheduled_at: Optional[datetime] = None,
) -> Dict[str, Any]:
"""创建任务"""
import uuid
task_id = str(uuid.uuid4())[:8]
task = Task(
id=task_id,
name=name,
description=description,
scheduled_at=scheduled_at,
)
self._tasks[task_id] = task
return {
"status": "success",
"result": {
"id": task_id,
"name": task.name,
"status": task.status.value,
}
}
async def list_tasks(
self,
status: Optional[str] = None,
) -> Dict[str, Any]:
"""列出任务"""
tasks = list(self._tasks.values())
if status:
tasks = [t for t in tasks if t.status.value == status]
return {
"status": "success",
"result": [
{
"id": t.id,
"name": t.name,
"status": t.status.value,
"created_at": t.created_at.isoformat(),
}
for t in tasks
]
}
async def get_task(self, task_id: str) -> Dict[str, Any]:
"""获取任务"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "任务不存在"}
return {
"status": "success",
"result": {
"id": task.id,
"name": task.name,
"description": task.description,
"status": task.status.value,
"result": task.result,
"error": task.error,
}
}
async def complete_task(
self,
task_id: str,
result: Any,
) -> Dict[str, Any]:
"""完成任务"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "任务不存在"}
task.status = TaskStatus.COMPLETED
task.result = result
return {"status": "success"}
async def fail_task(
self,
task_id: str,
error: str,
) -> Dict[str, Any]:
"""标记任务失败"""
task = self._tasks.get(task_id)
if not task:
return {"status": "error", "error": "任务不存在"}
task.status = TaskStatus.FAILED
task.error = error
return {"status": "success"}
```
---
## 6. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 实现 FileOperator | 🟢 高 |
| 2 | 实现 WebSearch | 🟡 中 |
| 3 | 实现 WebFetch | 🟡 中 |
| 4 | 实现 TaskManager | 🟡 中 |
| 5 | 创建 Manifest 文件 | 🟢 高 |
| 6 | 注册到工具中心 | 🟢 高 |
| 7 | 单元测试 | 🟡 中 |
---
## 7. 核心文件变更
| 文件 | 变更 |
|------|------|
| `tools/implementations/__init__.py` | 新增 |
| `tools/implementations/file_operator.py` | 新增 |
| `tools/implementations/web_search.py` | 新增 |
| `tools/implementations/web_fetch.py` | 新增 |
| `tools/implementations/task_manager.py` | 新增 |
| `tools/manifests/file_operator.yaml` | 更新 |
| `tools/manifests/web_search.yaml` | 新增 |
| `tools/manifests/web_fetch.yaml` | 新增 |
| `tools/manifests/task_manager.yaml` | 新增 |
---
## 8. 工作量估算
| 任务 | 工作量 |
|------|--------|
| FileOperator | 1.5 天 |
| WebSearch | 1 天 |
| WebFetch | 1 天 |
| TaskManager | 0.5 天 |
| Manifest + 注册 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **5 天** |
---
## 9. 验收标准
- [ ] FileOperator 可正确读写文件
- [ ] FileOperator 支持多种格式解析
- [ ] FileOperator 路径安全检查正常
- [ ] WebSearch 可执行搜索
- [ ] WebFetch 可抓取网页
- [ ] TaskManager 可管理任务
- [ ] 所有工具注册到工具中心
- [ ] 单元测试通过

View File

@@ -0,0 +1,642 @@
# Phase T.4:高级特性
日期2026-04-04
状态:待开始
依赖T.3(待完成)
---
## 1. 本阶段目的
实现 Jarvis 工具系统的高级特性:
- 多运行时支持Python/JS/原生)
- Agent 间协作
- 定时任务
---
## 2. 多运行时支持
### 2.1 运行时架构
```
┌─────────────────────────────────────────────────────────────┐
│ Runtime Manager │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Python │ │ JS │ │ Native │ │ WASM │ │
│ │ Runtime │ │ Runtime │ │ Runtime │ │ Runtime │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ └─────────────┴──────┬──────┴─────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ │ Tool Executor │ │
│ │ (统一接口) │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 运行时基类
```python
# tools/runtime/base.py
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
class BaseRuntime(ABC):
"""运行时基类"""
@abstractmethod
async def execute(
self,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
"""执行工具"""
pass
@abstractmethod
async def validate(self, entry: str) -> bool:
"""验证工具是否可用"""
pass
@abstractmethod
def get_name(self) -> str:
"""获取运行时名称"""
pass
```
### 2.3 Python 运行时
```python
# tools/runtime/python_runtime.py
import asyncio
from pathlib import Path
from tools.runtime.base import BaseRuntime
class PythonRuntime(BaseRuntime):
"""Python 运行时"""
def __init__(self):
self._executors: Dict[str, Callable] = {}
def get_name(self) -> str:
return "python"
async def validate(self, entry: str) -> bool:
path = Path(entry)
return path.exists() and path.suffix == ".py"
async def execute(
self,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
# 动态加载并执行
# 或者通过 subprocess 调用
pass
```
### 2.4 JavaScript 运行时
```python
# tools/runtime/js_runtime.py
import asyncio
import subprocess
import json
from tools.runtime.base import BaseRuntime
class JavaScriptRuntime(BaseRuntime):
"""JavaScript 运行时"""
def __init__(self):
self.node_path = "node" # 可配置
def get_name(self) -> str:
return "javascript"
async def validate(self, entry: str) -> bool:
# 检查 node 是否可用
try:
result = await asyncio.create_subprocess_exec(
self.node_path, "--version",
stdout=asyncio.subprocess.PIPE,
)
return result.returncode == 0
except:
return False
async def execute(
self,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
# 通过 stdio 调用 Node.js 脚本
input_data = json.dumps({
"command": command,
"parameters": parameters,
})
process = await asyncio.create_subprocess_exec(
self.node_path, entry,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await asyncio.wait_for(
process.communicate(input=input_data.encode()),
timeout=timeout / 1000,
)
if process.returncode != 0:
return {
"status": "error",
"error": stderr.decode(),
}
return json.loads(stdout.decode())
```
### 2.5 原生运行时
```python
# tools/runtime/native_runtime.py
import asyncio
import subprocess
from tools.runtime.base import BaseRuntime
class NativeRuntime(BaseRuntime):
"""原生二进制运行时"""
def get_name(self) -> str:
return "native"
async def validate(self, entry: str) -> bool:
from pathlib import Path
path = Path(entry)
return path.exists() and path.stat().st_mode & 0o111
async def execute(
self,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
# 调用原生可执行文件
args = [entry, command] + self._format_args(parameters)
process = await asyncio.create_subprocess_exec(
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await asyncio.wait_for(
process.communicate(),
timeout=timeout / 1000,
)
if process.returncode != 0:
return {
"status": "error",
"error": stderr.decode(),
}
return {
"status": "success",
"result": stdout.decode(),
}
def _format_args(self, parameters: Dict[str, Any]) -> list:
"""格式化参数"""
args = []
for key, value in parameters.items():
args.extend([f"--{key}", str(value)])
return args
```
### 2.6 运行时管理器
```python
# tools/runtime/manager.py
from tools.runtime.base import BaseRuntime
from tools.runtime.python_runtime import PythonRuntime
from tools.runtime.js_runtime import JavaScriptRuntime
from tools.runtime.native_runtime import NativeRuntime
class RuntimeManager:
"""运行时管理器"""
def __init__(self):
self._runtimes: Dict[str, BaseRuntime] = {
"python": PythonRuntime(),
"javascript": JavaScriptRuntime(),
"native": NativeRuntime(),
}
def get_runtime(self, name: str) -> BaseRuntime:
return self._runtimes.get(name)
async def execute(
self,
runtime_name: str,
entry: str,
command: str,
parameters: Dict[str, Any],
timeout: int,
) -> Dict[str, Any]:
runtime = self.get_runtime(runtime_name)
if not runtime:
return {
"status": "error",
"error": f"未知运行时: {runtime_name}",
}
return await runtime.execute(entry, command, parameters, timeout)
```
---
## 3. Agent 间协作
### 3.1 协作协议
```python
# agents/tools/collaboration.py
from typing import Dict, Any, Optional, List
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
class MessageType(str, Enum):
REQUEST = "request" # 请求协作
RESPONSE = "response" # 响应结果
PROGRESS = "progress" # 进度更新
CANCEL = "cancel" # 取消请求
@dataclass
class CollaborationMessage:
"""协作消息"""
id: str
type: MessageType
from_agent: str
to_agent: str
content: Any
metadata: Dict[str, Any]
timestamp: datetime = None
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.utcnow()
class CollaborationProtocol:
"""Agent 协作协议"""
def __init__(self):
self._pending_requests: Dict[str, CollaborationMessage] = {}
self._handlers: Dict[str, callable] = {}
def register_handler(self, tool_name: str, handler: callable) -> None:
"""注册工具处理器"""
self._handlers[tool_name] = handler
async def request_collaboration(
self,
from_agent: str,
to_agent: str,
tool_name: str,
parameters: Dict[str, Any],
timeout: int = 30000,
) -> Dict[str, Any]:
"""请求协作"""
import uuid
request_id = str(uuid.uuid4())
message = CollaborationMessage(
id=request_id,
type=MessageType.REQUEST,
from_agent=from_agent,
to_agent=to_agent,
content={
"tool": tool_name,
"parameters": parameters,
},
metadata={"timeout": timeout},
)
self._pending_requests[request_id] = message
# 发送请求
await self._send_message(message)
# 等待响应
try:
response = await self._wait_for_response(
request_id,
timeout,
)
return response
except TimeoutError:
return {
"status": "error",
"error": "协作请求超时",
}
async def handle_request(
self,
message: CollaborationMessage,
) -> CollaborationMessage:
"""处理协作请求"""
tool_name = message.content["tool"]
parameters = message.content["parameters"]
handler = self._handlers.get(tool_name)
if not handler:
return CollaborationMessage(
id=str(uuid.uuid4()),
type=MessageType.RESPONSE,
from_agent=message.to_agent,
to_agent=message.from_agent,
content={
"status": "error",
"error": f"未知工具: {tool_name}",
},
metadata={},
)
try:
result = await handler(**parameters)
return CollaborationMessage(
id=str(uuid.uuid4()),
type=MessageType.RESPONSE,
from_agent=message.to_agent,
to_agent=message.from_agent,
content={"status": "success", "result": result},
metadata={},
)
except Exception as e:
return CollaborationMessage(
id=str(uuid.uuid4()),
type=MessageType.RESPONSE,
from_agent=message.to_agent,
to_agent=message.from_agent,
content={"status": "error", "error": str(e)},
metadata={},
)
async def _send_message(self, message: CollaborationMessage) -> None:
"""发送消息"""
# TODO: 实现消息发送WebSocket/消息队列)
pass
async def _wait_for_response(
self,
request_id: str,
timeout: int,
) -> Dict[str, Any]:
"""等待响应"""
# TODO: 实现等待逻辑
pass
```
---
## 4. 定时任务
### 4.1 定时任务服务
```python
# tools/scheduler.py
import asyncio
from typing import Dict, Any, Callable, Optional
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from enum import Enum
class ScheduleType(str, Enum):
ONCE = "once" # 单次
INTERVAL = "interval" # 间隔
CRON = "cron" # Cron 表达式
@dataclass
class ScheduledTask:
"""定时任务"""
id: str
name: str
schedule_type: ScheduleType
schedule_value: str # 时间/间隔/cron
tool_name: str
parameters: Dict[str, Any]
enabled: bool = True
last_run: Optional[datetime] = None
next_run: Optional[datetime] = None
run_count: int = 0
callback: Optional[Callable] = field(default=None)
class ToolScheduler:
"""工具定时调度器"""
def __init__(self):
self._tasks: Dict[str, ScheduledTask] = {}
self._running = False
self._loop_task = None
async def schedule(
self,
name: str,
schedule_type: ScheduleType,
schedule_value: str,
tool_name: str,
parameters: Dict[str, Any],
callback: Optional[Callable] = None,
) -> str:
"""创建定时任务"""
import uuid
task_id = str(uuid.uuid4())[:8]
task = ScheduledTask(
id=task_id,
name=name,
schedule_type=schedule_type,
schedule_value=schedule_value,
tool_name=tool_name,
parameters=parameters,
callback=callback,
)
task.next_run = self._calculate_next_run(task)
self._tasks[task_id] = task
# 启动调度器
if not self._running:
await self.start()
return task_id
def _calculate_next_run(self, task: ScheduledTask) -> datetime:
"""计算下次运行时间"""
now = datetime.utcnow()
if task.schedule_type == ScheduleType.ONCE:
return datetime.fromisoformat(task.schedule_value)
elif task.schedule_type == ScheduleType.INTERVAL:
seconds = int(task.schedule_value)
return now + timedelta(seconds=seconds)
elif task.schedule_type == ScheduleType.CRON:
# 解析 cron 表达式
# TODO: 实现 cron 解析
return now + timedelta(hours=1)
return now
async def start(self) -> None:
"""启动调度器"""
self._running = True
self._loop_task = asyncio.create_task(self._run_loop())
async def stop(self) -> None:
"""停止调度器"""
self._running = False
if self._loop_task:
self._loop_task.cancel()
async def _run_loop(self) -> None:
"""调度循环"""
while self._running:
now = datetime.utcnow()
for task in self._tasks.values():
if not task.enabled:
continue
if task.next_run and task.next_run <= now:
await self._execute_task(task)
await asyncio.sleep(1) # 每秒检查一次
async def _execute_task(self, task: ScheduledTask) -> None:
"""执行任务"""
# 调用工具
executor = get_executor(task.tool_name)
result = await executor(
command=task.parameters.get("command"),
parameters=task.parameters,
)
# 更新状态
task.last_run = datetime.utcnow()
task.run_count += 1
# 计算下次运行
if task.schedule_type != ScheduleType.ONCE:
task.next_run = self._calculate_next_run(task)
else:
task.enabled = False
# 调用回调
if task.callback:
await task.callback(task, result)
async def cancel(self, task_id: str) -> bool:
"""取消任务"""
if task_id in self._tasks:
del self._tasks[task_id]
return True
return False
async def list_tasks(self) -> list:
"""列出所有任务"""
return [
{
"id": t.id,
"name": t.name,
"type": t.schedule_type.value,
"enabled": t.enabled,
"next_run": t.next_run.isoformat() if t.next_run else None,
"run_count": t.run_count,
}
for t in self._tasks.values()
]
```
---
## 5. 实现步骤
| 步骤 | 任务 | 优先级 |
|------|------|--------|
| 1 | 实现运行时基类 | 🟢 高 |
| 2 | 实现 Python 运行时 | 🟢 高 |
| 3 | 实现 JS 运行时 | 🟡 中 |
| 4 | 实现原生运行时 | 🟡 中 |
| 5 | 实现运行时管理器 | 🟢 高 |
| 6 | 实现协作协议 | 🟡 中 |
| 7 | 实现定时调度器 | 🟡 中 |
| 8 | 单元测试 | 🟡 中 |
---
## 6. 核心文件变更
| 文件 | 变更 |
|------|------|
| `tools/runtime/__init__.py` | 新增 |
| `tools/runtime/base.py` | 新增 |
| `tools/runtime/python_runtime.py` | 新增 |
| `tools/runtime/js_runtime.py` | 新增 |
| `tools/runtime/native_runtime.py` | 新增 |
| `tools/runtime/manager.py` | 新增 |
| `agents/tools/collaboration.py` | 新增 |
| `tools/scheduler.py` | 新增 |
---
## 7. 工作量估算
| 任务 | 工作量 |
|------|--------|
| 运行时基类 | 0.5 天 |
| Python 运行时 | 0.5 天 |
| JS 运行时 | 0.5 天 |
| 原生运行时 | 0.5 天 |
| 运行时管理器 | 0.5 天 |
| 协作协议 | 1 天 |
| 定时调度器 | 0.5 天 |
| 单元测试 | 0.5 天 |
| **总计** | **4 天** |
---
## 8. 验收标准
- [ ] Python 运行时可正常执行工具
- [ ] JS 运行时可通过 stdio 调用
- [ ] 原生运行时可执行二进制
- [ ] 运行时管理器正确路由
- [ ] 协作协议可正常请求/响应
- [ ] 定时调度器可按计划执行任务
- [ ] 单元测试通过