# agent2 通用能力接入文档 ## 1. 文档目的 本文档用于说明 `agent2` 目录下“通用能力”的边界、放置位置、接入方式与维护要求。 这里的“通用能力”特指: 1. 不只服务于某一个技能链路,而是可能被 `chat`、`quicknote`、`taskquery`、`schedule` 等多个模块共同复用的能力。 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. 统一收口模型调用接口,减少各技能重复拼装 `messages`、`thinking`、`temperature`、`max_tokens`。 2. 提供通用 JSON 解析与 usage 合并能力。 3. 把具体厂商 SDK 细节尽量压在适配层,不向上层节点扩散。 适合放什么: 1. 所有技能都可能复用的模型调用壳。 2. 通用 JSON 提取与反序列化。 3. 流式/非流式调用的统一适配接口。 4. usage 收敛、空响应错误格式化。 不适合放什么: 1. 只服务于某一个技能的 prompt 文案。 2. 某一个技能特有的输出结构体。 3. 某一个技能独占的“成功文案润色”规则。 说明: 1. 如果只是“基于通用 `Client` 再包一层技能专用函数”,例如 quicknote 的聚合规划调用,这种代码可以放在 `llm/`,但文件名应明确带技能语义,避免误认为完全通用能力。 2. 真正跨技能复用的内容,优先沉到 `client.go`、`ark.go`、`json.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 == xxx`、`switch 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. 后续只要扩展通用能力,必须同步更新本文档,否则视为迁移或重构未完成。