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 逐步微排链路已趋于稳定,但两个复合操作函数仍未恢复可用,后续将继续排查
This commit is contained in:
@@ -9,43 +9,48 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// timezoneName 固定排程链路使用的业务时区,避免容器默认时区导致“明天/今晚”偏移。
|
||||
// 固定业务时区,避免“今天/明天”在容器默认时区下偏移。
|
||||
timezoneName = "Asia/Shanghai"
|
||||
// datetimeLayout 统一使用分钟级时间文本,方便模型理解与日志比对。
|
||||
// 统一分钟级时间文本格式。
|
||||
datetimeLayout = "2006-01-02 15:04"
|
||||
// defaultPlanMax 是 Planner 最大调用次数(包含首次规划 + 重规划)。
|
||||
defaultPlanMax = 2
|
||||
// defaultExecuteMax 是执行阶段最大工具动作轮次。
|
||||
defaultExecuteMax = 16
|
||||
// defaultReplanMax 是执行阶段允许触发的重规划次数上限。
|
||||
defaultReplanMax = 2
|
||||
// defaultRepairReserve 表示为“终审修复”保留的最小动作预算。
|
||||
defaultRepairReserve = 1
|
||||
|
||||
// 预算默认值。
|
||||
defaultPlanMax = 2
|
||||
defaultExecuteMax = 24
|
||||
defaultPerTaskBudget = 4
|
||||
defaultReplanMax = 2
|
||||
defaultCompositeRetry = 2
|
||||
defaultRepairReserve = 1
|
||||
)
|
||||
|
||||
// RefineContract 表示“微调意图契约”。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责承载“本轮微调到底要满足什么”的结构化目标;
|
||||
// 2. 负责给后续 ReAct 动作与终审硬校验提供统一语义;
|
||||
// 3. 不负责实际排程修改动作执行(动作由工具层负责)。
|
||||
// RefineContract 表示本轮微调意图契约。
|
||||
type RefineContract struct {
|
||||
Intent string `json:"intent"`
|
||||
Strategy string `json:"strategy"`
|
||||
HardRequirements []string `json:"hard_requirements"`
|
||||
KeepRelativeOrder bool `json:"keep_relative_order"`
|
||||
OrderScope string `json:"order_scope"`
|
||||
Reason string `json:"reason"`
|
||||
Intent string `json:"intent"`
|
||||
Strategy string `json:"strategy"`
|
||||
HardRequirements []string `json:"hard_requirements"`
|
||||
HardAssertions []RefineAssertion `json:"hard_assertions,omitempty"`
|
||||
KeepRelativeOrder bool `json:"keep_relative_order"`
|
||||
OrderScope string `json:"order_scope"`
|
||||
}
|
||||
|
||||
// HardCheckReport 表示“终审硬校验报告”。
|
||||
// RefineAssertion 表示可由后端直接判定的结构化硬断言。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 记录规则层(物理冲突)是否通过;
|
||||
// 2. 记录语义层(是否满足用户要求)是否通过;
|
||||
// 3. 记录顺序层(是否保持相对顺序)是否通过;
|
||||
// 4. 记录失败原因与修复尝试信息,便于后续持续优化 prompt;
|
||||
// 5. 不负责直接决定是否落库(落库决策仍由服务层控制)。
|
||||
// 字段说明:
|
||||
// 1. Metric:断言指标名,例如 source_move_ratio_percent;
|
||||
// 2. Operator:比较操作符,支持 == / <= / >= / between;
|
||||
// 3. Value/Min/Max:阈值;
|
||||
// 4. Week/TargetWeek:可选周次上下文。
|
||||
type RefineAssertion struct {
|
||||
Metric string `json:"metric"`
|
||||
Operator string `json:"operator"`
|
||||
Value int `json:"value,omitempty"`
|
||||
Min int `json:"min,omitempty"`
|
||||
Max int `json:"max,omitempty"`
|
||||
Week int `json:"week,omitempty"`
|
||||
TargetWeek int `json:"target_week,omitempty"`
|
||||
}
|
||||
|
||||
// HardCheckReport 表示终审硬校验结果。
|
||||
type HardCheckReport struct {
|
||||
PhysicsPassed bool `json:"physics_passed"`
|
||||
PhysicsIssues []string `json:"physics_issues,omitempty"`
|
||||
@@ -60,17 +65,11 @@ type HardCheckReport struct {
|
||||
RepairTried bool `json:"repair_tried"`
|
||||
}
|
||||
|
||||
// ReactRoundObservation 用于沉淀“每轮 ReAct 的可见观测信息”。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责记录每轮“计划 -> 动作 -> 观察 -> 反思”的关键信息;
|
||||
// 2. 既用于 SSE 透传,也用于下一轮 prompt 的上下文回灌;
|
||||
// 3. 不承担排程真实数据存储职责(真实排程仍在 HybridEntries)。
|
||||
// ReactRoundObservation 记录每轮 ReAct 的关键观察。
|
||||
type ReactRoundObservation struct {
|
||||
Round int `json:"round"`
|
||||
GoalCheck string `json:"goal_check,omitempty"`
|
||||
Decision string `json:"decision,omitempty"`
|
||||
MissingInfo []string `json:"missing_info,omitempty"`
|
||||
ToolName string `json:"tool_name,omitempty"`
|
||||
ToolParams map[string]any `json:"tool_params,omitempty"`
|
||||
ToolSuccess bool `json:"tool_success"`
|
||||
@@ -79,27 +78,47 @@ type ReactRoundObservation struct {
|
||||
Reflect string `json:"reflect,omitempty"`
|
||||
}
|
||||
|
||||
// PlannerPlan 表示“本轮执行前的结构化计划”。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责记录模型当前建议的执行路径(先查什么、再做什么);
|
||||
// 2. 负责在失败重规划后替换为新版本,供执行器下一轮参考;
|
||||
// 3. 不直接约束工具执行结果(执行合法性仍由工具层硬校验负责)。
|
||||
// PlannerPlan 表示 Planner 生成的阶段执行计划。
|
||||
type PlannerPlan struct {
|
||||
Summary string `json:"summary"`
|
||||
Steps []string `json:"steps,omitempty"`
|
||||
SuccessSignals []string `json:"success_signals,omitempty"`
|
||||
Fallback string `json:"fallback,omitempty"`
|
||||
Summary string `json:"summary"`
|
||||
Steps []string `json:"steps,omitempty"`
|
||||
}
|
||||
|
||||
// ScheduleRefineState 是“连续微调图”的统一状态容器。
|
||||
// RefineSlicePlan 表示切片节点输出。
|
||||
type RefineSlicePlan struct {
|
||||
WeekFilter []int `json:"week_filter,omitempty"`
|
||||
SourceDays []int `json:"source_days,omitempty"`
|
||||
TargetDays []int `json:"target_days,omitempty"`
|
||||
ExcludeSections []int `json:"exclude_sections,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// RefineObjective 表示“可执行且可校验”的目标约束。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责在图节点间传递“上一版排程快照 + 本轮用户微调请求 + 动作日志 + 终审报告”;
|
||||
// 2. 负责承载最终对用户可见的 summary 与结构化 candidate_plans;
|
||||
// 3. 不负责 Redis/MySQL 读写(持久化由 service 层负责)。
|
||||
// 设计说明:
|
||||
// 1. 由 contract/slice 从自然语言编译得到;
|
||||
// 2. 执行阶段(done 收口)与终审阶段(hard_check)共用同一份约束;
|
||||
// 3. 避免“执行逻辑与终审逻辑各说各话”。
|
||||
type RefineObjective struct {
|
||||
Mode string `json:"mode,omitempty"` // none | move_all | move_ratio
|
||||
|
||||
SourceWeeks []int `json:"source_weeks,omitempty"`
|
||||
TargetWeeks []int `json:"target_weeks,omitempty"`
|
||||
SourceDays []int `json:"source_days,omitempty"`
|
||||
TargetDays []int `json:"target_days,omitempty"`
|
||||
|
||||
ExcludeSections []int `json:"exclude_sections,omitempty"`
|
||||
|
||||
BaselineSourceTaskCount int `json:"baseline_source_task_count,omitempty"`
|
||||
RequiredMoveMin int `json:"required_move_min,omitempty"`
|
||||
RequiredMoveMax int `json:"required_move_max,omitempty"`
|
||||
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// ScheduleRefineState 是连续微调图的统一状态。
|
||||
type ScheduleRefineState struct {
|
||||
// 1. 基础请求上下文。
|
||||
// 1) 请求上下文
|
||||
TraceID string
|
||||
UserID int
|
||||
ConversationID string
|
||||
@@ -107,59 +126,85 @@ type ScheduleRefineState struct {
|
||||
RequestNow time.Time
|
||||
RequestNowText string
|
||||
|
||||
// 2. 继承自上一版预览快照的可调度数据。
|
||||
TaskClassIDs []int
|
||||
Constraints []string
|
||||
HybridEntries []model.HybridScheduleEntry
|
||||
AllocatedItems []model.TaskClassItem
|
||||
CandidatePlans []model.UserWeekSchedule
|
||||
// 2) 继承自预览快照的数据
|
||||
TaskClassIDs []int
|
||||
Constraints []string
|
||||
// InitialHybridEntries 保存本轮微调开始前的基线,用于终审做“前后对比”。
|
||||
// 说明:
|
||||
// 1. 只读语义,不参与执行期改写;
|
||||
// 2. 终审可基于它判断“来源任务是否真正迁移到目标区域”。
|
||||
InitialHybridEntries []model.HybridScheduleEntry
|
||||
HybridEntries []model.HybridScheduleEntry
|
||||
AllocatedItems []model.TaskClassItem
|
||||
CandidatePlans []model.UserWeekSchedule
|
||||
|
||||
// 3. 本轮微调过程状态。
|
||||
// 3) 本轮执行状态
|
||||
UserIntent string
|
||||
Contract RefineContract
|
||||
|
||||
PlanMax int
|
||||
ExecuteMax int
|
||||
ReplanMax int
|
||||
PlanMax int
|
||||
PerTaskBudget int
|
||||
ExecuteMax int
|
||||
ReplanMax int
|
||||
// CompositeRetryMax 表示复合路由失败后的最大重试次数(不含首次尝试)。
|
||||
CompositeRetryMax int
|
||||
|
||||
PlanUsed int
|
||||
ReplanUsed int
|
||||
|
||||
// MaxRounds 保留“总预算”语义,供终审修复节点继续复用:
|
||||
// MaxRounds = ExecuteMax + RepairReserve
|
||||
MaxRounds int
|
||||
RepairReserve int
|
||||
RoundUsed int
|
||||
ActionLogs []string
|
||||
|
||||
// ConsecutiveFailures 记录执行阶段连续失败次数,用于触发“失败兜底 thinking”。
|
||||
ConsecutiveFailures int
|
||||
// ThinkingBoostArmed 表示“当前失败串已触发过一次 thinking 兜底”。
|
||||
ThinkingBoostArmed bool
|
||||
ThinkingBoostArmed bool
|
||||
ObservationHistory []ReactRoundObservation
|
||||
|
||||
CurrentPlan PlannerPlan
|
||||
BatchMoveAllowed bool
|
||||
// DisableCompositeTools=true 表示已进入 ReAct 兜底,禁止再调用复合工具。
|
||||
DisableCompositeTools bool
|
||||
// CompositeRouteTried 标记是否尝试过“复合批处理路由”。
|
||||
CompositeRouteTried bool
|
||||
// CompositeRouteSucceeded 标记复合批处理路由是否成功收口。
|
||||
CompositeRouteSucceeded bool
|
||||
TaskActionUsed map[int]int
|
||||
EntriesVersion int
|
||||
SeenSlotQueries map[string]struct{}
|
||||
|
||||
// RequiredCompositeTool 表示本轮策略要求“必须至少成功一次”的复合工具。
|
||||
// 取值约定:"" | "SpreadEven" | "MinContextSwitch"。
|
||||
RequiredCompositeTool string
|
||||
// CompositeToolCalled 记录复合工具是否至少调用过一次(不区分成功失败)。
|
||||
CompositeToolCalled map[string]bool
|
||||
// CompositeToolSuccess 记录复合工具是否至少成功过一次。
|
||||
CompositeToolSuccess map[string]bool
|
||||
|
||||
SlicePlan RefineSlicePlan
|
||||
Objective RefineObjective
|
||||
WorksetTaskIDs []int
|
||||
WorksetCursor int
|
||||
CurrentTaskID int
|
||||
CurrentTaskAttempt int
|
||||
|
||||
LastToolResult string
|
||||
ObservationHistory []ReactRoundObservation
|
||||
CurrentPlan PlannerPlan
|
||||
LastPostStrategy string
|
||||
// LastFailedCallSignature 记录“上一轮失败动作签名(tool+params)”。用于后端硬拦截重复失败动作。
|
||||
LastFailedCallSignature string
|
||||
OriginOrderMap map[int]int
|
||||
|
||||
// 4. 终审校验状态。
|
||||
// 4) 终审状态
|
||||
HardCheck HardCheckReport
|
||||
|
||||
// 5. 最终输出。
|
||||
// 5) 最终输出
|
||||
FinalSummary string
|
||||
Completed bool
|
||||
}
|
||||
|
||||
// NewScheduleRefineState 基于“上一版排程预览快照”初始化连续微调状态。
|
||||
// NewScheduleRefineState 基于上一版预览快照初始化状态。
|
||||
//
|
||||
// 步骤化说明:
|
||||
// 1. 先初始化请求基础字段与默认预算,保证图内每个节点都能读取到稳定上下文。
|
||||
// 2. 再把 preview 的核心排程数据做深拷贝注入,避免跨请求引用污染。
|
||||
// 3. 最后构建 origin_order_map,作为“保持相对顺序”硬约束的判定基线。
|
||||
// 4. 若 preview 为空,仍返回可用 state,由上层决定是报错还是降级。
|
||||
// 职责边界:
|
||||
// 1. 负责初始化预算、上下文字段与可变状态容器;
|
||||
// 2. 负责拷贝 preview 数据,避免跨请求引用污染;
|
||||
// 3. 不负责做任何调度动作。
|
||||
func NewScheduleRefineState(traceID string, userID int, conversationID string, userMessage string, preview *model.SchedulePlanPreviewCache) *ScheduleRefineState {
|
||||
now := nowToMinute()
|
||||
st := &ScheduleRefineState{
|
||||
@@ -170,22 +215,38 @@ func NewScheduleRefineState(traceID string, userID int, conversationID string, u
|
||||
RequestNow: now,
|
||||
RequestNowText: now.In(loadLocation()).Format(datetimeLayout),
|
||||
PlanMax: defaultPlanMax,
|
||||
PerTaskBudget: defaultPerTaskBudget,
|
||||
ExecuteMax: defaultExecuteMax,
|
||||
ReplanMax: defaultReplanMax,
|
||||
CompositeRetryMax: defaultCompositeRetry,
|
||||
RepairReserve: defaultRepairReserve,
|
||||
MaxRounds: defaultExecuteMax + defaultRepairReserve,
|
||||
ActionLogs: make([]string, 0, 24),
|
||||
ObservationHistory: make([]ReactRoundObservation, 0, 16),
|
||||
ActionLogs: make([]string, 0, 32),
|
||||
ObservationHistory: make([]ReactRoundObservation, 0, 24),
|
||||
TaskActionUsed: make(map[int]int),
|
||||
SeenSlotQueries: make(map[string]struct{}),
|
||||
OriginOrderMap: make(map[int]int),
|
||||
CompositeToolCalled: map[string]bool{
|
||||
"SpreadEven": false,
|
||||
"MinContextSwitch": false,
|
||||
},
|
||||
CompositeToolSuccess: map[string]bool{
|
||||
"SpreadEven": false,
|
||||
"MinContextSwitch": false,
|
||||
},
|
||||
CurrentPlan: PlannerPlan{
|
||||
Summary: "初始化完成,等待 Planner 生成执行计划。",
|
||||
},
|
||||
SlicePlan: RefineSlicePlan{
|
||||
Reason: "尚未切片",
|
||||
},
|
||||
}
|
||||
if preview == nil {
|
||||
return st
|
||||
}
|
||||
|
||||
st.TaskClassIDs = append([]int(nil), preview.TaskClassIDs...)
|
||||
st.InitialHybridEntries = cloneHybridEntries(preview.HybridEntries)
|
||||
st.HybridEntries = cloneHybridEntries(preview.HybridEntries)
|
||||
st.AllocatedItems = cloneTaskClassItems(preview.AllocatedItems)
|
||||
st.CandidatePlans = cloneWeekSchedules(preview.CandidatePlans)
|
||||
@@ -193,7 +254,6 @@ func NewScheduleRefineState(traceID string, userID int, conversationID string, u
|
||||
return st
|
||||
}
|
||||
|
||||
// loadLocation 返回排程链路使用的业务时区。
|
||||
func loadLocation() *time.Location {
|
||||
loc, err := time.LoadLocation(timezoneName)
|
||||
if err != nil {
|
||||
@@ -202,12 +262,10 @@ func loadLocation() *time.Location {
|
||||
return loc
|
||||
}
|
||||
|
||||
// nowToMinute 返回当前时刻并截断到分钟级,降低 prompt 中秒级噪声。
|
||||
func nowToMinute() time.Time {
|
||||
return time.Now().In(loadLocation()).Truncate(time.Minute)
|
||||
}
|
||||
|
||||
// cloneHybridEntries 深拷贝混合日程切片。
|
||||
func cloneHybridEntries(src []model.HybridScheduleEntry) []model.HybridScheduleEntry {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
@@ -217,7 +275,6 @@ func cloneHybridEntries(src []model.HybridScheduleEntry) []model.HybridScheduleE
|
||||
return dst
|
||||
}
|
||||
|
||||
// cloneTaskClassItems 深拷贝任务块切片(包含指针字段)。
|
||||
func cloneTaskClassItems(src []model.TaskClassItem) []model.TaskClassItem {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
@@ -250,7 +307,6 @@ func cloneTaskClassItems(src []model.TaskClassItem) []model.TaskClassItem {
|
||||
return dst
|
||||
}
|
||||
|
||||
// cloneWeekSchedules 深拷贝周视图切片。
|
||||
func cloneWeekSchedules(src []model.UserWeekSchedule) []model.UserWeekSchedule {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
@@ -267,12 +323,7 @@ func cloneWeekSchedules(src []model.UserWeekSchedule) []model.UserWeekSchedule {
|
||||
return dst
|
||||
}
|
||||
|
||||
// buildOriginOrderMap 从当前 suggested 排程位置构建“初始相对顺序映射”。
|
||||
//
|
||||
// 步骤化说明:
|
||||
// 1. 先筛出所有可调的 suggested 任务;
|
||||
// 2. 按 week/day/section/task_item_id 稳定排序,得到“时间先后基线”;
|
||||
// 3. 把 task_item_id -> rank 写入 map,后续 Move/Swap 都基于该 rank 做顺序硬校验。
|
||||
// buildOriginOrderMap 构建 suggested 任务的初始顺序基线(task_item_id -> rank)。
|
||||
func buildOriginOrderMap(entries []model.HybridScheduleEntry) map[int]int {
|
||||
orderMap := make(map[int]int)
|
||||
if len(entries) == 0 {
|
||||
@@ -280,7 +331,7 @@ func buildOriginOrderMap(entries []model.HybridScheduleEntry) map[int]int {
|
||||
}
|
||||
suggested := make([]model.HybridScheduleEntry, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.Status == "suggested" && entry.TaskItemID > 0 {
|
||||
if isMovableSuggestedTask(entry) {
|
||||
suggested = append(suggested, entry)
|
||||
}
|
||||
}
|
||||
@@ -301,8 +352,8 @@ func buildOriginOrderMap(entries []model.HybridScheduleEntry) map[int]int {
|
||||
}
|
||||
return left.TaskItemID < right.TaskItemID
|
||||
})
|
||||
for idx, entry := range suggested {
|
||||
orderMap[entry.TaskItemID] = idx + 1
|
||||
for i, entry := range suggested {
|
||||
orderMap[entry.TaskItemID] = i + 1
|
||||
}
|
||||
return orderMap
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user