architecture adr ADR 004 domain aware agent profiles - WangYajun369/ai-writing-platform GitHub Wiki
ADR-004:Agent 领域画像与能力技能正交分层
状态:提议 日期:2026-10-09 影响范围:Agent 引擎 / 数据模型 / 前端 AI 面板 适用版本:
1.23.0| 最后核对:2026-10-15
背景
用户希望「把 AI 助手封装为 Agent,方便后期扩展」,并举例:小说作品用小说 Agent,论文作品用论文 Agent,书籍作品用拆书 Agent,学科笔记作品用学科笔记 Agent(记录学习笔记),且可以自己选择作品使用哪个 Agent。
在动手设计前,对现状做了全量核查,得到三个决定方案走向的事实:
事实一:Agent 引擎已经存在,不需要重建。
commands/agent/ 是一套从 Python LangGraph 迁移来的 Rust 原生实现,含 ReAct 流式循环、6 个数据库工具、三层记忆(memories 表)、推理轨迹(agent_traces 表)。更关键的是——常规「AI 聊天」模式早已走这套引擎(useAiChat 调用的是 agentApi.executeSkill,默认 skill='writing'),而 stream_ai_chat 这条直连链路全项目只剩 AI 工具箱一个调用点。所以这是一套引擎、两种 UI 呈现,不是并行两套。
事实二:现有 Skill 是「能力维度」,而需求描述的是「领域维度」。
现有 4 个 Skill(writing / analysis / research / polish)回答的是「现在要干什么」;需求里的「小说 / 论文 / 拆书 / 学科笔记」回答的是「这是一本什么书」。二者是正交的——同一份 writing 提示词目前对任何作品都一模一样,模型完全不知道自己在写小说、写论文还是整理学习笔记。
事实三:Skill 定义散落 7 处硬编码,是最大的扩展障碍。
新增一个 Skill 需要同时改:prompts.rs 的基准提示与动态提示、tools.rs 的工具子集、engine.rs 的执行预算、前端 agent/types.ts 的类型与元数据、ai/panel/constants.ts 的快捷语(穷举 switch,漏改直接编译报错)。没有统一的数据结构、没有注册表、没有配置段、没有数据库表。
此外两个约束:
- 作品类型字段完全不存在。
Book模型前后端各 14 个字段,无book_type/genre/kind之类字段。 - 作品大纲进不了模型上下文。工具集只有 6 个(读章节 / 读章节块 / 读章节摘要 / 章节列表 / 检索世界观 / 整书概览),没有任何工具能读到
books.outline或chapters.outline,大纲目前只在前端做「是否存在」的前置校验,内容本身模型看不到。
备选方案
维度关系
| 方案 | 优点 | 缺点 |
|---|---|---|
| A. 领域与能力双层正交 | 语义正确;book_type 回答「什么书」、Skill 回答「干什么」;组合自由(论文 + 润色可行) |
UI 需多一个选择器;Prompt 组装要处理两段拼接 |
| B. 领域直接作为新 Skill 值 | 改动最小,复用现有 chips | 语义混乱;chips 从 4 个变 7 个;「论文 + 润色」无法表达;用户会困惑该选哪个 |
| C. 领域 + 能力预组合成 Profile | UI 最简洁,用户只选一个 | 组合爆炸(4 × 4 = 16 种,且领域仍在增长);灵活性差;新增维度要重排全部组合 |
画像定义存放位置
| 方案 | 优点 | 缺点 |
|---|---|---|
A. Rust 注册表(新建 profiles.rs) |
类型安全;可写单测;顺带收敛现有 7 处硬编码;工具集与预算可与提示词绑定 | 改提示词需重新编译 |
B. app_config 新增 agent_profiles 段 |
运行时可改,用户可自定义 | 长文本在配置里编辑体验差;需扩 ConfigSection 枚举与前端 client;宽松校验下坏数据易入库 |
| C. 内置 + 配置覆盖 | 灵活度最高 | 两套来源合并逻辑复杂;出问题时难判断生效的是哪一份 |
作品类型落库方式
| 方案 | 优点 | 缺点 |
|---|---|---|
A. Book 新增 book_type 字段 |
按作品持久记忆;可随备份迁移;是「作品级属性」的正确归属 | 改动面约 18 处(含 DDL、迁移、备份导出 / 导入 / 一致性校验);需补一个「编辑作品」弹窗(当前只有新建弹窗) |
| B. 不落库,会话内选择 | 零数据迁移,可立刻用起来 | 换设备 / 重装丢失;无法「按书记住」 |
C. 复用现有 tags 推断 |
零 DDL 改动 | tags 是自由文本,不可靠;用户无感知 |
决策
采用 A + A + A 组合,即:
-
领域与能力双层正交。
Book.book_type(小说 / 论文 / 拆书 / 学科笔记,作品级)+Skill(写作 / 分析 / 研究 / 润色,会话级),二者相乘决定最终行为。四个领域的定位与差异化要点:
领域 回答什么 差异化重点 小说 这是一部虚构叙事作品 情节推进、人物弧光、场景质感、文风一致性 论文 这是一篇学术论证文本 论点 / 论据 / 引用规范、章节逻辑、术语严谨 拆书 这是对某本书的拆解消化 章节拆解、要点提炼、卡片化输出、 actionable 结论 学科笔记 这是某学科的学习记录 概念定义、公式定理、例题与错因、知识点关联与复习导向 -
画像与工具双层收敛到 Rust 注册表。新建
commands/agent/profiles.rs,并分两步、先工具后画像:- L0 工具注册表:把
tools.rs中散落的三处match(订阅tools_for_skill、定义tool_schema、分发execute_tool)合并为一个ToolDef { name, description, parameters, execute }数组。工具引擎本就按名字字符串派发(engine.rs的react_loop中tools::execute_tool(&conn, &tc.name, ...)),且build_tools_schema已从名字列表自动生成 schema,因此这一步是低阻力纯重构。 - L1 画像注册表:用
AgentProfile结构体数组统一承载「基准提示 + 动态提示 + 工具子集(引用 L0 的工具名)+ 执行预算」,把现有散落 7 处的硬编码收敛为「一处定义、多处查表」。
顺序不可颠倒:L1 的画像要引用 L0 的工具名,先做 L0 才能让画像定义是纯数据。
- L0 工具注册表:把
-
作品类型落库。新增
books.book_type字段(TEXT NOT NULL DEFAULT ''),新建作品时选择,并补齐「编辑作品」弹窗以便后期修改。 -
明确「领域 × 能力」的合并规则(原方案缺失,实施时必然卡住):
维度 合并规则 理由 提示词 领域基准 → 能力基准 → 能力动态提示,纯拼接 领域定义「世界观」,能力定义「动作」,后者更具体应靠后 工具集 取并集 领域工具(如拆章)与能力工具(如读章节)互补,取交集会导致某一侧工具不可用 执行预算 取较大值 保守策略,避免领域需要的多轮被能力的小预算截断 动态提示 各自独立命中,领域提示在前 两者关键词域不同,冲突概率低 -
book_type值域与兜底语义(原方案缺失):存储值 含义 对应画像 novel小说 情节 · 人物 · 文风 thesis论文 论点 · 论据 · 引用 breakdown拆书 拆解 · 卡片 · 要点 note学科笔记 概念 · 公式 · 例题 · 复习 ''(空)存量作品或未指定 回退 novel不引入「通用」画像——多一个画像就多一份维护成本,且「通用」与「小说」的差异对模型而言不明确。未知值同样回退
novel并记一条告警日志。
Prompt 组装的唯一入口(engine.rs 的 run_skill_inner)相应扩展为四段拼接:
领域基准提示(来自 AgentProfile) ← 新增
+ 能力基准提示与动态提示(来自 Skill,既有)
+ 历史记忆段(既有)
+ 历史对话摘要段(既有)
+ 固定尾缀(既有)
理由
- 不重建引擎。现有 ReAct 引擎、6 个工具、三层记忆、轨迹回放都是可复用资产;需求本质是「提示词与工具集的差异化」,不是「执行机制的替换」。改造集中在 Prompt 组装层与画像定义层。
- 正交分层避免组合爆炸。4 个领域 × 4 个能力 = 16 种组合,用两层各存一份(4 + 4 = 8 份定义)即可表达;预组合方案要维护 16 份。领域后续仍可能增长(如「剧本」「公文」),正交分层的边际成本恒为「加 1 份定义」,而预组合是「乘 4 份」。
- 注册表顺带还清技术债。方案 2 不只是「加功能」,更把「加一个 Skill 要改 7 处」降为「改 1 处」、「加一个工具要改 3 处」降为「改 1 处」。后者尤其关键——本 ADR 的 follow-up 与实施计划都要新增多个工具(大纲、拆章、概念卡),若不做 L0 收敛,每加一个工具都要在三处
match间来回改。 book_type从book_id在 Rust 侧反查,而不是让前端多传一个参数。因为工具层的get_book_context已经在 Rust 侧按book_id读过Book,顺手取字段即可,前端链路零改动,也避免前端传参与数据库不一致。- 落库而非会话内选择。作品类型本质上是作品的属性而非会话偏好,应当随作品走、随备份迁移;否则「换台电脑所有书都变回默认 Agent」,与需求中「可以自己选择作品使用哪个 Agent」的持久预期不符。
后果
正面
- 模型终于知道自己在处理什么类型的作品,小说 / 论文 / 拆书 / 学科笔记的语气、结构、术语可差异化
- 新增一个领域或能力从「改 7 处」降为「改 1 处」
- 工具集可按领域裁剪(例如拆书 Agent 需要「拆章 / 生成卡片」类工具,学科笔记 Agent 需要「概念卡 / 复习题」类工具,论文 Agent 不需要小说的人物卡工具)
- 学科笔记可复用现有数据模型,无需新表:作品 = 学科(或课程),卷 = 模块 / 章节组,章节 = 单条笔记,世界观卡片 = 概念卡。这一映射让学科笔记在阶段三即可落地,不必等新实体设计
book_type落库后可被后续功能复用(导出模板、统计口径、默认大纲模板)
负面 / 代价
- 数据迁移成本:
book_type涉及约 18 处改动,且备份的导出 / 导入 / 一致性校验三处都需同步列清单,漏一处会导致备份往返丢字段(这类问题在 ADR-004 之前的备份审查中已出现过一次:app_config未纳入备份) - 需要新增「编辑作品」弹窗:当前
components/library/下只有NewBookDialog,没有编辑入口,作品类型建后无法修改 - Prompt 变长:领域提示叠加能力提示会占用更多上下文窗口,需要控制领域提示长度(建议 ≤ 400 字)
- UI 复杂度上升:AI 面板头部要多一个领域选择器,与既有模式切换(聊天 / Agent)+ 4 个技能 chips 叠加,需重新规划头部布局避免拥挤
- 默认值语义:存量作品
book_type为空字符串,统一回退novel;不引入「通用」画像(理由见决策 5) - ⚠️ 执行模式仍是单一 ReAct:本 ADR 只解决了「提示词与工具集的差异化」,没有解决「执行流程的差异化」。而「拆书」本质是「读章节 → 提炼要点 → 生成卡片」的固定流水线,「学科笔记」的「整理 → 出题」同理,它们靠自由 ReAct 循环 + 换提示词做不出稳定效果。这一缺口由 ADR-005 单独处理,本 ADR 的所有决策在其前提下仍成立
需要 follow-up 的事项
- 🔜 补齐「作品大纲 / 章节大纲」读取工具 —— 拆书 Agent、论文 Agent、学科笔记 Agent 都强依赖大纲(学科笔记里大纲即「知识结构 / 目录」),当前工具集读不到,是比领域提示更实质的能力缺口。须在 L0 工具注册表落地之后再做,否则仍要改三处
match - 🔜 厘清「学科笔记」与既有日记、生词本的边界 —— 日记是每日随笔(
diaries表)、生词本是语言词汇(vocab_*表)、学科笔记是按学科组织的结构化知识(复用 books / chapters / world_cards)。三者定位不同但用户可能混淆,需在新建入口给出说明 - 🔜 领域选择是否允许「会话级临时覆盖」(不选作品默认类型,临时切成别的)—— 本 ADR 未定,建议二期按用户反馈决定
- 🔜 AI 工具箱(单轮生成、无工具无记忆)是否也要感知
book_type—— 本 ADR 未覆盖,其链路走stream_ai_chat,与 Agent 引擎不同源。ADR-005 的SingleShot模式提供了归并路径(实施计划阶段六任务 6),但迁移前需先对齐容错能力——工具箱当前有网络重试而 Agent 链路没有 - 🔜 若后续领域数量增长(超过 5 个),再评估是否迁移到
app_config配置化 - ⚠️ 领域提示与能力提示的拼接顺序需要实测:不同模型对「系统提示前后位置」的敏感度不同,建议用同一组测试用例对比效果
相关文档
- ADR-005:Agent 扩展模型(工具注册表 / 执行模式 / 前端自动发现) —— 承接本 ADR 遗留的「工具未收敛、执行模式单一、前后端双份」三处缺口
- ADR-006:Agent 插件宿主(第三方 Agent 的安装与卸载) —— 在本 ADR 与 ADR-005 之上支持第三方插件
- 领域感知 Agent 实施计划
- Agent 引擎架构
- AI 模块架构
- ADR-002:Python Agent 经 Bridge 只读回调 —— 已废弃,其遗留的 Python 链路正是本 ADR 所述 Rust 原生引擎的前身