17 KiB
Agent 独立化架构文档
分支:
feat/agent-independence| Commit:adeb09a| 日期:2026-08-09
目录
- 1. 设计目标
- 2. 数据库 Schema
- 3. 服务端架构
- 4. API 端点
- 5. 前端架构
- 6. 多 Session 并行流式输出
- 7. 标题生成机制
- 8. 模型解析优先级
- 9. 速率限制
- 10. 协作接口预留
- 11. Seed 数据
- 12. 已知问题与后续工作
1. 设计目标
将原本单一的"agent"(全局配置 + 硬编码 system prompt)改造为独立化、可配置的多 agent 架构,为多 agent 协作做准备。
核心变更
| 改造前 | 改造后 |
|---|---|
全局 system prompt(/api/agent/system-prompt) |
per-agent systemPrompt 字段(DB) |
| 工具集全局共享 | per-agent 工具关联(多对多表) |
| 模型由用户选择 | 模型优先级:用户 preferred > agent.defaultModelId > 全局 fallback |
路由 /api/agent/... |
路由 /api/agents/:agentSlug/... |
前端单页 / |
前端动态路由 /chat/:agentSlug |
| 单 session 流式 | 多 session 并行流式(instance 隔离) |
兼容性
- Breaking change:旧 API 路径
/api/agent/...已删除 - DB 迁移策略:清库重建(dev 阶段)
- 旧迁移文件全部删除,仅保留单个
0000_natural_ken_ellis.sql
2. 数据库 Schema
文件:packages/drizzle-pkg/lib/schema/agent.ts
2.1 agents 表(新增)
Agent 定义表,每个 agent 是一个独立可配置的 AI 助手。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id |
integer PK | auto | 主键 |
slug |
text(50) unique | - | URL 标识(如 default、coder) |
name |
text(100) | - | 显示名称 |
description |
text | null | 描述 |
systemPrompt |
text | "" |
系统提示词 |
defaultModelId |
integer | null | 默认模型 ID |
titleModelId |
integer | null | 标题生成模型 ID(fallback 到 defaultModelId) |
titleStrategy |
text(20) | "llm" |
标题策略:llm / first-line / none |
enableThinking |
integer | 0 |
是否启用思考模式 |
enableTools |
integer | 1 |
是否启用工具 |
maxStepCount |
integer | 6 |
最大工具调用步数 |
isDefault |
integer | 0 |
是否默认 agent(全局唯一) |
isCallable |
integer | 0 |
是否可被其他 agent 调用(协作接口) |
sortOrder |
integer | 0 |
排序 |
enabled |
integer | 1 |
是否启用 |
createdAt / updatedAt |
timestamp_ms | now | 时间戳 |
索引:agents_enabled_idx、agents_sort_order_idx
2.2 agent_tool_associations 表(新增)
Agent 与工具的多对多关联表。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
integer PK | 主键 |
agentId |
integer FK → agents.id (cascade) | Agent ID |
toolId |
text FK → agentTools.id (cascade) | 工具 ID |
needsApproval |
integer | 是否需要审批(覆盖工具级配置) |
sortOrder |
integer | 排序 |
createdAt |
timestamp_ms | 创建时间 |
唯一索引:agent_tool_assoc_uniq(agentId, toolId)
2.3 agent_sessions 表(改造)
新增 agentId 外键字段。
| 新增字段 | 类型 | 说明 |
|---|---|---|
agentId |
integer FK → agents.id (set null) | 所属 agent(agent 删除时 session 保留) |
enableThinking/enableTools改为可空(null 表示继承 agent 配置)
新增索引:idx_agent_sessions_agent(agentId)
2.4 其他表(未改动)
agent_messages:消息表agent_message_feedback:消息反馈表
3. 服务端架构
3.1 目录结构
server/service/agent/
├── agent.ts # Agent CRUD + 工具关联查询
├── chat-engine.ts # 核心聊天引擎(executeChat)
├── session.ts # Session CRUD + 消息持久化
├── title.ts # 标题生成(llm / first-line / none)
├── rate-limit.ts # 速率限制(per-agentSlug 隔离)
├── abort-manager.ts # 流式中断管理
├── collaboration.ts # 协作接口 stub(预留)
├── stream-buffer.ts # 流缓冲(SSE 重连支持)
├── stored-part.ts # 消息 parts 存储类型
└── types.ts # 类型定义
server/service/llm/
└── model-resolver.ts # 模型解析(用户/系统/任意)
3.2 Agent Service (agent.ts)
核心函数:
| 函数 | 说明 |
|---|---|
getAgentBySlug(slug) |
按 slug 查询启用的 agent |
getAgentBySlugAny(slug) |
按 slug 查询(含禁用) |
getDefaultAgent() |
获取默认 agent(isDefault=1,fallback 到 sortOrder 最小) |
listAgents(onlyEnabled) |
列出 agent |
getAgentWithTools(slug) |
Agent + 关联工具 |
getAgentTools(agentId) |
获取 agent 的工具列表 |
createAgent(input) |
创建 agent(含工具关联 + isDefault 唯一性维护) |
updateAgent(id, input) |
更新 agent(含工具关联重建) |
deleteAgent(id) |
删除 agent(cascade 删关联,session 的 agentId set null) |
3.3 Chat Engine (chat-engine.ts)
从原 HTTP handler 中抽离的核心逻辑,函数签名:
export interface ChatEngineParams {
agent: AgentRow;
session: AgentSessionRow;
user: { id: number; role: string | null } | null;
tempToken: string | null;
body: { content, editMessageId?, regenerate?, ... };
ip: string;
preferredModelId?: number | null;
requestId?: string;
registerAbort: (sessionId, controller) => void;
unregisterAbort: (sessionId) => void;
setRateLimitHeaders?: (headers) => void;
onClientClose?: (cb) => void;
}
export async function executeChat(params: ChatEngineParams): Promise<ChatEngineResult>
关键流程:
- 速率限制检查(未登录用户)
- 模型解析(优先级见 §8)
- 构建 ModelMessage(含历史消息 + 工具调用 parts)
streamText()流式生成onFinish回调:保存消息 + 触发标题生成(fire-and-forget)onAbort回调:保存已生成内容
3.4 Model Resolver (model-resolver.ts)
| 函数 | 说明 |
|---|---|
resolveModelForUser(modelId, userId) |
为用户解析模型(查用户私有 provider) |
resolveModelAny(modelId) |
任意解析(系统级,用于未登录用户) |
toLanguageModel(resolved) |
转换为 AI SDK 的 LanguageModel |
resolveLanguageModel(provider, modelId) |
底层:根据 parseMode 创建 OpenAI / OpenAI-Compatible |
3.5 Rate Limit (rate-limit.ts)
- Key 隔离:
${agentSlug}:${sessionId}/${agentSlug}:${ip} - 限制:每 session 20 次、每 IP 每日 50 次
- 仅对未登录用户生效
3.6 Abort Manager (abort-manager.ts)
全局 Map<sessionId, AbortController>,支持按 session 中断流式生成。
4. API 端点
4.1 Agent 管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/agents |
列出所有 agent |
| POST | /api/agents |
创建 agent |
| GET | /api/agents/:agentSlug |
获取 agent 详情 |
| PUT | /api/agents/:agentSlug |
更新 agent |
| DELETE | /api/agents/:agentSlug |
删除 agent |
| GET | /api/agents/:agentSlug/info |
获取 agent 信息(含工具列表,前端初始化用) |
4.2 Session 管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/agents/:agentSlug/sessions |
列出 session |
| POST | /api/agents/:agentSlug/sessions |
创建 session |
| GET | /api/agents/:agentSlug/sessions/:id |
获取 session 详情 |
| PUT | /api/agents/:agentSlug/sessions/:id |
更新 session(标题等) |
| DELETE | /api/agents/:agentSlug/sessions/:id |
删除 session |
| PUT | /api/agents/:agentSlug/sessions/:id/config |
更新 session 配置(modelId、enableThinking、enableTools) |
| GET | /api/agents/:agentSlug/sessions/:id/messages |
获取 session 消息 |
4.3 聊天
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/agents/:agentSlug/chat |
发送消息(启动流式) |
| GET | /api/agents/:agentSlug/chat/stream |
SSE 流(断线重连) |
| POST | /api/agents/:agentSlug/chat/stop |
停止生成 |
| POST | /api/agents/:agentSlug/chat/tool-approve |
工具审批 |
| POST | /api/agents/:agentSlug/chat/cleanup-approvals |
清理审批 |
4.4 其他
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/agents/:agentSlug/models |
可用模型列表 |
| POST | /api/agents/:agentSlug/feedback |
消息反馈 |
| POST | /api/agents/:agentSlug/migrate |
旧数据迁移 |
4.5 已删除的端点
GET /api/agent/system-prompt— 已废弃(system prompt 在 agent 配置中)- 所有
/api/agent/...路径
4.6 鉴权白名单
文件:packages/common/config/index.ts
API_ALLOWLIST 和 FRONTEND_PAGE_ALLOWLIST 已更新,支持 :agentSlug 参数匹配。
FRONTEND_PAGE_ALLOWLIST 新增 /chat/:agentSlug,允许未登录用户访问聊天页面。
5. 前端架构
5.1 路由
| 路径 | 说明 |
|---|---|
/ |
重定向到 /chat/default |
/chat/:agentSlug |
Agent 聊天页面 |
文件:
app/pages/index.vue— 重定向页app/pages/chat/[agentSlug]/index.vue— 主聊天页面
5.2 Composables
useAgentSessions(agentSlug: string)
Session 列表管理,组件级实例(非全局)。
关键函数:
fetchSessions()— 加载 session 列表createSession()— 创建新 sessionnewSession()— 智能创建:若最新 session 为空(lastActiveAt === createdAt)则复用,否则创建新的selectSession(id)— 切换 sessiondeleteSession(id)— 删除 sessiontouchSessionOrder(id)— 将 session 移到列表顶部(更新lastActiveAt)refreshSessionTitle(id)— 从服务端同步标题
useAgentChatStore(options)
多 session 聊天实例管理,核心架构。
useAgentChatStore
├── instances: Map<agentSlug:sid, AgentChatInstance> # 每个 session 独立实例
├── currentSessionId: ref<string | null>
├── currentInstance: computed → 当前 session 的 instance
└── messages/isLoading/... : computed → 代理到 currentInstance
关键设计:
- 每个 session 有独立的
AgentChatInstance(独立的messagesref、isLoadingref、abortController) - 切换 session 只是改
currentSessionId,旧 instance 的流继续在后台运行 getInstance(sid)— 获取/创建 instanceremoveInstance(sid)— 停止 + 清除 + 移除 instancestopAll()— 停止所有 instance 的流(新建对话时不调用)getLoadingSessionIds()— 获取所有正在流式输出的 session
useAgentChat(options)
单 session 聊天逻辑,闭包隔离。
关键函数:
send(content, opts?)— 发送消息 + 流式处理processStream(res, assistantIdx)— SSE 流解析(操作闭包内的messagesref)stopGeneration()— 停止生成(abort)stopAndSave()— 停止 + 保存已生成内容loadMessages(sid)— 加载历史消息respondToApproval(toolCallId, approved)— 工具审批响应
5.3 页面组件 (app/pages/chat/[agentSlug]/index.vue)
关键逻辑:
// 流完成回调 → 3秒后刷新标题
onStreamComplete: (sid) => {
setTimeout(() => sessions.refreshSessionTitle(sid), 3000);
}
// 发送消息
async function handleSend(content: string) {
// 1. 确保有 session
// 2. touchSessionOrder(sid) — 先更新 lastActiveAt(避免 newSession 复用)
// 3. await chat.send(content)
// 4. 若是新 session,refreshTitleWithRetry(sid)
}
// 新建对话
async function handleNewSession() {
// 若当前 session 为空且非 loading → 不创建(复用)
// 否则 newSession()
// 不调用 stopAll() — 其他 session 的流继续运行
}
5.4 Auth Routes
文件:app/utils/auth-routes.ts
matchesExactOrPrefix 增强:支持 :param 正则匹配(如 /chat/:agentSlug)。
6. 多 Session 并行流式输出
设计
不同 session 可以同时流式输出,新建对话不会停止其他 session 的流。
实现原理
- Instance 隔离:每个 session 在
useAgentChatStore中有独立的AgentChatInstance,包含独立的messagesref 和abortController - 闭包捕获:
processStream操作的是 instance 闭包内的messages,不是 store 的 computed - 切换不中断:
handleNewSession不调用stopAll(),旧 session 的processStreampromise 继续在后台执行 - 数据不串:切换 session 后,
currentInstance指向新 instance,旧 instance 的流写入自己的messagesref
验证场景
- 流式输出中点"新对话" → 新 session 创建 → 页面切换到空状态 → 旧 session 流继续
- 旧 session 流完成后 →
onStreamComplete(sid)触发 →refreshSessionTitle(sid)更新左侧标题
7. 标题生成机制
触发链路
用户发送首条消息
→ chat-engine.ts onFinish 回调
→ generateSessionTitle() (fire-and-forget, .catch())
→ 根据 agent.titleStrategy:
- "none": 跳过
- "first-line": 截取首行(≤50字)
- "llm": 调用 titleModelId 生成
→ updateSession(sessionId, { title })
前端同步
onStreamComplete(sid)
→ setTimeout(3s) → refreshSessionTitle(sid) → GET session → 更新 sessions.value 中的 title
新 session 额外重试(refreshTitleWithRetry):3s / 6s / 10s / 15s / 22s 多次刷新。
切换 session 的影响
- 服务端:
onFinish在服务端触发,与前端无关,标题会正确写入 DB - 前端:
onStreamComplete在 instance 闭包中,切走 session 后仍会执行;sessionsref 是同一组件实例的 ref,refreshSessionTitle仍能找到并更新 session
结论:切换 session 不影响标题生成。
8. 模型解析优先级
已登录用户:
session.modelId ?? preferredModelId ?? agent.defaultModelId
→ resolveModelForUser(modelId, userId)
未登录用户:
session.modelId ?? agent.defaultModelId
→ resolveModelAny(modelId)
preferredModelId:用户在前端选择的模型(请求 body 传入)session.modelId:session 级别持久化的模型选择agent.defaultModelId:agent 配置的默认模型
9. 速率限制
| 维度 | Key 格式 | 限制 |
|---|---|---|
| Session | ${agentSlug}:${sessionId} |
20 次 |
| IP | ${agentSlug}:${ip} |
50 次/日 |
- 仅对未登录用户生效
- 按 agentSlug 隔离(不同 agent 独立计数)
10. 协作接口预留
文件:server/service/agent/collaboration.ts
export interface AgentInvocation {
agentSlug: string;
input: string;
context?: { sessionId?: string; userId?: number | null };
}
export interface AgentInvocationResult {
agentSlug: string;
output: string;
ok: boolean;
error?: string;
}
export async function invokeAgent(invocation: AgentInvocation): Promise<AgentInvocationResult>
当前为 stub,仅校验 agent 存在 + isCallable 字段,返回 "not yet implemented"。
agents.isCallable 字段控制 agent 是否可被其他 agent 调用。
11. Seed 数据
文件:packages/drizzle-pkg/seed.ts
管理员账号
- 用户名:
admin - 密码:
123456qaz
工具(11 个)
| ID | Slug | 类型 |
|---|---|---|
| tool_fetch_html | fetch_html | fetch |
| tool_fetch_md | fetch_md | fetch |
| tool_fetch_json | fetch_json | fetch |
| tool_calculator | calculator | calculator |
| tool_datetime | datetime | datetime |
| tool_uuid | uuid | uuid |
| tool_base64 | base64 | base64 |
| tool_json_formatter | json_formatter | json-formatter |
| tool_regex_tester | regex_tester | regex-tester |
| tool_user_info | user_info | user-info |
| tool_search | search | search |
Agent(2 个)
| Slug | Name | 工具数 | 特性 |
|---|---|---|---|
default |
默认助手 | 11(全部) | enableThinking=0, maxStepCount=6, isDefault=1, titleStrategy=llm |
coder |
编程助手 | 3(fetch_md, search, regex_tester) | enableThinking=1, maxStepCount=12, isCallable=1, titleStrategy=first-line |
12. 已知问题与后续工作
已完成
- DB schema 改造 + seed + 清库重建
- Service 层全部改造
- API 端点创建 + 旧目录删除
- 前端路由迁移 + composables 改造
- default + coder agent 端到端验证
- "输出时新建对话" bug 修复
- auth-routes
:param匹配修复 - fetch 工具 config 解析修复(zod safeParse + fallback)
待验证
- coder agent 端到端测试(工具调用、思考模式)
- 跨 agent session 访问拒绝(已验证基本逻辑)
- 多 session 并行流式输出压力测试
后续工作
- Agent 管理后台 UI(CRUD 界面)
- 协作接口实现(
invokeAgent编排逻辑) - Agent 级别模型配置 UI
- 更多 agent 类型(如 researcher、translator)