Browse Source
- TopNav: '文档' link only shows when loggedIn - AgentToolbar: document button v-if="loggedIn" - getAgentToolsForChatByAgentId: filter tools by agentPublicToolSlugs whitelist for guest users (previously only skipped adminOnly tools, exposing document and other non-public tools to unauthenticated users) Co-authored-by: CodeFree <codefree@chinatelcom.cn>feat/agent-independence
8 changed files with 341 additions and 15 deletions
@ -0,0 +1,306 @@ |
|||
# Agent 文档功能 — 交接文档 |
|||
|
|||
> 日期:2026-08-09 |
|||
> 分支:feat/agent-independence |
|||
> Commits:b62a417 → 10688be(共 16 个 commit) |
|||
|
|||
## 一、功能概述 |
|||
|
|||
Agent 文档功能让 agent 能够跨会话持久化信息,分为两种类型: |
|||
|
|||
| 类型 | 用途 | 上下文行为 | |
|||
|------|------|------------| |
|||
| `memory` | 内部记忆(用户偏好、身份信息等) | 每次对话自动注入 system prompt | |
|||
| `document` | 交付物(周报、需求文档、会议纪要等) | 不自动注入,需主动调用工具读取 | |
|||
|
|||
### 核心能力 |
|||
|
|||
1. **Agent 工具**:LLM 可通过 `document` 工具 create/read/update/delete/list 文档 |
|||
2. **记忆注入**:`type=memory` 的文档在每次对话时自动拼接到 system prompt,LLM 无需调工具即可使用 |
|||
3. **用户界面**:右侧抽屉组件,在会话页面内查看/搜索/删除文档,不跳转页面 |
|||
4. **REST API**:5 个端点供前端直接操作文档 |
|||
5. **权限隔离**:所有操作强制 `WHERE userId = ?`,用户级隔离 |
|||
6. **审批机制**:delete 操作默认需用户审批 |
|||
|
|||
## 二、架构设计 |
|||
|
|||
``` |
|||
┌─────────────────────────────────────────────────────────┐ |
|||
│ 前端 │ |
|||
│ ┌──────────────┐ ┌──────────────────────────────────┐ │ |
|||
│ │ AgentToolbar │ │ AgentSidebar (desktop + mobile) │ │ |
|||
│ │ 文档按钮 │ │ 文档按钮 │ │ |
|||
│ └──────┬───────┘ └────────────┬─────────────────────┘ │ |
|||
│ │ emit('showDocuments')│ │ |
|||
│ └──────────┬────────────┘ │ |
|||
│ ▼ │ |
|||
│ ┌──────────────────────────────────────────────────┐ │ |
|||
│ │ AgentChatArea → emit('showDocuments') │ │ |
|||
│ └──────────────────────┬───────────────────────────┘ │ |
|||
│ ▼ │ |
|||
│ ┌──────────────────────────────────────────────────┐ │ |
|||
│ │ chat/[agentSlug]/index.vue │ │ |
|||
│ │ showDocumentDrawer = true │ │ |
|||
│ │ ┌────────────────────────────────────────────┐ │ │ |
|||
│ │ │ AgentDocumentDrawer.vue │ │ │ |
|||
│ │ │ ┌──────────┐ ┌────────────────────────┐ │ │ │ |
|||
│ │ │ │ 列表视图 │ │ 详情视图 (markdown) │ │ │ │ |
|||
│ │ │ │ 筛选+搜索 │ │ 删除按钮 │ │ │ │ |
|||
│ │ │ │ 删除按钮 │ │ AgentMarkdown 渲染 │ │ │ │ |
|||
│ │ │ └──────────┘ └────────────────────────┘ │ │ │ |
|||
│ │ └────────────────────────────────────────────┘ │ │ |
|||
│ └──────────────────────────────────────────────────┘ │ |
|||
└─────────────────────────────────────────────────────────┘ |
|||
|
|||
┌─────────────────────────────────────────────────────────┐ |
|||
│ 后端 │ |
|||
│ │ |
|||
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │ |
|||
│ │ REST API │ │ chat-engine │ │ agent-tool │ │ |
|||
│ │ 5 endpoints │ │ │ │ registry │ │ |
|||
│ │ │ │ getMemory │ │ │ │ |
|||
│ │ GET /api │ │ Context() │ │ document │ │ |
|||
│ │ POST /api │ │ → systemPrompt│ │ executor │ │ |
|||
│ │ GET /:id │ │ │ │ │ │ |
|||
│ │ PATCH /:id │ └──────────────┘ └───────┬───────┘ │ |
|||
│ │ DELETE /:id │ │ │ |
|||
│ └──────┬──────┘ │ │ |
|||
│ │ │ │ |
|||
│ ▼ ▼ │ |
|||
│ ┌──────────────────────────────────────────────────┐ │ |
|||
│ │ agent-document service (CRUD + 权限隔离) │ │ |
|||
│ │ createDocument / getDocumentById / listDocuments│ │ |
|||
│ │ updateDocument / deleteDocument │ │ |
|||
│ │ getMemoryContext (查 memory 类型,拼接上下文) │ │ |
|||
│ └──────────────────────┬───────────────────────────┘ │ |
|||
│ ▼ │ |
|||
│ ┌──────────────────────────────────────────────────┐ │ |
|||
│ │ SQLite: agent_documents 表 │ │ |
|||
│ │ id / userId / sessionId / type / title / │ │ |
|||
│ │ content / summary / tags / createdAt / updatedAt │ │ |
|||
│ └──────────────────────────────────────────────────┘ │ |
|||
└─────────────────────────────────────────────────────────┘ |
|||
``` |
|||
|
|||
## 三、文件清单 |
|||
|
|||
### 数据层 |
|||
|
|||
| 文件 | 说明 | |
|||
|------|------| |
|||
| `packages/drizzle-pkg/lib/schema/agent-document.ts` | `agentDocuments` 表 schema,`createdAt`/`updatedAt` 用 `mode: "timestamp_ms"` | |
|||
| `packages/drizzle-pkg/lib/schema/agent-tool.ts` | `AgentToolTypes` 新增 `"document"` | |
|||
| `packages/drizzle-pkg/migrations/0002_noisy_ronan.sql` | 建表迁移 | |
|||
| `packages/drizzle-pkg/seed.ts` | document 工具种子数据(id=`tool_document`) | |
|||
|
|||
### Service 层 |
|||
|
|||
| 文件 | 说明 | |
|||
|------|------| |
|||
| `server/service/agent-document/index.ts` | CRUD + `getMemoryContext()` + `deserializeTags` | |
|||
| `server/service/agent-document/index.test.ts` | 32 个单元测试 | |
|||
|
|||
### Executor 层 |
|||
|
|||
| 文件 | 说明 | |
|||
|------|------| |
|||
| `server/service/agent-tool/executors/document/config.ts` | document executor config(审批白名单、maxContentLength 等) | |
|||
| `server/service/agent-tool/executors/document/document.ts` | executor 实现,5 种 action 分发,`toISO()` 序列化 Date | |
|||
| `server/service/agent-tool/registry.ts` | `ToolContext` 扩展 `approved?: boolean` | |
|||
| `server/service/agent-tool/index.ts` | document executor 注册(line 128-133) | |
|||
|
|||
### API 层 |
|||
|
|||
| 文件 | 说明 | |
|||
|------|------| |
|||
| `server/api/agent-documents/index.get.ts` | GET 列表(分页 + 类型筛选 + 关键词搜索) | |
|||
| `server/api/agent-documents/index.post.ts` | POST 创建 | |
|||
| `server/api/agent-documents/[id].get.ts` | GET 详情 | |
|||
| `server/api/agent-documents/[id].patch.ts` | PATCH 更新 | |
|||
| `server/api/agent-documents/[id].delete.ts` | DELETE 删除 | |
|||
|
|||
### 前端 |
|||
|
|||
| 文件 | 说明 | |
|||
|------|------| |
|||
| `app/components/agent/AgentDocumentDrawer.vue` | 右侧抽屉(列表 + 详情 + 删除 + 筛选 + 搜索) | |
|||
| `app/components/agent/AgentToolbar.vue` | 工具栏文档按钮 → emit `showDocuments` | |
|||
| `app/components/agent/AgentSidebar.vue` | 侧边栏文档按钮(desktop + mobile)→ emit `showDocuments` | |
|||
| `app/components/agent/AgentChatArea.vue` | 透传 `showDocuments` 事件 | |
|||
| `app/pages/chat/[agentSlug]/index.vue` | 接入 `showDocumentDrawer` 状态 + 渲染抽屉 | |
|||
| `app/pages/agent-documents/index.vue` | 独立列表页(保留,导航入口在 TopNav) | |
|||
| `app/pages/agent-documents/[id].vue` | 独立详情页(保留) | |
|||
|
|||
### Chat Engine |
|||
|
|||
| 文件 | 说明 | |
|||
|------|------| |
|||
| `server/service/agent/chat-engine.ts` | 记忆注入 system prompt(line 250-260)+ `buildModelMessages` 修复 | |
|||
|
|||
### 测试 |
|||
|
|||
| 文件 | 说明 | |
|||
|------|------| |
|||
| `e2e/specs/agent-documents.spec.ts` | 10 个 E2E 测试用例 | |
|||
|
|||
### 文档 |
|||
|
|||
| 文件 | 说明 | |
|||
|------|------| |
|||
| `docs/superpowers/specs/2026-08-09-agent-document-tool-design.md` | 设计文档 | |
|||
| `docs/superpowers/plans/2026-08-09-agent-document-tool.md` | 实施计划 | |
|||
| `docs/handover/2026-08-09-agent-document-tool.md` | 本交接文档 | |
|||
|
|||
## 四、关键实现细节 |
|||
|
|||
### 4.1 记忆注入上下文 |
|||
|
|||
```typescript |
|||
// chat-engine.ts line 250-260 |
|||
let systemPrompt = baseSystemPrompt; |
|||
if (user) { |
|||
const memoryContext = await getMemoryContext(user.id); |
|||
if (memoryContext) { |
|||
const memoryInstruction = |
|||
"\n\n# 持久记忆(已注入上下文)\n" + |
|||
"以下 <memory> 标签内的信息是你的持久记忆,每次对话开始时自动加载,始终是最新的。\n" + |
|||
"**禁止调用 document 工具的 read 或 list 操作来读取这些记忆** — 它们已经在你的上下文中了。\n" + |
|||
"直接使用 <memory> 中的内容回答用户问题即可。\n" + |
|||
"只有需要创建新记忆或修改已有记忆时,才使用 document 工具。"; |
|||
systemPrompt = systemPrompt |
|||
? `${systemPrompt}${memoryInstruction}\n${memoryContext}` |
|||
: `${memoryInstruction}\n${memoryContext}`; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
- **实时查询**:每次 `executeChat` 调用时从数据库查 `type=memory` 的文档,文档更新后下一轮对话自动生效 |
|||
- **格式**:每条记忆用 `## {title}\n{content}` 格式,整体包裹在 `<memory>` 标签内 |
|||
- **防冗余调用**:system prompt 中明确禁止 LLM 调用 document 工具读取已有记忆 |
|||
|
|||
### 4.2 getMemoryContext 实现 |
|||
|
|||
```typescript |
|||
// agent-document/index.ts |
|||
export async function getMemoryContext(userId: number): Promise<string | null> { |
|||
const rows = await dbGlobal |
|||
.select({ title: agentDocuments.title, content: agentDocuments.content }) |
|||
.from(agentDocuments) |
|||
.where(and(eq(agentDocuments.userId, userId), eq(agentDocuments.type, "memory"))) |
|||
.orderBy(desc(agentDocuments.updatedAt)); |
|||
|
|||
if (rows.length === 0) return null; |
|||
|
|||
const parts = rows.map((r) => `## ${r.title}\n${r.content}`); |
|||
return `<memory>\n${parts.join("\n\n")}\n</memory>`; |
|||
} |
|||
``` |
|||
|
|||
### 4.3 Date 序列化问题 |
|||
|
|||
drizzle `timestamp_ms` mode 返回 `Date` 对象,AI SDK Zod 校验拒绝 Date(期望 string)。executor 中用 `toISO()` helper 序列化: |
|||
|
|||
```typescript |
|||
function toISO(date: Date | null | undefined): string | null { |
|||
return date ? date.toISOString() : null; |
|||
} |
|||
``` |
|||
|
|||
### 4.4 buildModelMessages 审批修复 |
|||
|
|||
`approval-responded` + `hasResult=false` 时只 push `tool-approval-request`,不 push 无 output 的 `tool-call`(AI SDK 校验失败)。 |
|||
|
|||
### 4.5 抽屉组件 |
|||
|
|||
- `Teleport to="body"` + `Transition` 实现滑入动画 |
|||
- 列表视图:类型筛选 tabs(全部/文档/记忆)+ 搜索框(400ms 防抖)+ 卡片列表 |
|||
- 详情视图:返回按钮 + 标题 + `AgentMarkdown` 渲染 + 删除按钮 |
|||
- 每次 `show` 变 true 都重新 fetch(不缓存) |
|||
- 删除带 `confirm()` 确认,删除后自动从列表移除 |
|||
|
|||
### 4.6 agent-tool 关联 |
|||
|
|||
工具必须通过 `agent_tool_associations` 表关联到 agent 才能被 LLM 使用,仅 `enabled=1` 不够。已为 default agent(id=1)手动插入关联记录。 |
|||
|
|||
## 五、数据库表结构 |
|||
|
|||
```sql |
|||
CREATE TABLE agent_documents ( |
|||
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|||
userId INTEGER NOT NULL, |
|||
sessionId TEXT, |
|||
type TEXT NOT NULL DEFAULT 'document', -- 'memory' | 'document' |
|||
title TEXT NOT NULL, |
|||
content TEXT NOT NULL, |
|||
summary TEXT, |
|||
tags TEXT, -- JSON array string |
|||
createdAt INTEGER NOT NULL, -- timestamp_ms |
|||
updatedAt INTEGER NOT NULL -- timestamp_ms |
|||
); |
|||
|
|||
-- 索引 |
|||
CREATE INDEX idx_agent_documents_userId ON agent_documents(userId); |
|||
CREATE INDEX idx_agent_documents_userId_type ON agent_documents(userId, type); |
|||
CREATE INDEX idx_agent_documents_userId_updatedAt ON agent_documents(userId, updatedAt); |
|||
``` |
|||
|
|||
## 六、API 接口 |
|||
|
|||
| 方法 | 路径 | 说明 | |
|||
|------|------|------| |
|||
| GET | `/api/agent-documents?page=1&pageSize=20&type=memory&keyword=xxx` | 列表查询 | |
|||
| POST | `/api/agent-documents` | 创建文档 | |
|||
| GET | `/api/agent-documents/:id` | 获取详情 | |
|||
| PATCH | `/api/agent-documents/:id` | 更新文档 | |
|||
| DELETE | `/api/agent-documents/:id` | 删除文档 | |
|||
|
|||
响应格式:`{ code: 0, message: 'success', data: ... }` |
|||
|
|||
## 七、Agent 工具接口 |
|||
|
|||
LLM 可调用的 `document` 工具,通过 `action` 字段分发: |
|||
|
|||
| action | 必填参数 | 可选参数 | 说明 | |
|||
|--------|----------|----------|------| |
|||
| create | title, content | type, tags | 创建文档/记忆 | |
|||
| read | id | fullContent | 读取详情(默认仅返回 summary) | |
|||
| update | id | title, content, tags | 更新文档 | |
|||
| delete | id | — | 删除文档(默认需审批) | |
|||
| list | — | type, keyword, page, pageSize | 列表查询 | |
|||
|
|||
## 八、已知问题与注意事项 |
|||
|
|||
1. **seed.ts 不是 upsert**:admin 用户已存在时重跑 seed 会 UNIQUE 冲突;开发库需手动插入新工具记录 |
|||
2. **guess-who 残留**:`packages/drizzle-pkg/seed.ts` line 215-225 有 guess-who 种子数据,但 executor 不存在 |
|||
3. **前端无 agent 编辑页**:无法通过 UI 管理 agent-tool 关联,需手动插库 |
|||
4. **typecheck 预先存在错误**:search、chat、favorite、ideas、llm 模块有 TS 错误,与本次改动无关 |
|||
5. **记忆无 token 上限**:当前 `getMemoryContext` 拼接所有 memory 文档,如果记忆数量多可能撑爆上下文。后续可加 token 预算截断 |
|||
|
|||
## 九、Commit 历史 |
|||
|
|||
| Commit | 说明 | |
|||
|--------|------| |
|||
| `b62a417` | feat: add agent_documents table schema, migration, and seed data | |
|||
| `2bd451a` | feat: add agent-document service layer with CRUD, permission isolation, and tests | |
|||
| `b40bfa4` | feat: add document tool executor with config, registration, and approved flag | |
|||
| `e5d7e68` | feat: add REST API endpoints for agent documents | |
|||
| `d06a642` | feat: add agent-documents frontend pages (list + detail) and nav entry | |
|||
| `0c8d51a` | feat: add POST create endpoint and E2E tests for agent-documents | |
|||
| `6b381d6` | docs: add agent-documents E2E test entry to AGENTS.md | |
|||
| `322accb` | fix: buildModelMessages approval-responded without result should not emit tool-call | |
|||
| `0ae318a` | feat(agent): add document entry in sidebar footer and toolbar | |
|||
| `50f0ca4` | fix(agent-document): serialize Date fields to ISO strings in executor output | |
|||
| `c5e9ae9` | fix(agent-document): deserialize tags in listDocuments + null guard in template | |
|||
| `72659af` | feat(agent-documents): replace page navigation with right-side drawer | |
|||
| `bf88935` | feat(agent-documents): add delete in drawer + inject memory into system prompt | |
|||
| `c2bf347` | fix(chat-engine): instruct LLM not to call document tool for existing memory | |
|||
| `10688be` | fix(chat-engine): strengthen memory instruction to prevent redundant tool calls | |
|||
|
|||
## 十、后续可优化方向 |
|||
|
|||
1. **记忆 token 预算**:`getMemoryContext` 加 token 上限,超限时按优先级截断 |
|||
2. **向量检索**:文档量大时用向量检索替代 SQL 模糊搜索 |
|||
3. **agent 编辑页**:前端 UI 管理 agent-tool 关联,避免手动插库 |
|||
4. **记忆分类**:memory 细分为 preference/identity/fact 等,按类别注入 |
|||
5. **文档分享**:document 类型支持生成分享链接 |
|||
6. **版本历史**:文档更新时保留历史版本 |
|||
Binary file not shown.
Loading…
Reference in new issue