后端: 1.新建conv/schedule_persist.go:ScheduleState Diff 持久化,事务内逐变更写库,支持 place/move/unplace 三种操作(当前 event source) 2.新建conv/schedule_provider.go:ScheduleState 加载适配,从 DB 合并 existing events + pending task items 3.新建dao/agent_state_store_adapter.go:Redis 状态快照存取适配,实现 AgentStateStore 接口 4.新建service/agentsvc/agent_newagent.go:newAgent service 集成层,串联 LLM 客户端、ScheduleProvider、SchedulePersistor 和 ChunkEmitter 5.更新node/execute.go:接入 SchedulePersistor(写操作确认后持久化)、完善 confirm resume 路径(PendingConfirmTool 恢复分支)、correction 机制增加连续失败计数上限 6.更新api/agent.go + cmd/start.go:接入 newAgent service,完成 API 层路由注册 7.新建node/execute_confirm_flow_test.go + llm_tool_orchestration_test.go:确认回路 7 个测试 + 端到端排课 5 个测试全部通过 8.新建newAgent/ARCHITECTURE.md + ROADMAP.md:全链路架构文档和缺口分析 9.代码审查整理:提取 prompt/base.go(通用 buildStageMessages 等5个辅助)、tools/args.go(参数解析辅助);write_tools 尾部辅助移入 write_helpers;修复 queryRangeSpecific sb.Reset() 逻辑缺陷和 Unplace guest Duration 未恢复;ScheduleStateProvider/SchedulePersistor 归入 state_store.go;emitter 内部 Build*Text 函数降级为私有 前端:无 仓库:无
11 KiB
NewAgent 全链路改造计划
本文档面向后续 coding agent,描述当前 newAgent 的架构现状、改造计划,以及距离"会主动问用户问题、生成多个任务类、自己 ReAct 排日程的智能体"还差什么。
一、目标智能体行为
用户说:"帮我安排下周的复习计划"。
期望的完整链路:
1. Chat 节点:LLM 主动追问
- "这周有几门考试?"
- "复习强度偏好?均匀分布还是集中在前几天?"
- "有没有要排除的时段?"
2. Plan 节点:LLM 生成结构化计划
- 识别意图为"批量安排任务类到日程"
- 输出 needs_rough_build=true + task_class_ids
- 或者:LLM 调用 create_task_class 工具创建新的任务类
3. Confirm 节点:展示计划,用户确认
4. RoughBuild 节点(新增):确定性粗排
- 调用 SmartPlanningRawItemsMulti() 算法
- 结果写入 ScheduleState(pending tasks 预填 suggested slots)
5. Execute 节点:LLM 用读写工具微调
- 查看 get_overview,发现粗排结果
- 用 move/swap 调整不合理的安排
- 用 place 处理粗排未能安排的任务
- 每次写操作经 confirm 流程
6. Deliver 节点:生成最终总结
- 变更持久化到 DB
- 向用户展示排课结果
二、当前架构
图结构
Chat → Plan → Confirm → Execute(ReAct) → Deliver
已实现的能力
| 模块 | 文件 | 状态 |
|---|---|---|
| 图骨架 | node/agent_nodes.go |
已实现,6 个节点 |
| Chat 节点 | node/chat.go |
已实现,支持 confirm resume |
| Plan 节点 | node/plan.go + prompt/plan.go |
已实现,LLM 生成结构化 PlanStep |
| Confirm 节点 | node/confirm.go |
已实现,创建 PendingInteraction |
| Execute 节点 | node/execute.go + prompt/execute.go |
已实现,ReAct + correction + confirm 流 |
| Deliver 节点 | node/deliver.go |
已实现,LLM 生成总结 |
| 10 个读写工具 | tools/read_tools.go + tools/write_tools.go |
已实现,5 读 5 写 |
| 工具注册表 | tools/registry.go |
已实现 |
| ScheduleState 加载 | conv/schedule_provider.go + conv/schedule_state.go |
已实现,从 DB 加载日程+任务类 |
| Confirm 回路测试 | node/execute_confirm_flow_test.go |
7 个测试全通过 |
| 端到端排课测试 | node/llm_tool_orchestration_test.go |
5 个测试全通过 |
| JSON 协议修正 | prompt/execute.go |
已修复,LLM 输出严格 JSON |
已有数据流
ScheduleProvider.LoadScheduleState(userID)
→ ScheduleDAO.GetUserWeeklySchedule() // 现有日程
→ TaskClassDAO.GetCompleteTaskClassesByIDs() // 任务类(含 Items)
→ LoadScheduleState() // 合并为 ScheduleState
- existing tasks (source="event")
- pending tasks (source="task_item", status="pending")
三、缺口分析
按优先级排列
P0:粗排接入(核心能力)
问题:新 agent 没有 SmartPlanningRawItemsMulti 的调用路径。让 LLM 一个个 place 效率极低且全局最优性差。
改造内容:
-
Plan 节点输出扩展
- 文件:
model/plan_contract.go(或 PlanDecision 所在文件) - PlanDecision 增加
NeedsRoughBuild bool和TaskClassIDs []int prompt/plan.go引导 LLM 判断意图:- 用户意图为"批量安排/智能排课/把任务类排进日程" →
needs_rough_build: true - 从前端
extra或对话中提取task_class_ids - 其他意图 →
needs_rough_build: false
- 用户意图为"批量安排/智能排课/把任务类排进日程" →
- 文件:
-
新增 RoughBuild 图节点
- 新文件:
node/rough_build.go - 不调 LLM,纯确定性逻辑:
输入:task_class_ids, userID 步骤: 1. 调 ScheduleService.HybridScheduleWithPlanMulti(ctx, userID, taskClassIDs) (内部调用 SmartPlanningRawItemsMulti 粗排) 2. 将粗排结果写入 ScheduleState: - pending tasks 的 Slots 字段填入 suggested 位置 - pending tasks 的 Status 保持 "pending"(LLM 可调整,也可以改为 "suggested" 区分) 3. 推送状态给前端 输出:ScheduleState 已填充粗排结果 - 路由:
needs_rough_build=true时 Confirm 之后走 RoughBuild,否则跳过
- 新文件:
-
图路由修改
- 文件:
node/agent_nodes.go - Confirm 之后、Execute 之前插入条件分支:
func (n *AgentNodes) branchAfterConfirm(st *AgentGraphState) string { plan := st.RuntimeState.PlanDecision if plan != nil && plan.NeedsRoughBuild { return "rough_build" } return "execute" }
- 文件:
-
依赖注入
- 文件:
model/graph_run_state.goAgentGraphDeps - 新增
RoughBuildFunc闭包(和旧 agent 的HybridScheduleWithPlanMultiFunc同理) - 文件:
service/agentsvc/agent_newagent.go - 注入
ScheduleService.HybridScheduleWithPlanMulti到 AgentGraphDeps
- 文件:
参考实现:旧 agent 的 agent/node/schedule_plan.go 的 runRoughBuildNode() 函数。
P0:持久化 task_item 放置(必须)
问题:conv/schedule_persist.go 的 applyPlaceChange 只处理 source="event",当 LLM place 一个 source="task_item" 的 pending 任务时会报错。
改造内容:
- 文件:
conv/schedule_persist.go applyPlaceChange增加source="task_item"分支:if source == "task_item": 1. 创建 ScheduleEvent(type="task", rel_id=SourceID, name=change.Name) 2. 为每个 NewCoord 创建 Schedule 记录 3. 如果有嵌入关系(EmbedHost 非空): - 找到宿主 schedule 记录 - 设置 schedules.embedded_task_id = SourceID 4. 更新 task_items.embedded_time(调用 TaskClassDAO.UpdateTaskClassItemEmbeddedTime) 5. 更新 task_items.status = 2 (applied)applyUnplaceChange也需要增加source="task_item"分支(反向清理)
参考实现:旧 agent 的 service/task-class.go 的 BatchApplyPlans() 函数(第 327-536 行)。
P1:TaskClass 约束元数据暴露给 LLM
问题:LLM 看到的 pending task 只有 name/category/duration,缺少 TaskClass 级别的调度约束。
改造内容:
-
扩展 ScheduleState 结构
- 文件:
newAgent/tools/state.go - 新增:
type TaskClassMeta struct { ID int `json:"id"` Name string `json:"name"` Strategy string `json:"strategy"` // "steady" | "rapid" ExcludedSlots []int `json:"excluded_slots"` // 排除的半天时段索引 AllowFillerCourse bool `json:"allow_filler_course"` // 是否允许嵌入水课 TotalSlots int `json:"total_slots"` // 总时间预算 } ScheduleState增加TaskClasses []TaskClassMeta
- 文件:
-
LoadScheduleState 填充元数据
- 文件:
conv/schedule_state.go - 在 Step 4(处理 pending task items)同时填充
state.TaskClasses
- 文件:
-
读工具输出约束信息
- 文件:
tools/read_tools.go get_overview输出中增加任务类约束描述- 示例:
任务类约束: [学习] 策略=均匀分布, 总预算=12节, 允许嵌入水课=是, 排除时段=[3,4]
- 文件:
P2:任务类 ID 从前端传入
问题:前端请求需要在 extra 中传递 task_class_ids,后端需要接收并传递到图内部。
改造内容:
- API 层:
api/agent.go的请求结构增加Extra map[string]any - Service 层:
service/agentsvc/agent_newagent.go从extra中提取task_class_ids,存入 RuntimeState 或 AgentGraphRequest - Plan 节点:从 RuntimeState/Request 中读取
task_class_ids,合并 LLM 从对话中提取的 IDs
参考实现:旧 agent 的 agent/node/schedule_plan.go 的 normalizeTaskClassIDs() 函数。
P3:LLM 主动追问能力增强
问题:当前 Chat 节点主要做"接收用户消息 + confirm resume",缺少"LLM 主动收集排课需求"的能力。
改造内容:
- Chat 节点的 prompt 增强:
- 引导 LLM 在信息不足时主动追问
- 追问内容:考试科目、复习偏好、时段排除、强度偏好
- 追问方式:通过
ask_useraction 或直接在 speak 中提问
- 可能需要新增 ConversationContext 的"收集到的需求"字段
- 收集到的需求在 Plan 节点中被使用
P4:LLM 创建任务类工具(锦上添花)
问题:用户说"帮我安排复习",但系统里没有对应的 TaskClass,LLM 无法创建。
改造内容:
-
新增工具:
create_task_class- 参数:
name,strategy,total_slots,allow_filler_course,items: [{content, duration}] - 行为:调用 TaskClassDAO 在 DB 中创建 TaskClass + Items
- 返回:创建结果 + 新的 task_class_id
- 参数:
-
新增工具:
update_task_class(可选)- 修改已有任务类的参数
-
ScheduleState 动态刷新
- 创建后需要重新加载 ScheduleState 以反映新的 pending tasks
四、改造顺序建议
Phase 1(打通核心链路)
├── P0: 粗排接入(RoughBuild 节点 + 图路由 + 依赖注入)
├── P0: 持久化 task_item 放置
└── P2: task_class_ids 从前端传入
Phase 2(提升排课质量)
├── P1: TaskClass 约束元数据暴露
└── P1: 读工具输出优化(携带约束信息)
Phase 3(智能化)
├── P3: Chat 节点追问能力增强
└── P4: create_task_class 工具
五、关键设计决策记录
- 粗排由图节点驱动,不由 LLM 驱动:粗排是确定性算法,浪费 LLM 调用不划算。
- 粗排通过 Plan 节点的
needs_rough_build标签触发:不是所有请求都需要粗排,LLM 判断意图后打标签。 - 粗排结果写入 ScheduleState 的 Slots 字段:LLM 在 Execute 阶段看到的是"已粗排、可调整"的状态,用 move/swap 微调。
- task_class_ids 来源:前端
extra传入为主,LLM 从对话提取为辅。 - 持久化用 Diff 模式:对比 original 和 modified ScheduleState,只持久化变更部分。
六、关键文件索引
| 用途 | 文件 |
|---|---|
| 图骨架与路由 | newAgent/node/agent_nodes.go |
| Chat 节点 | newAgent/node/chat.go |
| Plan 节点 | newAgent/node/plan.go |
| Confirm 节点 | newAgent/node/confirm.go |
| Execute 节点 | newAgent/node/execute.go |
| Deliver 节点 | newAgent/node/deliver.go |
| Execute prompt | newAgent/prompt/execute.go |
| Plan prompt | newAgent/prompt/plan.go |
| 工具状态模型 | newAgent/tools/state.go |
| 读工具 | newAgent/tools/read_tools.go |
| 写工具 | newAgent/tools/write_tools.go |
| 工具注册表 | newAgent/tools/registry.go |
| 图运行态 | newAgent/model/graph_run_state.go |
| 公共状态 | newAgent/model/common_state.go |
| 日程状态加载 | conv/schedule_provider.go |
| DB→State 转换 | conv/schedule_state.go |
| Diff 算法 | conv/schedule_state.go (DiffScheduleState) |
| 持久化 | conv/schedule_persist.go |
| Service 集成 | service/agentsvc/agent_newagent.go |
| 粗排算法(旧,复用) | logic/smart_planning.go |
| 旧 agent 粗排节点(参考) | agent/node/schedule_plan.go |
| 旧 agent 批量应用(参考) | service/task-class.go BatchApplyPlans |