后端:
1.Chat 四路由升级(二分类 chat/task → 四路由 direct_reply/execute/deep_answer/plan)
- 新建model/chat_contract.go:路由决策模型,含 NeedsRoughBuild 粗排标记
- 更新node/chat.go:四路由分流;新增 deep_answer 深度回答路径(二次 LLM 开 thinking)
- 更新prompt/chat.go:意图分类 prompt 升级为四路由 prompt;新增 deep_answer prompt
2.粗排节点(RoughBuild)全链路
- 新建node/rough_build.go:粗排节点,调用注入的算法函数,结果写入 ScheduleState 后进 Execute 微调
- 更新graph/common_graph.go:注册 RoughBuild 节点;Chat/Confirm 后可路由至粗排
- 更新model/graph_run_state.go:新增 RoughBuildPlacement/RoughBuildFunc 类型;Deps 注入入口
- 更新model/plan_contract.go:PlanDecision 新增 NeedsRoughBuild/TaskClassIDs 字段
- 更新node/plan.go:plan_done 时写入粗排标记和 TaskClassIDs
3.任务类约束元数据(TaskClassMeta)贯穿 prompt → tools → 持久化
- 更新tools/state.go:新增 TaskClassMeta;ScheduleState.TaskClasses;ScheduleTask.TaskClassID;Clone 深拷贝
- 更新conv/schedule_state.go:加载时构建 TaskClassMeta;Diff 支持 HostEventID 嵌入关系
- 更新conv/schedule_provider.go:新增 LoadTaskClassMetas 按需加载
- 更新model/state_store.go:ScheduleStateProvider 接口新增 LoadTaskClassMetas
- 更新prompt/base.go:renderStateSummary 渲染任务类约束
- 更新prompt/plan.go:注入任务类 ID 上下文和粗排识别规则
- 更新tools/read_tools.go:GetOverview 展示任务类约束
- 更新model/common_state.go:CommonState 新增 TaskClassIDs/TaskClasses/NeedsRoughBuild
4.Execute 健壮性增强(correction 重试 + 纯 ReAct 模式)
- 更新node/execute.go:未知工具名/空文本走 correction 重试而非 fatal;maxConsecutiveCorrections 提升为包级常量;新增无 plan 纯ReAct 模式;工具结果截断;speak 排除 ask_user/confirm
- 更新prompt/execute.go:新增 ReAct 模式 system prompt 和 contract
5.写入持久化完善(task_item source + 嵌入水课)
- 更新conv/schedule_persist.go:place/move/unplace 支持 task_item source,含嵌入水课和普通 task event 两条路径
- 新建conv/schedule_preview.go:ScheduleState → 排程预览缓存,复用旧格式,前端无需改动
6.状态持久化体系(Redis → MySQL outbox 异步)
- 更新dao/cache.go:Redis 快照 TTL 从 24h 改为 2h,配合 MySQL outbox
- 新建model/agent_state_snapshot_record.go:快照 MySQL 记录模型
- 新建service/events/agent_state_persist.go:outbox 异步持久化处理器
- 更新cmd/start.go + inits/mysql.go:注册快照事件处理器 + AutoMigrate
- 更新service/agentsvc/agent_newagent.go:注入 RoughBuildFunc;outbox 异步写快照;排程结果写 Redis 预览缓存
7.基础设施与稳定性
- 更新stream/sse_adapter.go:outChan 满时静默丢弃,保证持久化不被 SSE 阻断
- 更新service/agentsvc/agent.go:新增 readAgentExtraIntSlice;outChan 容量 8→256
- 更新node/agent_nodes.go:Chat 注入工具 schema;Deliver 改 saveAgentState 替代 deleteAgentState
前端:无
仓库:无
158 lines
5.0 KiB
Go
158 lines
5.0 KiB
Go
package conv
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/LoveLosita/smartflow/backend/dao"
|
||
"github.com/LoveLosita/smartflow/backend/model"
|
||
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
|
||
)
|
||
|
||
// ScheduleProvider 实现 model.ScheduleStateProvider 接口。
|
||
// 通过 DAO 层加载用户的日程和任务数据,调用 LoadScheduleState 构建内存状态。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只负责"从 DB 查数据 + 调 LoadScheduleState 转换",不含业务逻辑;
|
||
// 2. 不负责缓存(由上层 Service 决定是否缓存);
|
||
// 3. 不负责 Diff 和持久化(由 Confirm 流程负责)。
|
||
type ScheduleProvider struct {
|
||
scheduleDAO *dao.ScheduleDAO
|
||
taskClassDAO *dao.TaskClassDAO
|
||
}
|
||
|
||
// NewScheduleProvider 创建 ScheduleProvider。
|
||
func NewScheduleProvider(scheduleDAO *dao.ScheduleDAO, taskClassDAO *dao.TaskClassDAO) *ScheduleProvider {
|
||
return &ScheduleProvider{
|
||
scheduleDAO: scheduleDAO,
|
||
taskClassDAO: taskClassDAO,
|
||
}
|
||
}
|
||
|
||
// LoadScheduleState 实现 model.ScheduleStateProvider 接口。
|
||
// 加载用户当前周的日程和所有待安排任务,构建 ScheduleState。
|
||
func (p *ScheduleProvider) LoadScheduleState(ctx context.Context, userID int) (*newagenttools.ScheduleState, error) {
|
||
// 1. 确定当前周。
|
||
now := time.Now()
|
||
week, _, err := RealDateToRelativeDate(now.Format(DateFormat))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("解析当前日期失败: %w", err)
|
||
}
|
||
|
||
// 2. 加载当前周的所有日程(含 Event + EmbeddedTask 预加载)。
|
||
schedules, err := p.scheduleDAO.GetUserWeeklySchedule(ctx, userID, week)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加载用户周日程失败: %w", err)
|
||
}
|
||
|
||
// 3. 加载用户所有任务类(含 Items 预加载)。
|
||
// 两步:先拿 ID 列表,再批量获取完整数据(含 Items)。
|
||
taskClasses, err := p.loadCompleteTaskClasses(ctx, userID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 4. 构建 WindowDay 列表(当前周 7 天)。
|
||
windowDays := make([]WindowDay, 7)
|
||
for i := 0; i < 7; i++ {
|
||
windowDays[i] = WindowDay{Week: week, DayOfWeek: i + 1}
|
||
}
|
||
|
||
// 5. 构建额外 item category 映射(已加载全部 taskClass,通常为空)。
|
||
extraItemCategories := buildExtraItemCategories(schedules, taskClasses)
|
||
|
||
// 6. 调用已有的 LoadScheduleState 构建内存状态。
|
||
return LoadScheduleState(schedules, taskClasses, extraItemCategories, windowDays), nil
|
||
}
|
||
|
||
// loadCompleteTaskClasses 批量加载用户所有任务类(含 Items 预加载)。
|
||
func (p *ScheduleProvider) loadCompleteTaskClasses(ctx context.Context, userID int) ([]model.TaskClass, error) {
|
||
basicClasses, err := p.taskClassDAO.GetUserTaskClasses(userID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加载用户任务类失败: %w", err)
|
||
}
|
||
if len(basicClasses) == 0 {
|
||
return nil, nil
|
||
}
|
||
|
||
ids := make([]int, len(basicClasses))
|
||
for i, tc := range basicClasses {
|
||
ids[i] = tc.ID
|
||
}
|
||
|
||
complete, err := p.taskClassDAO.GetCompleteTaskClassesByIDs(ctx, userID, ids)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加载完整任务类失败: %w", err)
|
||
}
|
||
return complete, nil
|
||
}
|
||
|
||
// LoadTaskClassMetas 加载指定任务类的约束元数据(不含 Items、不含日程),供 Plan 阶段提前消费。
|
||
func (p *ScheduleProvider) LoadTaskClassMetas(ctx context.Context, userID int, taskClassIDs []int) ([]newagenttools.TaskClassMeta, error) {
|
||
if len(taskClassIDs) == 0 {
|
||
return nil, nil
|
||
}
|
||
complete, err := p.taskClassDAO.GetCompleteTaskClassesByIDs(ctx, userID, taskClassIDs)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("加载任务类元数据失败: %w", err)
|
||
}
|
||
metas := make([]newagenttools.TaskClassMeta, 0, len(complete))
|
||
for _, tc := range complete {
|
||
meta := newagenttools.TaskClassMeta{
|
||
ID: tc.ID,
|
||
Name: derefString(tc.Name),
|
||
}
|
||
if tc.Strategy != nil {
|
||
meta.Strategy = *tc.Strategy
|
||
}
|
||
if tc.TotalSlots != nil {
|
||
meta.TotalSlots = *tc.TotalSlots
|
||
}
|
||
if tc.AllowFillerCourse != nil {
|
||
meta.AllowFillerCourse = *tc.AllowFillerCourse
|
||
}
|
||
if tc.ExcludedSlots != nil {
|
||
meta.ExcludedSlots = []int(tc.ExcludedSlots)
|
||
}
|
||
if tc.StartDate != nil {
|
||
meta.StartDate = tc.StartDate.Format("2006-01-02")
|
||
}
|
||
if tc.EndDate != nil {
|
||
meta.EndDate = tc.EndDate.Format("2006-01-02")
|
||
}
|
||
metas = append(metas, meta)
|
||
}
|
||
return metas, nil
|
||
}
|
||
|
||
func derefString(s *string) string {
|
||
if s == nil {
|
||
return ""
|
||
}
|
||
return *s
|
||
}
|
||
|
||
// buildExtraItemCategories 从已有日程中提取不属于给定 taskClasses 的 task event 的 category 映射。
|
||
// 当加载全部 taskClass 时,通常返回空 map。
|
||
func buildExtraItemCategories(schedules []model.Schedule, taskClasses []model.TaskClass) map[int]string {
|
||
knownItemIDs := make(map[int]bool)
|
||
for _, tc := range taskClasses {
|
||
for _, item := range tc.Items {
|
||
knownItemIDs[item.ID] = true
|
||
}
|
||
}
|
||
|
||
categories := make(map[int]string)
|
||
for _, s := range schedules {
|
||
if s.Event == nil || s.Event.Type != "task" || s.Event.RelID == nil {
|
||
continue
|
||
}
|
||
itemID := *s.Event.RelID
|
||
if !knownItemIDs[itemID] {
|
||
categories[itemID] = "任务"
|
||
}
|
||
}
|
||
return categories
|
||
}
|