✨ feat(schedulerefine): 新增 refine 子路由,优先执行复合操作,失败后降级至禁复合 ReAct 兜底 ReAct 升级 - ♻️ 将原有链路升级为真正的 ReAct 执行模式,进一步增强整体调度过程的可靠性 Refine 子路由 - 🧭 在 refine 主链路中新增 `route` 节点,整体流程调整为 `contract -> plan -> slice -> route -> react -> hard_check -> summary` - ⚡ 当 `route` 命中全局复合目标时,优先尝试一次调用 `SpreadEven` / `MinContextSwitch`,失败后最多重试 2 次 - 🔀 `route` 成功后直接跳过 `ReAct`;若执行失败,则自动切换至 `fallback` 模式 - 🛡️ 在 `fallback` 模式下增加后端硬约束:禁用 `SpreadEven` / `MinContextSwitch` / `BatchMove`,仅允许使用 `Move` / `Swap` 逐任务处理 - 🧠 在 `ReAct` 的 prompt 与上下文中新增 `COMPOSITE_TOOLS_ALLOWED`,显式告知当前是否允许使用复合工具 - 🧩 扩展状态字段以承载路由与降级状态:`CompositeRetryMax` / `DisableCompositeTools` / `CompositeRouteTried` / `CompositeRouteSucceeded` - 👀 增加 `route` 相关阶段日志,便于排查命中、重试、收口与降级原因 修复 - 🐛 修复 JWT Token 过期时间未按 `config.yaml` 配置生效的问题 备注 - 🚧 当前 ReAct 逐步微排链路已趋于稳定,但两个复合操作函数仍未恢复可用,后续将继续排查
96 lines
3.4 KiB
Go
96 lines
3.4 KiB
Go
package logic
|
||
|
||
import (
|
||
"sort"
|
||
"testing"
|
||
)
|
||
|
||
func TestPlanEvenSpreadMovesPrefersLowerLoadDay(t *testing.T) {
|
||
tasks := []RefineTaskCandidate{
|
||
{TaskItemID: 101, Week: 16, DayOfWeek: 1, SectionFrom: 1, SectionTo: 2, OriginRank: 1},
|
||
{TaskItemID: 102, Week: 16, DayOfWeek: 1, SectionFrom: 3, SectionTo: 4, OriginRank: 2},
|
||
}
|
||
slots := []RefineSlotCandidate{
|
||
{Week: 12, DayOfWeek: 1, SectionFrom: 1, SectionTo: 2},
|
||
{Week: 12, DayOfWeek: 2, SectionFrom: 1, SectionTo: 2},
|
||
{Week: 12, DayOfWeek: 3, SectionFrom: 1, SectionTo: 2},
|
||
}
|
||
moves, err := PlanEvenSpreadMoves(tasks, slots, RefineCompositePlanOptions{
|
||
ExistingDayLoad: map[string]int{
|
||
composeDayKey(12, 1): 5,
|
||
composeDayKey(12, 2): 1,
|
||
composeDayKey(12, 3): 0,
|
||
},
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("PlanEvenSpreadMoves 返回错误: %v", err)
|
||
}
|
||
if len(moves) != 2 {
|
||
t.Fatalf("期望移动 2 条,实际=%d", len(moves))
|
||
}
|
||
|
||
// 1. 低负载日(周三)应优先被填充;
|
||
// 2. 第二条应落在次低负载日(周二),而不是高负载日(周一)。
|
||
weekDayByID := make(map[int][2]int, len(moves))
|
||
for _, move := range moves {
|
||
weekDayByID[move.TaskItemID] = [2]int{move.ToWeek, move.ToDay}
|
||
}
|
||
if got := weekDayByID[101]; got != [2]int{12, 3} {
|
||
t.Fatalf("任务101应优先落到 W12D3,实际=%v", got)
|
||
}
|
||
if got := weekDayByID[102]; got != [2]int{12, 2} {
|
||
t.Fatalf("任务102应落到 W12D2,实际=%v", got)
|
||
}
|
||
}
|
||
|
||
func TestPlanMinContextSwitchMovesGroupsSameContext(t *testing.T) {
|
||
tasks := []RefineTaskCandidate{
|
||
{TaskItemID: 201, Week: 16, DayOfWeek: 1, SectionFrom: 1, SectionTo: 2, ContextTag: "数学", OriginRank: 1},
|
||
{TaskItemID: 202, Week: 16, DayOfWeek: 1, SectionFrom: 3, SectionTo: 4, ContextTag: "算法", OriginRank: 2},
|
||
{TaskItemID: 203, Week: 16, DayOfWeek: 1, SectionFrom: 5, SectionTo: 6, ContextTag: "数学", OriginRank: 3},
|
||
}
|
||
slots := []RefineSlotCandidate{
|
||
{Week: 12, DayOfWeek: 1, SectionFrom: 1, SectionTo: 2},
|
||
{Week: 12, DayOfWeek: 1, SectionFrom: 3, SectionTo: 4},
|
||
{Week: 12, DayOfWeek: 1, SectionFrom: 5, SectionTo: 6},
|
||
}
|
||
moves, err := PlanMinContextSwitchMoves(tasks, slots, RefineCompositePlanOptions{})
|
||
if err != nil {
|
||
t.Fatalf("PlanMinContextSwitchMoves 返回错误: %v", err)
|
||
}
|
||
if len(moves) != 3 {
|
||
t.Fatalf("期望移动 3 条,实际=%d", len(moves))
|
||
}
|
||
|
||
// 1. “数学”有 2 条,分组后应先连续落在最早两个坑位;
|
||
// 2. 因此 201 与 203 对应的目标节次应是 1-2 与 3-4(顺序由 origin_rank 决定)。
|
||
sort.SliceStable(moves, func(i, j int) bool {
|
||
if moves[i].ToWeek != moves[j].ToWeek {
|
||
return moves[i].ToWeek < moves[j].ToWeek
|
||
}
|
||
if moves[i].ToDay != moves[j].ToDay {
|
||
return moves[i].ToDay < moves[j].ToDay
|
||
}
|
||
return moves[i].ToSectionFrom < moves[j].ToSectionFrom
|
||
})
|
||
if moves[0].TaskItemID != 201 || moves[1].TaskItemID != 203 {
|
||
t.Fatalf("期望前两个坑位由同上下文任务占据,实际=%+v", moves)
|
||
}
|
||
if moves[2].TaskItemID != 202 {
|
||
t.Fatalf("期望最后一个坑位为算法任务,实际=%+v", moves[2])
|
||
}
|
||
}
|
||
|
||
func TestPlanEvenSpreadMovesReturnsErrorWhenSpanNotMatched(t *testing.T) {
|
||
tasks := []RefineTaskCandidate{
|
||
{TaskItemID: 301, Week: 16, DayOfWeek: 1, SectionFrom: 1, SectionTo: 3, OriginRank: 1}, // span=3
|
||
}
|
||
slots := []RefineSlotCandidate{
|
||
{Week: 12, DayOfWeek: 1, SectionFrom: 1, SectionTo: 2}, // span=2
|
||
}
|
||
_, err := PlanEvenSpreadMoves(tasks, slots, RefineCompositePlanOptions{})
|
||
if err == nil {
|
||
t.Fatalf("期望 span 不匹配时报错,实际 err=nil")
|
||
}
|
||
}
|