9.8 KiB
角色卡(Character Card)功能 — 架构与修改速查
日期:2026-08-16 分支:feat/character-card(从 feat/agent-independence 切出) 状态:已完成,构建 + 冒烟测试通过
本文档是角色卡模块的总结 + 修改速查。读完可快速理解全貌,并知道「想改 X 该动哪个文件」。
目录
- 1. 一句话概述
- 2. 设计原则:为何独立成新模块
- 3. 数据库 Schema
- 4. 服务层
- 5. API 端点
- 6. 前端
- 7. 两个核心机制
- 8. 模型解析优先级
- 9. 鉴权与白名单
- 10. 修改速查表
- 11. 已知边界与后续工作
- 12. 验证命令
1. 一句话概述
角色卡是与 agent 完全解耦的角色扮演体系:每个角色卡(人设/世界观/开场白/对话示例)有独立隔离的对话,运行时由 persona.ts 把卡片字段自动组装成上下文,由独立的纯文本流式引擎生成回复。
- 入口:前台 /characters(画廊)、/characters/:slug(对话页)
- 管理:/admin/characters(列表)、/admin/characters/:slug(编辑器)
2. 设计原则:为何独立成新模块
需求约束:每个角色独立隔离对话;引擎不改旧代码、重新写一个。
| 层 | 做法 | 理由 |
|---|---|---|
| 角色专属逻辑(人设组装/开场白/纯文本流式/会话) | 重写(server/service/character/) | 角色扮演不需要工具/审批/记忆/多步推理,独立更内聚 |
| LLM 基础设施(模型解析/AI SDK/流式) | 复用(model-resolver + ai 的 streamText) | 非 agent 逻辑,是接 LLM 的原语 |
| 数据表 | 新增 3 张,不碰 agents 相关表 | 数据隔离 |
关键边界:现有 server/service/agent/chat-engine.ts(1021 行,agentic 专用)一行未改。角色引擎 chat-engine.ts 只有约 150 行,只做纯文本流式。
3. 数据库 Schema
文件:packages/drizzle-pkg/lib/schema/character.ts(迁移 migrations/0005_open_franklin_richards.sql)
3.1 character_cards(角色卡定义)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | integer PK autoincrement | 主键 |
| slug | text(50) unique | URL 标识 |
| name | text(100) | 角色名 |
| avatar | text | 头像 URL(可空,前端用首字母兜底) |
| description | text | 简介 |
| personality | text | 性格 |
| scenario | text | 世界观 / 场景 |
| firstMessage | text | 开场白(进入会话自动落库为首条 assistant 消息) |
| exampleDialogue | text | 对话示例(few-shot) |
| systemPrompt | text default '' | 底层行为规范 |
| defaultModelId | integer | 默认模型 |
| tags | text | JSON 数组字符串 |
| isPublic | integer default 1 | 是否在画廊展示 |
| enabled | integer default 1 | 是否启用 |
| sortOrder | integer default 0 | 排序 |
| createdAt / updatedAt | timestamp_ms | 时间戳 |
3.2 character_sessions(会话)
id(text pk)、cardId(fk→cards set null)、userId(fk→users cascade)、tempToken、title(default 新对话)、modelId、lastActiveAt、expiresAt、deletedAt、createdAt、updatedAt
3.3 character_messages(消息)
id(text pk)、sessionId(fk→sessions cascade)、role、content、sortOrder、createdAt
ID 前缀:会话 cs_*,消息 cm_*(区别于 agent 的 as_* / am_*,避免 abort-manager 等全局 Map 的 key 冲突)。
4. 服务层
目录:server/service/character/
| 文件 | 职责 | 关键导出 |
|---|---|---|
| types.ts | 行类型 + 身份类型 | CharacterCardRow/SessionRow/MessageRow, CharacterIdentity |
| identity.ts | 解析调用方身份 | resolveCharacterIdentity(复用 agent 的 temp-token 原语) |
| persona.ts | 上下文自主设计 | buildCharacterSystemPrompt(card), parseTags(tags) |
| character-card.ts | 角色卡 CRUD | get/list/create/update/deleteCharacterCard |
| session.ts | 会话/消息 CRUD | createCharacterSession, listCharacterSessions, getMessagesByCharacterSession, saveCharacterMessage 等 |
| abort.ts | 流式中断(Map 管理) | register/unregister/abortCharacterStream |
| chat-engine.ts | 纯文本流式引擎 | executeCharacterChat |
5. API 端点
文件:server/api/characters/ + server/api/admin/characters/
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | /api/characters | 公开 | 角色卡列表(仅启用 + 公开) |
| GET | /api/characters/:slug | 公开 | 角色卡详情 |
| GET | /api/characters/:slug/info | 公开 | 前端初始化信息(含全部人设字段) |
| GET | /api/characters/:slug/models | 公开 | 可用模型列表 |
| GET/POST | /api/characters/:slug/sessions | 公开(tempToken) | 会话列表 / 创建(创建时插入开场白) |
| GET/PUT/DELETE | /api/characters/:slug/sessions/:id | 公开 | 会话详情/更新/删除 |
| PUT | /api/characters/:slug/sessions/:id/config | 公开 | 会话配置(modelId) |
| GET | /api/characters/:slug/sessions/:id/messages | 公开 | 消息列表 |
| POST | /api/characters/:slug/chat | 公开 | 发送消息(流式) |
| POST | /api/characters/:slug/chat/stop | 公开 | 停止生成 |
| POST | /api/characters | admin | 创建角色卡 |
| PUT/DELETE | /api/characters/:slug | admin | 更新/删除角色卡 |
| GET | /api/admin/characters | admin | 列表(含禁用) |
| GET | /api/admin/characters/:slug | admin | 详情(含禁用) |
6. 前端
| 文件 | 职责 |
|---|---|
| app/composables/useCharacterSessions.ts | 会话列表/创建/切换/删除/模型配置(镜像 useAgentSessions) |
| app/composables/useCharacterChat.ts | 纯文本流式(镜像 useAgentChat,但只处理 text-delta) |
| app/layouts/character.vue | 对话页全屏 flex 布局 |
| app/components/character/CharacterCard.vue | 通用卡片展示(头像/名称/简介/标签),画廊复用 |
| app/pages/characters/index.vue | 角色卡画廊 |
| app/pages/characters/[slug].vue | 对话页(侧边栏 + 欢迎态 + 消息流 + 输入框) |
| app/pages/admin/characters/index.vue | 管理列表 |
| app/pages/admin/characters/[slug].vue | 创建/编辑表单 |
| app/pages/admin.vue | 导航加了「角色卡管理」入口 |
7. 两个核心机制
7.1 上下文自主设计(persona.ts)
buildCharacterSystemPrompt(card) 按序组装,空字段跳过:
你将扮演以下角色,请始终以该角色的身份、语气与立场进行对话,不要跳出角色…
【角色】name
【简介】description
【性格】personality
【世界观 / 场景】scenario
【对话示例】exampleDialogue
【行为规范】systemPrompt
chat-engine.ts 中:systemPrompt = buildCharacterSystemPrompt(card),直接传给 streamText({ system })。角色卡字段→上下文,全自动成型,无散落拼接。
7.2 开场白机制
会话创建路由(sessions/index.post.ts)在 createCharacterSession 后,若 card.firstMessage 存在,调用 saveCharacterMessage 插入 role: assistant, sortOrder: 1 的首条消息。前端加载消息时即看到角色「先开口」。
会话标题:首条用户消息 → 首行截断(≤50 字),不调用 LLM(与 agent 的 titleStrategy=llm 不同)。
8. 模型解析优先级
session.modelId ?? preferredModelId(body) ?? card.defaultModelId ?? 全局 agentDefaultModelId
- 复用 resolveModelForUser(登录)/ resolveModelAny(访客)+ toLanguageModel
- 全局默认模型复用现有配置 key agentDefaultModelId(未新增 config key,避免改 registry)
9. 鉴权与白名单
文件:packages/common/config/index.ts(仅追加,未改旧条目)
- API_ALLOWLIST:新增 10 条 /api/characters/...(公开读写,靠 tempToken 隔离)
- FRONTEND_PAGE_ALLOWLIST:新增 /characters、/characters/:slug
tempToken 复用 agent 的 agent_temp_token cookie(本质是「访客身份」,agent 与 character 共享同一访客身份)。
10. 修改速查表
| 想改什么 | 改哪里 |
|---|---|
| 角色卡新增字段 | schema/character.ts → db:generate + db:migrate → character-card.ts(CRUD)+ info.get.ts + 前端表单 |
| 上下文的组装顺序/措辞 | server/service/character/persona.ts |
| 流式引擎行为(温度/停止/标题) | server/service/character/chat-engine.ts |
| 开场白触发条件/内容 | server/api/characters/[slug]/sessions/index.post.ts |
| 模型解析优先级 | chat-engine.ts 的 targetModelId 行 |
| 鉴权白名单 | packages/common/config/index.ts |
| 前端流式解析 | app/composables/useCharacterChat.ts(processStream) |
| 对话页 UI | app/pages/characters/[slug].vue |
| 画廊/卡片样式 | app/components/character/CharacterCard.vue |
| 后台编辑器字段 | app/pages/admin/characters/[slug].vue |
| 示例角色 | packages/drizzle-pkg/seed.ts 的 seedCharacters() |
11. 已知边界与后续工作
- 会话标题不实时刷新(首条消息后侧边栏需刷新页面才更新)
- 单会话流式,切换会话中止当前流(多实例并行未做)
- 头像仅 URL 文本输入,未接 /api/file/upload 上传
- 访客→登录的会话迁移未做(agent 有 migrate 链路,角色未接)
- 未写 E2E(可复用 Mock LLM 架构补 agent-chat 式用例)
12. 验证命令
bun run db:generate # schema 变更后生成迁移
bun run db:migrate # 应用迁移
bun run db:seed # 重新 seed(含示例角色)
bun run build # 完整构建
冒烟测试(dev server 起在 5789 端口):
curl http://localhost:5789/api/characters
curl http://localhost:5789/api/characters/luna/info
curl -X POST http://localhost:5789/api/characters/luna/sessions -H 'Content-Type: application/json' -d '{}'