Files
smartmate/backend/newAgent/tools/state.go
LoveLosita 821c2cde5d Version: 0.9.10.dev.260409
后端:
1. newAgent 运行态重置双保险落地,并补齐写工具后的实时排程预览刷新
   - 更新 model/common_state.go:新增 ResetForNextRun,统一清理 round/plan/rough_build/allow_reorder/terminal 等执行期临时状态
   - 更新 node/chat.go + service/agentsvc/agent_newagent.go:在“无 pending 且上一轮已 done”时分别于 chat 主入口与 loadOrCreateRuntimeState 冷加载处执行兜底重置,覆盖正常新一轮对话与断线恢复场景
   - 更新 model/graph_run_state.go + node/agent_nodes.go + node/execute.go:写工具执行后立即刷新 Redis 排程预览,Deliver 继续保留最终覆盖写,保证前端能及时看到最新操作结果
2. 顺序守卫从“直接中止”改为“优先自动复原 suggested 相对顺序”
   - 更新 node/order_guard.go:检测到 suggested 顺序被打乱后,不再直接 abort;改为复用当前坑位按 baseline 自动回填,并在复原失败时仅记录诊断日志后继续交付
   - 更新 tools/state.go:ScheduleState 新增 RuntimeQueue 运行态快照字段,支持队列化处理与断线恢复
3. 多任务微调工具链升级:新增筛选/队列工具并替换首空位查询口径
   - 新建 tools/read_filter_tools.go + tools/runtime_queue.go + tools/queue_tools.go:新增 query_available_slots / query_target_tasks / queue_pop_head / queue_apply_head_move / queue_skip_head / queue_status,支持“先筛选目标,再逐项处理”的稳定微调链路
   - 更新 tools/registry.go + tools/write_tools.go + tools/read_helpers.go:移除 find_first_free 注册口径;batch_move 限制为最多 2 条,超过时引导改走队列逐项处理;queue_apply_head_move 纳入写工具集合
4. 复合规划工具扩充,并改为在 newAgent/tools 本地实现以规避循环导入
   - 更新 tools/compound_tools.go + tools/registry.go:spread_even 正式接入,并与 min_context_switch 一起作为复合写工具保留在 newAgent/tools 内部实现,不再依赖外层 logic
5. prompt 与工具文档同步升级,明确当前用户诉求锚点与队列化执行约束
   - 更新 prompt/execute.go + prompt/execute_context.go + prompt/plan.go:执行提示默认引导 query_target_tasks(enqueue=true) → queue_pop_head → query_available_slots → queue_apply_head_move / queue_skip_head;补齐 batch_move 上限、spread_even 使用边界、顺序策略与工具 JSON 返回示例
   - 更新 prompt/execute_context.go:将“初始用户目标”改为“当前用户诉求”,并保留首轮目标来源;旧 observation 折叠文案改为“当前工具调用结果已经被使用过,当前无需使用,为节省上下文空间,已折叠”
   - 更新 tools/SCHEDULE_TOOLS.md:同步补齐 query_* / queue_* / spread_even / min_context_switch 的说明、限制与返回示例
6. 同步更新调试日志文件
前端:无
仓库:无
2026-04-09 16:17:56 +08:00

144 lines
5.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 newagenttools
// DayMapping maps a day_index to a real (week, day_of_week) coordinate.
type DayMapping struct {
DayIndex int `json:"day_index"`
Week int `json:"week"`
DayOfWeek int `json:"day_of_week"`
}
// ScheduleWindow defines the planning window.
type ScheduleWindow struct {
TotalDays int `json:"total_days"`
DayMapping []DayMapping `json:"day_mapping"`
}
// TaskSlot is a compressed time slot using day_index and section range.
type TaskSlot struct {
Day int `json:"day"`
SlotStart int `json:"slot_start"`
SlotEnd int `json:"slot_end"`
}
// TaskClassMeta 是任务类级别的调度约束,供 LLM 在排课时参考。
// 只记录影响排课决策的字段,不暴露数据库内部细节。
type TaskClassMeta struct {
ID int `json:"id"`
Name string `json:"name"`
Strategy string `json:"strategy"` // "steady"=均匀分布 | "rapid"=集中突击
TotalSlots int `json:"total_slots"` // 该任务类总时段预算
AllowFillerCourse bool `json:"allow_filler_course"` // 是否允许嵌入水课时段
ExcludedSlots []int `json:"excluded_slots"` // 排除的半天时段索引(空=无限制)
StartDate string `json:"start_date,omitempty"` // 排程起始日期YYYY-MM-DD
EndDate string `json:"end_date,omitempty"` // 排程截止日期YYYY-MM-DD
}
// ScheduleTask is a unified task representation in the tool state.
// It merges existing schedules (from schedule_events) and pending tasks (from task_items)
// into one flat list that the tool layer operates on.
type ScheduleTask struct {
StateID int `json:"state_id"`
Source string `json:"source"` // "event" | "task_item"
SourceID int `json:"source_id"` // ScheduleEvent.ID or TaskClassItem.ID
Name string `json:"name"`
Category string `json:"category"` // e.g. "课程", "学习", "作业"
Status string `json:"status"` // "existing" | "suggested" | "pending"
Locked bool `json:"locked"`
// Existing / suggested task: compressed slot ranges. Pending task: nil until placed.
Slots []TaskSlot `json:"slots,omitempty"`
// Pending / suggested task: required consecutive slot count.
Duration int `json:"duration,omitempty"`
// source=task_item only: TaskClass.ID用于反查任务类约束。
TaskClassID int `json:"task_class_id,omitempty"`
// source=task_item only: TaskClass.ID for category lookup (internal alias).
CategoryID int `json:"category_id,omitempty"`
// source=event only: whether this slot allows embedding other tasks.
CanEmbed bool `json:"can_embed,omitempty"`
// Embed relationships (resolved after all tasks are loaded).
EmbeddedBy *int `json:"embedded_by,omitempty"` // host: which state_id is embedded into me
EmbedHost *int `json:"embed_host,omitempty"` // guest: which state_id's slot I'm embedded into
// Internal: not exposed to LLM, used for flush/diff logic.
EventType string `json:"event_type,omitempty"` // "course" | "task" (source=event only)
}
// ScheduleState is the full tool operation state.
type ScheduleState struct {
Window ScheduleWindow `json:"window"`
Tasks []ScheduleTask `json:"tasks"`
TaskClasses []TaskClassMeta `json:"task_classes,omitempty"` // 任务类约束元数据,供 LLM 排课参考
// RuntimeQueue 是“本轮 execute 微调”的临时待处理队列。
//
// 职责边界:
// 1. 负责承载 LLM 队列化微调时的运行态(待处理/当前处理/已完成/已跳过);
// 2. 只用于 newAgent 运行期,不参与数据库持久化;
// 3. 支持随 AgentStateSnapshot 一起快照,便于断线恢复后继续处理队首任务。
RuntimeQueue *TaskProcessingQueue `json:"runtime_queue,omitempty"`
}
// DayToWeekDay converts day_index to (week, day_of_week).
func (s *ScheduleState) DayToWeekDay(day int) (week, dayOfWeek int, ok bool) {
for _, m := range s.Window.DayMapping {
if m.DayIndex == day {
return m.Week, m.DayOfWeek, true
}
}
return 0, 0, false
}
// WeekDayToDay converts (week, day_of_week) to day_index.
func (s *ScheduleState) WeekDayToDay(week, dayOfWeek int) (day int, ok bool) {
for _, m := range s.Window.DayMapping {
if m.Week == week && m.DayOfWeek == dayOfWeek {
return m.DayIndex, true
}
}
return 0, false
}
// TaskByStateID finds a task by state_id. Returns nil if not found.
func (s *ScheduleState) TaskByStateID(stateID int) *ScheduleTask {
for i := range s.Tasks {
if s.Tasks[i].StateID == stateID {
return &s.Tasks[i]
}
}
return nil
}
// Clone returns a deep copy of the ScheduleState.
func (s *ScheduleState) Clone() *ScheduleState {
if s == nil {
return nil
}
clone := &ScheduleState{
Window: ScheduleWindow{
TotalDays: s.Window.TotalDays,
DayMapping: make([]DayMapping, len(s.Window.DayMapping)),
},
Tasks: make([]ScheduleTask, len(s.Tasks)),
TaskClasses: make([]TaskClassMeta, len(s.TaskClasses)),
}
copy(clone.Window.DayMapping, s.Window.DayMapping)
copy(clone.TaskClasses, s.TaskClasses)
for i, t := range s.Tasks {
clone.Tasks[i] = t
if t.Slots != nil {
clone.Tasks[i].Slots = make([]TaskSlot, len(t.Slots))
copy(clone.Tasks[i].Slots, t.Slots)
}
if t.EmbeddedBy != nil {
v := *t.EmbeddedBy
clone.Tasks[i].EmbeddedBy = &v
}
if t.EmbedHost != nil {
v := *t.EmbedHost
clone.Tasks[i].EmbedHost = &v
}
}
clone.RuntimeQueue = cloneTaskProcessingQueue(s.RuntimeQueue)
return clone
}