Version: 0.9.46.dev.260427
后端: 1. taskclass 执行闭环继续收紧——Plan / Execute 全面切到“最小工具闭环”视角,明确学习目标/总节数/禁排时段/排除星期默认停留 taskclass 域;未给日期范围时禁止擅自补 start_date/end_date,upsert_task_class 重试前先做写前检查并区分“内部表示修正”与“必须追问用户”的关键时间事实 2. QuickTask / TaskQuery 轻量链路继续收敛——新增 model/taskquery_contract.go 统一查询协议,QuickTaskDeps / start.go 改用 model 层参数;删除 query_tasks / quick_note_create 旧工具实现,避免任务查询与随口记再回流 execute 工具链 3. schedule 微调工具继续瘦身——下线 spread_even / min_context_switch 及其复合规划逻辑,清理 analyze_load / analyze_subjects / analyze_context / analyze_tolerance 等历史能力;execute 顺序策略收敛为局部 move / swap,提示词与工具目录仅暴露当前真实可用工具 4. 执行与时间线体验补齐——execute 为流式 speak 补发归一化尾部,避免 deliver 文案黏连;前端时间线新增 interrupt / status 协议识别、工具事件归并与状态过滤,减少 ToolTrace 重复和会话重建误判 前端: 5. AssistantPanel 适配新版 timeline extra 事件——schedule_agent.ts 补齐 interrupt / status kind,工具调用与结果按摘要/参数/工具名合并,恢复历史时不再把协议事件误判成用户消息
This commit is contained in:
@@ -90,7 +90,7 @@ func buildExecutePromptWithFormatGuard(base string) string {
|
||||
输出协议硬约束:
|
||||
1. 只输出当前 action 真正需要的字段;不要输出空字符串、空对象、空数组或 null 占位。
|
||||
2. tool_call 只能是 {"name":"工具名","arguments":{...}};不能写 parameters,也不能一次输出多个 tool_call。
|
||||
3. action=ask_user / confirm 时,标签后必须有自然语言正文;action=continue 可为空。
|
||||
3. action=ask_user / confirm 时,标签后必须有自然语言正文;action=continue 可为空,但只允许配合读工具或纯思考,不能携带任何写工具。
|
||||
4. action=done 时不要携带 tool_call;action=next_plan / done 时,goal_check 必须是字符串。
|
||||
5. 只有 action=abort 时才允许输出 abort 字段。
|
||||
6. <SMARTFLOW_DECISION> 标签内只放 JSON,不要放自然语言。
|
||||
@@ -111,14 +111,27 @@ func buildExecuteStrictJSONUserPrompt() string {
|
||||
执行提醒:
|
||||
- JSON 中不要包含 speak 字段;给用户看的话放在 </SMARTFLOW_DECISION> 标签之后
|
||||
- 不要在 <SMARTFLOW_DECISION> 标签之前输出任何文字;哪怕只有一句“我先看下”也不行
|
||||
- 日程写工具(place/move/swap/batch_move/unplace)一律走 action=confirm
|
||||
- 任何写工具都一律走 action=confirm,包括 upsert_task_class 与日程写工具(place/move/swap/batch_move/unplace);哪怕只是“按 validation.issues 重试一次”,也不能输出 continue + 写工具
|
||||
- 若当前处于粗排后主动优化专用模式,先调 analyze_health,再直接从 decision.candidates 里选一个合法候选去执行;不要自行发明新的全窗搜索步骤
|
||||
- 若读工具结果与已知事实明显冲突,先修正参数并重查一次,再决定是否 ask_user
|
||||
- 不要连续两轮调用“同一读工具 + 等价 arguments”;上一轮已成功返回时,下一轮必须换工具、进入 confirm,或明确说明阻塞
|
||||
- 若上下文已明确“当前未收到微调偏好,本轮先收口”,请直接输出 action=done
|
||||
- web_search 仅用于通用学习资料补充,不可用于考试时间、DDL、个人时段等时间字段填充
|
||||
- 任何写工具在真正输出 action=confirm 前,都必须先做一次“写前检查”:确认参数已齐全、格式合法、业务前提已满足;若尚未通过检查,就先补齐/归一/生成/ask_user,不要把 validation 失败当成正常探索路径
|
||||
- upsert_task_class 若返回 validation.ok=false,必须先按 validation.issues 补齐,再重试;禁止直接 done
|
||||
- 对 upsert_task_class,写前至少检查:mode=auto 时日期边界是否已满足;subject_type / difficulty_level / cognitive_intensity 是否齐;difficulty_level 是否已映射到合法枚举;items 是否非空且顺序内容已生成;config 中已知约束字段是否已落到合法格式
|
||||
- 若像 items 这种内容本就由当前轮模型负责生成,就应先把内容生成齐、顺序排好,再写入;不要先写一个 items 为空的 taskclass 去让 validation 提醒你补内容
|
||||
- 处理 validation.issues 时先分类:若是用户关键信息确实缺失,才 action=ask_user;若是 schema 字段名、字段位置、内部索引、枚举值、日期格式、工具语义映射等内部表示问题,应静默改参后直接重试,不要把底层表示教学抛给用户
|
||||
- 像 config.excluded_slots 的半天块索引映射,默认属于内部表示修正:你应自己把“第1-2节 / 第11-12节”换算成合法块索引,不要为此 ask_user,不要长篇解释底层表示
|
||||
- 当前时间锚点只用于解析用户已经明确说出的相对时间(如“今天开始”“两周内”“下周一前”),不能反过来把“现在是今天”当成用户已经同意从今天开始,更不能据此默认生成 start_date / end_date
|
||||
- 像 auto 模式缺 start_date/end_date 这类问题,先检查当前对话、历史、记忆、已知工具结果里是否已经出现可用日期;若已出现就静默补齐并重试,只有在上下文里确实没有时再 ask_user
|
||||
- 若当前是首次创建/修正 taskclass,且上下文里并没有用户明确给出的开始日期、结束日期、日期范围、完成期限、或可直接换算出的相对时间承诺,就不要擅自写 start_date / end_date;此时若工具闭环确实要求这些字段,必须 ask_user
|
||||
- 对 taskclass 来说,以下属于必须 ask_user 的关键信息:start_date、end_date、明确的日期范围、明确的开始时间承诺、明确的完成期限;这些会决定任务类的真实时间边界,不能由模型自行拍板
|
||||
- subject_type / difficulty_level / cognitive_intensity 是任务类语义画像必填;优先静默推断,只有确实无法判断时再 ask_user
|
||||
- 学习目标、总节数、难度、节次偏好、禁排时段、排除星期、内容拆分授权,这些默认都只是 taskclass 语义;不要因为信息完整就自动切进 schedule
|
||||
- 只有用户明确要求“排进日程 / 给出具体时间安排 / 现在就排一版”时,才允许 context_tools_add domain="schedule"、触发 rough_build,或继续 schedule 链路
|
||||
- 例:“我要复习离散数学,基础较差,大概学 8 节课,不要早上第 1-2 节和晚上第 11-12 节,周末也不想学,每节课内容你自己来”——应先生成或更新 taskclass,而不是主动排进日程
|
||||
- 仅 upsert_task_class 成功不代表已开始排程;若未触发 rough_build 且未调用任何日程修改工具,禁止承诺“接下来会自动排程”
|
||||
- 当前轮目标若是创建/修正 taskclass,就优先把 taskclass 静默闭环;除非真缺用户关键信息,否则不要把主要篇幅花在解释工具内部约束上
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -231,9 +231,9 @@ func buildExecuteMessage3(state *newagentmodel.CommonState, ctx *newagentmodel.C
|
||||
|
||||
if state != nil {
|
||||
if state.AllowReorder {
|
||||
lines = append(lines, "- 顺序策略:用户已明确允许打乱顺序,可在必要时使用 min_context_switch。")
|
||||
lines = append(lines, "- 顺序策略:用户已明确允许打乱顺序,但当前主链不再提供顺序重排工具,请优先使用 move/swap 做局部调整。")
|
||||
} else {
|
||||
lines = append(lines, "- 顺序策略:默认保持 suggested 相对顺序,禁止调用 min_context_switch。")
|
||||
lines = append(lines, "- 顺序策略:默认保持 suggested 相对顺序,仅做局部 move/swap 调整。")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ func buildExecuteMessage3(state *newagentmodel.CommonState, ctx *newagentmodel.C
|
||||
//
|
||||
// 1. 这里只给模型最低必要的参数和返回值感知,不重复塞完整 schema JSON。
|
||||
// 2. 对复杂工具额外给一条调用示例,降低“参数字段写错”的概率。
|
||||
// 3. P1 阶段隐藏 min_context_switch,避免模型误用已禁能力。
|
||||
// 3. 这里只展示当前真实可用工具,避免历史残留能力继续污染工具面。
|
||||
func renderExecuteToolCatalogCompact(ctx *newagentmodel.ConversationContext, state *newagentmodel.CommonState) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
@@ -286,10 +286,6 @@ func renderExecuteToolCatalogCompact(ctx *newagentmodel.ConversationContext, sta
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if shouldHideMinContextSwitchForP1(state, name) {
|
||||
continue
|
||||
}
|
||||
|
||||
index++
|
||||
desc := strings.TrimSpace(schemaItem.Desc)
|
||||
if desc == "" {
|
||||
@@ -329,7 +325,6 @@ func shouldRenderExecuteToolReturnSample(toolName string) bool {
|
||||
"web_fetch",
|
||||
"analyze_health",
|
||||
"analyze_rhythm",
|
||||
"analyze_tolerance",
|
||||
"upsert_task_class":
|
||||
return true
|
||||
default:
|
||||
@@ -340,7 +335,7 @@ func shouldRenderExecuteToolReturnSample(toolName string) bool {
|
||||
func renderExecuteToolCallHint(toolName string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(toolName)) {
|
||||
case "upsert_task_class":
|
||||
return `{"name":"upsert_task_class","arguments":{"task_class":{"name":"线性代数复习","mode":"auto","start_date":"2026-06-01","end_date":"2026-06-20","subject_type":"quantitative","difficulty_level":"high","cognitive_intensity":"high","config":{"total_slots":8,"strategy":"steady","allow_filler_course":false,"excluded_slots":[1,11],"excluded_days_of_week":[6,7]},"items":[{"order":1,"content":"行列式定义与基础计算"},{"order":2,"content":"矩阵及其运算规则"},{"order":3,"content":"逆矩阵与矩阵的秩"}]}}}`
|
||||
return `仅当用户或上下文已明确给出日期范围时,才允许写入 start_date/end_date;写前先检查 difficulty_level 已归一为 low/medium/high,items 已非空且内容顺序已生成完成:{"name":"upsert_task_class","arguments":{"task_class":{"name":"线性代数复习","mode":"auto","start_date":"2026-06-01","end_date":"2026-06-20","subject_type":"quantitative","difficulty_level":"high","cognitive_intensity":"high","config":{"total_slots":8,"strategy":"steady","allow_filler_course":false,"excluded_slots":[1,6],"excluded_days_of_week":[6,7]},"items":[{"order":1,"content":"行列式定义与基础计算"},{"order":2,"content":"矩阵及其运算规则"},{"order":3,"content":"逆矩阵与矩阵的秩"}]}}}`
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -375,10 +370,6 @@ func renderExecuteToolReturnHint(toolName string) (returnType string, sample str
|
||||
return returnType, "交换完成:[35]... ↔ [36]..."
|
||||
case "batch_move":
|
||||
return returnType, "批量移动完成,2 个任务全部成功。"
|
||||
case "spread_even":
|
||||
return returnType, "均匀化调整完成:共处理 6 个任务,候选坑位 24 个。"
|
||||
case "min_context_switch":
|
||||
return returnType, "最少上下文切换重排完成:共处理 6 个任务,上下文切换次数 5 -> 2。"
|
||||
case "unplace":
|
||||
return returnType, "已将 [35]... 移除,恢复为待安排状态。"
|
||||
case "web_search":
|
||||
@@ -389,8 +380,6 @@ func renderExecuteToolReturnHint(toolName string) (returnType string, sample str
|
||||
return "string(JSON字符串)", `{"tool":"analyze_health","success":true,"metrics":{"rhythm":{"avg_switches_per_day":1.1,"max_switch_count":4,"heavy_adjacent_days":2,"same_type_transition_ratio":0.58,"block_balance":0,"fragmented_count":0,"compressed_run_count":0},"tightness":{"locally_movable_task_count":3,"avg_local_alternative_slots":1.7,"cross_class_swap_options":1,"forced_heavy_adjacent_days":0,"tightness_level":"tight"},"can_close":false},"decision":{"should_continue_optimize":true,"recommended_operation":"swap","primary_problem":"第4天存在高认知背靠背","candidates":[{"candidate_id":"swap_35_44","tool":"swap","arguments":{"task_a":35,"task_b":44}}]}}`
|
||||
case "analyze_rhythm":
|
||||
return "string(JSON字符串)", `{"tool":"analyze_rhythm","success":true,"metrics":{"overview":{"avg_switches_per_day":3.4,"max_switch_day":4,"max_switch_count":5,"heavy_adjacent_days":2,"long_high_intensity_days":1,"same_type_transition_ratio":0.42}}}`
|
||||
case "analyze_tolerance":
|
||||
return "string(JSON字符串)", `{"tool":"analyze_tolerance","success":true,"metrics":{"overall":{"fragmentation_rate":0.52,"days_without_buffer":1}}}`
|
||||
case "upsert_task_class":
|
||||
return "string(JSON字符串)", `{"tool":"upsert_task_class","success":true,"task_class_id":123,"created":true,"validation":{"ok":true,"issues":[]},"error":"","error_code":""}`
|
||||
default:
|
||||
@@ -564,9 +553,8 @@ func hasExecuteRoughBuildDone(ctx *newagentmodel.ConversationContext) bool {
|
||||
|
||||
func renderExecuteLatestAnalyzeSummary(ctx *newagentmodel.ConversationContext) string {
|
||||
record, ok := findExecuteLatestToolRecord(ctx, map[string]struct{}{
|
||||
"analyze_health": {},
|
||||
"analyze_rhythm": {},
|
||||
"analyze_tolerance": {},
|
||||
"analyze_health": {},
|
||||
"analyze_rhythm": {},
|
||||
})
|
||||
if !ok {
|
||||
return ""
|
||||
@@ -582,8 +570,6 @@ func renderExecuteLatestMutationSummary(ctx *newagentmodel.ConversationContext)
|
||||
"batch_move": {},
|
||||
"unplace": {},
|
||||
"queue_apply_head_move": {},
|
||||
"spread_even": {},
|
||||
"min_context_switch": {},
|
||||
})
|
||||
if !ok {
|
||||
return ""
|
||||
@@ -790,14 +776,13 @@ func renderTaskClassUpsertRuntime(state *newagentmodel.CommonState) string {
|
||||
}
|
||||
}
|
||||
if !state.TaskClassUpsertLastSuccess {
|
||||
lines = append(lines, "- 写前最少检查项:mode=auto 的 start_date/end_date、subject_type/difficulty_level/cognitive_intensity、difficulty_level 合法枚举、items 非空且内容已生成、config 约束字段合法。")
|
||||
lines = append(lines, "- 先判断当前 issues 属于哪一类:若是 schema 字段名、字段位置、半天块索引、枚举值、日期格式、工具语义映射等内部表示问题,直接静默改参重试。")
|
||||
lines = append(lines, "- 若 issue 指向 start_date/end_date 等字段,先检查当前对话、历史、记忆、最近工具结果里是否已出现可用值;只有确实没有时再 ask_user。")
|
||||
lines = append(lines, "- 若缺的是 start_date/end_date/日期范围/开始日期承诺/完成期限,而这些值并未在上下文中出现,就必须 ask_user;不能把当前日期或默认周期当成用户已同意的时间边界。")
|
||||
lines = append(lines, "- 若 issue 像 difficulty_level 非法、items 为空、约束字段格式不合法,就先在本轮静默归一/补齐/生成,再 confirm 重试;不要把 validation 当试错器。")
|
||||
lines = append(lines, "- 若再次调用 upsert_task_class,动作必须是 confirm,不能输出 continue + tool_call。")
|
||||
lines = append(lines, "- 在 issues 处理完之前,不要用 done 收口。")
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func shouldHideMinContextSwitchForP1(state *newagentmodel.CommonState, toolName string) bool {
|
||||
if strings.TrimSpace(toolName) != "min_context_switch" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ func renderExecuteNextStepHintV2(
|
||||
if roughBuildDone {
|
||||
return `先激活 schedule 业务域;当前是粗排后的微调场景,通常至少需要 mutation+analyze。若要按统一条件逐个处理一批任务,再加 packs=["queue"]。`
|
||||
}
|
||||
return `先判断当前任务属于哪个业务域,再用 context_tools_add 激活对应工具。`
|
||||
return `先判断当前任务属于哪个业务域,再用 context_tools_add 激活对应工具。若用户只是在描述学习目标、总节数、难度、节次偏好、禁排时段、排除星期、内容拆分授权,默认先走 taskclass;只有用户明确要求“排进日程 / 给出具体时间安排 / 现在就排一版”时,才切 schedule。`
|
||||
}
|
||||
|
||||
if activeDomain == "schedule" &&
|
||||
@@ -97,7 +97,7 @@ func renderExecuteNextStepHintV2(
|
||||
if activeDomain == "taskclass" &&
|
||||
state.TaskClassUpsertLastTried &&
|
||||
!state.TaskClassUpsertLastSuccess {
|
||||
return `先根据 validation.issues 补齐缺失字段,再重试 upsert_task_class,不要直接收口。`
|
||||
return `先判断 validation.issues 是“用户缺信息”还是“内部表示修正”;能从上下文补的先静默补齐,再用 confirm 重试 upsert_task_class,不要继续解释底层约束,更不要直接收口。`
|
||||
}
|
||||
|
||||
return ""
|
||||
|
||||
@@ -178,9 +178,11 @@ func buildExecuteCoreMinPack() executeRulePack {
|
||||
Name: executeRulePackCoreMin,
|
||||
Content: strings.TrimSpace(fmt.Sprintf(`
|
||||
- 当前时间锚点:%s。涉及“今天/明天/本周”等相对时间时,先按该锚点换算。
|
||||
- 用户意图优先:只推进用户当前明确要求;未明确部分优先 ask_user。
|
||||
- 用户意图优先:只推进用户当前明确要求;未明确部分先看能否从当前对话、历史、记忆、已知工具结果里静默补齐,只有补不出来时再 ask_user。
|
||||
- 域切换要克制:用户若只是在描述学习目标、总节数、难度、节次偏好、禁排时段、排除星期、内容拆分授权,这默认仍是 taskclass,不要主动切到 schedule。
|
||||
- 只有用户明确要求“排进日程 / 给出具体时间安排 / 现在就排一版”时,才允许进入 schedule 或触发粗排。
|
||||
- 先事实后动作:优先读工具补齐事实,再决定下一步。
|
||||
- 只要决定调用 place/move/swap/batch_move/unplace 这类写工具,就必须输出 action=confirm;continue + 写工具无效。
|
||||
- 只要决定调用任何写工具,就必须输出 action=confirm;continue + 写工具无效。这个纪律同样适用于 upsert_task_class 的每一次重试。
|
||||
- 输出格式固定:先 <SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>,再输出用户可见正文。`,
|
||||
buildExecuteNowAnchorLine())),
|
||||
}
|
||||
@@ -197,9 +199,9 @@ func buildExecuteSafetyHardPack() executeRulePack {
|
||||
Name: executeRulePackSafetyHard,
|
||||
Content: strings.TrimSpace(`
|
||||
- 严禁伪造工具结果;若新结果与既有事实冲突,先重查一次再决定。
|
||||
- P1 阶段禁止调用 min_context_switch。
|
||||
- 工具参数必须严格使用 schema 字段名,禁止自造别名。
|
||||
- JSON 只保留当前 action 必需字段;不要输出空字符串、空对象、空数组或 null 占位。
|
||||
- P1 阶段禁止调用 min_context_switch。
|
||||
- 连续两轮同类读查询后,必须转执行 / ask_user / 明确说明阻塞,不能无限空转。`),
|
||||
}
|
||||
}
|
||||
@@ -210,6 +212,7 @@ func buildExecuteContextProtocolPack() executeRulePack {
|
||||
Content: strings.TrimSpace(`
|
||||
- msg0 动态区初始仅保留 context_tools_add / context_tools_remove。
|
||||
- 需要业务工具前先 context_tools_add:排程用 domain="schedule",任务类写入用 domain="taskclass"。
|
||||
- 切 schedule 前先判断用户是否明确提出排程诉求;若只是描述任务类内容与排程偏好,先留在 taskclass。
|
||||
- schedule 可选 packs=["mutation","analyze","detail_read","deep_analyze","queue","web"];core 固定注入,不要显式传 core。
|
||||
- 只在业务方向切换时再 remove;done 后的动态区清理由系统自动完成,不必手动 remove。
|
||||
- 如果目标工具当前不在可用列表,先 add 对应 domain / packs,再继续执行。`),
|
||||
@@ -232,7 +235,7 @@ func buildExecuteModeReActPack() executeRulePack {
|
||||
Name: executeRulePackModeReAct,
|
||||
Content: strings.TrimSpace(`
|
||||
- 当前为自由执行(ReAct)模式:可自主决定 continue / confirm / ask_user / done / abort。
|
||||
- 如果关键事实无法通过工具补齐,优先 ask_user,不做猜测落库。
|
||||
- 如果关键事实既无法通过工具补齐,也无法从当前对话、历史、记忆中补齐,才 ask_user;不要把本可静默修正的内部表示问题转嫁给用户。
|
||||
- 自主推进时要小步快跑,优先闭合当前局部问题,不要发散成大范围开放搜索。`),
|
||||
}
|
||||
}
|
||||
@@ -242,6 +245,8 @@ func buildExecuteSchedulePack() executeRulePack {
|
||||
Name: executeRulePackDomainSchedule,
|
||||
Content: strings.TrimSpace(`
|
||||
- 当前业务域为 schedule:只处理当前目标任务类,不重排无关内容。
|
||||
- 只有用户已明确要求“排进日程 / 给出具体时间安排 / 现在就排一版”时,才应停留或切入 schedule。
|
||||
- 单纯看到总节数、难度、节次偏好、禁排时段、排除星期,不足以进入 schedule;这些默认仍属于 taskclass 约束。
|
||||
- existing 只作事实参考;真正可调对象优先看 suggested。
|
||||
- 同任务类内部顺序必须保持,任何越过前驱/后继边界的移动都会被写工具拒绝。`),
|
||||
}
|
||||
@@ -281,8 +286,28 @@ func buildExecuteTaskClassPack() executeRulePack {
|
||||
Name: executeRulePackDomainTaskClass,
|
||||
Content: strings.TrimSpace(`
|
||||
- taskclass 域只负责生成或修正任务类,不代表已经开始排程。
|
||||
- 学习目标、总节数、难度、节次偏好、禁排时段、排除星期、内容拆分授权,默认都先落在 taskclass 语义中。
|
||||
- 例:“我要复习离散数学,基础较差,大概学 8 节课,不要早上第 1-2 节和晚上第 11-12 节,周末也不想学,每节课内容你自己来”——应进入或停留 taskclass,而不是主动切 schedule,也通常不需要 ask_user。
|
||||
- 在真正调用 upsert_task_class 前,必须先做一轮写前检查;只有当参数已齐全、格式合法、业务前提已满足时,才允许输出 confirm。
|
||||
- 不要把 validation 失败当成正常试错器;validation 只用于兜底发现漏项,不应成为“先乱写一次看看后端报什么”的主流程。
|
||||
- upsert_task_class 写前最少检查项:
|
||||
1. mode=auto 时,task_class 顶层 start_date/end_date 是否已经满足。
|
||||
2. subject_type / difficulty_level / cognitive_intensity 是否齐全。
|
||||
3. difficulty_level 是否已归一到合法枚举 low/medium/high。
|
||||
4. items 是否非空,且顺序与内容是否已在当前轮生成完成。
|
||||
5. config 中已知约束字段是否已是合法格式,例如 excluded_slots 半天块索引、excluded_days_of_week 取值范围、total_slots/strategy 等。
|
||||
- 若像 items 这种内容本就由当前轮模型负责生成,就应先生成齐再写,不要把空 items 提交给 validation 去提醒你补课表内容。
|
||||
- upsert_task_class 若返回 validation.ok=false,必须先处理 validation.issues,再考虑重试或 ask_user。
|
||||
- 先区分 issue 类型:schema 字段名、字段位置、内部索引、枚举值、日期格式、工具语义映射,属于内部表示修正,应静默改参后直接重试;真正缺少用户关键信息时,才 ask_user。
|
||||
- taskclass 里的“关键信息缺失”要收窄定义:真正必须 ask_user 的,是会决定任务类真实时间边界/时间承诺的字段,而不是内部表示问题。
|
||||
- 必须 ask_user 的时间参数/条件包括:start_date、end_date、明确日期范围、明确开始日期承诺、明确完成期限;如果这些信息在当前对话、历史、记忆里都不存在,就不能由你自行拍板。
|
||||
- 当前时间锚点只能用来解析用户已经说出的相对时间;若用户没说“今天开始 / 本周内 / 两周内 / 下周前”这类时间承诺,不能因为“今天是 2026-04-27”就默认 start_date=今天,也不能默认补一个 end_date。
|
||||
- 禁排时段、排除星期、总节数、难度、内容拆分授权,不等于用户已经给出了日期范围;这些信息再完整,也不能单独推出 start_date/end_date。
|
||||
- config.excluded_slots 使用 1~6 的半天块索引;像“第1-2节”应映射到 1,“第11-12节”应映射到 6。这类换算由你内部处理,不要把底层表示解释成主要回复内容。
|
||||
- 若 validation 指出 auto 模式缺 start_date/end_date,先检查当前对话、历史、记忆里是否已有日期范围;已有就静默补齐并重试,只有确实没有时再 ask_user。
|
||||
- subject_type / difficulty_level / cognitive_intensity 是任务类语义画像必填项;优先静默推断,只有确实无法判断时再 ask_user。
|
||||
- 只要再次调用 upsert_task_class,无论是首次写入还是失败后的重试,都必须走 action=confirm。
|
||||
- 当前轮目标若是创建/修正 taskclass,应优先追求静默闭环,不要把主要篇幅花在教育用户理解工具内部约束上。
|
||||
- excluded_slots 取值应与系统节次定义一致;excluded_days_of_week 使用 1~7 表示周一到周日。`),
|
||||
}
|
||||
}
|
||||
@@ -301,6 +326,13 @@ func buildExecuteTaskClassRetryMicroPack() executeRulePack {
|
||||
Name: executeRulePackMicroTaskRetry,
|
||||
Content: strings.TrimSpace(`
|
||||
- 最近一次 upsert_task_class 失败时,优先围绕 validation.issues 修补。
|
||||
- 先回到“写前检查”再决定是否重试:确认 mode=auto 的日期边界、difficulty_level 合法枚举、subject_type/difficulty_level/cognitive_intensity 齐全、items 非空且已生成、config 约束字段合法。
|
||||
- 先判断 issue 是“用户关键信息缺失”还是“内部表示/工具语义修正”:前者才 ask_user,后者直接静默改参重试。
|
||||
- 如果 issue 最终落到 start_date / end_date / 日期范围 / 开始日期承诺 / 完成期限,而这些值在当前对话、历史、记忆、最近工具结果里都没有出现,就必须 ask_user;不要再拿当前时间锚点去替用户补。
|
||||
- 若用户只给了禁排时段、排除星期、总节数、难度、内容拆分授权,这仍不构成日期范围;不要把这类偏好误判成已经拿到了可写入的 start_date/end_date。
|
||||
- 如果 issue 像 difficulty_level 非法、items 为空、约束字段格式不合法,这都属于“写前本应整理好”的问题:应先在本轮静默归一/补齐/生成,再 confirm 重试,不要继续拿 validation 探路。
|
||||
- 若 issue 所需字段已在当前对话、历史、记忆或最近工具结果里出现,优先静默补齐,不要多轮解释后再写。
|
||||
- 重试 upsert_task_class 时仍然必须输出 action=confirm;不要输出 continue + tool_call。
|
||||
- 问题未解决前,不要用 done 假装收口;要么重试,要么 ask_user 补关键信息。`),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,24 @@ const planSystemPromptCore = `
|
||||
|
||||
最高优先级规则:
|
||||
1. 意图边界:只规划用户当前明确要求,禁止擅自扩展后续动作。
|
||||
2. 事实边界:禁止伪造工具调用和执行结果。
|
||||
2. 事实边界:禁止伪造工具调用、工具结果、外部事实和执行结论。
|
||||
3. 规划视角:先判断“最小工具闭环”再写步骤;不要先写抽象语义步骤,再让 execute 自己猜该怎么落工具。
|
||||
|
||||
规划规则:
|
||||
1. 每轮只做一次决策(continue / ask_user / plan_done)。
|
||||
2. 信息足够时优先 plan_done;信息不足时才 ask_user,且只问最小必要问题。
|
||||
3. action=plan_done 时必须返回完整 plan_steps(不是增量)。
|
||||
4. plan_steps 使用自然语言描述目标与完成判定,不写执行结果。
|
||||
5. 若意图满足批量排程识别条件,可在 plan_done 时附加 needs_rough_build 与 task_class_ids。
|
||||
6. 可在 plan_done 时附加 context_hook(执行阶段注入建议);规划阶段禁止调用 context_tools_add/remove。`
|
||||
4. plan_steps 必须优先按“工具闭环”拆步,而不是按抽象语义拆步。
|
||||
5. 若一个目标可由单个工具闭环完成,优先生成单步计划;禁止把本可直接执行的工具动作,拆成“先分析、再设计、再确认、再执行”这类抽象多步。
|
||||
6. 每个 step 的 done_when 都应尽量贴近可观察证据,优先锚定工具回执、校验结果、查询 observation,而不是“方案完整”“分析完成”“用户应该满意”这类抽象描述。
|
||||
7. 只有单工具无法闭环,或当前步骤天然依赖上一步 observation / 用户补充信息时,才允许拆成多步。
|
||||
8. 先判断为完成目标“首个可执行闭环”最小需要的 domain / packs,再围绕这些工具写 steps,最后再产出 context_hook。context_hook 不是顺手填空,而是计划的自然推导结果。
|
||||
9. context_hook 只有一份,供 execute 首轮激活工具域使用;它应对齐“第一个可执行 step”的最小工具需求,而不是试图一次覆盖整份计划的所有后续能力。
|
||||
10. 用户若只是在描述学习目标、总节数、难度、节次偏好、禁排时段、排除星期、内容拆分授权,这默认仍是 taskclass 语义,不等于已经要求排进日程。
|
||||
11. 只有用户明确要求“排进日程 / 给出具体时间安排 / 现在就排一版”时,才允许把目标规划为 schedule;否则优先停留在 taskclass。
|
||||
12. 若意图满足批量排程识别条件,可在 plan_done 时附加 needs_rough_build 与 task_class_ids;但仅当用户明确提出排程请求时才允许这样做。
|
||||
13. 可在 plan_done 时附加 context_hook(执行阶段注入建议);若用户尚未明确要求排程,则 context_hook.domain 不得写 schedule。规划阶段禁止调用 context_tools_add/remove。
|
||||
14. 例:“我要复习离散数学,基础较差,大概学 8 节课,不要早上第 1-2 节和晚上第 11-12 节,周末也不想学,每节课内容你自己来”——这应判定为 taskclass 设计;planner 应优先理解为 taskclass 域可闭环的请求,通常单步或极少步即可,不应抽象拆成多轮。`
|
||||
|
||||
// BuildPlanSystemPrompt 返回规划阶段系统提示词。
|
||||
func BuildPlanSystemPrompt() string {
|
||||
@@ -33,10 +42,6 @@ func BuildPlanSystemPrompt() string {
|
||||
}
|
||||
|
||||
// BuildPlanMessages 组装规划阶段的 messages。
|
||||
//
|
||||
// 1. 规划阶段只保留 Planner 专用规则,跳过通用人格底座,避免角色指令冲突。
|
||||
// 2. msg1 展示真实对话,msg2 展示规划工作区,msg3 仅给最小执行指令与用户本轮输入。
|
||||
// 3. 工具目录使用轻量版,仅提供“有什么工具”,不注入执行态大段参数示例。
|
||||
func BuildPlanMessages(state *newagentmodel.CommonState, ctx *newagentmodel.ConversationContext, userInput string) []*schema.Message {
|
||||
return buildUnifiedStageMessages(
|
||||
ctx,
|
||||
@@ -58,6 +63,9 @@ func BuildPlanUserPrompt(state *newagentmodel.CommonState, userInput string) str
|
||||
|
||||
sb.WriteString("请继续当前任务规划,只输出一组 SMARTFLOW_DECISION 决策。\n")
|
||||
sb.WriteString("请基于最近对话与规划工作区推进,不要重复已有计划内容。\n")
|
||||
sb.WriteString("请先判断最小工具闭环,再决定是否需要拆步;能单步就单步。\n")
|
||||
sb.WriteString("若需要 context_hook,请先根据第一个可执行 step 所需的最小 domain / packs 推导,再写入 hook。\n")
|
||||
sb.WriteString("禁止把本可直接落工具的动作,抽象写成“完成设计 / 确认方案 / 整理思路”之类空步骤。\n")
|
||||
sb.WriteString("输出格式与字段约束严格按 msg0 协议执行。\n")
|
||||
|
||||
trimmedInput := strings.TrimSpace(userInput)
|
||||
@@ -72,28 +80,38 @@ func BuildPlanUserPrompt(state *newagentmodel.CommonState, userInput string) str
|
||||
|
||||
// BuildPlanDecisionContractText 返回规划阶段的输出协议说明。
|
||||
func BuildPlanDecisionContractText() string {
|
||||
return strings.TrimSpace(fmt.Sprintf(`
|
||||
输出协议(唯一口径):
|
||||
1. 先输出:<SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>
|
||||
2. 再输出:给用户看的自然语言正文
|
||||
|
||||
JSON 字段:
|
||||
- action:只能是 %s / %s / %s
|
||||
- reason:给后端和日志看的简短说明
|
||||
- complexity:只能是 simple / moderate / complex
|
||||
- plan_steps:仅当 action=%s 时允许返回,且必须是完整计划
|
||||
- plan_steps[].content:步骤正文,必填
|
||||
- plan_steps[].done_when:可选,建议写完成判定
|
||||
- needs_rough_build:仅满足粗排识别条件时为 true,否则省略
|
||||
- task_class_ids:needs_rough_build=true 时必填,从上下文读取
|
||||
- context_hook:可选,仅用于给 execute 阶段提供注入建议
|
||||
- context_hook.domain:schedule / taskclass
|
||||
- context_hook.packs:string 数组,可选;core 固定注入,不要填写 core
|
||||
- context_hook.reason:可选,说明为何建议该注入
|
||||
|
||||
注意:
|
||||
- JSON 中不要包含 speak 字段
|
||||
- 不要在 planning 阶段调用任何工具(包括 context_tools_add/remove)`,
|
||||
return strings.TrimSpace(fmt.Sprintf(strings.Join([]string{
|
||||
"输出协议(唯一口径):",
|
||||
"1. 先输出:<SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>",
|
||||
"2. 再输出:给用户看的自然语言正文",
|
||||
"",
|
||||
"JSON 字段:",
|
||||
"- action:只能是 %s / %s / %s",
|
||||
"- reason:给后端和日志看的简短说明",
|
||||
"- complexity:只能是 simple / moderate / complex",
|
||||
"- plan_steps:仅当 action=%s 时允许返回,且必须是完整计划",
|
||||
"- plan_steps[].content:步骤正文,必填",
|
||||
"- plan_steps[].done_when:可选;若提供,必须尽量写成 observation / 工具回执可直接证明的完成判定",
|
||||
"- needs_rough_build:仅满足粗排识别条件时为 true,否则省略",
|
||||
"- task_class_ids:needs_rough_build=true 时必填,从上下文读取",
|
||||
"- context_hook:可选,仅用于给 execute 阶段提供注入建议",
|
||||
"- context_hook.domain:schedule / taskclass",
|
||||
"- context_hook.packs:string 数组,可选;core 固定注入,不要填入 core",
|
||||
"- context_hook.reason:可选,说明为何建议该注入",
|
||||
"",
|
||||
"注意:",
|
||||
"- JSON 中不要包含 speak 字段",
|
||||
"- 不要在 planning 阶段调用任何工具(包括 context_tools_add/remove)",
|
||||
"- 写 plan_steps 前,先判断当前目标能否由单个工具或单个紧凑工具闭环完成;若能,优先输出单步计划",
|
||||
"- 禁止把本可直接执行的工具动作,拆成抽象语义步骤,例如“先分析需求”“完成设计”“确认方案完整”",
|
||||
"- 多步计划只应用于:上一步 observation 决定下一步;或确实需要先问用户补关键事实;或目标天然跨域",
|
||||
"- context_hook 必须从 plan_steps 自然推导:优先对齐第一个可执行 step 的最小 domain / packs,不要脱离步骤单独拍脑袋生成",
|
||||
"- 若用户只给出学习目标、总节数、难度、节次偏好、禁排时段、排除星期、内容拆分授权,这默认属于 taskclass 设计;不要因此写 needs_rough_build=true,也不要把 context_hook.domain 设为 schedule",
|
||||
"- 只有用户明确要求\"排进日程 / 给出具体时间安排 / 现在就排一版\"时,才允许输出 needs_rough_build=true 或 context_hook.domain=schedule",
|
||||
"- 若首步本质上是任务类写入或修正,context_hook 通常应对齐 taskclass;若首步需要 schedule 查询/分析/修改,再按最小 packs 推导 schedule hook",
|
||||
"- step 的 done_when 应优先锚定:查询结果已返回、validation 已通过、写工具已成功回执、粗排标记已产生、分析结论已可直接支撑下一步",
|
||||
"- 例:\"我要复习离散数学,基础较差,大概学 8 节课,不要早上第 1-2 节和晚上第 11-12 节学习,周末也不想学,每节课内容你自己来\"——应规划为 taskclass,而不是 schedule,也通常不需要 ask_user",
|
||||
}, "\n"),
|
||||
newagentmodel.PlanActionContinue,
|
||||
newagentmodel.PlanActionAskUser,
|
||||
newagentmodel.PlanActionDone,
|
||||
|
||||
@@ -16,13 +16,14 @@ func buildPlanConversationMessage(ctx *newagentmodel.ConversationContext) string
|
||||
// buildPlanWorkspace 渲染 plan 节点自己的工作区。
|
||||
//
|
||||
// 设计说明:
|
||||
// 1. 这里只保留“规划真正需要知道的东西”:已有计划、当前步骤、task_class_ids、任务类约束;
|
||||
// 2. 不再复用通用胖状态摘要,避免把 execute / deliver 无关状态一起塞给 plan;
|
||||
// 3. 若当前没有正式计划,则明确告诉模型“从零开始规划”,避免继续误沿用旧上下文。
|
||||
// 1. 这里既保留“当前已有计划/任务类约束”,也显式补充“规划视角的工具摘要”;
|
||||
// 2. planner 需要先理解工具边界,才能把步骤收敛到最小闭环,而不是按抽象语义乱拆;
|
||||
// 3. 工具摘要不展开全量 schema,只提供规划真正需要的:负责什么、不负责什么、常见闭环、完成证据、域切换条件。
|
||||
func buildPlanWorkspace(state *newagentmodel.CommonState) string {
|
||||
lines := []string{"规划工作区:"}
|
||||
if state == nil {
|
||||
lines = append(lines, "- 当前缺少流程状态,请主要依据最近对话与本轮输入继续规划。")
|
||||
lines = append(lines, buildPlanToolPlanningSummary())
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
@@ -43,6 +44,7 @@ func buildPlanWorkspace(state *newagentmodel.CommonState) string {
|
||||
lines = append(lines, taskClassMeta)
|
||||
}
|
||||
|
||||
lines = append(lines, buildPlanToolPlanningSummary())
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
@@ -142,6 +144,76 @@ func renderPlanTaskClassMeta(state *newagentmodel.CommonState) string {
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// buildPlanToolPlanningSummary 生成“规划视角的工具摘要”。
|
||||
//
|
||||
// 步骤化说明:
|
||||
// 1. 先讲 domain:让 planner 先判断目标应该停留在哪个业务域;
|
||||
// 2. 再讲 schedule packs:让 planner 知道若进入 schedule,该选最小哪组能力;
|
||||
// 3. 最后讲 hook 推导规则:因为 context_hook 只有一份,必须和“首个可执行闭环”对齐。
|
||||
func buildPlanToolPlanningSummary() string {
|
||||
sections := []string{
|
||||
"规划视角的工具摘要:",
|
||||
buildPlanToolDomainTaskClassSummary(),
|
||||
buildPlanToolDomainScheduleSummary(),
|
||||
buildPlanToolPackSummary(),
|
||||
buildPlanContextHookSummary(),
|
||||
}
|
||||
return strings.Join(sections, "\n")
|
||||
}
|
||||
|
||||
func buildPlanToolDomainTaskClassSummary() string {
|
||||
lines := []string{
|
||||
"1. taskclass 域:",
|
||||
"- 负责什么:创建 / 更新任务类,沉淀学习目标、总节数、难度、节次偏好、禁排时段、排除星期、内容拆分授权、任务项结构。",
|
||||
"- 不负责什么:不给出具体日期/节次落位,不负责把任务真正排进日程。",
|
||||
"- 常见一步闭环:任务类设计或修正通常可由 taskclass 域单步闭环,核心写入动作为 upsert_task_class。",
|
||||
"- 何时停留在本域:用户仍在描述目标、偏好、约束、拆分方式,而不是要求现在排进日程。",
|
||||
"- 何时切到下一个域:只有用户明确要求“排进日程 / 给出具体时间安排 / 现在就排一版”,或当前目标本身已变成排程执行。",
|
||||
"- done_when 证据偏好:优先锚定 upsert_task_class 成功回执、validation.ok=true、validation.issues 已清空。",
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func buildPlanToolDomainScheduleSummary() string {
|
||||
lines := []string{
|
||||
"2. schedule 域:",
|
||||
"- 负责什么:查询日程现状、粗排、具体落位、局部移动/交换、批量同规则调整、排程健康分析。",
|
||||
"- 不负责什么:不凭空补考试时间、DDL、个人空闲、外部时间事实;这类信息拿不到时应 ask_user。",
|
||||
"- 常见一步闭环:单次查询通常一个读工具即可闭环;单次移动/交换/放置通常一个写工具即可闭环;局部分析通常一个 analyze 工具即可闭环。",
|
||||
"- 何时停留在本域:用户明确要求查询、安排、调整、优化当前日程。",
|
||||
"- 何时先回 taskclass:如果用户还在定义“学什么、学多少、怎么拆、哪些时段不要学”,而不是要求立刻排程,应先停留在 taskclass。",
|
||||
"- done_when 证据偏好:优先锚定查询 observation、写工具成功回执、rough_build_done 标记、analyze observation 已能直接支撑下一步。",
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func buildPlanToolPackSummary() string {
|
||||
lines := []string{
|
||||
"3. schedule packs 选择参考:",
|
||||
"- detail_read:查看总览、查询区间、看任务详情;适合“先读事实再决定”的首步。",
|
||||
"- mutation:place / move / swap / batch_move / unplace;适合真正落日程或调日程。",
|
||||
"- analyze:analyze_health / analyze_rhythm;适合先判断是否还有优化空间、该往哪里动。",
|
||||
"- queue:适合“按同一规则逐个处理一批任务”的计划,不必把整批任务细节都堆进 steps。",
|
||||
"- web:仅补通用学习资料或通识信息;不用于补个人时间事实。",
|
||||
"- deep_analyze:适合确实需要更深一层 schedule 分析时再加,默认不要为了“看起来完整”就提前注入。",
|
||||
"- 选 pack 原则:只选首个可执行 step 真的需要的最小 packs,不要为了保险一次全带上。",
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func buildPlanContextHookSummary() string {
|
||||
lines := []string{
|
||||
"4. context_hook 推导规则:",
|
||||
"- 先确定 steps,再看第一个可执行 step 需要哪个 domain / 哪组最小 packs,最后才写 hook。",
|
||||
"- 若第一个可执行 step 本质上是任务类写入或修正,hook 通常应为 taskclass,且一般不需要 packs。",
|
||||
"- 若第一个可执行 step 是 schedule 查询,hook 应为 schedule,并优先只带 detail_read。",
|
||||
"- 若第一个可执行 step 是 schedule 分析,hook 应为 schedule,并优先带 analyze;若分析后立刻要落写,再补 mutation。",
|
||||
"- 若第一个可执行 step 是批量同规则处理,hook 应在 schedule 基础上按需加 queue。",
|
||||
"- hook 只有一份,不要求提前覆盖整份计划的所有后续能力;execute 可以在后续按计划再切域或补 packs。",
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func planSemanticValue(value string) string {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
|
||||
Reference in New Issue
Block a user