后端: 1. execute 主链路重构为“上下文工具域 + 主动优化候选闭环”——移除 order_guard,粗排后默认进入主动微调,先诊断再从后端候选中选择 move/swap,避免 LLM 自由全局乱搜 2. 工具体系升级为动态注入协议——新增 context_tools_add / remove、工具域与二级包映射、主动优化白名单;schedule / taskclass / web 工具按域按包暴露,msg0 规则包与 execute 上下文同步重写 3. analyze_health 升级为主动优化唯一裁判入口——补齐 rhythm / tightness / profile / feasibility 指标、候选扫描与复诊打分、停滞信号、forced imperfection 判定,并把连续优化状态写回运行态 4. 任务类能力并入新 Agent 执行链——新增 upsert_task_class 写工具与启动注入事务写入;任务类模型补充学科画像与整天屏蔽配置,粗排支持 excluded_days_of_week,steady 策略改为基于目标位置/单日负载/分散度/缓冲的候选打分 5. 运行态与路由补齐优化模式语义——新增 active tool domain/packs、pending context hook、active optimize only、taskclass 写入回盘快照;区分 first_full / global_reopt / local_adjust,并完善首次粗排后默认 refine 的判定 前端: 6. 助手时间线渲染细化——推理内容改为独立 reasoning block,支持与工具/状态/正文按时序交错展示,自动收口折叠,修正 confirm reject 恢复动作 仓库: 7. newAgent 文档整体迁入 docs/backend,补充主动优化执行规划与顺序约束拆解文档,删除旧调试日志文件 PS:这次科研了2天,总算是有些进展了——LLM永远只适合做选择题、判断题,不适合做开放创新题。
524 lines
16 KiB
Markdown
524 lines
16 KiB
Markdown
# 主动优化顺序约束拆分执行计划
|
||
|
||
## 1. 本轮目标
|
||
|
||
本轮要解决的不是单点 bug,而是一个架构错位:
|
||
|
||
1. 主动优化希望 LLM 在窗口内自主微调,围绕负载、节奏、容错做多轮观察与挪动。
|
||
2. 现有顺序保护却是“全局 suggested 基线 + 收口时自动复原”,本质是事后抢救。
|
||
3. 两者叠加后,LLM 前面刚优化完,后面又可能被 `order_guard` 否掉,甚至否不回去,只能带着异常结果交付。
|
||
|
||
因此,本轮的核心目标是:
|
||
|
||
1. 把“顺序约束”从 graph 收口节点,下沉为写工具层的前置约束。
|
||
2. 把“全局顺序冻结”改成“允许跨科目交错,但锁住同任务类内部顺序”。
|
||
3. 顺手修掉当前主动优化链路里由旧守卫带来的提示污染、卡片误导、兼容性 bug。
|
||
4. 借这次改造,把 `node/execute.go` 继续拆职责,避免后续主动优化逻辑继续堆在单文件里。
|
||
|
||
---
|
||
|
||
## 2. 当前问题诊断
|
||
|
||
### 2.1 产品语义错位
|
||
|
||
当前系统默认语义仍是:
|
||
|
||
1. `AllowReorder=false` 时,尽量保持所有 suggested 的全局相对顺序。
|
||
2. 若被打乱,则在 `order_guard` 节点尝试按 baseline 复原。
|
||
|
||
这和我们已经对齐的新产品语义冲突:
|
||
|
||
1. 用户默认不是“完全不许动顺序”。
|
||
2. 用户要的是“每门课内部别乱序,但不同课之间可以交错来换负载”。
|
||
3. 主动优化阶段的目标是优化坑位分布,不是死守粗排全局序列。
|
||
|
||
### 2.2 约束位置放错了
|
||
|
||
当前顺序保护发生在:
|
||
|
||
1. `execute` 完成后。
|
||
2. `graph/order_guard` 收口前。
|
||
|
||
这会导致三个问题:
|
||
|
||
1. 非法移动已经发生,后面只能补救。
|
||
2. 补救失败也不会阻断交付,只会吐一句“顺序异常但未复原”。
|
||
3. LLM 在执行时完全不知道哪些移动其实不该做,容易白跑。
|
||
|
||
### 2.3 约束粒度过粗
|
||
|
||
当前基线是“所有 suggested 任务的时间顺序快照”,这会把下面两类本来合理的操作也一起误伤:
|
||
|
||
1. 不同任务类之间为了均衡负载而做的交错。
|
||
2. 在不破坏科目内部先后关系的前提下做的跨天平衡。
|
||
|
||
### 2.4 当前 bug 已经暴露
|
||
|
||
从日志看,至少已有这些具体问题:
|
||
|
||
1. `order_guard` 尝试复原时出现 `slot_incompatible`。
|
||
- 本质说明旧复原逻辑对“任务时长单位”和“坑位跨度单位”的理解并不稳。
|
||
- 这条链本来就不该继续扩展,而该整体退场。
|
||
2. 前端会收到“已记录本轮建议任务顺序基线”“顺序异常但未执行自动复原”这类对用户价值很低的系统话术。
|
||
3. `execute` prompt 仍在强调“默认保持 suggested 相对顺序”,这会继续把模型往旧目标上拽。
|
||
4. `spread_even` / `move` / `swap` / `batch_move` 当前都不知道“同任务类兄弟节点边界”,所以无法在写入前拦住越界调整。
|
||
|
||
### 2.5 代码结构已经不适合继续堆功能
|
||
|
||
当前 `node/execute.go` 已经承载了:
|
||
|
||
1. execute 主循环。
|
||
2. 工具执行。
|
||
3. 工具结果摘要。
|
||
4. feasibility 守门。
|
||
5. task class 写入状态回盘。
|
||
6. preview 实时写。
|
||
7. 顺序相关拦截。
|
||
8. scope 解析。
|
||
|
||
这类文件继续加主动优化逻辑,后续回归会越来越难定位。
|
||
|
||
---
|
||
|
||
## 3. 目标行为
|
||
|
||
改造后的目标行为如下:
|
||
|
||
1. LLM 仍然可以主动观察、主动微调、再观察,不退化成一次性确定性求解。
|
||
2. 默认允许跨任务类交错调整。
|
||
3. 默认不允许打乱同一任务类内部的学习顺序。
|
||
4. 每次写工具调用前,后端都能判断这次移动是否越过“同任务类上一个/下一个任务”的合法边界。
|
||
5. 如果越界,工具直接返回失败原因,让 LLM 换别的任务或别的坑位,而不是先写进去、最后再抢救。
|
||
6. 交付阶段不再出现旧 `order_guard` 的提示文案,也不再依赖它去修复顺序。
|
||
|
||
一句话概括:
|
||
|
||
> 允许跨科目穿插优化,但每门课内部始终保持原有学习推进顺序。
|
||
|
||
---
|
||
|
||
## 4. 必须补齐的数据
|
||
|
||
这是本轮最关键的数据面。没有这些字段,后端没法在写工具层判断“这个任务能挪到哪”。
|
||
|
||
### 4.1 任务类内部顺序 rank
|
||
|
||
当前 `ScheduleTask` 里有:
|
||
|
||
1. `TaskClassID`
|
||
2. `SourceID`(`task_item.id`)
|
||
|
||
但没有:
|
||
|
||
1. 该 `task_item` 在所属任务类里的 `order`
|
||
|
||
这意味着后端知道“它属于哪门课”,但不知道“它是这门课里的第几个任务”。
|
||
|
||
本轮需要补:
|
||
|
||
1. 在 `schedule.ScheduleTask` 增加类似 `TaskOrder` 的运行态字段。
|
||
2. 在 `conv/schedule_state.go` 从 `model.TaskClassItem.Order` 映射进来。
|
||
|
||
### 4.2 顺序边界计算所需的同类兄弟信息
|
||
|
||
有了 `TaskClassID + TaskOrder` 后,不一定非要把前后兄弟 ID 也落进 state;两种方案都可行:
|
||
|
||
1. 轻量方案:运行时动态扫描同任务类任务,按 `TaskOrder` 算前驱/后继。
|
||
2. 预计算方案:在 state 初始化时直接建立 sibling index。
|
||
|
||
本轮建议先走轻量方案,原因:
|
||
|
||
1. 改动面更小。
|
||
2. 不引入新的状态同步负担。
|
||
3. 足够支撑写工具前置校验。
|
||
|
||
### 4.3 合法时间边界的统一定义
|
||
|
||
需要明确一个统一规则:
|
||
|
||
1. 一个任务的目标位置,必须晚于同任务类前驱任务的结束时间。
|
||
2. 必须早于同任务类后继任务的开始时间。
|
||
3. 若前驱/后继不存在,则该侧边界开放。
|
||
4. 若前驱/后继当前是 pending、未落位,则该侧边界暂不收紧。
|
||
|
||
这样 LLM 仍有自由度,但自由度被严格限制在“本任务合法活动区间”里。
|
||
|
||
---
|
||
|
||
## 5. 方案总览
|
||
|
||
### 5.1 总体策略
|
||
|
||
本轮不再沿用“先放任移动,最后 graph 收口时修”的模式,而改成:
|
||
|
||
1. 写工具调用前先验边界。
|
||
2. 合法才允许写。
|
||
3. 非法直接返回失败。
|
||
4. 收口阶段只做轻量断言,不再自动复原。
|
||
|
||
### 5.2 顺序保护新哲学
|
||
|
||
旧哲学:
|
||
|
||
1. 保护粗排全局时间序列。
|
||
|
||
新哲学:
|
||
|
||
1. 保护每个任务类内部的推进顺序。
|
||
2. 不保护不同任务类之间的相对先后。
|
||
|
||
### 5.3 对主动优化的意义
|
||
|
||
这套改法的直接意义是:
|
||
|
||
1. LLM 终于可以真的做“负载优化”而不是被全局顺序锁死。
|
||
2. LLM 即使选错目标,也会在写工具层收到具体失败原因。
|
||
3. 失败原因足够明确时,模型下一步就知道该换任务、换天、还是换工具。
|
||
|
||
---
|
||
|
||
## 6. 具体拆分与改动计划
|
||
|
||
## 6.1 第一步:给 ScheduleState 补顺序语义
|
||
|
||
涉及文件:
|
||
|
||
1. `backend/newAgent/tools/schedule/state.go`
|
||
2. `backend/newAgent/conv/schedule_state.go`
|
||
|
||
计划动作:
|
||
|
||
1. 在 `ScheduleTask` 增加任务类内部顺序字段。
|
||
- 建议名:`TaskOrder int`
|
||
2. 仅 `source=task_item` 时填充该字段。
|
||
3. 从 `model.TaskClassItem.Order` 注入运行态。
|
||
4. 对缺失 order 的历史数据做兜底。
|
||
- 优先使用数据库 order。
|
||
- 若为空,则按 `TaskClass.Items` 当前顺序补稳定序号。
|
||
|
||
验收结果:
|
||
|
||
1. 每个 `task_item` 在工具层都能知道自己是所属任务类里的第几项。
|
||
2. 查询工具输出里不一定要暴露这个字段给 LLM,但后端必须可用。
|
||
|
||
## 6.2 第二步:新增“局部顺序约束”公共层
|
||
|
||
涉及文件:
|
||
|
||
1. 新增 `backend/newAgent/tools/schedule/order_constraints.go`
|
||
2. 复用 `backend/newAgent/tools/schedule/write_helpers.go`
|
||
|
||
计划动作:
|
||
|
||
1. 抽一个独立公共层,不把顺序判断散落在每个写工具里重复写。
|
||
2. 公共层职责只做一件事:判断某个任务能否落到某个目标时段。
|
||
3. 需要提供的核心能力:
|
||
- 找到同任务类前驱任务
|
||
- 找到同任务类后继任务
|
||
- 计算合法最早起点 / 最晚终点
|
||
- 判断目标位置是否越界
|
||
- 输出中文失败原因
|
||
|
||
建议返回信息:
|
||
|
||
1. `ok=true/false`
|
||
2. 失败原因中文摘要
|
||
3. 命中的前驱/后继任务是谁
|
||
4. 合法范围描述
|
||
|
||
这样后面各写工具都能直接复用,不再复制逻辑。
|
||
|
||
## 6.3 第三步:把约束前置到基础写工具
|
||
|
||
涉及文件:
|
||
|
||
1. `backend/newAgent/tools/schedule/write_tools.go`
|
||
|
||
计划动作:
|
||
|
||
1. `move` 接入局部顺序约束。
|
||
2. `swap` 在交换前对双方交换后的目标位置分别校验。
|
||
3. `batch_move` 在克隆态上统一校验整批目标是否都满足局部顺序约束。
|
||
4. `place` 也要接入。
|
||
- 因为被 `unplace` 后再次放回,仍然可能破坏同类顺序。
|
||
5. `unplace` 暂时不做顺序阻断。
|
||
- 它只是把任务拿出来,不直接打乱同类内部先后。
|
||
- 真正的顺序问题应在后续 `place/move` 时拦截。
|
||
|
||
验收目标:
|
||
|
||
1. 任一基础写工具都不能把任务挪出自己的合法兄弟区间。
|
||
2. 非法时工具直接失败,且提示能被 LLM 看懂。
|
||
|
||
## 6.4 第四步:让复合写工具也遵守边界
|
||
|
||
涉及文件:
|
||
|
||
1. `backend/newAgent/tools/schedule/compound_tools.go`
|
||
|
||
计划动作:
|
||
|
||
1. `spread_even` 生成候选位置后,回填前逐任务校验局部顺序边界。
|
||
2. 若规划器给出的结果越界,整次复合写失败并给出明确原因。
|
||
3. `min_context_switch` 继续维持 P1 不暴露。
|
||
4. 即使未来重开,也必须走同一套局部顺序约束,不允许绕过。
|
||
|
||
原因:
|
||
|
||
1. 复合工具最容易“整体看起来更均匀,但把单科内部顺序打乱”。
|
||
2. 如果只拦基础写工具,不拦复合工具,系统规则会不一致。
|
||
|
||
## 6.5 第五步:退役旧 order_guard
|
||
|
||
涉及文件:
|
||
|
||
1. `backend/newAgent/node/order_guard.go`
|
||
2. `backend/newAgent/graph/common_graph.go`
|
||
3. `backend/newAgent/model/common_state.go`
|
||
4. `backend/newAgent/node/execute.go`
|
||
|
||
计划动作:
|
||
|
||
1. 移除“全局 baseline + 收口复原”的主逻辑。
|
||
2. 删除或停用 `SuggestedOrderBaseline` 运行态。
|
||
3. 删除 `order_guard` 节点在主动优化链路中的强依赖。
|
||
4. 交付前若仍需要安全兜底,只保留一个轻量 final assert:
|
||
- 仅检查每个任务类内部顺序是否仍合法
|
||
- 不自动复原
|
||
- 若非法,视为执行层 bug,直接中止交付并打日志
|
||
|
||
推荐做法:
|
||
|
||
1. P1 先彻底切掉 graph 层 `order_guard` 分支。
|
||
2. 若担心过渡期风险,再补一个极轻的校验函数在 deliver 前调用。
|
||
|
||
## 6.6 第六步:同步调整 prompt 与模型目标
|
||
|
||
涉及文件:
|
||
|
||
1. `backend/newAgent/prompt/execute_context.go`
|
||
2. `backend/newAgent/prompt/execute.go`
|
||
3. 视需要补充 `prompt/execute_rule_packs.go`
|
||
|
||
计划动作:
|
||
|
||
1. 删除“默认保持 suggested 相对顺序”的旧表述。
|
||
2. 改成新的明确描述:
|
||
- 默认保持同任务类内部顺序
|
||
- 允许跨任务类交错调整
|
||
- 不得擅自突破同任务类内部先后
|
||
3. 把“非法时工具会直接失败”作为模型可感知规则写进 prompt。
|
||
|
||
这样 LLM 会更接近真实规则,不会一直沿着旧目标空转。
|
||
|
||
## 6.7 第七步:拆 execute.go 职责
|
||
|
||
涉及文件:
|
||
|
||
1. `backend/newAgent/node/execute.go`
|
||
2. 新增若干并行文件
|
||
|
||
建议拆分方向:
|
||
|
||
1. `node/execute.go`
|
||
- 只保留主循环、决策分发、节点入口
|
||
2. `node/execute_scope_guard.go`
|
||
- 当前步骤作用域解析与日期范围守门
|
||
3. `node/execute_tool_runtime.go`
|
||
- `executeToolCall` / `executePendingTool` / preview 写入
|
||
4. `node/execute_tool_summary.go`
|
||
- 工具摘要、参数摘要、结果摘要
|
||
5. `node/execute_taskclass_runtime.go`
|
||
- task class upsert 状态回盘相关
|
||
6. `node/execute_health_runtime.go`
|
||
- feasibility / health 快照更新
|
||
|
||
这一步的目的不是“为了好看”,而是避免后面继续把主动优化规则、task class 流程规则、工具结果摘要全塞回一个文件。
|
||
|
||
---
|
||
|
||
## 7. 本轮顺手修复的 bug 清单
|
||
|
||
## 7.1 bug A:顺序异常提示污染用户体验
|
||
|
||
现象:
|
||
|
||
1. 前端会看到“已记录本轮建议任务顺序基线”
|
||
2. 以及“检测到顺序异常,但本次未执行自动复原”
|
||
|
||
修法:
|
||
|
||
1. 随 `order_guard` 退役一起移除这两类状态文案。
|
||
2. 这类内部守卫信息不再面向用户显式展示。
|
||
|
||
## 7.2 bug B:`slot_incompatible` 兼容性问题
|
||
|
||
现象:
|
||
|
||
1. 日志里出现 `expected_duration=1 slot_duration=2`
|
||
|
||
判断:
|
||
|
||
1. 这是旧 `order_guard` 复原链上的单位不一致问题。
|
||
2. 该问题不值得单独继续修补。
|
||
|
||
修法:
|
||
|
||
1. 旧复原链退役后,这条 bug 自然消失。
|
||
2. 本轮只保留一个动作:确认写工具本身的时长计算口径仍正确。
|
||
|
||
## 7.3 bug C:prompt 仍把模型往“全局不乱序”上引
|
||
|
||
现象:
|
||
|
||
1. `execute_context` 里仍写着默认保持 suggested 相对顺序。
|
||
|
||
修法:
|
||
|
||
1. 改成“默认保持同任务类内部顺序”。
|
||
|
||
## 7.4 bug D:复合工具可能绕过新规则
|
||
|
||
现象:
|
||
|
||
1. `spread_even` 当前只校验冲突,不校验同类前后边界。
|
||
|
||
修法:
|
||
|
||
1. 接入统一局部顺序约束层。
|
||
|
||
## 7.5 bug E:active optimize 链路和 execute 文件职责缠得太紧
|
||
|
||
现象:
|
||
|
||
1. 任何主动优化 bug 都容易改进 `execute.go`,继续涨文件体积。
|
||
|
||
修法:
|
||
|
||
1. 本轮同步拆文件,至少把工具执行与摘要逻辑拆出去。
|
||
|
||
---
|
||
|
||
## 8. 实施顺序
|
||
|
||
建议按下面顺序推进,避免中途状态既不兼容旧逻辑,也没完全切到新逻辑。
|
||
|
||
### 阶段 1:补数据
|
||
|
||
1. 给 `ScheduleTask` 增加 `TaskOrder`
|
||
2. 在 state loader 中完成映射
|
||
3. 保证查询 / 粗排 / 预览链路不受影响
|
||
|
||
### 阶段 2:落局部顺序约束公共层
|
||
|
||
1. 实现前驱/后继查找
|
||
2. 实现目标落位合法性判断
|
||
3. 输出中文失败原因
|
||
|
||
### 阶段 3:接入基础写工具
|
||
|
||
1. `move`
|
||
2. `swap`
|
||
3. `batch_move`
|
||
4. `place`
|
||
|
||
### 阶段 4:接入复合写工具
|
||
|
||
1. `spread_even`
|
||
2. 保持 `min_context_switch` 继续禁用
|
||
|
||
### 阶段 5:切掉旧 order_guard
|
||
|
||
1. 删除 graph 分支
|
||
2. 删除 baseline 运行态
|
||
3. 去掉用户可见状态文案
|
||
|
||
### 阶段 6:更新 prompt
|
||
|
||
1. 改目标描述
|
||
2. 改顺序策略说明
|
||
3. 明确非法写工具会被后端拒绝
|
||
|
||
### 阶段 7:拆 execute.go
|
||
|
||
1. 先无行为变化拆文件
|
||
2. 再补必要注释与最小验证
|
||
|
||
---
|
||
|
||
## 9. 验证口径
|
||
|
||
## 9.1 正向场景
|
||
|
||
要验证这些场景能通过:
|
||
|
||
1. 同任务类内部顺序不变,但不同任务类交错后负载更均衡。
|
||
2. LLM 将某任务从第 3 天挪到第 20 天,只要仍在其前后兄弟之间,就允许。
|
||
3. `spread_even` 可以把多门课拉开,但不会把某一门课内部顺序反过来。
|
||
|
||
## 9.2 反向场景
|
||
|
||
要验证这些场景被拦住:
|
||
|
||
1. 把某门课的第 4 个任务挪到第 1 个任务前面。
|
||
2. 把某门课的中间任务挪到其后继任务之后。
|
||
3. `swap` 后导致同任务类内部出现逆序。
|
||
4. `batch_move` 中有一条越界时整批失败。
|
||
|
||
## 9.3 交付场景
|
||
|
||
要确认这些旧副作用消失:
|
||
|
||
1. 不再出现 `order_guard_initialized`
|
||
2. 不再出现 `order_guard_restore_skipped`
|
||
3. 不再依赖 `SuggestedOrderBaseline`
|
||
|
||
## 9.4 代码结构场景
|
||
|
||
要确认:
|
||
|
||
1. `execute.go` 文件职责明显变轻
|
||
2. 局部顺序约束逻辑只存在一份公共实现
|
||
|
||
---
|
||
|
||
## 10. 本轮建议的最小落地范围
|
||
|
||
如果要控制风险,本轮建议先做到这里:
|
||
|
||
1. `TaskOrder` 注入
|
||
2. 局部顺序约束公共层
|
||
3. `move/swap/batch_move/place` 接入
|
||
4. `spread_even` 接入
|
||
5. prompt 改口径
|
||
6. 切掉旧 `order_guard`
|
||
7. 拆出 `execute_tool_runtime.go` 与 `execute_tool_summary.go`
|
||
|
||
这个范围已经足够让主动优化链路从“旧哲学打架”切到“新哲学能跑”。
|
||
|
||
---
|
||
|
||
## 11. 预期收益
|
||
|
||
做完之后,预期表现会变成:
|
||
|
||
1. LLM 会更敢做真实优化,因为它不再被全局顺序锁死。
|
||
2. 后端会在写工具层直接给出“能不能这么挪”的明确反馈。
|
||
3. 同一门课的学习推进顺序能被稳定锁住。
|
||
4. 不同门课之间仍有足够空间做均衡、分散、减压。
|
||
5. 前端不会再收到旧 `order_guard` 带来的迷惑状态。
|
||
6. 后续如果继续加主动优化策略,也有更干净的承载位置,不必继续往 `execute.go` 里堆。
|
||
|
||
---
|
||
|
||
## 12. 本文档对应的实施结论
|
||
|
||
本轮建议按以下原则执行:
|
||
|
||
1. 删除“全局 suggested 顺序守卫”思路。
|
||
2. 改为“同任务类内部顺序约束前置到写工具层”。
|
||
3. 允许跨任务类交错优化。
|
||
4. 顺手清理旧 guard 带来的用户可见噪音与兼容性问题。
|
||
5. 同步拆分 execute 相关职责文件,避免继续堆史山。
|
||
|