Files
smartmate/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

389 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# agent2 逐批搬迁实施细节
## 1. 文档目的
- 本文档用于指导 `backend/agent -> backend/agent2` 的渐进式重构。
- 目标是“在逻辑不变的前提下重整结构、消除冗余、逐步切流”,而不是一次性重写。
- 该文档优先服务于施工过程,强调“怎么搬、先搬什么、如何验证、何时删除旧代码”。
## 2. 总体迁移原则
### 2.1 不直接改老目录,先并行维护
- 保持现有 `backend/agent` 不动,作为稳定对照组。
- 新建 `backend/agent2`,所有重构都先落在新目录。
- `backend/service/agentsvc` 在迁移期充当“总开关层”,决定某条链路接旧目录还是新目录。
### 2.2 先搬结构,再收冗余
- 第一优先级不是“顺手优化业务逻辑”,而是把职责边界搬顺。
- 迁移时允许短期存在“代码原样搬过去”的情况,但必须同步记录哪些地方后续要抽公共层。
- 严禁一边大改业务逻辑、一边改目录结构,否则回归问题无法定位。
### 2.3 新旧链路必须可对照
- 每搬完一个 skill都要保证
- 老链路还能跑;
- 新链路能独立接入;
- service 层可以随时切回旧实现。
- 迁移期间所有新入口都必须保留 feature flag 或切换点。
### 2.4 先做“能收敛冗余”的迁移
- 优先搬那些已经出现明显重复代码的能力,不要先搬边缘功能。
- 迁移不是“把史山平移到新目录”,而是“借搬迁之机把重复公共件抽出来”。
---
## 3. agent2 目标结构
```text
backend/agent2/
entrance.go
router/
route.go
route_model.go
graph/
quicknote.go
taskquery.go
schedule.go
node/
quicknote.go
taskquery.go
schedule_plan.go
schedule_refine.go
llm/
client.go
json.go
route.go
quicknote.go
taskquery.go
schedule.go
model/
common.go
route.go
quicknote.go
taskquery.go
schedule.go
prompt/
route.go
quicknote.go
taskquery.go
schedule.go
stream/
emitter.go
openai.go
shared/
clone.go
retry.go
time.go
```
## 4. 各层职责约束
### 4.1 `entrance.go`
- 是整个 `agent2` 模块唯一总入口。
- 负责:
- 接收请求上下文;
- 调路由;
- 调 skill graph
- 收口 token、SSE、持久化。
- 不负责:
- 某个 skill 内部节点的业务实现;
- 直接写 prompt
- 直接调用模型。
### 4.2 `router/`
- 只负责一级分流。
- 输出统一路由结果,例如:
- `chat`
- `quick_note_create`
- `task_query`
- `schedule_plan_create`
- `schedule_plan_refine`
- 不负责 skill 内部二次判断。
### 4.3 `graph/`
- 只负责画 graph。
- 文件里应尽量只出现:
- `AddLambdaNode`
- `AddEdge`
- `AddBranch`
- 不允许在 `graph/` 内直接写复杂业务逻辑、模型调用或数据转换。
### 4.4 `node/`
- 只负责每个节点内部的业务逻辑。
- 可以调用:
- `llm/`
- `shared/`
- service/dao 注入依赖
- 不负责:
- Graph 连线;
- OpenAI chunk 输出格式;
- 大段 prompt 常量定义。
### 4.5 `llm/`
- 专门负责和模型交互。
- 统一收口:
- `GenerateText`
- `GenerateJSON`
- `Stream`
- JSON 提取与解析
- thinking/temperature/maxTokens 等参数
- 这一层的定位,等价于“模型数据访问层”。
### 4.6 `model/`
- 存放 agent 域模型,而不是数据库模型。
- 包括:
- state
- dto
- route decision
- tool result
### 4.7 `prompt/`
- 统一收口 prompt 文本。
- 禁止在 `node/``llm/` 中内联大段 prompt。
### 4.8 `stream/`
- 统一收口 SSE/OpenAI 兼容块输出。
- 所有阶段推送、单条 assistant 回复、finish chunk 都从这里走。
### 4.9 `shared/`
- 只放纯工具,不放业务编排。
- 当前预计优先放:
- 深拷贝
- 重试策略
- 时间解析
---
## 5. 逐批搬迁顺序
## 第 0 批:先搭骨架,不接流量
### 目标
- 先把 `agent2` 目录骨架建起来。
- 不迁任何业务,只搭结构。
### 产物
- `backend/agent2/` 目录
- `entrance.go`
- `router/graph/node/llm/model/prompt/stream/shared` 空壳
### 验证
- 能编译通过。
- 不接任何线上/实际业务入口。
---
## 第 1 批:先迁公共能力,不迁 skill
### 目标
- 先抽最明显的公共重复件,给后续 skill 迁移打基础。
### 优先抽取的公共件
- `llm/json.go`
- 统一 `GenerateJSON`
- 统一 JSON object 提取
- 统一空响应/解析失败错误
- `llm/client.go`
- 统一 `GenerateText`
- 统一 thinking/maxTokens/temperature 参数装配
- `stream/openai.go`
- 迁入现有 `ToOpenAIStream`
- 迁入 `ToOpenAIFinishStream`
- `stream/emitter.go`
- 统一阶段推送
- 统一单条 assistant completion 输出
- `shared/clone.go`
-`cloneWeekSchedules`
-`cloneHybridEntries`
-`cloneTaskClassItems`
### 说明
- 这一批不碰 quicknote/taskquery/schedule 的业务语义。
- 只把公共重复代码先搬进 `agent2`
---
## 第 2 批:迁 `quicknote`,作为样板 skill
### 目标
- 用最小 skill 验证三层结构是否顺手。
### 对应关系
- 旧:
- `backend/agent/quicknote/graph.go`
- `backend/agent/quicknote/nodes.go`
- `backend/agent/quicknote/tool.go`
- `backend/agent/quicknote/state.go`
- `backend/agent/quicknote/prompt.go`
- 新:
- `backend/agent2/graph/quicknote.go`
- `backend/agent2/node/quicknote.go`
- `backend/agent2/llm/quicknote.go`
- `backend/agent2/model/quicknote.go`
- `backend/agent2/prompt/quicknote.go`
### 迁移要求
- 逻辑不变。
- 原有测试优先复用。
- 如果出现可直接删除的冗余函数,必须当场删除,不允许“先复制一份以后再说”。
### 接入方式
-`agentsvc` 里增加切换开关:
- `run quicknote with agent`
- `run quicknote with agent2`
---
## 第 3 批:迁 `taskquery`
### 目标
- 用第二个 skill 验证公共层抽取是否真的通用,而不是只服务于 quicknote。
### 核心检查点
- `taskquery` 是否能完全复用第 1 批抽出的 `llm/json.go`
- fallback/retry 是否还能保持一致。
- 阶段推送是否已经不需要 skill 自己再包一层。
### 完成标准
- `taskquery` 不再保留自己私有的 JSON 调用封装。
---
## 第 4 批:迁 `route`
### 目标
- 把统一分流也纳入 `agent2`
### 要求
- `entrance.go` 以后只依赖 `agent2/router`
- `router` 只做一级分流,不再夹杂 skill 级 fallback。
---
## 第 5 批:迁 `scheduleplan`
### 目标
- 把排程首次创建链路迁入新结构。
### 迁移策略
- 不直接优化复杂业务。
- 先把它拆成:
- `graph/schedule.go`
- `node/schedule_plan.go`
- `llm/schedule.go`
- `model/schedule.go`
- `prompt/schedule.go`
### 强制要求
- 不允许继续保留“节点文件里夹杂模型调用帮助函数”的写法。
- 不允许在 `node/` 里再内联 JSON parse helper。
---
## 第 6 批:迁 `schedulerefine`
### 目标
- 处理当前最重的史山。
### 原则
- 这一步不是“原样平移”。
- 这是第一次允许在迁移中顺手拆大文件。
### 最低拆分要求
-`nodes.go` 至少拆成:
- `schedule_refine_contract`
- `schedule_refine_plan`
- `schedule_refine_react`
- `schedule_refine_review`
- `schedule_refine_summary`
-`tool.go` 至少拆成:
- `tool_defs`
- `tool_dispatch`
- `tool_payload`
- `tool_policy`
---
## 第 7 批:切流与删除旧目录
### 前提条件
- `quicknote`
- `taskquery`
- `route`
- `scheduleplan`
- `schedulerefine`
以上链路都已经切到 `agent2`,并经过回归验证。
### 操作顺序
- 1. service 层入口全部切到 `agent2`
- 2. 跑一轮完整回归
- 3. 删除旧 `backend/agent`
- 4. `backend/agent2` 改名为 `backend/agent`
### 注意
- 第 4 步必须最后做。
- 在此之前不要提前重命名,避免 review 和 diff 变得混乱。
---
## 6. 迁移过程中的强约束
### 6.1 逻辑不变优先于结构完美
- 只要旧逻辑还能清晰搬进新层次,就先搬。
- 不要在同一轮同时追求“换架构 + 换策略 + 换 prompt”。
### 6.2 每次只迁一个能力域
- 一次只动一个 skill 或一类公共件。
- 严禁“这一轮顺手把三个 skill 一起改了”。
### 6.3 旧代码不删前,新代码必须已接入验证
- 先搬
- 再切
- 再验
- 最后删
### 6.4 公共能力一旦发现第二处重复,就必须考虑抽层
- 不允许在 `agent2` 里继续复制第三份、第四份。
- `agent2` 的目标不是“新目录复刻旧史山”,而是“迁移过程中顺手收口公共能力”。
---
## 7. 每一批迁移完成后的检查项
- 是否出现了新的重复模型调用 helper
- 是否出现了新的重复 JSON parse helper
- 是否又把 prompt 写回节点文件里了?
- 是否又在 service 层偷偷加了一层业务桥接?
- 是否还能一眼看出:
- 入口在哪
- 分流在哪
- 编排在哪
- 节点逻辑在哪
- 模型交互在哪
---
## 8. 当前最值得优先搬的公共件
- `GenerateJSON/GenerateText`
- JSON 提取与解析
- OpenAI 兼容 SSE 包装
- 阶段推送 emitter
- schedule 相关深拷贝工具
- schedule preview / snapshot 读写逻辑
---
## 9. 一句话版本
- 先建 `agent2` 骨架。
- 先抽公共层,再迁 skill。
- 先迁 `quicknote` 做样板,再迁 `taskquery` 验证复用,再动 `schedule`
- 迁移期间新旧并存、随时可切回。
- 删除旧 `agent` 只能发生在所有 skill 全部切流并验证通过之后。