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
This commit is contained in:
LoveLosita
2026-03-24 21:35:22 +08:00
parent e6941f98f2
commit f4ef6fb256
55 changed files with 5492 additions and 235 deletions

View File

@@ -0,0 +1,388 @@
# 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 全部切流并验证通过之后。