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

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 调用
- [ ] 原生运行时可执行二进制
- [ ] 运行时管理器正确路由
- [ ] 协作协议可正常请求/响应
- [ ] 定时调度器可按计划执行任务
- [ ] 单元测试通过