You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

17 KiB

Agent 独立化架构文档

分支:feat/agent-independence | Commit:adeb09a | 日期:2026-08-09

目录


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>

关键流程:

  1. 速率限制检查(未登录用户)
  2. 模型解析(优先级见 §8)
  3. 构建 ModelMessage(含历史消息 + 工具调用 parts)
  4. streamText() 流式生成
  5. onFinish 回调:保存消息 + 触发标题生成(fire-and-forget)
  6. 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() — 创建新 session
  • newSession() — 智能创建:若最新 session 为空(lastActiveAt === createdAt)则复用,否则创建新的
  • selectSession(id) — 切换 session
  • deleteSession(id) — 删除 session
  • touchSessionOrder(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(独立的 messages ref、isLoading ref、abortController)
  • 切换 session 只是改 currentSessionId,旧 instance 的流继续在后台运行
  • getInstance(sid) — 获取/创建 instance
  • removeInstance(sid) — 停止 + 清除 + 移除 instance
  • stopAll() — 停止所有 instance 的流(新建对话时不调用)
  • getLoadingSessionIds() — 获取所有正在流式输出的 session

useAgentChat(options)

单 session 聊天逻辑,闭包隔离。

关键函数:

  • send(content, opts?) — 发送消息 + 流式处理
  • processStream(res, assistantIdx) — SSE 流解析(操作闭包内的 messages ref)
  • 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 的流。

实现原理

  1. Instance 隔离:每个 session 在 useAgentChatStore 中有独立的 AgentChatInstance,包含独立的 messages ref 和 abortController
  2. 闭包捕获:processStream 操作的是 instance 闭包内的 messages,不是 store 的 computed
  3. 切换不中断:handleNewSession 不调用 stopAll(),旧 session 的 processStream promise 继续在后台执行
  4. 数据不串:切换 session 后,currentInstance 指向新 instance,旧 instance 的流写入自己的 messages ref

验证场景

  • 流式输出中点"新对话" → 新 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 后仍会执行;sessions ref 是同一组件实例的 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)