Version: 0.9.9.dev.260408

后端:
1. 粗排后分流与顺序守卫落地,支持“无明确微调偏好时粗排后直接收口”,并新增 allow_reorder / needs_refine_after_rough_build 语义,打通 chat→rough_build→execute/order_guard→deliver 路由。
2. execute 工具执行链路修复:清理乱码坏块与重复分支;新增 min_context_switch 未授权拦截;补齐 suggested 顺序基线初始化与顺序守卫联动。
3. 新增复合写工具 min_context_switch(减少上下文切换)并接入注册、参数解析、写工具白名单、提示词与文档;仅在用户明确允许打乱顺序时可用。
4. 工具口径升级:find_first_free 支持 day/day_start/day_end 范围参数并统一文案;移除 find_free 兼容别名;读写工具输出统一到“第N天(星期X)”格式。
5. prompt 同步升级:chat/execute/execute_context 增加粗排后是否继续微调、顺序授权、min_context_switch 使用边界与返回示例约束。
6. handoff 文档重命名并重写下班交接重点:下一步聚焦“工具收敛能力研究 + 运行态必要参数重置(不丢运行态)”。
7. 同步更新调试日志文件。
前端:无
仓库:无
This commit is contained in:
Losita
2026-04-08 23:55:09 +08:00
parent 4195e65cba
commit 21b864390b
21 changed files with 3546 additions and 1009 deletions

View File

@@ -0,0 +1,207 @@
package newagentnode
import (
"context"
"fmt"
"sort"
"strings"
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
)
const (
orderGuardStageName = "order_guard"
orderGuardStatusBlock = "order_guard.status"
)
type suggestedOrderItem struct {
StateID int
Day int
SlotStart int
SlotEnd int
}
// RunOrderGuardNode 负责在收口前校验 suggested 任务相对顺序是否被打乱。
//
// 职责边界:
// 1. 只做“相对顺序守卫”这一件事,不负责执行调度工具,也不负责写库;
// 2. 仅当 AllowReorder=false 时生效,用户明确授权可打乱顺序时直接放行;
// 3. 校验失败只写入统一终止结果Abort由 Deliver 节点统一收口文案。
func RunOrderGuardNode(ctx context.Context, st *newagentmodel.AgentGraphState) error {
if st == nil {
return fmt.Errorf("order_guard node: state is nil")
}
flowState := st.EnsureFlowState()
if flowState == nil {
return fmt.Errorf("order_guard node: flow state is nil")
}
// 1. 用户明确授权可打乱顺序时,顺序守卫节点直接放行。
if flowState.AllowReorder {
return nil
}
// 2. 读取当前 ScheduleState提取 suggested 任务的“时间顺序快照”。
scheduleState, err := st.EnsureScheduleState(ctx)
if err != nil {
return fmt.Errorf("order_guard node: load schedule state failed: %w", err)
}
if scheduleState == nil {
return nil
}
currentOrder := buildSuggestedOrderSnapshot(scheduleState)
// 3. 基线为空时,仅初始化基线并放行,避免第一次进入守卫就误判。
if len(flowState.SuggestedOrderBaseline) == 0 {
flowState.SuggestedOrderBaseline = append([]int(nil), currentOrder...)
_ = st.EnsureChunkEmitter().EmitStatus(
orderGuardStatusBlock,
orderGuardStageName,
"order_guard_initialized",
"已记录本轮建议任务顺序基线,继续交付当前结果。",
false,
)
return nil
}
// 4. 基线存在时做逆序检测;一旦发现逆序,立即终止本轮自动微调。
violated, detail := detectRelativeOrderViolation(flowState.SuggestedOrderBaseline, currentOrder)
if !violated {
_ = st.EnsureChunkEmitter().EmitStatus(
orderGuardStatusBlock,
orderGuardStageName,
"order_guard_passed",
"顺序守卫校验通过,保持原有相对顺序。",
false,
)
return nil
}
userMessage := "检测到当前方案打乱了原有建议任务顺序,本轮先停止自动微调。若你确认可以打乱顺序,请明确说明“允许打乱顺序”。"
flowState.Abort(
orderGuardStageName,
"relative_order_violation",
userMessage,
fmt.Sprintf("baseline=%v current=%v detail=%s", flowState.SuggestedOrderBaseline, currentOrder, detail),
)
_ = st.EnsureChunkEmitter().EmitStatus(
orderGuardStatusBlock,
orderGuardStageName,
"order_guard_failed",
userMessage,
true,
)
return nil
}
// buildSuggestedOrderSnapshot 生成 suggested 任务的相对顺序快照(按时间坐标排序)。
//
// 说明:
// 1. 这里只关心 suggested 任务,因为顺序守卫目标是约束“本轮建议层”的相对次序;
// 2. 多 slot 任务取“最早 slot”作为排序锚点保证排序键稳定
// 3. 返回值是 state_id 列表,便于写入 CommonState 做跨节点持久化。
func buildSuggestedOrderSnapshot(state *newagenttools.ScheduleState) []int {
if state == nil || len(state.Tasks) == 0 {
return nil
}
items := make([]suggestedOrderItem, 0, len(state.Tasks))
for i := range state.Tasks {
task := state.Tasks[i]
if !newagenttools.IsSuggestedTask(task) || len(task.Slots) == 0 {
continue
}
day, slotStart, slotEnd := earliestTaskSlot(task.Slots)
items = append(items, suggestedOrderItem{
StateID: task.StateID,
Day: day,
SlotStart: slotStart,
SlotEnd: slotEnd,
})
}
sort.SliceStable(items, func(i, j int) bool {
if items[i].Day != items[j].Day {
return items[i].Day < items[j].Day
}
if items[i].SlotStart != items[j].SlotStart {
return items[i].SlotStart < items[j].SlotStart
}
if items[i].SlotEnd != items[j].SlotEnd {
return items[i].SlotEnd < items[j].SlotEnd
}
return items[i].StateID < items[j].StateID
})
order := make([]int, 0, len(items))
for _, item := range items {
order = append(order, item.StateID)
}
return order
}
func earliestTaskSlot(slots []newagenttools.TaskSlot) (day int, slotStart int, slotEnd int) {
if len(slots) == 0 {
return 0, 0, 0
}
best := slots[0]
for i := 1; i < len(slots); i++ {
current := slots[i]
if current.Day < best.Day {
best = current
continue
}
if current.Day == best.Day && current.SlotStart < best.SlotStart {
best = current
continue
}
if current.Day == best.Day && current.SlotStart == best.SlotStart && current.SlotEnd < best.SlotEnd {
best = current
}
}
return best.Day, best.SlotStart, best.SlotEnd
}
// detectRelativeOrderViolation 检查 current 是否破坏 baseline 的相对顺序。
//
// 规则:
// 1. 仅比较 baseline 与 current 的交集任务,避免新增/删除任务引发误报;
// 2. 一旦出现 rank 逆序即判定为 violation
// 3. detail 只用于内部排查,不直接给用户。
func detectRelativeOrderViolation(baseline []int, current []int) (bool, string) {
if len(baseline) == 0 || len(current) == 0 {
return false, ""
}
rankByID := make(map[int]int, len(baseline))
for idx, id := range baseline {
rankByID[id] = idx
}
filtered := make([]int, 0, len(current))
for _, id := range current {
if _, ok := rankByID[id]; ok {
filtered = append(filtered, id)
}
}
if len(filtered) < 2 {
return false, ""
}
prevID := filtered[0]
prevRank := rankByID[prevID]
for i := 1; i < len(filtered); i++ {
id := filtered[i]
rank := rankByID[id]
if rank < prevRank {
return true, strings.TrimSpace(fmt.Sprintf(
"reverse pair detected: prev_id=%d prev_rank=%d current_id=%d current_rank=%d",
prevID, prevRank, id, rank,
))
}
prevID = id
prevRank = rank
}
return false, ""
}