🐛 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
13 KiB
13 KiB
Agent 代码复用清单(施工备忘)
1. 文档目的
- 这份文档只做一件事:记录当前
backend/agent与backend/service/agentsvc中“明明可以复用、却被重复实现”的代码点。 - 目标不是立刻改代码,而是防止后续压缩上下文后忘记哪些地方最值得先收口。
- 这里优先关注“公共能力重复实现”,不讨论具体业务对错。
2. 当前最明显的重复结论
- 重复最严重的不是某一个 skill,而是“每个 skill 都在各自实现一套模型调用、JSON 解析、阶段推送、兜底、深拷贝、缓存快照读写、工具分发”。
quicknote、taskquery、scheduleplan、schedulerefine已经形成了“四套相似但不统一的 agent 基建”。service/agentsvc里也开始出现重复胶水层逻辑,尤其是排程预览和连续微调相关读写。
3. 高优先级复用点
3.1 模型调用封装重复
现状
quicknote自己实现了callModelForJSON/callModelForJSONWithMaxTokens。taskquery自己实现了callTaskQueryModelForJSON。scheduleplan自己实现了callScheduleModelForJSON。schedulerefine自己实现了callModelText,而且附带了一整套 JSON contract 变体。
证据
问题
- 都在做同一类事情:
- 拼
system + user消息 - 关闭/开启 thinking
- 设置
temperature/maxTokens - 调
Generate - 判空响应
- 返回文本
- 拼
- 现在每个模块一套,导致:
- 参数风格不统一
- 错误文案不统一
- token 统计与 callback 复用也难进一步规范
建议抽取
- 抽到统一的
agent/core/modelx或类似目录。 - 至少统一三个入口:
GenerateTextGenerateJSONGenerateJSONWithContract
优先级
P0
3.2 JSON 解析与对象提取重复
现状
quicknote自己实现了parseJSONPayload+extractJSONObject。taskquery有自己的parseTaskQueryJSON。scheduleplan有自己的parseScheduleJSON。schedulerefine有自己的parseJSON,并且带重试解析逻辑。
证据
问题
- 都在解决“模型返回不是纯 JSON,需要做容错提取”。
- 解析失败后的兜底策略也高度相似,只是文案不同。
建议抽取
- 抽统一的
agent/core/jsonx:ParseJSONObject[T]ExtractJSONObjectParseWithRetryHint(后续可选)
优先级
P0
3.3 阶段推送(EmitStage)重复
现状
quicknote/graph.go自己封装EmitStage判空。taskquery/graph.go自己写runner.emit。scheduleplan/graph.go自己包一层emitStage。schedulerefine/graph.go也自带一套。
证据
问题
- 所有 graph 都在做相同事情:封装一个
func(stage, detail string),把 nil 判断藏起来。 - 这一层目前很轻,但 skill 一多就会持续复制。
建议抽取
- 抽统一的
agent/core/progress:type Emitter func(stage, detail string)func WrapEmitter(fn func(string, string)) Emitterfunc NoopEmitter() Emitter
优先级
P1
3.4 OpenAI 兼容流式包装能力重复调用方式不统一
现状
- 统一实现其实已经有了,在 chat/stream.go 里:
ToOpenAIStreamToOpenAIFinishStream
- 但
agentsvc侧又额外自己实现了“阶段推送器”和“单条 assistant completion 包装”。
证据
问题
- 现在虽然底层函数复用了,但“如何构造阶段消息 / 一次性正文 / finish chunk”的协议层仍散落在 service 层。
- 后续 schedule、memory、websearch 也大概率继续复制。
建议抽取
- 抽统一的
agent/core/streamx:EmitReasoningStageEmitSingleAssistantReplyEmitErrorChunk
优先级
P1
3.5 深拷贝函数重复
现状
agentsvc/agent_schedule_preview.go有:cloneWeekSchedulescloneHybridEntriescloneTaskClassItems
schedulerefine/state.go又复制了一份同类深拷贝函数。scheduleplan侧也多处依赖同样的拷贝语义。
证据
问题
- 这是非常典型的“纯工具函数”重复实现。
- 后面只要结构体字段一变,就容易出现一边更新一边漏更。
建议抽取
- 抽统一的
agent/core/clone或agent/core/snapshot。
优先级
P0
3.6 预览快照读取/回填逻辑重复
现状
runSchedulePlanFlow自己在做:- 查 Redis 预览
- miss 后查 MySQL snapshot
- 回填 Redis
- 清旧 preview
runScheduleRefineFlow又通过loadSchedulePreviewContext再做一套类似的逻辑。GetSchedulePlanPreview也重复做:- 查 Redis
- miss 查 MySQL
- 回填 Redis
证据
问题
- 这是同一个“排程预览仓储语义”,却被 service 层复制成了三种读法。
- 长期会导致缓存行为不一致。
建议抽取
- 抽成统一
schedule preview repository/gateway:LoadPreviewContextSavePreviewContextDeletePreviewContextLoadPreviewForRead
优先级
P0
3.7 fallback 文案与早退逻辑重复
现状
quicknote有自己的 fallback reply / persisted 判定。taskquery有buildTaskQueryFallbackReply。scheduleplan和schedulerefine里也散落大量“解析失败/模型失败/回退继续”的逻辑。
证据
问题
- 不是所有 fallback 文案都能共用,但 fallback 模式本身是共性的:
- 模型失败 -> 本地兜底
- JSON 失败 -> 本地兜底
- 工具失败 -> 重试 / 降级
建议抽取
- 不建议直接抽“文案”。
- 但建议抽“失败策略框架”:
FallbackPolicyRetryOrFallbackParseOrFallback
优先级
P1
3.8 时间解析能力重复 / 分散
现状
quicknote/tool.go内有一整套时间解析:- 相对时间
- 中文时间
- deadline hint
- 默认时刻
- 排程链路后续也非常可能要继续用类似时间语义。
证据
问题
- 目前还没出现“多份复制”,但这是非常明确的“已形成公共能力、却还放在 skill 私有目录”的情况。
- 如果不提前抽,schedule / memory / websearch 接进来后一定复制。
建议抽取
- 提前迁出到
agent/core/timeparse。
优先级
P1
3.9 runner 形态重复
现状
quicknote/runner.goscheduleplan/runner.goschedulerefine/runner.go
问题
- 本质都在做同一件事:把 graph 输入依赖注入到每个步骤函数。
- 结构相似,但没有统一约定。
建议抽取
- 不一定要抽通用基类。
- 但至少要统一 runner 模板风格,减少 skill 间阅读切换成本。
优先级
P2
3.10 route 控制码能力与 skill 判定方式分裂
现状
- 统一路由已经在 route/route.go。
- 但 skill 内部仍有不少二次意图确认 / fallback 判定。
问题
- 这不是完全重复代码问题,而是“判定责任分裂”。
- 结果是:
- route 做一遍
- skill 首节点再做一遍
- service 再决定回不回聊天
建议抽取
- 统一成:
route只做一级分流- skill 只做 skill 内业务判定
- service 不再写 skill 特有 fallback 判定
优先级
P1
4. 中优先级复用点
4.1 Extra 参数解析工具只在 scheduleplan 私有
现状
scheduleplan/tool.go有ExtraInt、ExtraIntSlice。
问题
- 这明显属于通用 agent 请求参数解析能力,不该锁死在某个 skill 下。
建议抽取
- 抽到
agent/core/extrax。
优先级
P2
4.2 工具分发 dispatch 逻辑重复
现状
scheduleplan/tools_react.go有dispatchReactTool、dispatchWeeklySingleActionTool。scheduleplan/daily_refine.go有dispatchDailyReactTool。schedulerefine/tool.go有dispatchRefineTool。
问题
- 都在做“执行工具调用 -> 应用结果 -> 返回标准结果”的模式。
- 虽然业务不同,但骨架高度类似。
建议抽取
- 抽公共的
tool dispatcher框架,业务只实现 apply。
优先级
P2
4.3 preview/snapshot DTO 映射重复
现状
snapshotToSchedulePlanPreviewCachesnapshotToSchedulePlanPreviewResponsebuildSchedulePlanSnapshotFromStateconvertRefineStateToPlanState
问题
- 同一批结构在 service 层到处转来转去。
- 说明“排程预览状态”还没有独立成一个稳定模型层。
建议抽取
- 抽
schedule snapshot mapper。
优先级
P2
5. 已经出现“文件承担过多职责”的区域
5.1 schedulerefine/nodes.go
- 3140 行。
- 当前同时承担:
- 模型调用
- JSON 解析
- 规则归一化
- planner
- react loop
- hard check
- summary
- 这是最先需要拆的文件。
5.2 schedulerefine/tool.go
- 1768 行。
- 当前同时承担:
- tool 定义
- tool 分发
- payload 解析
- policy
- fallback query
- slot hint 构造
- 应拆成:
tool_defstool_dispatchtool_payloadtool_policy
5.3 scheduleplan/nodes.go
- 713 行。
- 当前已经开始混:
- plan
- rough build
- preview return
- model call helper
- 现在还来得及拆。
6. 建议的第一批收口顺序
第一批(先抽公共层,不碰业务语义)
P0-1抽模型调用公共层。P0-2抽 JSON 解析公共层。P0-3抽深拷贝公共层。P0-4抽排程预览加载/保存 gateway。
第二批(统一 skill 编排骨架)
P1-1统一EmitStage。P1-2统一流式阶段输出辅助。P1-3统一 fallback/retry 框架。
第三批(拆超大文件)
P2-1拆schedulerefine/nodes.goP2-2拆schedulerefine/tool.goP2-3拆scheduleplan/nodes.go
7. 可以考虑直接删除/合并的嫌疑区域
7.1 scheduleplan 与 schedulerefine 的边界可能切得过细
- 这两个包共享大量状态与工具语义。
- 后续大概率应该合并成一个
schedule能力域,内部再区分create/refineflow。
7.2 agentsvc 侧的排程桥接文件可能过多
- 当前已有:
- 这些文件后续应下沉部分逻辑到 gateway / repository / snapshot service。
8. 后续重构时的硬约束
- 先抽公共能力,再动 skill 业务逻辑,避免一边搬家一边改语义导致回归难查。
- 每抽一类公共件,都优先让
quicknote和taskquery先接一次,确认不是只为schedule定制。 - 抽公共层时,不要先追求“最优抽象”,先追求“把四份重复收成一份”。