后端: 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永远只适合做选择题、判断题,不适合做开放创新题。
548 lines
16 KiB
Markdown
548 lines
16 KiB
Markdown
# 主动优化整套改造计划
|
||
|
||
## 1. 文档目的
|
||
|
||
本文档只回答一件事:这轮主动优化链路改完之后,整体会如何工作。
|
||
|
||
重点不放在实现细节,而放在以下 4 个问题:
|
||
|
||
1. 哪些能力保留,哪些能力直接删除。
|
||
2. `task_class`、粗排、主动优化三者之间的职责如何重新划分。
|
||
3. 首次排程时,agent 的完整执行链路会变成什么样。
|
||
4. 时间窗口过紧时,agent 应该如何自动放宽要求,避免陷入无意义重试。
|
||
|
||
---
|
||
|
||
## 2. 改造后的核心原则
|
||
|
||
### 2.1 LLM 只负责“语义认知优化”
|
||
|
||
这轮改造后的总原则是:
|
||
|
||
1. 确定性算法负责“排得下、排得合法、排得别太离谱”。
|
||
2. LLM 负责“学起来舒不舒服、搭配顺不顺、是否符合用户偏好”。
|
||
|
||
换句话说,LLM 不再负责:
|
||
|
||
1. 全局负载均衡。
|
||
2. 均匀铺开任务。
|
||
3. 追逐空窗、碎片率、最大 gap 之类的统计指标。
|
||
4. 为了把报表修漂亮而反复搬运任务。
|
||
|
||
LLM 保留的价值只有两类:
|
||
|
||
1. 学科语义理解。
|
||
2. 基于语义和偏好的认知微调。
|
||
|
||
### 2.2 主动优化从“统计修表”改成“认知微调”
|
||
|
||
改造完成后,主动优化不再问:
|
||
|
||
1. 哪天更满。
|
||
2. 哪天更空。
|
||
3. 哪门课间隔是不是又多了 1 天。
|
||
4. 空窗碎片率是不是还可以再低一点。
|
||
|
||
改造完成后,主动优化只问:
|
||
|
||
1. 这一天切换是不是太碎。
|
||
2. 两门课放在一起,认知上是不是太累。
|
||
3. 连续学习块是不是太长。
|
||
4. 当前安排是不是违背了用户偏好。
|
||
5. 在当前时间窗口下,这个问题是不是值得继续修。
|
||
|
||
---
|
||
|
||
## 3. 工具去留
|
||
|
||
## 3.1 保留
|
||
|
||
### 3.1.1 `analyze_health`
|
||
|
||
保留,且继续作为主动优化的唯一总入口。
|
||
|
||
新职责:
|
||
|
||
1. 汇总当前排程在认知节奏上的主要问题。
|
||
2. 汇总当前排程和用户偏好的冲突。
|
||
3. 判断当前是否还有足够可调整空间继续优化。
|
||
4. 判断当前是否已经可以合理收口。
|
||
|
||
### 3.1.2 `analyze_rhythm`
|
||
|
||
保留,作为 `analyze_health` 的下钻工具。
|
||
|
||
新职责:
|
||
|
||
1. 解释某一天为什么学起来别扭。
|
||
2. 解释某几个任务为什么不适合连在一起。
|
||
3. 解释当前切换多、连续块长、高强度相邻等问题落在哪些具体任务上。
|
||
|
||
### 3.1.3 现有点查工具
|
||
|
||
全部保留,尤其是:
|
||
|
||
1. `query_range`
|
||
2. `query_target_tasks`
|
||
3. `query_available_slots`
|
||
4. `get_task_info`
|
||
|
||
原因很简单:
|
||
|
||
1. `health/rhythm` 只负责指出问题和方向。
|
||
2. LLM 真正落到“挪哪个任务、往哪里挪”时,仍然必须依赖这些点查工具。
|
||
|
||
### 3.1.4 现有写工具
|
||
|
||
全部保留。
|
||
|
||
主动优化改的是“如何观察和决策”,不是“如何写入”。
|
||
|
||
但写工具在主动优化里的使用优先级要重排:
|
||
|
||
1. `slack` 高时,允许 `move` 和 `swap` 一起参与小范围微调。
|
||
2. `slack` 低时,默认优先考虑 `swap`,不优先考虑 `move`。
|
||
3. `slack` 低时若使用 `swap`,只允许交换属于不同 `task_class` 的任务。
|
||
4. 这样做的目的不是保守,而是用“跨类互换”天然保证类内顺序不被破坏。
|
||
|
||
---
|
||
|
||
## 3.2 删除
|
||
|
||
### 3.2.1 删除 `analyze_load`
|
||
|
||
原因:
|
||
|
||
1. 负载均衡是确定性算法的职责。
|
||
2. 它会强烈诱导 LLM 变成搬格子苦力。
|
||
3. 它无法体现 LLM 真正有优势的学科语义判断。
|
||
|
||
### 3.2.2 删除 `analyze_tolerance`
|
||
|
||
原因:
|
||
|
||
1. 容错本质上是粗排风格与窗口宽松度问题。
|
||
2. 它不适合作为主动优化主链路的独立分析工具。
|
||
3. 它容易继续把模型引向“留空窗/修空窗”的伪目标。
|
||
|
||
### 3.2.3 删除所有 gap/load/tolerance 驱动指标
|
||
|
||
以下指标全部退出主动优化链路:
|
||
|
||
1. `max_gap`
|
||
2. `avg_gap`
|
||
3. `gap_std_dev`
|
||
4. `fragmentation_rate`
|
||
5. `avg_gap_size`
|
||
6. `max_gap_size`
|
||
7. `days_without_buffer`
|
||
8. `utilization_rate`
|
||
9. `load_std_dev`
|
||
10. `load_range`
|
||
11. `budget_progress`
|
||
12. `days_to_end`
|
||
|
||
说明:
|
||
|
||
1. 它们可以在未来作为统计观察数据重建。
|
||
2. 但本轮改造后,它们不再参与主动优化决策,不再生成 issue,不再生成 next action。
|
||
|
||
---
|
||
|
||
## 4. `task_class` 改造后会怎样
|
||
|
||
## 4.1 新增 3 个语义字段
|
||
|
||
每个 `task_class` 新增以下字段:
|
||
|
||
1. `subject_type`
|
||
2. `difficulty_level`
|
||
3. `cognitive_intensity`
|
||
|
||
这 3 个字段只服务于一个目标:让后续排程和主动优化不再对着学科名裸猜。
|
||
|
||
## 4.2 写入时机
|
||
|
||
这 3 个字段不在排程时临时生成,而是在创建或更新 `task_class` 时就提前写好。
|
||
|
||
也就是说:
|
||
|
||
1. 用户创建任务类。
|
||
2. LLM 在任务类阶段补全这 3 个字段。
|
||
3. 任务类一旦落库,后续粗排和主动优化都直接读取。
|
||
|
||
兜底策略:
|
||
|
||
1. 老数据如果没有这 3 个字段,排程时允许临时现判一次。
|
||
2. 现判完成后,应补写回 `task_class`,避免下次重复猜。
|
||
|
||
## 4.3 这 3 个字段后续如何被使用
|
||
|
||
粗排阶段:
|
||
|
||
1. 可以作为轻量参考,但不是主驱动。
|
||
|
||
主动优化阶段:
|
||
|
||
1. `analyze_health` 直接消费这 3 个字段。
|
||
2. `analyze_rhythm` 直接消费这 3 个字段。
|
||
3. LLM 在诊断“背靠背是否太累、连续块是否太长、某种切换是否合理”时,统一以这 3 个字段为事实基础。
|
||
|
||
---
|
||
|
||
## 5. 粗排算法改造后会怎样
|
||
|
||
## 5.1 粗排负责的事
|
||
|
||
改造后,粗排要提前吃掉原本不该交给 LLM 的工作。
|
||
|
||
粗排的职责固定为:
|
||
|
||
1. 保证可行。
|
||
2. 保证顺序合法。
|
||
3. 保证基础分布别太离谱。
|
||
4. 保证不要明显堆到少数几天。
|
||
5. 保证不要把整段窗口排成毫无操作空间的死局。
|
||
|
||
## 5.2 粗排不负责的事
|
||
|
||
粗排不追求:
|
||
|
||
1. 认知体验最优。
|
||
2. 学科搭配最优。
|
||
3. 用户偏好最优。
|
||
|
||
这些交给 LLM 后续做 1 到 2 轮小范围微调。
|
||
|
||
## 5.3 粗排后的预期结果
|
||
|
||
粗排完成后,产物应该是:
|
||
|
||
1. 一个合法可执行的初稿。
|
||
2. 一个从统计上看不难看,但未必最舒服的日程。
|
||
3. 一个仍然留有少量可调整空间的底盘。
|
||
|
||
也就是说,粗排之后不需要“完美”,只需要“足够好,值得微调”。
|
||
|
||
---
|
||
|
||
## 6. `analyze_health` 改造后会怎样
|
||
|
||
## 6.1 定位
|
||
|
||
`analyze_health` 变成“认知健康总览”。
|
||
|
||
它不再是统计体检工具,而是 execute 阶段判断“要不要继续动、该往哪种认知方向动”的入口。
|
||
|
||
## 6.2 新职责
|
||
|
||
改造后它只看三件事:
|
||
|
||
1. 当前认知节奏是否别扭。
|
||
2. 当前安排是否违背用户偏好。
|
||
3. 当前窗口是否还允许继续优化。
|
||
|
||
## 6.3 新输出口径
|
||
|
||
它输出的问题应该是这种风格:
|
||
|
||
1. 某天高强度切换过多。
|
||
2. 两门高强度课背靠背。
|
||
3. 某天连续高强度学习块过长。
|
||
4. 当前安排违背“早上别排硬课”之类的用户偏好。
|
||
5. 当前可调整空间过低,剩余问题属于必要妥协。
|
||
|
||
它不再输出这种风格:
|
||
|
||
1. 哪天负载更满。
|
||
2. 最大空窗还有几天。
|
||
3. 空窗碎片率还可以再压多少。
|
||
4. 某一科是不是再均匀一点更漂亮。
|
||
|
||
## 6.4 新 `can_close` 含义
|
||
|
||
改造后,`can_close` 的语义要收紧为:
|
||
|
||
1. 当前没有明显值得继续修的认知问题。
|
||
2. 当前没有明显违背用户偏好的安排。
|
||
3. 或者虽然还存在小问题,但当前 `slack` 已低,继续优化收益不高。
|
||
|
||
也就是说,`can_close` 不再由统计指标主导,而由“是否还有高价值认知问题”主导。
|
||
|
||
---
|
||
|
||
## 7. `analyze_rhythm` 改造后会怎样
|
||
|
||
## 7.1 定位
|
||
|
||
`analyze_rhythm` 变成 `analyze_health` 的明细镜。
|
||
|
||
只有当 `health` 发现某类认知问题值得继续查时,才进一步调用 `rhythm`。
|
||
|
||
## 7.2 新职责
|
||
|
||
它要回答的不是“排得均不均”,而是:
|
||
|
||
1. 哪一天切换太碎。
|
||
2. 哪一段连续块太长。
|
||
3. 哪几个任务挨在一起会特别累。
|
||
4. 哪些切换虽然换科了,但其实仍属于同一种脑力模式。
|
||
|
||
## 7.3 新输出风格
|
||
|
||
它的输出重点应围绕:
|
||
|
||
1. 日内切换次数。
|
||
2. 连续学习块结构。
|
||
3. 高强度相邻关系。
|
||
4. 同类/异类学科切换关系。
|
||
5. 某一天内部的认知压力分布。
|
||
|
||
它不再承担:
|
||
|
||
1. 跨天 gap 追踪。
|
||
2. 学科分散度统计优化。
|
||
3. 预算推进告警。
|
||
|
||
---
|
||
|
||
## 8. 新增 `slack` 后会怎样
|
||
|
||
## 8.1 为什么必须加 `slack`
|
||
|
||
有些用户给的时间窗口非常紧。
|
||
|
||
这时“高强度背靠背”不一定是错误,而可能是当前窗口下的必要代价。
|
||
|
||
如果没有 `slack` 概念,agent 会误以为:
|
||
|
||
1. 这是可修的问题。
|
||
2. 我应该继续搬。
|
||
3. 继续搬总能更好。
|
||
|
||
然后就进入无意义重试。
|
||
|
||
## 8.2 `slack` 的职责
|
||
|
||
`slack` 不负责决定“舒服不舒服”,只负责决定“还有没有优化余地”。
|
||
|
||
也就是说,它是健康分析里的第二层判断:
|
||
|
||
1. 有问题,不代表值得继续修。
|
||
2. 值得继续修,还要看当前有没有空间修。
|
||
|
||
## 8.3 `slack` 接入后的行为
|
||
|
||
改造后:
|
||
|
||
1. 若 `slack` 高,按正常标准检查认知问题。
|
||
2. 若 `slack` 中,允许小问题存在,但仍可做 1 次小范围微调。
|
||
3. 若 `slack` 低,自动放宽要求,允许必要的背靠背、较长连续块、略多切换。
|
||
4. 若 `slack` 低但仍存在明显可改善的认知问题,优先尝试一次低成本 `swap`,而不是优先尝试 `move`。
|
||
5. 这个 `swap` 必须限定为“只交换不同 `task_class` 的任务”,从而避免打乱任一类内部顺序。
|
||
6. 若一次 `swap` 后没有明显改善,则倾向收口,不进入连续搬运。
|
||
|
||
## 8.4 `slack` 带来的收口变化
|
||
|
||
改造后,agent 不会再因为下面这类场景反复挣扎:
|
||
|
||
1. 时间太紧,不得不连着上两门硬课。
|
||
2. 可动任务几乎都被前驱后继夹死。
|
||
3. 当前再动只会拆东墙补西墙。
|
||
|
||
这时 `analyze_health` 应直接给出结论:
|
||
|
||
1. 当前仍有认知妥协点。
|
||
2. 但由于可调整空间有限,已属于合理结果。
|
||
3. 可以收口,或只在用户明确要求时继续深挖。
|
||
|
||
---
|
||
|
||
## 9. 改造后的首次排程完整链路
|
||
|
||
这是你最关心的部分:改完以后,首次排程到底怎么跑。
|
||
|
||
## 9.1 第 0 步:任务类先带语义字段
|
||
|
||
在真正排程前,相关 `task_class` 已经具备:
|
||
|
||
1. `subject_type`
|
||
2. `difficulty_level`
|
||
3. `cognitive_intensity`
|
||
|
||
如果缺失,先补齐再进入完整主动优化链路。
|
||
|
||
## 9.2 第 1 步:确定性粗排先出底盘
|
||
|
||
系统先用确定性算法完成一版粗排。
|
||
|
||
这一步的结果要求是:
|
||
|
||
1. 可排下。
|
||
2. 顺序合法。
|
||
3. 分布不难看。
|
||
4. 还留有一点可调整空间。
|
||
|
||
## 9.3 第 2 步:进入 `analyze_health`
|
||
|
||
粗排完成后,不再先看 load,不再先看 gap,而是直接进入 `analyze_health`。
|
||
|
||
这一步会判断:
|
||
|
||
1. 当前有哪些高价值认知问题。
|
||
2. 当前是否存在用户偏好冲突。
|
||
3. 当前 `slack` 高不高。
|
||
4. 当前是否值得继续动。
|
||
|
||
## 9.4 第 3 步:必要时下钻 `analyze_rhythm`
|
||
|
||
只有当 `health` 发现值得修的问题时,LLM 才进一步调用 `analyze_rhythm`。
|
||
|
||
这一步的作用是:
|
||
|
||
1. 把问题定位到某一天、某几个任务、某种相邻关系。
|
||
2. 给 LLM 读写工具调用提供更明确的认知方向。
|
||
|
||
## 9.5 第 4 步:LLM 用旧点查工具锁定目标
|
||
|
||
接下来 LLM 不会根据 `health/rhythm` 直接拍脑袋写入。
|
||
|
||
它仍然要走:
|
||
|
||
1. `query_range`
|
||
2. `query_target_tasks`
|
||
3. `query_available_slots`
|
||
4. `get_task_info`
|
||
|
||
也就是说,新的分析工具负责“告诉它为什么动、朝哪动”,旧点查工具负责“告诉它具体怎么动”。
|
||
|
||
当 `slack` 低时,这一步的目标还会进一步收窄为:
|
||
|
||
1. 先找有没有值得做的一次性交换机会。
|
||
2. 优先找跨 `task_class` 的互换对象。
|
||
3. 只有在没有合适 `swap`,且单步 `move` 的收益明显高于风险时,才考虑 `move`。
|
||
|
||
## 9.6 第 5 步:LLM 做 1 到 2 次小范围微调
|
||
|
||
改造后,主动优化默认只做小范围微调,不做全盘翻修。
|
||
|
||
默认目标是:
|
||
|
||
1. 消除最明显的认知别扭点。
|
||
2. 避免新问题比旧问题更重。
|
||
3. 不为了报表漂亮而继续搬运。
|
||
|
||
这里再补一条强规则:
|
||
|
||
1. `slack` 高时,可以正常比较 `move` 与 `swap`。
|
||
2. `slack` 低时,优先考虑一次跨 `task_class` 的 `swap` 来调整不同科目间的相对顺序。
|
||
3. `slack` 低时,不鼓励进入多步 `move` 链路。
|
||
4. `swap` 的价值在于:它更像“整理现有坑位里的学科顺序”,而不是“重新开一轮搬家”。
|
||
|
||
## 9.7 第 6 步:再做一次 `analyze_health`
|
||
|
||
写操作后再次进入 `analyze_health`。
|
||
|
||
这一步不是看统计报表有没有更均匀,而是看:
|
||
|
||
1. 主要认知问题是否缓解。
|
||
2. 用户偏好冲突是否减少。
|
||
3. 当前 `slack` 是否已不支持继续动。
|
||
4. 是否可以收口。
|
||
|
||
## 9.8 第 7 步:合理收口
|
||
|
||
最终存在三种收口方式:
|
||
|
||
1. 问题已明显改善,可以正常收口。
|
||
2. 还存在小问题,但 `slack` 过低,按“合理妥协”收口。
|
||
3. 用户明确还不满意,再继续下一轮。
|
||
|
||
其中第 2 种收口还要补一层判断:
|
||
|
||
1. 不是一看到 `slack` 低就立刻停手。
|
||
2. 而是先看是否存在一次低成本、跨 `task_class` 的 `swap` 机会。
|
||
3. 若存在且收益明确,可先做这一次整理式调整。
|
||
4. 若不存在,或做完后仍无明显改善,再按“合理妥协”收口。
|
||
|
||
---
|
||
|
||
## 10. 改造后的局部调整链路
|
||
|
||
改造后,不是所有用户请求都要走完整主动优化链路。
|
||
|
||
## 10.1 默认仍走旧链路
|
||
|
||
用户如果只是说:
|
||
|
||
1. 把这个任务挪一下。
|
||
2. 这节课换一天。
|
||
3. 给我把这个排到周末。
|
||
|
||
这类请求默认继续走旧点查 + 旧写工具链路。
|
||
|
||
原因:
|
||
|
||
1. 这是局部执行问题。
|
||
2. 不值得每次都拉起 `health/rhythm` 做一轮体检。
|
||
|
||
## 10.2 只有两类情况再启动主动优化分析
|
||
|
||
1. 首次排程。
|
||
2. 用户明确表达认知感受或结构性问题。
|
||
|
||
例如:
|
||
|
||
1. “切换太多了,心累。”
|
||
2. “这些硬课连着看着就难受。”
|
||
3. “帮我整体调顺一点。”
|
||
|
||
---
|
||
|
||
## 11. 改造后的最终表现
|
||
|
||
改完以后,这条链路的预期表现应该是:
|
||
|
||
1. 用户创建任务类时,学科语义先被沉淀下来。
|
||
2. 粗排算法先给出一个合法且分布不难看的初稿。
|
||
3. 主动优化不再围着负载、空窗、gap 打转。
|
||
4. `analyze_health` 只关心认知体验、偏好冲突、可调整空间。
|
||
5. `analyze_rhythm` 只负责解释具体哪段学起来别扭。
|
||
6. LLM 只做 1 到 2 次高价值认知微调,不再做长链路苦力搬运。
|
||
7. 时间窗口很紧时,agent 会承认“这是必要妥协”,而不是继续死磕。
|
||
|
||
一句话总结:
|
||
|
||
改造后,这套链路不再追求“把报表修漂亮”,而是追求“把这份日程修得更像人能学下去”。
|
||
|
||
---
|
||
|
||
## 12. 实施顺序
|
||
|
||
按以下顺序落地:
|
||
|
||
1. 先改 `task_class`,补 3 个语义字段及写入链路。
|
||
2. 再删 `analyze_load`、`analyze_tolerance` 及相关主链路接入。
|
||
3. 再重写 `analyze_health` 的职责、指标和收口口径。
|
||
4. 再重写 `analyze_rhythm` 的职责和输出结构。
|
||
5. 再补 `slack` 及其自动放宽规则。
|
||
6. 最后改 execute prompt 和链路收口逻辑。
|
||
|
||
这样做的原因是:
|
||
|
||
1. 先有语义数据,分析工具才不至于空转。
|
||
2. 先把旧统计驱动砍掉,execute 才不会继续被错误方向牵着跑。
|
||
3. 最后再调 prompt,才不会变成给旧结构打补丁。
|
||
|
||
---
|
||
|
||
## 13. 本轮验收口径
|
||
|
||
如果改造成功,至少应满足以下表现:
|
||
|
||
1. 首次排程时,agent 不再为了负载均匀或空窗漂亮反复搬任务。
|
||
2. 日志中的主动优化理由,主要变成认知体验和偏好,而不是统计指标。
|
||
3. 当时间很紧时,agent 会主动接受必要妥协,不再死循环。
|
||
4. 当用户只是提局部挪动需求时,不会动辄拉起全局体检。
|
||
5. 主动优化完成后的结果,解释口径更像“为什么这样学更顺”,而不是“哪些数字变好看了”。
|