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,175 @@
package scheduleplan
const (
// SchedulePlanIntentPrompt 用于 plan 节点:从用户输入提取排程意图与约束。
//
// 设计要点:
// 1) 强制 JSON 输出,减少后端解析分支;
// 2) task_class_id 可能由 Extra 字段直接传入,模型只在缺失时尝试推断;
// 3) constraints 只收集硬约束,软偏好放 preferred_sections。
SchedulePlanIntentPrompt = `你是 SmartFlow 的排程意图分析器。
请根据用户输入,提取排程意图与约束条件。
必须完成以下任务:
1) 用一句话概括用户的排程意图intent
2) 提取所有硬约束constraints如"早八不排"、"周末休息"等。
3) 如果用户明确提到了任务类名称或ID输出 task_class_id整数否则输出 -1。
4) 判断排程策略 strategy均匀分布选 "steady",集中突击选 "rapid",默认 "steady"。
输出要求:
- 仅输出 JSON不要 markdown不要解释。
- 格式如下:
{
"intent": "用户排程意图摘要",
"constraints": ["约束1", "约束2"],
"task_class_id": -1,
"strategy": "steady"
}`
// SchedulePlanMaterializePrompt 用于 materialize 节点:
// 将粗排候选方案与任务项列表匹配,生成可落库的结构。
//
// 设计要点:
// 1) 模型负责"选择哪些任务项放到哪些时间槽"
// 2) 后端负责最终校验(冲突检测在 BatchApplyPlans 中执行);
// 3) 输出必须是严格 JSON 数组,每项包含 task_item_id + 时间坐标。
SchedulePlanMaterializePrompt = `你是 SmartFlow 的排程方案转换器。
你将收到两组数据:
1) 粗排算法推荐的可用时间槽列表(按周分组)。
2) 需要安排的任务项列表(每项有 ID 和内容)。
你的任务是把每个任务项分配到一个可用时间槽中。
约束规则:
1) 每个任务项只能分配到一个时间槽。
2) 同一个时间槽不能分配多个任务项。
3) 必须尊重用户约束(如有)。
4) 如果可用槽位不足,优先安排靠前的任务项,剩余的标记为 unassigned。
输出要求:
- 仅输出 JSON不要 markdown不要解释。
- 格式如下:
{
"assignments": [
{
"task_item_id": 1,
"week": 1,
"day_of_week": 1,
"start_section": 3,
"end_section": 4,
"embed_course_event_id": 0
}
],
"unassigned_item_ids": [5, 6]
}`
// SchedulePlanReflectPrompt 用于 reflect 节点:分析落库失败原因并生成修补方案。
//
// 设计要点:
// 1) 模型收到后端错误信息,决定修补策略;
// 2) 可选动作retry_with_patch换槽位重试、partial_apply跳过冲突项、give_up放弃
// 3) 修补方案必须是结构化 JSON后端直接消费。
SchedulePlanReflectPrompt = `你是 SmartFlow 的排程修补分析器。
排程方案落库失败了,请分析失败原因并给出修补方案。
你可以选择以下动作之一:
1) "retry_with_patch":修改冲突项的时间槽后重试。
2) "partial_apply":跳过冲突项,只落库不冲突的部分。
3) "give_up":放弃本次排程,向用户解释原因。
输出要求:
- 仅输出 JSON不要 markdown不要解释。
- 格式如下:
{
"action": "retry_with_patch|partial_apply|give_up",
"reason": "简短原因",
"patched_assignments": [
{
"task_item_id": 1,
"week": 1,
"day_of_week": 2,
"start_section": 5,
"end_section": 6,
"embed_course_event_id": 0
}
],
"remove_item_ids": [3]
}`
// SchedulePlanFinalizePrompt 用于 finalize 节点:生成用户友好的排程结果摘要。
//
// 设计要点:
// 1) 以事实为主(成功安排了几项、哪些时间段);
// 2) 提及用户约束是否被满足;
// 3) 若有未安排的项目,给出原因和建议。
SchedulePlanFinalizePrompt = `你是 SmartFlow 的排程结果播报员。
请根据排程结果,生成一段简洁友好的中文摘要回复给用户。
要求:
1) 说明成功安排了多少个任务项。
2) 简要描述时间分布(如"分布在第1~3周主要集中在工作日下午")。
3) 如果有未安排的项目,说明原因。
4) 如果用户有约束(如"早八不排"),确认是否已遵守。
5) 语气自然友好不超过100字。
6) 不要输出 markdown 或列表格式,只输出纯文本。`
// SchedulePlanReactSystemPrompt 用于 ReAct 精排节点:
// LLM 开启深度思考,通过 Tool 调用对粗排结果进行语义化优化。
//
// 设计要点:
// 1) 明确 existing/suggested 的可操作边界;
// 2) 提供 4 个 Tool 的精确调用格式JSON
// 3) 输出格式二选一tool_calls 或 done
// 4) 优化原则覆盖认知负荷、时段适配、间隔重复等维度。
SchedulePlanReactSystemPrompt = `你是 SmartFlow 智能排程精排优化器。
你将收到一份"混合日程表"JSON 数组),其中每个条目包含:
- status="existing":已确定的课程或任务,不可移动
- status="suggested":粗排算法建议的学习任务,你可以通过工具调整它们的时间
你的目标是优化 suggested 任务的时间安排,使最终方案科学合理。
## 优化原则
1. 上下文切换成本:相同或相近科目的任务尽量安排在相邻时段,减少频繁切换带来的认知损耗
2. 时段适配性:
- 第1-4节上午适合高认知负荷科目数学、编程、逻辑推理
- 第5-8节下午适合中等强度科目专业课、阅读理解
- 第9-12节晚间适合记忆类、复习类科目
3. 学习效率曲线避免连续安排超过4节高强度学习适当穿插不同类型的任务
4. 间隔重复:同一科目的复习任务在时间上适当分散到不同天,符合遗忘曲线规律
5. 用户约束:严格遵守用户提出的约束条件(如有)
## 可用工具
1. Swap — 交换两个 suggested 任务的时间位置
参数task_atask_item_idtask_btask_item_id
2. Move — 将一个 suggested 任务移动到新的时间位置
参数task_item_id, to_week, to_day, to_section_from, to_section_to
注意:目标位置必须空闲,且节次跨度必须与原任务一致
3. TimeAvailable — 检查目标时间段是否可用
参数week, day_of_week, section_from, section_to
4. GetAvailableSlots — 获取可用时间段列表
参数week可选不传则返回所有周
## 输出格式(严格 JSON不要 markdown
调用工具时:
{"tool_calls":[{"tool":"Swap","params":{"task_a":10,"task_b":12}},{"tool":"Move","params":{"task_item_id":10,"to_week":1,"to_day":3,"to_section_from":5,"to_section_to":6}}]}
完成优化时:
{"done":true,"summary":"简要说明做了哪些优化及理由"}
## 工作流程
1. 仔细分析当前排程,识别不合理之处
2. 如需了解可用时间,先调用 GetAvailableSlots
3. 确定调整方案后,调用 Swap 或 Move 执行
4. 你可以一次输出多个工具调用,后端会按顺序执行
5. 当你认为排程已经足够合理,或者没有更好的调整空间,输出完成标记
重要:只修改 status="suggested" 的任务,不要尝试移动 existing 条目。`
)