Files
smartmate/backend/newAgent/model/plan_contract.go
Losita 2038185730 Version: 0.9.2.dev.260406
后端:
   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
前端:无
仓库:无
2026-04-06 23:15:54 +08:00

153 lines
5.1 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 model
import (
"fmt"
"strings"
)
// PlanComplexity 表示规划阶段评估的任务复杂度。
type PlanComplexity string
const (
// PlanComplexitySimple 表示简单明确的操作,步骤之间无复杂依赖。
PlanComplexitySimple PlanComplexity = "simple"
// PlanComplexityModerate 表示多步操作,需要一定推理但不涉及深度分析。
PlanComplexityModerate PlanComplexity = "moderate"
// PlanComplexityComplex 表示需要深度推理、多方案比较或复杂依赖关系的任务。
PlanComplexityComplex PlanComplexity = "complex"
)
// PlanAction 表示规划阶段单轮决策的动作类型。
//
// 设计原则:
// 1. 规划阶段只关心“继续规划 / 追问用户 / 规划完成”这三类动作;
// 2. 这里先不把工具调用塞进 contract避免过早把 plan loop 复杂化;
// 3. 规划层产出的是“自然语言计划”,不是执行层的工具动作。
type PlanAction string
const (
// PlanActionContinue 表示当前信息已足够,继续规划下一轮。
PlanActionContinue PlanAction = "continue"
// PlanActionAskUser 表示当前规划缺少关键信息,需要中断并追问用户。
PlanActionAskUser PlanAction = "ask_user"
// PlanActionDone 表示规划已经完成,可以进入 confirm 或下一阶段。
PlanActionDone PlanAction = "plan_done"
)
// PlanDecision 是 plan prompt 单轮产出的统一决策结构。
//
// 职责边界:
// 1. Speak 是本轮先对用户说的话;若 action=ask_user通常这里会承载要追问的问题
// 2. Action 是规划阶段的下一步动作类型;
// 3. Reason 是给后端和日志看的简短解释;
// 4. PlanSteps 只在 plan_done 时要求返回,表示本轮最终确认下来的完整自然语言计划;
// 5. NeedsRoughBuild 为 true 时Confirm 后自动触发粗排节点,不需要 LLM 在 plan_steps 里手动描述放置步骤;
// 6. TaskClassIDs 是本次粗排涉及的任务类 ID 列表,与 CommonState.TaskClassIDs 保持一致。
type PlanDecision struct {
Speak string `json:"speak,omitempty"`
Action PlanAction `json:"action"`
Reason string `json:"reason,omitempty"`
Complexity PlanComplexity `json:"complexity"`
NeedThinking bool `json:"need_thinking"`
PlanSteps []PlanStep `json:"plan_steps,omitempty"`
NeedsRoughBuild bool `json:"needs_rough_build,omitempty"`
TaskClassIDs []int `json:"task_class_ids,omitempty"`
}
// Normalize 统一清洗规划决策中的字符串字段。
func (d *PlanDecision) Normalize() {
if d == nil {
return
}
d.Speak = strings.TrimSpace(d.Speak)
d.Action = PlanAction(strings.TrimSpace(string(d.Action)))
d.Reason = strings.TrimSpace(d.Reason)
d.Complexity = PlanComplexity(strings.TrimSpace(string(d.Complexity)))
for i := range d.PlanSteps {
d.PlanSteps[i].Normalize()
}
}
// Validate 校验规划决策的最小合法性。
//
// 校验原则:
// 1. 这里只校验“协议是否自洽”,不校验规划内容是否聪明、是否足够好;
// 2. 只有 plan_done 允许返回完整 plan_steps
// 3. 真正的规划质量判断仍留给后续 node 层和用户确认环节。
func (d *PlanDecision) Validate() error {
if d == nil {
return fmt.Errorf("plan decision 不能为空")
}
d.Normalize()
if d.Action == "" {
return fmt.Errorf("plan decision.action 不能为空")
}
// 复杂度兜底:未填写时默认 moderate不因此拒绝整个决策。
switch d.Complexity {
case PlanComplexitySimple, PlanComplexityModerate, PlanComplexityComplex:
// ok
case "":
d.Complexity = PlanComplexityModerate
default:
return fmt.Errorf("未知 complexity: %s", d.Complexity)
}
switch d.Action {
case PlanActionContinue, PlanActionAskUser:
if len(d.PlanSteps) > 0 {
return fmt.Errorf("%s 动作不应携带 plan_steps", d.Action)
}
return nil
case PlanActionDone:
if len(d.PlanSteps) == 0 {
return fmt.Errorf("plan_done 动作必须携带完整 plan_steps")
}
for i := range d.PlanSteps {
if err := d.PlanSteps[i].Validate(); err != nil {
return fmt.Errorf("plan_steps[%d] 非法: %w", i, err)
}
}
return nil
default:
return fmt.Errorf("未知 plan action: %s", d.Action)
}
}
// PlanStep 表示规划阶段产出的一条自然语言步骤。
//
// 设计说明:
// 1. Content 是步骤正文,后续可直接落到 CommonState.PlanSteps
// 2. DoneWhen 是可选的完成判定描述,用来给 execute 阶段提供最小退出条件;
// 3. 这里仍然保持“自然语言优先”,不把 plan step 过度结构化。
type PlanStep struct {
Content string `json:"content"`
DoneWhen string `json:"done_when,omitempty"`
}
// Normalize 统一清洗 plan step 中的字符串字段。
func (s *PlanStep) Normalize() {
if s == nil {
return
}
s.Content = strings.TrimSpace(s.Content)
s.DoneWhen = strings.TrimSpace(s.DoneWhen)
}
// Validate 校验单条 plan step 的最小合法性。
func (s *PlanStep) Validate() error {
if s == nil {
return fmt.Errorf("plan step 不能为空")
}
s.Normalize()
if s.Content == "" {
return fmt.Errorf("plan step.content 不能为空")
}
return nil
}