Files
smartmate/backend/logic/refine_compound_ops_test.go
Losita e6941f98f2 Version: 0.7.4.dev.260323
 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 逐步微排链路已趋于稳定,但两个复合操作函数仍未恢复可用,后续将继续排查
2026-03-23 23:14:19 +08:00

96 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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")
}
}