Files
smartmate/backend/agent2/通用能力接入文档.md
LoveLosita f4ef6fb256 Version: 0.7.5.dev.260324
🐛 fix(agent/schedulerefine): 修复复合微调分支链路问题,并将 MinContextSwitch 重构为固定坑位重排语义

- 🔧 修复 `schedulerefine` 复合路由中参数透传不完整、缺少 deterministic objective 时错误降级,以及“复合工具执行成功”与“终审通过”语义混淆的问题
-  保证新的独立复合分支能够正确执行、正确出站,并统一交由 `hard_check` 裁决最终结果
- 🔍 排查时发现 `MinContextSwitch` 上游 `context_tag` 存在整体退化为 `General` 的风险,影响MinContextSwitch
- 🛡️ 为 `MinContextSwitch` 增加兜底策略:当标签整体退化时,按任务名关键词推断学科分组,避免分组能力失效
- ♻️ 将 `MinContextSwitch` 从“整周重新寻找新坑位”调整为“坑位不变,任务顺序改变”
- 🎯 将落地方式从顺序 `BatchMove` 改为固定坑位原子重写,避免出现远距离跳位、跨天错迁、异常嵌入课位及循环换位冲突
- 🧹 修复 `hard_check` 在 `MinContextSwitch` 成功后仍执行 `origin_rank` 顺序归位、并导致逆序终审误判的问题
- 🚦 命中该分支后跳过顺序归位与顺序硬校验,避免 `summary` / `hard_check` 将有效重排结果误判为失败

📈 当前连续微调规划涉及的全部功能已可以稳定运行;下一步将继续扩展能力边界,并进一步优化 `schedule_plan` 流程

♻️ refactor: 重整 agent2 架构,并迁移 quicknote/chat 新链路,目前还剩3个模块未迁移,后续迁移完成后会删除原agent并将此目录命名为agent

- 🏗️ 明确 `agent2` 采用“统一分层目录 + 文件分层 + 依赖注入”的重构方案,不再沿用模块目录多层嵌套结构
- 🧩 完善 `agent2` 基础骨架,统一收口 `entrance` / `router` / `llm` / `stream` / `shared` / `model` / `prompt` / `node` / `graph` 等层级职责
- 🚚 将通用路由能力迁移至 `agent2/router`,沉淀统一的 `Action`、`RoutingDecision`、控制码解析,以及 `Dispatcher` / `Resolver` 抽象
- 💬 将普通聊天链路迁移至 `agent2/chat`,复用 `stream` 的 OpenAI 兼容输出协议与 LLM usage 聚合能力
- 📝 将 `quicknote` 链路迁移到 `agent2` 新结构,拆分为 `model` / `prompt` / `llm` / `node` / `graph` 多层实现,替换对旧 `agent/quicknote` 的直接依赖
- 🔌 调整 `agentsvc` 对 `agent2` 的引用,普通聊天、通用分流与 `quicknote` 全部切换到新链路
- ✂️ 去除 graph 内部 `runner` 转接层,改为由 node 层直接持有请求级依赖,并向 graph 暴露节点方法
- 🧹 合并 `graph/quicknote` 与 `graph/quicknote_run`,删除冗余骨架文件,收敛为单一 `quicknote graph` 文件
- 📚 新增 `agent2`《通用能力接入文档》,明确公共能力边界、接入方式以及 graph/node 协作约定
- 📝 更新 `AGENTS.md`,要求后续扩展 `agent2` 通用能力时必须同步维护接入文档

♻️ refactor: 删除了现Agent目录内Chat模块的两条冗余Prompt
2026-03-24 21:35:22 +08:00

9.6 KiB
Raw Blame History

agent2 通用能力接入文档

1. 文档目的

本文档用于说明 agent2 目录下“通用能力”的边界、放置位置、接入方式与维护要求。

这里的“通用能力”特指:

  1. 不只服务于某一个技能链路,而是可能被 chatquicknotetaskqueryschedule 等多个模块共同复用的能力。
  2. 与具体业务语义弱耦合,抽出来后不会强行把某个单一技能的 prompt、状态字段、业务规则污染到其它模块。
  3. 抽出来后,能够明显减少样板代码、降低重复实现和后续迁移成本。

本文档不负责描述某个具体技能的业务流程技能自身的图编排、状态字段、prompt 细节,应继续放在对应技能目录或对应决策记录中维护。

2. 当前目录分层

2.1 总入口层

文件:

  • entrance.go

职责:

  1. 作为 agent2 模块对上层服务的统一入口。
  2. 负责把“路由器 + 各技能 handler”装配到一起。
  3. 不负责具体技能逻辑,不负责直接调模型,也不负责工具执行。

适合放什么:

  1. 模块级入口对象。
  2. 通用注册方法。
  3. 与“总分发”有关的最小门面封装。

不适合放什么:

  1. 某个具体技能的节点逻辑。
  2. 具体业务 DAO 调用。
  3. 某个技能独占的 prompt 或状态机。

2.2 路由层

目录:

  • router/

当前通用能力:

  1. Dispatcher
  2. Resolver
  3. AgentRequest / AgentResponse
  4. Action 与路由控制码解析

职责:

  1. 统一处理“请求该走哪条技能链路”的分流问题。
  2. 提供对上层稳定的动作枚举与请求壳结构。
  3. 兼容迁移期的新旧 action 语义,避免上层服务直接依赖旧目录。

适合放什么:

  1. 通用路由协议。
  2. 控制码解析。
  3. 分发器。
  4. 所有技能共用的路由请求/响应结构。

不适合放什么:

  1. 某个技能内部的二次判断。
  2. 某个技能专属的 prompt。
  3. 技能内部重试或状态流转逻辑。

2.3 模型交互层

目录:

  • llm/

当前通用能力:

  1. Client
  2. GenerateOptions
  3. TextResult
  4. BuildSystemUserMessages
  5. GenerateJSON
  6. ParseJSONObject
  7. MergeUsage / CloneUsage
  8. ark.go 中的 Ark 适配实现

职责:

  1. 统一收口模型调用接口,减少各技能重复拼装 messagesthinkingtemperaturemax_tokens
  2. 提供通用 JSON 解析与 usage 合并能力。
  3. 把具体厂商 SDK 细节尽量压在适配层,不向上层节点扩散。

适合放什么:

  1. 所有技能都可能复用的模型调用壳。
  2. 通用 JSON 提取与反序列化。
  3. 流式/非流式调用的统一适配接口。
  4. usage 收敛、空响应错误格式化。

不适合放什么:

  1. 只服务于某一个技能的 prompt 文案。
  2. 某一个技能特有的输出结构体。
  3. 某一个技能独占的“成功文案润色”规则。

说明:

  1. 如果只是“基于通用 Client 再包一层技能专用函数”,例如 quicknote 的聚合规划调用,这种代码可以放在 llm/,但文件名应明确带技能语义,避免误认为完全通用能力。
  2. 真正跨技能复用的内容,优先沉到 client.goark.gojson.go 这类公共文件。

2.4 流输出协议层

目录:

  • stream/

当前通用能力:

  1. OpenAI 兼容 chunk DTO
  2. reasoning chunk 构造
  3. assistant chunk 构造
  4. finish / done 输出
  5. 阶段推送 emitter

职责:

  1. 统一处理 SSE / OpenAI 兼容输出格式。
  2. 让 service、graph、node 只关心“我要推什么内容”,而不是自己拼 JSON。
  3. 为后续前端协议升级预留统一修改点。

适合放什么:

  1. chunk DTO。
  2. reasoning / content / finish 的统一封装。
  3. 阶段消息推送器接口。

不适合放什么:

  1. 某个技能的阶段命名表。
  2. 某个技能专属的正文文案。
  3. 具体业务状态对象。

2.5 共享工具层

目录:

  • shared/

当前通用能力:

  1. 时间格式化与分钟级归一化
  2. 深拷贝
  3. 通用重试辅助

职责:

  1. 承载纯工具型、无业务语义、无技能耦合的辅助函数。
  2. 作为多个技能都能直接复用的最底层工具层。

适合放什么:

  1. 时间工具。
  2. clone 工具。
  3. retry 帮助函数。
  4. 纯函数型小工具。

不适合放什么:

  1. 夹带具体技能字段名的工具。
  2. 依赖数据库、缓存、模型、路由动作的逻辑。

2.6 技能内部层

目录:

  • graph/
  • node/
  • prompt/
  • model/
  • chat/

职责:

  1. 这几层主要承载技能内部能力。
  2. 即使其中某个文件现在位于 agent2 根体系内,只要它带明显技能语义,就不要误判成“通用能力”。

判断标准:

  1. 如果代码里天然绑定某个技能状态结构、某个技能阶段名、某个技能 prompt 输出契约,一般不应硬抽成通用能力。
  2. 如果只是多个技能都重复了同一段样板代码,且抽出后不会让抽象变形,就应该评估下沉。

2.7 图层与节点层的协作约定

这是当前 agent2 已经明确下来的结构约束:

  1. graph/ 只负责“画图”:
    • 注册节点
    • 添加边
    • 添加分支
    • 编译与运行图
  2. graph/ 不再负责:
    • 额外创建 runner 适配层
    • 在图内继续堆请求级依赖转发逻辑
    • 直接实现节点业务
  3. node/ 负责:
    • 定义节点容器(例如 QuickNoteNodes
    • 通过对象方法直接向 graph 暴露可挂载节点
    • 在节点方法内部消费请求级依赖

推荐形态:

  1. graph 里直接挂:
    • nodes.Intent
    • nodes.Priority
    • nodes.Persist
    • nodes.Exit
  2. 分支也直接挂:
    • nodes.NextAfterIntent
    • nodes.NextAfterPersist
  3. 不推荐再额外引入 runner -> node 这种转接层。

这样设计的目的:

  1. 避免 graph 文件随着模块变多再次长成“大装配文件”。
  2. 让“请求级依赖注入”回到 node 层自己的节点容器里。
  3. 让阅读路径稳定成:
    • 先看 graph 知道流程图
    • 再跳 node 看节点方法实现
    • 不需要在 graph 和 runner 两层之间来回跳。

3. 什么应该抽成通用能力

满足以下任意两条,一般就应该认真评估抽公共层:

  1. 在第二个技能里出现了明显重复实现。
  2. 这段逻辑本质上不依赖某个技能独占状态。
  3. 抽出来后接口可以做到“入参少、职责清、语义稳定”。
  4. 上层重复代码主要是在做样板装配,而不是业务决策。

典型例子:

  1. 模型消息拼装。
  2. JSON 提取与解析。
  3. usage 合并。
  4. OpenAI chunk 构造。
  5. 时间归一化。
  6. 深拷贝与重试工具。
  7. 总入口路由与技能分发。

4. 什么不应该强行抽公共层

出现以下情况时,不要为了“看起来复用”而硬抽:

  1. 抽完之后函数签名反而要塞一堆技能专属参数。
  2. 公共层开始知道某个技能的状态字段、阶段名、错误文案。
  3. 表面相似,实则每个技能的业务约束完全不同。
  4. 为了复用而引入大量 if action == xxxswitch skill 这类分支。

典型例子:

  1. quicknote 的优先级判定输出结构。
  2. taskquery 的查询规划字段。
  3. schedule 的排程状态快照。
  4. 某个技能特有的 prompt 模板。

5. 新增通用能力的接入步骤

5.1 先判断是否值得抽

  1. 先确认这段逻辑是否已经在第二处出现重复。
  2. 再确认它是不是可以脱离单一技能语义独立存在。
  3. 如果暂时还不能抽,也要在代码注释或决策记录里写明原因,避免后面第三次重复时忘记。

5.2 选择落点目录

按职责优先落到以下目录:

  1. 路由协议与分发:router/
  2. 模型调用与 JSON 解析:llm/
  3. 流输出协议:stream/
  4. 纯工具:shared/
  5. 技能专属但可复用的壳:放对应技能语义文件,不要伪装成完全公共层

5.3 定义最小接口

  1. 先定义最小可复用接口,只暴露上层真正需要的能力。
  2. 不要把下层 SDK、DAO、缓存实现细节直接透传到所有调用方。
  3. 优先让“公共层知道得更少”,而不是让“上层为了复用被迫知道更多”。

5.4 补注释

必须写清楚:

  1. 这个通用能力负责什么。
  2. 不负责什么。
  3. 为什么它适合抽到公共层。
  4. 失败时由谁兜底。

5.5 补测试

至少覆盖:

  1. 正常路径。
  2. 关键边界。
  3. 明确的失败路径。

如果迁移期暂时没法完整补齐,也要优先保证公共函数本身有最小回归测试。

5.6 更新本文档

只要出现以下任一情况,必须同步更新本文档:

  1. 新增了一个通用能力。
  2. 调整了某个通用能力的落点目录。
  3. 修改了某个公共接口的职责边界。
  4. 删掉了某个旧的公共实现,并由新实现替代。

6. 推荐接入模板

可以按下面这个思路接入:

  1. 先在技能代码里识别重复片段。
  2. 提炼出“最小公共函数 / 最小公共结构体 / 最小公共接口”。
  3. 放进 router / llm / stream / shared 之一。
  4. 先让新技能接这个公共实现。
  5. 再逐步回收旧技能里重复的老代码。
  6. 最后补本文档,说明这个能力现在归谁管、上层该怎么用。

7. 当前维护要求

  1. agent2 的公共层要尽量保持“低耦合、强注释、易迁移”。
  2. 新技能开发时,优先复用这里已有的公共能力,而不是直接复制旧技能代码。
  3. 如果发现某段逻辑已经出现第二份实现,应优先评估抽公共层,而不是继续写第三份。
  4. 后续只要扩展通用能力,必须同步更新本文档,否则视为迁移或重构未完成。