Version: 0.7.0.dev.260319

 feat(agent): 新增智能排程 Agent 全链路 + ReAct 精排引擎

  🏗️ 智能排程 Graph 编排(阶段 1 基础链路)
  - 新增 scheduleplan 包:state / tool / prompt / nodes / runner / graph 六件套
  - 实现 plan → preview → materialize → apply → reflect → finalize 完整图编排
  - 通过函数注入解耦 agent 层与 service 层,避免循环依赖
  - 路由层新增 schedule_plan 动作,复用现有 SSE + 持久化链路

  🧠 ReAct 精排引擎(阶段 1.5 语义化微调)
  - 粗排后构建"混合日程"(既有课程 + 建议任务),统一为 HybridScheduleEntry
  - LLM 开启深度思考,通过 Swap / Move / TimeAvailable / GetAvailableSlots 四个 Tool 在内存中优化任务时间
  - reasoning_content 实时流式推送前端,用户可见 AI 思考过程
  - 精排结果仅预览不落库,向后兼容(未注入依赖时走原有 materialize 路径)

  📝 文档
  - 新增 ReAct 精排引擎决策记录

  ⚠️ 已知问题:深度思考模式耗时较长,超时策略待优化
This commit is contained in:
Losita
2026-03-19 23:16:35 +08:00
parent cd95aeeaaa
commit d3cec2a5b9
24 changed files with 2737 additions and 24 deletions

View File

@@ -0,0 +1,76 @@
package scheduleplan
import (
"context"
"errors"
"strconv"
"github.com/LoveLosita/smartflow/backend/model"
)
// SchedulePlanToolDeps 描述"智能排程工具包"需要的外部依赖。
//
// 设计目标:
// 1) 通过函数注入把 agent 包与 service/dao 解耦,避免循环依赖;
// 2) 每个函数对应一个可独立 mock 的业务能力;
// 3) 后续可按需扩展(如局部修补、任务类自动生成等)。
type SchedulePlanToolDeps struct {
// SmartPlanningRaw 调用粗排算法,同时返回展示结构和已分配的任务项。
// 返回值:
// - []UserWeekSchedule展示型结构供 SSE 阶段推送给前端预览;
// - []TaskClassItem已分配的任务项EmbeddedTime 已回填),供 materialize 直接转换。
SmartPlanningRaw func(ctx context.Context, userID, taskClassID int) ([]model.UserWeekSchedule, []model.TaskClassItem, error)
// BatchApplyPlans 将排程方案批量落库。
// 输入taskClassID、userID、落库请求体。
// 输出errornil 表示全部成功)。
BatchApplyPlans func(ctx context.Context, taskClassID, userID int, plans *model.UserInsertTaskClassItemToScheduleRequestBatch) error
// GetTaskClassByID 获取任务类详情(含关联的 Items
// 用于:
// 1) 校验 task_class_id 合法性;
// 2) 获取 Items 列表,为连续对话微调提供上下文。
GetTaskClassByID func(ctx context.Context, taskClassID, userID int) (*model.TaskClass, error)
// HybridScheduleWithPlan 构建混合日程(既有日程 + 粗排建议),供 ReAct 精排使用。
// 可选依赖:未注入时 ReAct 精排阶段不可用,走原有 materialize 路径。
HybridScheduleWithPlan func(ctx context.Context, userID, taskClassID int) ([]model.HybridScheduleEntry, []model.TaskClassItem, error)
}
// validate 校验依赖完整性,缺失任意一个都无法完成排程链路。
func (d SchedulePlanToolDeps) validate() error {
if d.SmartPlanningRaw == nil {
return errors.New("schedule plan tool deps: SmartPlanningRaw is nil")
}
if d.BatchApplyPlans == nil {
return errors.New("schedule plan tool deps: BatchApplyPlans is nil")
}
if d.GetTaskClassByID == nil {
return errors.New("schedule plan tool deps: GetTaskClassByID is nil")
}
return nil
}
// ExtraInt 从 extra map 中安全提取整数值。
//
// 兼容策略:
// 1) JSON 数字默认解析为 float64做 int 转换;
// 2) 兼容字符串形式(如 "42"),用 Atoi 解析;
// 3) 其余类型返回 false由调用方决定后续处理。
func ExtraInt(extra map[string]any, key string) (int, bool) {
v, ok := extra[key]
if !ok {
return 0, false
}
switch n := v.(type) {
case float64:
return int(n), true
case int:
return n, true
case string:
i, err := strconv.Atoi(n)
return i, err == nil
default:
return 0, false
}
}