Version: 0.9.45.dev.260427
后端: 1. execute 主链路重构为“上下文工具域 + 主动优化候选闭环”——移除 order_guard,粗排后默认进入主动微调,先诊断再从后端候选中选择 move/swap,避免 LLM 自由全局乱搜 2. 工具体系升级为动态注入协议——新增 context_tools_add / remove、工具域与二级包映射、主动优化白名单;schedule / taskclass / web 工具按域按包暴露,msg0 规则包与 execute 上下文同步重写 3. analyze_health 升级为主动优化唯一裁判入口——补齐 rhythm / tightness / profile / feasibility 指标、候选扫描与复诊打分、停滞信号、forced imperfection 判定,并把连续优化状态写回运行态 4. 任务类能力并入新 Agent 执行链——新增 upsert_task_class 写工具与启动注入事务写入;任务类模型补充学科画像与整天屏蔽配置,粗排支持 excluded_days_of_week,steady 策略改为基于目标位置/单日负载/分散度/缓冲的候选打分 5. 运行态与路由补齐优化模式语义——新增 active tool domain/packs、pending context hook、active optimize only、taskclass 写入回盘快照;区分 first_full / global_reopt / local_adjust,并完善首次粗排后默认 refine 的判定 前端: 6. 助手时间线渲染细化——推理内容改为独立 reasoning block,支持与工具/状态/正文按时序交错展示,自动收口折叠,修正 confirm reject 恢复动作 仓库: 7. newAgent 文档整体迁入 docs/backend,补充主动优化执行规划与顺序约束拆解文档,删除旧调试日志文件 PS:这次科研了2天,总算是有些进展了——LLM永远只适合做选择题、判断题,不适合做开放创新题。
This commit is contained in:
@@ -123,12 +123,22 @@ func renderStateSummary(state *newagentmodel.CommonState) string {
|
||||
if tc.StartDate != "" || tc.EndDate != "" {
|
||||
line += fmt.Sprintf(",日期范围=%s ~ %s", tc.StartDate, tc.EndDate)
|
||||
}
|
||||
if tc.SubjectType != "" || tc.DifficultyLevel != "" || tc.CognitiveIntensity != "" {
|
||||
line += fmt.Sprintf(",语义画像=%s/%s/%s",
|
||||
defaultSemanticValue(tc.SubjectType),
|
||||
defaultSemanticValue(tc.DifficultyLevel),
|
||||
defaultSemanticValue(tc.CognitiveIntensity),
|
||||
)
|
||||
}
|
||||
if tc.AllowFillerCourse {
|
||||
line += ",允许嵌入水课"
|
||||
}
|
||||
if len(tc.ExcludedSlots) > 0 {
|
||||
line += fmt.Sprintf(",排除时段=%v", tc.ExcludedSlots)
|
||||
}
|
||||
if len(tc.ExcludedDaysOfWeek) > 0 {
|
||||
line += fmt.Sprintf(",排除星期=%v", tc.ExcludedDaysOfWeek)
|
||||
}
|
||||
sb.WriteString(line + "\n")
|
||||
}
|
||||
}
|
||||
@@ -136,6 +146,14 @@ func renderStateSummary(state *newagentmodel.CommonState) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func defaultSemanticValue(value string) string {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return "未标注"
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
// renderPinnedBlocks 把 ConversationContext 中的置顶块渲染成独立的 system 文本。
|
||||
func renderPinnedBlocks(ctx *newagentmodel.ConversationContext) string {
|
||||
if ctx == nil {
|
||||
|
||||
@@ -26,6 +26,11 @@ quick_task 判别要点:
|
||||
- 但如果用户同时提了日程排布(如"把明天的课调一下,再记一下周五开会"),混合操作走 execute
|
||||
- 如果信息不足(如"帮我记一下"但没说记什么),走 direct_reply 追问
|
||||
|
||||
任务类设计路由要点:
|
||||
- 普通"创建/修改任务类"默认走 execute(由 execute 负责补字段与写入)。
|
||||
- 仅当用户明确要"补课程学习资料/学习建议/学习路径(需要外部知识)"时,走 plan(后续可使用 web_search)。
|
||||
- 考试时间、DDL、课程具体时间安排、个人可用时段等时间信息,必须向用户本人确认,不能作为 web 搜索补齐目标。
|
||||
|
||||
通用回答约束:
|
||||
- 非日程、非任务类问题,只要不需要工具,也应当正常回答。
|
||||
- 不要因为用户的问题不涉及排程,就说自己“只能处理日程/任务安排”。
|
||||
@@ -39,8 +44,8 @@ quick_task 判别要点:
|
||||
- "移动/微调/优化/均匀化/调顺序"等请求默认视为 refine,不得再次触发 rough build。
|
||||
粗排后微调判断:
|
||||
- 仅当 rough_build=true 时才判断 refine。
|
||||
- 若用户明确提出优化目标/偏好(如"尽量均衡""周三别太满""某门课往后挪"),设 refine=true。
|
||||
- 若用户只要求"先排进去/给初稿",未提出微调目标,设 refine=false。
|
||||
- 默认策略:首次粗排完成后应进入微调(refine=true),按中位标准做主动优化。
|
||||
- 仅当用户明确表达"只要初稿/先排进去别优化/先不微调/排完就收口"时,才设 refine=false。
|
||||
顺序授权判断:
|
||||
- reorder 仅在用户明确说明"允许打乱顺序/顺序不重要"时才为 true。
|
||||
- 用户明确要求"保持顺序/不要打乱"时必须为 false。
|
||||
|
||||
@@ -8,261 +8,14 @@ import (
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
const executeSystemPromptWithPlan = `
|
||||
你是 SmartMate 的执行器。你需要在"当前 plan 步骤"约束下推进任务。
|
||||
|
||||
你可以做什么:
|
||||
1. 只围绕当前步骤推进,先读后写,逐步完成当前步骤。
|
||||
2. 可调用读工具补充事实,再决定下一步。
|
||||
3. 日程写操作时输出 action=confirm 并附带 tool_call,等待用户确认。
|
||||
4. 若用户给出了"二次微调方向"(如负载均衡、某天减负、某类任务后移),优先围绕该方向推进,并在 goal_check 说明满足情况。
|
||||
5. 只有在用户明确允许打乱顺序时,才可使用 min_context_switch 做重排。
|
||||
6. 多任务微调时默认走队列链路:query_target_tasks(enqueue=true) → queue_pop_head → query_available_slots → queue_apply_head_move / queue_skip_head。
|
||||
|
||||
你不要做什么:
|
||||
1. 不要跳到其他 plan 步骤,不要越级执行。
|
||||
2. 不要伪造工具结果。
|
||||
3. 如果上下文明确"粗排已完成/rough_build_done",不要把任务当成未排入,不要重新逐个手动 place。
|
||||
4. 如果上下文明确"当前未收到明确微调偏好/本轮先收口",不要继续微调,直接输出 action=done。
|
||||
5. 不要连续重复同类查询而没有推进;连续两轮同类读查询后,必须转入执行、ask_user,或明确阻塞原因。
|
||||
6. 若工具结果与已知事实明显冲突(如无写操作却从"有任务"变成"0任务"),先自我纠错并重查一次,不要直接 ask_user。
|
||||
7. 不要连续两轮调用"同一读工具 + 等价 arguments";若上一轮已成功返回,下一轮必须换工具或进入 confirm。
|
||||
8. 不要忽略用户最新补充的微调方向;若与旧目标冲突,以最新用户要求为准。
|
||||
9. 若当前顺序策略是"默认保持顺序",禁止调用 min_context_switch。
|
||||
10. 不要把超过 2 条任务打包到 batch_move;大批量调整请改走队列逐项处理。
|
||||
11. 不要在未获取队首(queue_pop_head)时直接调用 queue_apply_head_move。
|
||||
12. 工具参数必须严格使用 schema 字段,禁止自造别名;例如 day_from/day_to 非法,必须改用 day_start/day_end。
|
||||
13. web_search 仅在"制定学习计划需要查外部资料"时使用(如考试日期、课程信息、校历政策等);日程排布本身(place/move/swap)不需要搜索。
|
||||
14. web_search 拿到 summary 后通常已够用;仅当需要页面详细内容时才调用 web_fetch。
|
||||
|
||||
执行规则:
|
||||
1. 输出格式:先输出一行 <SMARTFLOW_DECISION>{JSON 决策}</SMARTFLOW_DECISION>,然后换行输出给用户看的自然语言正文。JSON 中不要包含 speak 字段——用户可见的话放在标签之后。
|
||||
2. 读操作:action=continue + tool_call。
|
||||
3. 写操作(日程变更,如 place/move/swap/batch_move/unplace/spread_even/min_context_switch):action=confirm + tool_call。
|
||||
4. 缺关键上下文且无法通过工具补齐:action=ask_user。
|
||||
5. 仅当当前步骤完成时输出 action=next_plan,并在 goal_check 对照 done_when 给出证据。
|
||||
6. 仅当整体任务完成时输出 action=done,并在 goal_check 总结完成证据。
|
||||
7. 流程应正式终止时输出 action=abort。`
|
||||
|
||||
const executeSystemPromptReAct = `
|
||||
你是 SmartMate 的执行器,当前处于自由执行模式(无预定义 plan 步骤)。
|
||||
|
||||
阶段事实(强约束):
|
||||
1. 若上下文给出"粗排已完成/rough_build_done",表示目标任务类已经进入 suggested/existing,不是待排入状态。
|
||||
2. 当前阶段目标是"微调",不是"重新粗排"。
|
||||
3. 若上下文明确"当前未收到明确微调偏好/本轮先收口",应直接结束而不是继续优化循环。
|
||||
4. 若用户提出了二次微调方向,本轮优先目标就是满足该方向。
|
||||
|
||||
你可以做什么:
|
||||
1. 你可以基于用户给定的二次微调方向,对 suggested 做定向微调。
|
||||
2. existing 属于已安排事实层,可用于冲突判断和参考,不作为 move/batch_move/spread_even 的目标。
|
||||
3. 你可以先调用读工具补充必要事实(例如 get_overview/query_target_tasks/query_available_slots/get_task_info)。
|
||||
4. 你可以在需要日程写操作时提出 confirm(move/swap/unplace/batch_move/spread_even)。
|
||||
5. 只有用户明确允许打乱顺序时,才可使用 min_context_switch。
|
||||
6. 多任务处理默认使用队列链路:先 query_target_tasks(enqueue=true) 入队,再 queue_pop_head 逐项处理。
|
||||
|
||||
你不要做什么:
|
||||
1. 不要假设任务还没排进去,然后改成逐个手动 place。
|
||||
2. 不要伪造工具结果。
|
||||
3. 不要重复做同类查询而没有新增结论;连续两轮同类读查询后,必须转入执行、ask_user,或明确阻塞原因。
|
||||
4. 若工具结果与已知事实明显冲突(如无写操作却从"有任务"变成"0任务"),先自我纠错并重查一次,不要直接 ask_user。
|
||||
5. 不要连续两轮调用"同一读工具 + 等价 arguments";若上一轮已成功返回,下一轮必须换工具或进入 confirm。
|
||||
6. 若已明确"本轮先收口",不要继续调用 query_available_slots/move 做无目标微调。
|
||||
7. 若用户明确了微调方向,不要只做"局部看起来更空"的随机调整;每次改动都要能对应到该方向。
|
||||
8. 若顺序策略为"保持顺序",禁止调用 min_context_switch。
|
||||
9. 不要在同一轮构造大规模 batch_move;batch_move 最多 2 条,超过请走队列逐项处理。
|
||||
10. 未调用 queue_pop_head 获取 current 前,不要调用 queue_apply_head_move。
|
||||
11. 工具参数必须严格使用 schema 字段,禁止自造别名;例如 day_from/day_to 非法,必须改用 day_start/day_end。
|
||||
12. web_search 仅在"制定学习计划需要查外部资料"时使用(如考试日期、课程信息、校历政策等);日程排布本身(place/move/swap)不需要搜索。
|
||||
13. web_search 拿到 summary 后通常已够用;仅当需要页面详细内容时才调用 web_fetch。
|
||||
|
||||
执行规则:
|
||||
1. 输出格式:先输出一行 <SMARTFLOW_DECISION>{JSON 决策}</SMARTFLOW_DECISION>,然后换行输出给用户看的自然语言正文。JSON 中不要包含 speak 字段——用户可见的话放在标签之后。
|
||||
2. 读操作:action=continue + tool_call。
|
||||
3. 写操作(日程变更,如 place/move/swap/batch_move/unplace/spread_even/min_context_switch):action=confirm + tool_call。
|
||||
4. 缺关键上下文且无法通过工具补齐:action=ask_user。
|
||||
5. 任务完成:action=done,并在 goal_check 总结完成证据。
|
||||
6. 流程应正式终止:action=abort。`
|
||||
|
||||
// BuildExecuteSystemPrompt 返回执行阶段系统提示词(有 plan 模式)。
|
||||
func BuildExecuteSystemPrompt() string {
|
||||
return buildExecutePromptWithFormatGuard(executeSystemPromptWithPlan)
|
||||
return buildExecutePromptWithFormatGuard(executeSystemPromptBaseWithPlan)
|
||||
}
|
||||
|
||||
// BuildExecuteReActSystemPrompt 返回执行阶段系统提示词(自由执行模式)。
|
||||
func BuildExecuteReActSystemPrompt() string {
|
||||
return buildExecutePromptWithFormatGuard(executeSystemPromptReAct)
|
||||
}
|
||||
|
||||
// BuildExecuteDecisionContractText 返回执行阶段输出协议(有 plan 模式)。
|
||||
func BuildExecuteDecisionContractText() string {
|
||||
return strings.TrimSpace(fmt.Sprintf(`
|
||||
输出协议(两阶段格式):
|
||||
|
||||
先输出一行决策标签,标签内是 JSON;标签之后换行输出给用户看的自然语言正文。
|
||||
决策标签格式:<SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>
|
||||
|
||||
JSON 字段说明:
|
||||
- action:只能是 %s / %s / %s / %s / %s
|
||||
- reason:给后端和日志看的简短说明
|
||||
- goal_check:输出 %s 或 %s 时必填,对照 done_when 逐条验证
|
||||
- tool_call:输出 %s(写操作,需 confirm)或 %s(读操作)时可附带,格式 {"name":"工具名","arguments":{...}}
|
||||
|
||||
注意:JSON 中不要包含 speak 字段。给用户看的话放在 </SMARTFLOW_DECISION> 标签之后。
|
||||
|
||||
示例:
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"需要先调用 get_overview 获取事实","tool_call":{"name":"get_overview","arguments":{}}}</SMARTFLOW_DECISION>
|
||||
我先查看当前整体安排。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"已完成当前步骤所需查询与校验","goal_check":"已满足当前步骤 done_when 条件"}</SMARTFLOW_DECISION>
|
||||
当前步骤已完成。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"整体任务已完成"}</SMARTFLOW_DECISION>
|
||||
`,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionAskUser,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionNextPlan,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
newagentmodel.ExecuteActionNextPlan,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionNextPlan,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
))
|
||||
}
|
||||
|
||||
// BuildExecuteReActContractText 返回自由执行模式输出协议。
|
||||
func BuildExecuteReActContractText() string {
|
||||
return strings.TrimSpace(fmt.Sprintf(`
|
||||
输出协议(两阶段格式):
|
||||
|
||||
先输出一行决策标签,标签内是 JSON;标签之后换行输出给用户看的自然语言正文。
|
||||
决策标签格式:<SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>
|
||||
|
||||
JSON 字段说明:
|
||||
- action:只能是 %s / %s / %s / %s
|
||||
- reason:给后端和日志看的简短说明
|
||||
- goal_check:输出 %s 时必填,总结任务完成证据
|
||||
- tool_call:输出 %s(写操作,需 confirm)或 %s(读操作)时可附带,格式 {"name":"工具名","arguments":{...}}
|
||||
|
||||
注意:JSON 中不要包含 speak 字段。给用户看的话放在 </SMARTFLOW_DECISION> 标签之后。
|
||||
|
||||
示例:
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"先读取概览再决定微调方向","tool_call":{"name":"get_overview","arguments":{}}}</SMARTFLOW_DECISION>
|
||||
我先看一下现在的安排分布。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"写操作需要确认","tool_call":{"name":"swap","arguments":{"task_a":1,"task_b":2}}}</SMARTFLOW_DECISION>
|
||||
我准备把两项任务对调位置,你确认后执行。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"微调执行完毕并已校验结果","goal_check":"目标任务类已完成微调,且关键约束满足"}</SMARTFLOW_DECISION>
|
||||
已完成你的请求。
|
||||
`,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionAskUser,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
))
|
||||
}
|
||||
|
||||
// BuildExecuteDecisionContractTextV2 返回补齐 abort 协议后的执行输出契约(有 plan 模式)。
|
||||
func BuildExecuteDecisionContractTextV2() string {
|
||||
return strings.TrimSpace(fmt.Sprintf(`
|
||||
输出协议(两阶段格式):
|
||||
|
||||
先输出一行决策标签,标签内是 JSON;标签之后换行输出给用户看的自然语言正文。
|
||||
决策标签格式:<SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>
|
||||
|
||||
JSON 字段说明:
|
||||
- action:只能是 %s / %s / %s / %s / %s / %s
|
||||
- reason:给后端和日志看的简短说明
|
||||
- goal_check:输出 %s 或 %s 时必填,对照 done_when 逐条验证
|
||||
- tool_call:输出 %s(写操作,需 confirm)或 %s(读操作)时可附带,格式 {"name":"工具名","arguments":{...}}
|
||||
- abort:仅在 action=%s 时必填,格式为 {"code":"...","user_message":"...","internal_reason":"..."}
|
||||
- tool_call 与 abort 互斥,禁止同时出现
|
||||
|
||||
注意:JSON 中不要包含 speak 字段。给用户看的话放在 </SMARTFLOW_DECISION> 标签之后。若 action=%s,标签后通常留空。
|
||||
|
||||
示例:
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"先读取事实再决策","tool_call":{"name":"get_overview","arguments":{}}}</SMARTFLOW_DECISION>
|
||||
我先查看当前安排。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"步骤完成条件满足","goal_check":"已满足当前步骤 done_when"}</SMARTFLOW_DECISION>
|
||||
当前步骤完成。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"流程不应继续执行","abort":{"code":"execute_abort","user_message":"当前流程无法继续执行,本轮先终止。","internal_reason":"execute declared abort"}}</SMARTFLOW_DECISION>
|
||||
`,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionAskUser,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionNextPlan,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
newagentmodel.ExecuteActionAbort,
|
||||
newagentmodel.ExecuteActionNextPlan,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionAbort,
|
||||
newagentmodel.ExecuteActionAbort,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionNextPlan,
|
||||
newagentmodel.ExecuteActionAbort,
|
||||
))
|
||||
}
|
||||
|
||||
// BuildExecuteReActContractTextV2 返回补齐 abort 协议后的自由执行输出契约。
|
||||
func BuildExecuteReActContractTextV2() string {
|
||||
return strings.TrimSpace(fmt.Sprintf(`
|
||||
输出协议(两阶段格式):
|
||||
|
||||
先输出一行决策标签,标签内是 JSON;标签之后换行输出给用户看的自然语言正文。
|
||||
决策标签格式:<SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>
|
||||
|
||||
JSON 字段说明:
|
||||
- action:只能是 %s / %s / %s / %s / %s
|
||||
- reason:给后端和日志看的简短说明
|
||||
- goal_check:输出 %s 时必填,总结任务完成证据
|
||||
- tool_call:输出 %s(写操作,需 confirm)或 %s(读操作)时可附带,格式 {"name":"工具名","arguments":{...}}
|
||||
- abort:仅在 action=%s 时必填,格式为 {"code":"...","user_message":"...","internal_reason":"..."}
|
||||
- tool_call 与 abort 互斥,禁止同时出现
|
||||
|
||||
注意:JSON 中不要包含 speak 字段。给用户看的话放在 </SMARTFLOW_DECISION> 标签之后。若 action=%s,标签后通常留空。
|
||||
|
||||
示例:
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"先获取事实再决策","tool_call":{"name":"get_overview","arguments":{}}}</SMARTFLOW_DECISION>
|
||||
我先读取当前安排。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"写操作需要确认","tool_call":{"name":"move","arguments":{"task_id":5,"new_day":3,"new_slot_start":1}}}</SMARTFLOW_DECISION>
|
||||
我准备执行写操作,等待你确认。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"当前流程不应继续执行","abort":{"code":"domain_abort","user_message":"当前流程无法继续执行,本轮先终止。","internal_reason":"execute declared abort"}}</SMARTFLOW_DECISION>
|
||||
`,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionAskUser,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
newagentmodel.ExecuteActionAbort,
|
||||
newagentmodel.ExecuteActionDone,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionAbort,
|
||||
newagentmodel.ExecuteActionAbort,
|
||||
newagentmodel.ExecuteActionContinue,
|
||||
newagentmodel.ExecuteActionConfirm,
|
||||
newagentmodel.ExecuteActionAbort,
|
||||
))
|
||||
return buildExecutePromptWithFormatGuard(executeSystemPromptBaseReAct)
|
||||
}
|
||||
|
||||
// BuildExecuteMessages 组装执行阶段消息。
|
||||
@@ -304,6 +57,7 @@ func buildExecuteStrictJSONUserPromptWithPlan(state *newagentmodel.CommonState)
|
||||
计划步骤强约束:
|
||||
- 当前没有可执行的计划步骤,请先基于已有事实检查是否已完成全部计划。
|
||||
- 若全部计划已完成:输出 action=done,并在 goal_check 总结完成证据。
|
||||
- goal_check 字段类型必须为 string,不要输出对象或数组。
|
||||
- 若未完成但缺少关键信息:输出 action=ask_user。`)
|
||||
}
|
||||
|
||||
@@ -324,6 +78,7 @@ func buildExecuteStrictJSONUserPromptWithPlan(state *newagentmodel.CommonState)
|
||||
- 当前步骤完成判定(done_when):%s
|
||||
- 未满足 done_when 时:只能输出 continue / confirm / ask_user,禁止输出 next_plan。
|
||||
- 满足 done_when 时:优先输出 action=next_plan,并在 goal_check 逐条对照 done_when 给出证据。
|
||||
- goal_check 字段类型固定为 string(示例:"已满足 done_when:...;证据:..."),禁止输出 {"done_when":"...","evidence":"..."}。
|
||||
- 禁止跳步:不要提前执行后续步骤。`,
|
||||
base, current, total, stepContent, doneWhen))
|
||||
}
|
||||
@@ -332,13 +87,15 @@ func buildExecuteStrictJSONUserPromptWithPlan(state *newagentmodel.CommonState)
|
||||
func buildExecutePromptWithFormatGuard(base string) string {
|
||||
base = strings.TrimSpace(base)
|
||||
guard := strings.TrimSpace(`
|
||||
补充 JSON 约束:
|
||||
1. 只输出当前 action 真正需要的字段;无关字段直接省略,不要用 ""、{}、[]、null 占位。
|
||||
2. 若输出 tool_call,参数字段名只能是 arguments,禁止写成 parameters。
|
||||
3. tool_call 只能是单个对象:{"name":"工具名","arguments":{...}},不能输出数组。
|
||||
4. 只有 action=abort 时才允许输出 abort 字段;非 abort 动作不要输出 abort。
|
||||
5. action=continue / ask_user / confirm 时,标签后的正文必须是非空自然语言。
|
||||
6. <SMARTFLOW_DECISION> 标签内只放 JSON,不要放自然语言。`)
|
||||
输出协议硬约束:
|
||||
1. 只输出当前 action 真正需要的字段;不要输出空字符串、空对象、空数组或 null 占位。
|
||||
2. tool_call 只能是 {"name":"工具名","arguments":{...}};不能写 parameters,也不能一次输出多个 tool_call。
|
||||
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,不要放自然语言。
|
||||
7. 不要在 <SMARTFLOW_DECISION> 标签前输出任何前言、寒暄、解释或铺垫;给用户看的正文只能放在 </SMARTFLOW_DECISION> 之后。
|
||||
8. 任何动作都不得擅自超出用户当前明确意图;用户没让你做的下一步,不要自作主张推进。`)
|
||||
if base == "" {
|
||||
return guard
|
||||
}
|
||||
@@ -351,37 +108,17 @@ func buildExecuteStrictJSONUserPrompt() string {
|
||||
请继续当前任务的执行阶段,严格按 SMARTFLOW_DECISION 标签格式输出。
|
||||
输出格式:先输出 <SMARTFLOW_DECISION>{JSON 决策}</SMARTFLOW_DECISION>,然后换行输出给用户看的正文。
|
||||
|
||||
补充格式要求:
|
||||
- JSON 中不要包含 speak 字段,给用户看的话放在 </SMARTFLOW_DECISION> 标签之后
|
||||
- 与当前 action 无关的字段直接省略,不要输出空字符串、空对象、空数组或 null 占位
|
||||
- tool_call 只能写 {"name":"工具名","arguments":{...}},且每轮最多一个
|
||||
- 不要写 {"tool_call":{"name":"工具名","parameters":{...}}}
|
||||
- 非 abort 动作不要输出 abort 字段
|
||||
- action 为 continue / ask_user / confirm 时,标签后必须输出非空正文
|
||||
执行提醒:
|
||||
- JSON 中不要包含 speak 字段;给用户看的话放在 </SMARTFLOW_DECISION> 标签之后
|
||||
- 不要在 <SMARTFLOW_DECISION> 标签之前输出任何文字;哪怕只有一句“我先看下”也不行
|
||||
- 日程写工具(place/move/swap/batch_move/unplace)一律走 action=confirm
|
||||
- 若当前处于粗排后主动优化专用模式,先调 analyze_health,再直接从 decision.candidates 里选一个合法候选去执行;不要自行发明新的全窗搜索步骤
|
||||
- 若读工具结果与已知事实明显冲突,先修正参数并重查一次,再决定是否 ask_user
|
||||
- 不要连续两轮调用"同一读工具 + 等价 arguments";若上一轮已成功返回,下一轮必须换工具或进入 confirm
|
||||
- 若用户本轮给了二次微调方向,优先满足该方向,再考虑通用均衡优化
|
||||
- 若上下文已明确"当前未收到微调偏好,本轮先收口",请直接输出 action=done
|
||||
- 仅当顺序策略明确允许打乱顺序时,才可以调用 min_context_switch
|
||||
- spread_even 用于"范围内均匀化",必须先用 query_target_tasks 明确目标任务集合
|
||||
- 多任务调整默认先调用 query_target_tasks(enqueue=true),再用 queue_pop_head 逐项处理
|
||||
- queue_apply_head_move 只能用于 current 任务;若当前任务无法落位,调用 queue_skip_head 后继续
|
||||
- batch_move 一次最多 2 条;超过 2 条必须改走队列逐项处理
|
||||
`)
|
||||
}
|
||||
|
||||
// BuildExecuteUserPrompt 构造有 plan 模式的用户提示词。
|
||||
func BuildExecuteUserPrompt(_ *newagentmodel.CommonState) string {
|
||||
return strings.TrimSpace(`
|
||||
请继续当前任务的执行阶段,严格按 SMARTFLOW_DECISION 标签格式输出。
|
||||
输出格式:先输出 <SMARTFLOW_DECISION>{JSON 决策}</SMARTFLOW_DECISION>,然后换行输出给用户看的正文。
|
||||
`)
|
||||
}
|
||||
|
||||
// BuildExecuteReActUserPrompt 构造自由执行模式的用户提示词。
|
||||
func BuildExecuteReActUserPrompt(_ *newagentmodel.CommonState) string {
|
||||
return strings.TrimSpace(`
|
||||
请继续当前任务的执行阶段,严格按 SMARTFLOW_DECISION 标签格式输出。
|
||||
输出格式:先输出 <SMARTFLOW_DECISION>{JSON 决策}</SMARTFLOW_DECISION>,然后换行输出给用户看的正文。
|
||||
- 不要连续两轮调用“同一读工具 + 等价 arguments”;上一轮已成功返回时,下一轮必须换工具、进入 confirm,或明确说明阻塞
|
||||
- 若上下文已明确“当前未收到微调偏好,本轮先收口”,请直接输出 action=done
|
||||
- web_search 仅用于通用学习资料补充,不可用于考试时间、DDL、个人时段等时间字段填充
|
||||
- upsert_task_class 若返回 validation.ok=false,必须先按 validation.issues 补齐,再重试;禁止直接 done
|
||||
- subject_type / difficulty_level / cognitive_intensity 是任务类语义画像必填;优先静默推断,只有确实无法判断时再 ask_user
|
||||
- 仅 upsert_task_class 成功不代表已开始排程;若未触发 rough_build 且未调用任何日程修改工具,禁止承诺“接下来会自动排程”
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// executeHistoryKindKey 用于在 history 中打运行态标记,供 prompt 分层识别。
|
||||
// 说明:loop_closed / step_advanced 等边界标记仍由节点层写入,但 prompt 层已不再消费它们——
|
||||
// 因为 msg1/msg2 已经按"真实对话流 + 当前活跃 ReAct 记录"重构,不再做 msg2→msg1 的归档搬运。
|
||||
// executeHistoryKindKey 用于在 history 里区分普通用户消息与后端注入的纠错提示。
|
||||
// 这里负责“识别并过滤”,不负责写入该标记。
|
||||
executeHistoryKindKey = "newagent_history_kind"
|
||||
executeHistoryKindCorrectionUser = "llm_correction_prompt"
|
||||
)
|
||||
@@ -31,20 +30,29 @@ type executeLoopRecord struct {
|
||||
Observation string
|
||||
}
|
||||
|
||||
// buildExecuteStageMessages 组装 execute 阶段 4 条消息骨架。
|
||||
type conversationTurn struct {
|
||||
Role string
|
||||
Content string
|
||||
}
|
||||
|
||||
type executeLatestToolRecord struct {
|
||||
ToolName string
|
||||
Observation string
|
||||
}
|
||||
|
||||
// buildExecuteStageMessages 组装 execute 阶段的四段式消息。
|
||||
//
|
||||
// 消息结构(固定):
|
||||
// 1. message[0] 固定 prompt(规则 + 微调硬引导 + 输出约束 + 工具简表)
|
||||
// 2. message[1] 历史上下文(真实对话流 + 早期 ReAct 摘要)
|
||||
// 3. message[2] 当轮 ReAct Loop 窗口(thought/reason + tool_call + observation 绑定展示)
|
||||
// 4. message[3] 当前执行状态(轮次、模式、plan 步骤、任务类、相关记忆等)
|
||||
// 1. msg0:系统提示 + 动态规则包 + 工具简表。
|
||||
// 2. msg1:真实对话流,只保留 user 和 assistant speak。
|
||||
// 3. msg2:当前 ReAct tool loop 记录。
|
||||
// 4. msg3:执行状态、阶段约束、记忆和本轮指令。
|
||||
func buildExecuteStageMessages(
|
||||
stageSystemPrompt string,
|
||||
state *newagentmodel.CommonState,
|
||||
ctx *newagentmodel.ConversationContext,
|
||||
runtimeUserPrompt string,
|
||||
) []*schema.Message {
|
||||
msg0 := buildExecuteMessage0(stageSystemPrompt, ctx)
|
||||
msg0 := buildExecuteMessage0(stageSystemPrompt, state, ctx)
|
||||
msg1 := buildExecuteMessage1V3(ctx)
|
||||
msg2 := buildExecuteMessage2V3(ctx)
|
||||
msg3 := buildExecuteMessage3(state, ctx, runtimeUserPrompt)
|
||||
@@ -57,27 +65,30 @@ func buildExecuteStageMessages(
|
||||
}
|
||||
}
|
||||
|
||||
// buildExecuteMessage0 生成固定规则消息,并附带工具简表。
|
||||
func buildExecuteMessage0(stageSystemPrompt string, ctx *newagentmodel.ConversationContext) string {
|
||||
// buildExecuteMessage0 生成 execute 阶段的固定规则消息。
|
||||
//
|
||||
// 1. 先拼基础 system prompt,保证身份和输出协议稳定。
|
||||
// 2. 再按当前 domain / packs 注入动态规则包,让模型先读到边界。
|
||||
// 3. 最后再附工具简表,避免模型只看到工具不看到纪律。
|
||||
func buildExecuteMessage0(stageSystemPrompt string, state *newagentmodel.CommonState, ctx *newagentmodel.ConversationContext) string {
|
||||
base := strings.TrimSpace(mergeSystemPrompts(ctx, stageSystemPrompt))
|
||||
if base == "" {
|
||||
base = "你是 SmartMate 执行器,请继续 execute 阶段。"
|
||||
base = "你是 SmartMate 执行器,请继续当前执行阶段。"
|
||||
}
|
||||
|
||||
toolCatalog := renderExecuteToolCatalogCompact(ctx)
|
||||
if toolCatalog == "" {
|
||||
return base
|
||||
rulePackSection, _ := renderExecuteRulePackSection(state, ctx)
|
||||
if rulePackSection != "" {
|
||||
base += "\n\n" + rulePackSection
|
||||
}
|
||||
return base + "\n\n" + toolCatalog
|
||||
|
||||
toolCatalog := renderExecuteToolCatalogCompact(ctx, state)
|
||||
if toolCatalog != "" {
|
||||
base += "\n\n" + toolCatalog
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
// buildExecuteMessage1V3 只渲染"真实对话流 + 阶段锚点"。
|
||||
//
|
||||
// 改造说明:
|
||||
// 1. msg1 只保留 user + assistant speak 组成的真实对话历史,全量注入;
|
||||
// 2. tool_call / observation 一律由 msg2 承载,这里不再重复;
|
||||
// 3. 不再从历史中"归档"上一轮 ReAct 结果到 msg1——归档搬运逻辑已随 splitExecuteLoopRecordsByBoundary 一并移除;
|
||||
// 4. token 预算由统一压缩层兜底,prompt 层不做提前裁剪。
|
||||
// buildExecuteMessage1V3 只渲染真实对话流,不混入 tool observation。
|
||||
func buildExecuteMessage1V3(ctx *newagentmodel.ConversationContext) string {
|
||||
lines := []string{"历史上下文:"}
|
||||
if ctx == nil {
|
||||
@@ -105,16 +116,13 @@ func buildExecuteMessage1V3(ctx *newagentmodel.ConversationContext) string {
|
||||
} else {
|
||||
lines = append(lines, "- 阶段锚点:按当前工具事实推进,不做无依据操作。")
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// buildExecuteMessage2V3 承载当前会话中全部 ReAct Loop 记录。
|
||||
// buildExecuteMessage2V3 只承载当轮 ReAct loop。
|
||||
//
|
||||
// 改造说明:
|
||||
// 1. 不再按 execute_loop_closed / execute_step_advanced 边界切分"归档/活跃"两段;
|
||||
// 2. 直接从 history 提取全部 assistant tool_call + 对应 observation 作为当前 Loop 视图;
|
||||
// 3. 新一轮刚开始(尚未产生 tool_call)时返回明确占位,方便模型识别"干净起点"。
|
||||
// 1. 每条记录固定展示 thought / tool_call / observation,方便模型做局部闭环。
|
||||
// 2. 如果当前还没有任何 tool loop,明确给“新一轮”占位,避免模型误判缺上下文。
|
||||
func buildExecuteMessage2V3(ctx *newagentmodel.ConversationContext) string {
|
||||
lines := []string{"当轮 ReAct Loop 记录:"}
|
||||
if ctx == nil {
|
||||
@@ -136,11 +144,19 @@ func buildExecuteMessage2V3(ctx *newagentmodel.ConversationContext) string {
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// buildExecuteMessage3 汇总当前执行状态和本轮指令。
|
||||
//
|
||||
// 1. 这里只放“当前轮真正会影响决策”的状态,避免 msg3 继续膨胀。
|
||||
// 2. 读工具最近结果只给最新一条摘要,避免旧 observation 重复占上下文。
|
||||
// 3. 最后一行固定落到“本轮指令”,保证模型收尾时注意力还在执行目标上。
|
||||
func buildExecuteMessage3(state *newagentmodel.CommonState, ctx *newagentmodel.ConversationContext, runtimeUserPrompt string) string {
|
||||
lines := []string{"当前执行状态:"}
|
||||
roughBuildDone := hasExecuteRoughBuildDone(ctx)
|
||||
|
||||
roundUsed, maxRounds := 0, newagentmodel.DefaultMaxRounds
|
||||
modeText := "自由执行(无预定义步骤)"
|
||||
activeDomain := ""
|
||||
activePacks := []string{}
|
||||
if state != nil {
|
||||
roundUsed = state.RoundUsed
|
||||
if state.MaxRounds > 0 {
|
||||
@@ -149,15 +165,23 @@ func buildExecuteMessage3(state *newagentmodel.CommonState, ctx *newagentmodel.C
|
||||
if state.HasPlan() {
|
||||
modeText = "计划执行(有预定义步骤)"
|
||||
}
|
||||
activeDomain = strings.TrimSpace(state.ActiveToolDomain)
|
||||
activePacks = readExecuteActiveToolPacks(state)
|
||||
}
|
||||
|
||||
lines = append(lines,
|
||||
fmt.Sprintf("- 当前轮次:%d/%d", roundUsed, maxRounds),
|
||||
"- 当前模式:"+modeText,
|
||||
)
|
||||
|
||||
// 1. 有 plan 时,把当前步骤与完成判定强制写入 msg3。
|
||||
// 2. 该锚点用于约束模型只推进当前步骤,避免退化成泛化 ReAct。
|
||||
// 3. 当前步骤不可读时给出兜底指引,避免引用旧步骤。
|
||||
if activeDomain == "" {
|
||||
lines = append(lines, "- 动态工具区:当前仅激活 context 管理工具。")
|
||||
} else if len(activePacks) == 0 {
|
||||
lines = append(lines, fmt.Sprintf("- 动态工具区:domain=%s,未显式激活 packs。", activeDomain))
|
||||
} else {
|
||||
lines = append(lines, fmt.Sprintf("- 动态工具区:domain=%s,packs=[%s]。", activeDomain, strings.Join(activePacks, ",")))
|
||||
}
|
||||
|
||||
if state != nil && state.HasPlan() {
|
||||
current, total := state.PlanProgress()
|
||||
lines = append(lines, "计划步骤锚点(强约束):")
|
||||
@@ -170,26 +194,41 @@ func buildExecuteMessage3(state *newagentmodel.CommonState, ctx *newagentmodel.C
|
||||
if doneWhen == "" {
|
||||
doneWhen = "(未提供 done_when,需基于步骤目标给出可验证完成证据)"
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("- 当前步骤:第 %d/%d 步", current, total))
|
||||
lines = append(lines, "- 当前步骤内容:"+stepContent)
|
||||
lines = append(lines, "- 当前步骤完成判定(done_when):"+doneWhen)
|
||||
lines = append(lines, "- 动作纪律1:未满足 done_when 时,只能 continue / confirm / ask_user,禁止 next_plan")
|
||||
lines = append(lines, "- 动作纪律2:满足 done_when 时,优先 next_plan,并在 goal_check 对照 done_when 给证据")
|
||||
lines = append(lines, "- 动作纪律3:禁止跳到后续步骤执行")
|
||||
lines = append(lines,
|
||||
fmt.Sprintf("- 当前步骤:第 %d/%d 步", current, total),
|
||||
"- 当前步骤内容:"+stepContent,
|
||||
"- 当前步骤完成判定(done_when):"+doneWhen,
|
||||
"- 动作纪律1:未满足 done_when 时,只能 continue / confirm / ask_user,禁止 next_plan。",
|
||||
"- 动作纪律2:满足 done_when 时,优先 next_plan,并在 goal_check 对照 done_when 给证据。",
|
||||
"- 动作纪律3:禁止跳到后续步骤执行。",
|
||||
)
|
||||
} else {
|
||||
lines = append(lines, "- 当前计划步骤不可读;请先判断是否已完成全部计划")
|
||||
lines = append(lines, "- 若已完成全部计划,输出 done 并给出 goal_check 证据")
|
||||
lines = append(lines,
|
||||
"- 当前计划步骤不可读;请先判断是否已完成全部计划。",
|
||||
"- 若已完成全部计划,输出 done 并给出 goal_check 证据。",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if latestAnalyze := renderExecuteLatestAnalyzeSummary(ctx); latestAnalyze != "" {
|
||||
lines = append(lines, "- 最近一次诊断:"+latestAnalyze)
|
||||
}
|
||||
if latestMutation := renderExecuteLatestMutationSummary(ctx); latestMutation != "" {
|
||||
lines = append(lines, "- 最近一次写操作:"+latestMutation)
|
||||
}
|
||||
if taskClassText := renderExecuteTaskClassIDs(state); taskClassText != "" {
|
||||
lines = append(lines, "- 目标任务类:"+taskClassText)
|
||||
}
|
||||
lines = append(lines, "- 啥时候结束Loop:你可以根据工具调用记录自行判断。")
|
||||
lines = append(lines, "- 非目标:不重新粗排、不修改无关任务类。")
|
||||
if hasExecuteRoughBuildDone(ctx) {
|
||||
|
||||
lines = append(lines,
|
||||
"- 啥时候结束Loop:你可以根据工具调用记录自行判断。",
|
||||
"- 非目标:不重新粗排、不修改无关任务类。",
|
||||
)
|
||||
if roughBuildDone {
|
||||
lines = append(lines, "- 阶段约束:粗排已完成,本轮只微调 suggested;existing 仅作已安排事实参考,不作为可移动目标。")
|
||||
}
|
||||
lines = append(lines, "- 参数纪律:工具参数必须严格使用 schema 字段;若返回'参数非法',需先改参再继续。")
|
||||
lines = append(lines, "- 参数纪律:工具参数必须严格使用 schema 字段;若返回“参数非法”,需先改参再继续。")
|
||||
|
||||
if state != nil {
|
||||
if state.AllowReorder {
|
||||
lines = append(lines, "- 顺序策略:用户已明确允许打乱顺序,可在必要时使用 min_context_switch。")
|
||||
@@ -197,15 +236,27 @@ func buildExecuteMessage3(state *newagentmodel.CommonState, ctx *newagentmodel.C
|
||||
lines = append(lines, "- 顺序策略:默认保持 suggested 相对顺序,禁止调用 min_context_switch。")
|
||||
}
|
||||
}
|
||||
|
||||
if upsertRuntime := renderTaskClassUpsertRuntime(state); upsertRuntime != "" {
|
||||
lines = append(lines, "任务类写入运行态:")
|
||||
lines = append(lines, upsertRuntime)
|
||||
}
|
||||
|
||||
if memoryText := renderExecuteMemoryContext(ctx); memoryText != "" {
|
||||
lines = append(lines, "相关记忆(仅在确有帮助时参考,不要机械复述):")
|
||||
lines = append(lines, memoryText)
|
||||
}
|
||||
|
||||
// 兼容上层传入的执行指令;若为空则使用固定收口指令。
|
||||
latestAnalyze := renderExecuteLatestAnalyzeSummary(ctx)
|
||||
latestMutation := renderExecuteLatestMutationSummary(ctx)
|
||||
if nextStep := renderExecuteNextStepHintV2(state, latestAnalyze, latestMutation, roughBuildDone); nextStep != "" {
|
||||
lines = append(lines, "下一步提示:")
|
||||
lines = append(lines, "- "+nextStep)
|
||||
}
|
||||
|
||||
instruction := strings.TrimSpace(runtimeUserPrompt)
|
||||
if instruction == "" {
|
||||
instruction = "请继续当前任务执行阶段,严格输出 JSON。"
|
||||
instruction = "请继续当前任务执行阶段,严格按 SMARTFLOW_DECISION 标签格式输出。"
|
||||
} else {
|
||||
instruction = firstExecuteLine(instruction)
|
||||
}
|
||||
@@ -214,8 +265,12 @@ func buildExecuteMessage3(state *newagentmodel.CommonState, ctx *newagentmodel.C
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// renderExecuteToolCatalogCompact 将工具 schema 渲染成简表,避免大段 JSON 示例占用上下文。
|
||||
func renderExecuteToolCatalogCompact(ctx *newagentmodel.ConversationContext) string {
|
||||
// renderExecuteToolCatalogCompact 将当前 tool schemas 渲染为紧凑简表。
|
||||
//
|
||||
// 1. 这里只给模型最低必要的参数和返回值感知,不重复塞完整 schema JSON。
|
||||
// 2. 对复杂工具额外给一条调用示例,降低“参数字段写错”的概率。
|
||||
// 3. P1 阶段隐藏 min_context_switch,避免模型误用已禁能力。
|
||||
func renderExecuteToolCatalogCompact(ctx *newagentmodel.ConversationContext, state *newagentmodel.CommonState) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
@@ -225,36 +280,79 @@ func renderExecuteToolCatalogCompact(ctx *newagentmodel.ConversationContext) str
|
||||
}
|
||||
|
||||
lines := []string{"可用工具(简表):"}
|
||||
for i, schemaItem := range schemas {
|
||||
index := 0
|
||||
for _, schemaItem := range schemas {
|
||||
name := strings.TrimSpace(schemaItem.Name)
|
||||
desc := strings.TrimSpace(schemaItem.Desc)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if shouldHideMinContextSwitchForP1(state, name) {
|
||||
continue
|
||||
}
|
||||
|
||||
index++
|
||||
desc := strings.TrimSpace(schemaItem.Desc)
|
||||
if desc == "" {
|
||||
desc = "无描述"
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("%d. %s:%s", i+1, name, desc))
|
||||
lines = append(lines, fmt.Sprintf("%d. %s:%s", index, name, desc))
|
||||
|
||||
doc := parseExecuteToolSchema(schemaItem.SchemaText)
|
||||
paramSummary := renderExecuteToolParamSummary(doc.Parameters)
|
||||
lines = append(lines, " 参数:"+paramSummary)
|
||||
|
||||
returnType, returnSample := renderExecuteToolReturnHint(name)
|
||||
lines = append(lines, " 返回类型:"+returnType)
|
||||
lines = append(lines, " 返回示例:"+returnSample)
|
||||
if shouldRenderExecuteToolReturnSample(name) {
|
||||
lines = append(lines, " 返回示例:"+returnSample)
|
||||
}
|
||||
if callSample := renderExecuteToolCallHint(name); strings.TrimSpace(callSample) != "" {
|
||||
lines = append(lines, " 调用示例:"+callSample)
|
||||
}
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// renderExecuteToolReturnHint 返回工具的返回类型 + 最小示例。
|
||||
func shouldRenderExecuteToolReturnSample(toolName string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(toolName)) {
|
||||
case "query_available_slots",
|
||||
"query_target_tasks",
|
||||
"queue_pop_head",
|
||||
"queue_status",
|
||||
"queue_apply_head_move",
|
||||
"queue_skip_head",
|
||||
"web_search",
|
||||
"web_fetch",
|
||||
"analyze_health",
|
||||
"analyze_rhythm",
|
||||
"analyze_tolerance",
|
||||
"upsert_task_class":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
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":"逆矩阵与矩阵的秩"}]}}}`
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func renderExecuteToolReturnHint(toolName string) (returnType string, sample string) {
|
||||
returnType = "string(自然语言文本)"
|
||||
switch strings.ToLower(strings.TrimSpace(toolName)) {
|
||||
case "get_overview":
|
||||
return returnType, "规划窗口共27天...课程占位条目34个...任务清单(全量,已过滤课程)..."
|
||||
return returnType, "规划窗口共27天...课程占位条目34个...任务清单(已过滤课程)..."
|
||||
case "get_task_info":
|
||||
return returnType, "[35]第一章随机事件与概率 | 状态:已预排(suggested) | 占用时段:第3天第5-6节"
|
||||
return returnType, "[35] 第一章随机事件与概率 | 状态:已预排(suggested) | 占用时段:第3天第5-6节"
|
||||
case "query_available_slots":
|
||||
return "string(JSON字符串)", `{"tool":"query_available_slots","count":12,"strict_count":8,"embedded_count":4,"slots":[{"day":5,"week":12,"day_of_week":3,"slot_start":1,"slot_end":2,"slot_type":"empty"}]}`
|
||||
case "query_target_tasks":
|
||||
@@ -276,7 +374,7 @@ func renderExecuteToolReturnHint(toolName string) (returnType string, sample str
|
||||
case "swap":
|
||||
return returnType, "交换完成:[35]... ↔ [36]..."
|
||||
case "batch_move":
|
||||
return returnType, "批量移动完成,2个任务全部成功。(单次最多2条)"
|
||||
return returnType, "批量移动完成,2 个任务全部成功。"
|
||||
case "spread_even":
|
||||
return returnType, "均匀化调整完成:共处理 6 个任务,候选坑位 24 个。"
|
||||
case "min_context_switch":
|
||||
@@ -287,6 +385,14 @@ func renderExecuteToolReturnHint(toolName string) (returnType string, sample str
|
||||
return "string(JSON字符串)", `{"tool":"web_search","query":"检索关键词","count":2,"items":[{"title":"搜索结果标题","url":"https://example.com/page","snippet":"摘要片段...","domain":"example.com","published_at":"2025-04-10"}]}`
|
||||
case "web_fetch":
|
||||
return "string(JSON字符串)", `{"tool":"web_fetch","url":"https://example.com/page","title":"页面标题","content":"正文内容...","truncated":false}`
|
||||
case "analyze_health":
|
||||
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:
|
||||
return returnType, "自然语言结果(成功/失败原因/关键数据摘要)。"
|
||||
}
|
||||
@@ -353,12 +459,11 @@ func renderExecuteToolParamSummary(parameters map[string]any) string {
|
||||
return strings.Join(parts, ";")
|
||||
}
|
||||
|
||||
// collectExecuteLoopRecords 从历史中提取 ReAct 记录。
|
||||
// collectExecuteLoopRecords 从 history 里提取 thought + tool_call + observation 三元组。
|
||||
//
|
||||
// 提取策略:
|
||||
// 1. 以 assistant tool_call 消息为主键;
|
||||
// 2. 关联同 ToolCallID 的 tool result 作为 observation;
|
||||
// 3. 向前回溯最近一条 assistant 文本消息作为 thought/reason。
|
||||
// 1. 以 assistant tool_call 为主记录。
|
||||
// 2. 用 ToolCallID 去关联 tool observation,保证同轮绑定。
|
||||
// 3. thought 只向前取最近一条 assistant 纯文本消息,不跨越到更早的工具调用之前做复杂回溯。
|
||||
func collectExecuteLoopRecords(history []*schema.Message) []executeLoopRecord {
|
||||
if len(history) == 0 {
|
||||
return nil
|
||||
@@ -381,12 +486,14 @@ func collectExecuteLoopRecords(history []*schema.Message) []executeLoopRecord {
|
||||
if msg == nil || msg.Role != schema.Assistant || len(msg.ToolCalls) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
thought := findExecuteThoughtBefore(history, i)
|
||||
for _, call := range msg.ToolCalls {
|
||||
toolName := strings.TrimSpace(call.Function.Name)
|
||||
if toolName == "" {
|
||||
toolName = "unknown_tool"
|
||||
}
|
||||
|
||||
toolArgs := compactExecuteText(call.Function.Arguments, 160)
|
||||
if toolArgs == "" {
|
||||
toolArgs = "{}"
|
||||
@@ -424,10 +531,9 @@ func findExecuteThoughtBefore(history []*schema.Message, index int) string {
|
||||
continue
|
||||
}
|
||||
content := compactExecuteText(msg.Content, 140)
|
||||
if content == "" {
|
||||
continue
|
||||
if content != "" {
|
||||
return content
|
||||
}
|
||||
return content
|
||||
}
|
||||
return "(未记录)"
|
||||
}
|
||||
@@ -456,18 +562,116 @@ func hasExecuteRoughBuildDone(ctx *newagentmodel.ConversationContext) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// conversationTurn 表示对话历史中的一轮交互(user 或 assistant speak)。
|
||||
type conversationTurn struct {
|
||||
Role string
|
||||
Content string
|
||||
func renderExecuteLatestAnalyzeSummary(ctx *newagentmodel.ConversationContext) string {
|
||||
record, ok := findExecuteLatestToolRecord(ctx, map[string]struct{}{
|
||||
"analyze_health": {},
|
||||
"analyze_rhythm": {},
|
||||
"analyze_tolerance": {},
|
||||
})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s -> %s", record.ToolName, record.Observation)
|
||||
}
|
||||
|
||||
// collectExecuteConversationTurns 从历史消息中提取 user + assistant speak 对话流。
|
||||
func renderExecuteLatestMutationSummary(ctx *newagentmodel.ConversationContext) string {
|
||||
record, ok := findExecuteLatestToolRecord(ctx, map[string]struct{}{
|
||||
"place": {},
|
||||
"move": {},
|
||||
"swap": {},
|
||||
"batch_move": {},
|
||||
"unplace": {},
|
||||
"queue_apply_head_move": {},
|
||||
"spread_even": {},
|
||||
"min_context_switch": {},
|
||||
})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s -> %s", record.ToolName, record.Observation)
|
||||
}
|
||||
|
||||
func findExecuteLatestToolRecord(ctx *newagentmodel.ConversationContext, allowSet map[string]struct{}) (executeLatestToolRecord, bool) {
|
||||
if ctx == nil || len(allowSet) == 0 {
|
||||
return executeLatestToolRecord{}, false
|
||||
}
|
||||
history := ctx.HistorySnapshot()
|
||||
if len(history) == 0 {
|
||||
return executeLatestToolRecord{}, false
|
||||
}
|
||||
|
||||
toolNameByCallID := make(map[string]string, len(history))
|
||||
for _, msg := range history {
|
||||
if msg == nil || msg.Role != schema.Assistant || len(msg.ToolCalls) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, call := range msg.ToolCalls {
|
||||
callID := strings.TrimSpace(call.ID)
|
||||
toolName := strings.TrimSpace(call.Function.Name)
|
||||
if callID == "" || toolName == "" {
|
||||
continue
|
||||
}
|
||||
toolNameByCallID[callID] = toolName
|
||||
}
|
||||
}
|
||||
|
||||
for i := len(history) - 1; i >= 0; i-- {
|
||||
msg := history[i]
|
||||
if msg == nil || msg.Role != schema.Tool {
|
||||
continue
|
||||
}
|
||||
callID := strings.TrimSpace(msg.ToolCallID)
|
||||
if callID == "" {
|
||||
continue
|
||||
}
|
||||
toolName := strings.TrimSpace(toolNameByCallID[callID])
|
||||
if toolName == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := allowSet[toolName]; !ok {
|
||||
continue
|
||||
}
|
||||
return executeLatestToolRecord{
|
||||
ToolName: toolName,
|
||||
Observation: summarizeExecuteToolObservation(msg.Content),
|
||||
}, true
|
||||
}
|
||||
|
||||
return executeLatestToolRecord{}, false
|
||||
}
|
||||
|
||||
func summarizeExecuteToolObservation(raw string) string {
|
||||
content := strings.TrimSpace(raw)
|
||||
if content == "" {
|
||||
return "无返回内容。"
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal([]byte(content), &payload); err == nil && len(payload) > 0 {
|
||||
if toolName := strings.TrimSpace(asExecuteString(payload["tool"])); toolName == "analyze_health" {
|
||||
return summarizeExecuteAnalyzeHealthObservationV2(payload)
|
||||
}
|
||||
for _, key := range []string{"result", "message", "reason", "error"} {
|
||||
if text := strings.TrimSpace(asExecuteString(payload[key])); text != "" {
|
||||
return compactExecuteText(text, 120)
|
||||
}
|
||||
}
|
||||
if success, ok := payload["success"].(bool); ok {
|
||||
if success {
|
||||
return "执行成功。"
|
||||
}
|
||||
return "执行失败。"
|
||||
}
|
||||
}
|
||||
|
||||
return compactExecuteText(content, 120)
|
||||
}
|
||||
|
||||
// collectExecuteConversationTurns 只提取 user 和 assistant speak。
|
||||
//
|
||||
// 提取规则:
|
||||
// 1. 只保留 user 消息(排除 correction prompt)和 assistant speak 消息(非空 Content 且无 ToolCalls);
|
||||
// 2. 全量保留,不再限制轮数和单条长度(token 预算由 execute 层统一管理);
|
||||
// 3. 返回的条目按原始时间顺序排列。
|
||||
// 1. 过滤 correction prompt,避免把后端纠错提示伪装成用户真实意图。
|
||||
// 2. 过滤 assistant tool_call 消息,避免 msg1 和 msg2 重复。
|
||||
// 3. 保持原始顺序,不在这里裁剪长度。
|
||||
func collectExecuteConversationTurns(history []*schema.Message) []conversationTurn {
|
||||
if len(history) == 0 {
|
||||
return nil
|
||||
@@ -556,11 +760,44 @@ func renderExecuteTaskClassIDs(state *newagentmodel.CommonState) string {
|
||||
return fmt.Sprintf("task_class_ids=[%s]", strings.Join(parts, ","))
|
||||
}
|
||||
|
||||
// renderExecuteMemoryContext 提取 execute 阶段要注入 msg3 的记忆文本。
|
||||
//
|
||||
// 1. 只读取统一的 memory_context,避免把其他 pinned block 误塞进 prompt。
|
||||
// 2. 为空时直接返回空串,保持 msg3 干净。
|
||||
// 3. 复用统一记忆渲染逻辑,保证各阶段记忆入口一致。
|
||||
// renderExecuteMemoryContext 复用统一记忆入口,避免 execute 私自拼接其他 pinned block。
|
||||
func renderExecuteMemoryContext(ctx *newagentmodel.ConversationContext) string {
|
||||
return renderUnifiedMemoryContext(ctx)
|
||||
}
|
||||
|
||||
func renderTaskClassUpsertRuntime(state *newagentmodel.CommonState) string {
|
||||
if state == nil || !state.TaskClassUpsertLastTried {
|
||||
return ""
|
||||
}
|
||||
|
||||
lines := make([]string, 0, 4)
|
||||
if state.TaskClassUpsertLastSuccess {
|
||||
lines = append(lines, "- 最近一次 upsert_task_class 成功。")
|
||||
} else {
|
||||
lines = append(lines, "- 最近一次 upsert_task_class 失败。")
|
||||
}
|
||||
if state.TaskClassUpsertConsecutiveFailures > 0 {
|
||||
lines = append(lines, fmt.Sprintf("- 连续失败次数:%d", state.TaskClassUpsertConsecutiveFailures))
|
||||
}
|
||||
if len(state.TaskClassUpsertLastIssues) > 0 {
|
||||
lines = append(lines, "- 需要优先处理 validation.issues:")
|
||||
for _, issue := range state.TaskClassUpsertLastIssues {
|
||||
trimmed := strings.TrimSpace(issue)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, " - "+trimmed)
|
||||
}
|
||||
}
|
||||
if !state.TaskClassUpsertLastSuccess {
|
||||
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
|
||||
}
|
||||
|
||||
33
backend/newAgent/prompt/execute_context_health.go
Normal file
33
backend/newAgent/prompt/execute_context_health.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package newagentprompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func fallbackExecuteText(value string, fallback string) string {
|
||||
if text := strings.TrimSpace(value); text != "" {
|
||||
return text
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func compactHealthAny(value any) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(typed)
|
||||
case bool:
|
||||
if typed {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
case int:
|
||||
return fmt.Sprintf("%d", typed)
|
||||
case float64:
|
||||
return fmt.Sprintf("%.0f", typed)
|
||||
}
|
||||
return strings.TrimSpace(fmt.Sprintf("%v", value))
|
||||
}
|
||||
106
backend/newAgent/prompt/execute_context_health_v2.go
Normal file
106
backend/newAgent/prompt/execute_context_health_v2.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package newagentprompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// summarizeExecuteAnalyzeHealthObservationV2 把 analyze_health 结果压成更短的单行摘要。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只保留 execute 下一步真正需要消费的裁决字段,不重复展开整份 metrics。
|
||||
// 2. 若存在候选,会优先展示“候选数量 + 前两个候选工具”,帮助模型迅速进入选择题。
|
||||
// 3. 这里只做摘要,不负责改变决策含义;真实判定仍以 analyze_health 原始 JSON 为准。
|
||||
func summarizeExecuteAnalyzeHealthObservationV2(payload map[string]any) string {
|
||||
decision, _ := payload["decision"].(map[string]any)
|
||||
metrics, _ := payload["metrics"].(map[string]any)
|
||||
rhythmMetrics, _ := metrics["rhythm"].(map[string]any)
|
||||
tightnessMetrics, _ := metrics["tightness"].(map[string]any)
|
||||
candidates, _ := decision["candidates"].([]any)
|
||||
|
||||
parts := make([]string, 0, 7)
|
||||
if text := compactHealthAny(decision["should_continue_optimize"]); text != "" {
|
||||
parts = append(parts, "continue="+text)
|
||||
}
|
||||
if text := strings.TrimSpace(asExecuteString(decision["recommended_operation"])); text != "" {
|
||||
parts = append(parts, "recommended="+text)
|
||||
}
|
||||
if text := strings.TrimSpace(asExecuteString(tightnessMetrics["tightness_level"])); text != "" {
|
||||
parts = append(parts, "tightness="+text)
|
||||
}
|
||||
if text := buildBlockBalanceSummary(rhythmMetrics); text != "" {
|
||||
parts = append(parts, text)
|
||||
}
|
||||
if text := compactHealthAny(decision["is_forced_imperfection"]); text != "" {
|
||||
parts = append(parts, "forced="+text)
|
||||
}
|
||||
if len(candidates) > 0 {
|
||||
parts = append(parts, fmt.Sprintf("candidates=%d", len(candidates)))
|
||||
if preview := compactHealthCandidatePreview(candidates); preview != "" {
|
||||
parts = append(parts, "options="+preview)
|
||||
}
|
||||
}
|
||||
if text := strings.TrimSpace(asExecuteString(decision["primary_problem"])); text != "" {
|
||||
parts = append(parts, "problem="+compactExecuteText(text, 36))
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return "返回了健康裁决结果。"
|
||||
}
|
||||
return strings.Join(parts, " | ")
|
||||
}
|
||||
|
||||
// buildBlockBalanceSummary 把 block_balance 连同正负来源一起压成单段摘要。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 这里只做 execute 摘要层的可读性补充,避免 LLM 只看到 balance=0 却看不到来源。
|
||||
// 2. 不改变 analyze_health 原始 JSON 结构;原始结构仍由 metrics.rhythm 提供完整字段。
|
||||
// 3. 若三个字段都缺失,则直接留空,避免构造误导性的默认值。
|
||||
func buildBlockBalanceSummary(rhythmMetrics map[string]any) string {
|
||||
if len(rhythmMetrics) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
blockBalance := compactHealthAny(rhythmMetrics["block_balance"])
|
||||
fragmentedCount := compactHealthAny(rhythmMetrics["fragmented_count"])
|
||||
compressedCount := compactHealthAny(rhythmMetrics["compressed_run_count"])
|
||||
if blockBalance == "" && fragmentedCount == "" && compressedCount == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"block_balance=%s(fragmented=%s,compressed=%s)",
|
||||
fallbackExecuteText(blockBalance, "?"),
|
||||
fallbackExecuteText(fragmentedCount, "?"),
|
||||
fallbackExecuteText(compressedCount, "?"),
|
||||
)
|
||||
}
|
||||
|
||||
func compactHealthCandidatePreview(candidates []any) string {
|
||||
if len(candidates) == 0 {
|
||||
return ""
|
||||
}
|
||||
preview := make([]string, 0, 2)
|
||||
for _, raw := range candidates {
|
||||
item, _ := raw.(map[string]any)
|
||||
if len(item) == 0 {
|
||||
continue
|
||||
}
|
||||
id := strings.TrimSpace(asExecuteString(item["candidate_id"]))
|
||||
tool := strings.TrimSpace(asExecuteString(item["tool"]))
|
||||
if id == "" && tool == "" {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case id != "" && tool != "":
|
||||
preview = append(preview, id+":"+tool)
|
||||
case id != "":
|
||||
preview = append(preview, id)
|
||||
default:
|
||||
preview = append(preview, tool)
|
||||
}
|
||||
if len(preview) >= 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.Join(preview, ",")
|
||||
}
|
||||
104
backend/newAgent/prompt/execute_next_step_hint_v2.go
Normal file
104
backend/newAgent/prompt/execute_next_step_hint_v2.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package newagentprompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
|
||||
)
|
||||
|
||||
// renderExecuteNextStepHintV2 生成 execute.msg3 的轻量方向提示。
|
||||
//
|
||||
// 设计目标:
|
||||
// 1. 主动优化模式下,只强调“先 analyze_health,再从 candidates 里选”,不再散发额外搜索暗示。
|
||||
// 2. 普通链路仍保留必要的业务引导,避免误伤用户明确提出的普通调整请求。
|
||||
// 3. 提示只给方向,不替模型代填最终写参数。
|
||||
func renderExecuteNextStepHintV2(
|
||||
state *newagentmodel.CommonState,
|
||||
latestAnalyze string,
|
||||
latestMutation string,
|
||||
roughBuildDone bool,
|
||||
) string {
|
||||
if state == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
activeDomain := strings.TrimSpace(state.ActiveToolDomain)
|
||||
activePacks := newagenttools.ResolveEffectiveToolPacks(state.ActiveToolDomain, state.ActiveToolPacks)
|
||||
|
||||
if state.ActiveOptimizeOnly {
|
||||
switch {
|
||||
case activeDomain == "" && roughBuildDone:
|
||||
return "当前是粗排后主动优化专用模式;先激活 schedule,并只围绕 analyze_health -> move/swap 候选闭环推进。"
|
||||
case !state.HealthCheckDone:
|
||||
return "当前是粗排后主动优化专用模式;先调 analyze_health,等待后端给出 candidates,再做选择。"
|
||||
case !state.HealthIsFeasible || strings.EqualFold(strings.TrimSpace(state.HealthRecommendedOperation), "ask_user"):
|
||||
return "analyze_health 已判定当前更像时间窗或信息约束问题;不要继续挪动,先把冲突或缺失点明确告诉用户。"
|
||||
case !state.HealthShouldContinueOptimize:
|
||||
return "analyze_health 已判定当前无需继续主动优化;若用户没有新增要求,直接收口。"
|
||||
default:
|
||||
return "当前是粗排后主动优化专用模式;直接从 analyze_health 的 decision.candidates 里选一个合法 move/swap 执行,不要再自己搜索读工具。"
|
||||
}
|
||||
}
|
||||
|
||||
if activeDomain == "schedule" && state.HealthCheckDone {
|
||||
switch {
|
||||
case !state.HealthShouldContinueOptimize && state.HealthIsForcedImperfection:
|
||||
return fmt.Sprintf(
|
||||
"analyze_health 已判定当前更像约束代价:tightness=%s,主问题=%s。优先考虑收口。",
|
||||
fallbackExecuteText(state.HealthTightnessLevel, "unknown"),
|
||||
fallbackExecuteText(state.HealthPrimaryProblem, "无"),
|
||||
)
|
||||
case !state.HealthShouldContinueOptimize:
|
||||
return fmt.Sprintf(
|
||||
"analyze_health 已判定当前没有更值得继续处理的局部问题:%s。若用户未追加新要求,优先收口。",
|
||||
fallbackExecuteText(state.HealthPrimaryProblem, "当前可直接收口"),
|
||||
)
|
||||
case state.HealthStagnationCount > 0:
|
||||
return fmt.Sprintf(
|
||||
"最近诊断已连续 %d 次无明显改善;若本轮仍不能让主问题变轻,优先收口。当前主问题:%s。",
|
||||
state.HealthStagnationCount,
|
||||
fallbackExecuteText(state.HealthPrimaryProblem, "无"),
|
||||
)
|
||||
case strings.EqualFold(strings.TrimSpace(state.HealthRecommendedOperation), "swap"):
|
||||
return fmt.Sprintf(
|
||||
"当前主问题:%s。优先在已有落位之间做局部 swap,别把问题扩散到更远的天数。",
|
||||
fallbackExecuteText(state.HealthPrimaryProblem, "无"),
|
||||
)
|
||||
case strings.EqualFold(strings.TrimSpace(state.HealthRecommendedOperation), "move"):
|
||||
return fmt.Sprintf(
|
||||
"当前主问题:%s。若要 move,只在近范围合法落点里小修,不要做全窗口搜索。",
|
||||
fallbackExecuteText(state.HealthPrimaryProblem, "无"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if activeDomain == "" {
|
||||
if roughBuildDone {
|
||||
return `先激活 schedule 业务域;当前是粗排后的微调场景,通常至少需要 mutation+analyze。若要按统一条件逐个处理一批任务,再加 packs=["queue"]。`
|
||||
}
|
||||
return `先判断当前任务属于哪个业务域,再用 context_tools_add 激活对应工具。`
|
||||
}
|
||||
|
||||
if activeDomain == "schedule" &&
|
||||
strings.Contains(latestMutation, "batch_move") &&
|
||||
(strings.Contains(latestMutation, "缺少") || strings.Contains(latestMutation, "无效")) {
|
||||
return `当前 batch_move 路径受参数约束;若要处理一批符合同一条件的任务,优先加 packs=["queue"] 逐个处理。`
|
||||
}
|
||||
|
||||
if activeDomain == "schedule" &&
|
||||
latestAnalyze != "" &&
|
||||
strings.Contains(latestAnalyze, "metrics") &&
|
||||
!containsExecutePack(activePacks, newagenttools.ToolPackQueue) {
|
||||
return `若诊断已经完成,下一步应转入读事实或写操作,不要重复 analyze_health;涉及同类批量任务时优先考虑 packs=["queue"]。`
|
||||
}
|
||||
|
||||
if activeDomain == "taskclass" &&
|
||||
state.TaskClassUpsertLastTried &&
|
||||
!state.TaskClassUpsertLastSuccess {
|
||||
return `先根据 validation.issues 补齐缺失字段,再重试 upsert_task_class,不要直接收口。`
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
318
backend/newAgent/prompt/execute_rule_packs.go
Normal file
318
backend/newAgent/prompt/execute_rule_packs.go
Normal file
@@ -0,0 +1,318 @@
|
||||
package newagentprompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
newagenttools "github.com/LoveLosita/smartflow/backend/newAgent/tools"
|
||||
)
|
||||
|
||||
const (
|
||||
executeRulePackCoreMin = "core_min"
|
||||
executeRulePackSafetyHard = "safety_hard"
|
||||
executeRulePackContextProtocol = "context_protocol"
|
||||
executeRulePackModePlan = "mode_plan"
|
||||
executeRulePackModeReAct = "mode_react"
|
||||
executeRulePackDomainSchedule = "domain_schedule"
|
||||
executeRulePackDomainTaskClass = "domain_taskclass"
|
||||
executeRulePackScheduleMutation = "schedule_mutation"
|
||||
executeRulePackScheduleAnalyze = "schedule_analyze"
|
||||
executeRulePackScheduleWeb = "schedule_web"
|
||||
executeRulePackMicroRoughDone = "micro_rough_build_done"
|
||||
executeRulePackMicroDiagLoop = "micro_diag_tune_loop"
|
||||
executeRulePackMicroQueue = "micro_queue_chain"
|
||||
executeRulePackMicroTaskRetry = "micro_taskclass_retry"
|
||||
)
|
||||
|
||||
const executeSystemPromptBaseWithPlan = `
|
||||
你叫 SmartMate,是时伴(SmartMate)的中文 AI 排程伙伴,面向大学生提供陪伴式日程管理与日常协助。
|
||||
你擅长课表与任务安排、任务管理、学习规划和随口记,也可以正常回答日常问答、生活建议、信息整理、分析讨论等非排程问题。
|
||||
你的目标是像一个越用越懂用户的伙伴一样,结合历史对话、长期记忆和当前上下文,给出贴心、清晰、可信的帮助。
|
||||
你当前处于“计划执行”模式。你必须围绕当前计划步骤推进,并通过 SMARTFLOW_DECISION 输出结构化动作。`
|
||||
|
||||
const executeSystemPromptBaseReAct = `
|
||||
你叫 SmartMate,是时伴(SmartMate)的中文 AI 排程伙伴,面向大学生提供陪伴式日程管理与日常协助。
|
||||
你擅长课表与任务安排、任务管理、学习规划和随口记,也可以正常回答日常问答、生活建议、信息整理、分析讨论等非排程问题。
|
||||
你的目标是像一个越用越懂用户的伙伴一样,结合历史对话、长期记忆和当前上下文,给出贴心、清晰、可信的帮助。
|
||||
你当前处于“自由执行(ReAct)”模式。你需要根据当前目标自主推进、按需调用工具,并通过 SMARTFLOW_DECISION 输出结构化动作。`
|
||||
|
||||
type executeRulePack struct {
|
||||
Name string
|
||||
Content string
|
||||
}
|
||||
|
||||
// renderExecuteRulePackSection 渲染 execute.msg0 的动态规则包区域。
|
||||
//
|
||||
// 1. 这里负责“选哪些包 + 以什么顺序展示”,不负责工具目录本身。
|
||||
// 2. 固定先放通用硬约束,再放 mode/domain/micro 包,保证模型先读边界后读特例。
|
||||
// 3. 如果没有任何可展示规则包,则直接返回空串,避免无意义占位。
|
||||
func renderExecuteRulePackSection(state *newagentmodel.CommonState, ctx *newagentmodel.ConversationContext) (string, []string) {
|
||||
packs := selectExecuteRulePacks(state, ctx)
|
||||
if len(packs) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
lines := []string{"执行规则包(msg0 动态注入):"}
|
||||
names := make([]string, 0, len(packs))
|
||||
for _, pack := range packs {
|
||||
content := strings.TrimSpace(pack.Content)
|
||||
if content == "" {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, fmt.Sprintf("[%s]", pack.Name))
|
||||
lines = append(lines, content)
|
||||
names = append(names, pack.Name)
|
||||
}
|
||||
if len(names) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return strings.Join(lines, "\n"), names
|
||||
}
|
||||
|
||||
func selectExecuteRulePacks(state *newagentmodel.CommonState, ctx *newagentmodel.ConversationContext) []executeRulePack {
|
||||
selected := make([]executeRulePack, 0, 8)
|
||||
seen := map[string]bool{}
|
||||
|
||||
appendPack := func(pack executeRulePack) {
|
||||
name := strings.TrimSpace(pack.Name)
|
||||
if name == "" || seen[name] {
|
||||
return
|
||||
}
|
||||
seen[name] = true
|
||||
selected = append(selected, pack)
|
||||
}
|
||||
|
||||
appendPack(buildExecuteCoreMinPack())
|
||||
appendPack(buildExecuteSafetyHardPack())
|
||||
appendPack(buildExecuteContextProtocolPack())
|
||||
|
||||
if state != nil && state.HasPlan() {
|
||||
appendPack(buildExecuteModePlanPack())
|
||||
} else {
|
||||
appendPack(buildExecuteModeReActPack())
|
||||
}
|
||||
|
||||
switch normalizeExecuteToolDomain(readExecuteActiveToolDomain(state)) {
|
||||
case "schedule":
|
||||
activePacks := readExecuteActiveToolPacks(state)
|
||||
appendPack(buildExecuteSchedulePack())
|
||||
if hasExecutePack(activePacks, newagenttools.ToolPackQueue) {
|
||||
appendPack(buildExecuteQueueMicroPack())
|
||||
}
|
||||
if hasExecutePack(activePacks, newagenttools.ToolPackMutation) {
|
||||
appendPack(buildExecuteScheduleMutationPack())
|
||||
}
|
||||
if hasExecutePack(activePacks, newagenttools.ToolPackAnalyze) {
|
||||
appendPack(buildExecuteScheduleAnalyzePackV2())
|
||||
}
|
||||
if hasExecutePack(activePacks, newagenttools.ToolPackWeb) {
|
||||
appendPack(buildExecuteScheduleWebPack())
|
||||
}
|
||||
case "taskclass":
|
||||
appendPack(buildExecuteTaskClassPack())
|
||||
}
|
||||
|
||||
if hasExecuteRoughBuildDone(ctx) {
|
||||
appendPack(buildExecuteRoughDoneMicroPack())
|
||||
}
|
||||
if shouldInjectExecuteDiagLoopPack(state, ctx) {
|
||||
appendPack(buildExecuteDiagLoopMicroPackV2())
|
||||
}
|
||||
if state != nil && state.TaskClassUpsertLastTried && !state.TaskClassUpsertLastSuccess {
|
||||
appendPack(buildExecuteTaskClassRetryMicroPack())
|
||||
}
|
||||
|
||||
return selected
|
||||
}
|
||||
|
||||
func readExecuteActiveToolDomain(state *newagentmodel.CommonState) string {
|
||||
if state == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(state.ActiveToolDomain)
|
||||
}
|
||||
|
||||
func readExecuteActiveToolPacks(state *newagentmodel.CommonState) []string {
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
return newagenttools.ResolveEffectiveToolPacks(state.ActiveToolDomain, state.ActiveToolPacks)
|
||||
}
|
||||
|
||||
func hasExecutePack(packs []string, target string) bool {
|
||||
target = strings.ToLower(strings.TrimSpace(target))
|
||||
if target == "" {
|
||||
return false
|
||||
}
|
||||
for _, pack := range packs {
|
||||
if strings.ToLower(strings.TrimSpace(pack)) == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// containsExecutePack 兼容旧调用点。
|
||||
//
|
||||
// 1. 这里只做别名转发,不引入第二套判断口径。
|
||||
// 2. 保留它是为了避免下一轮再因为历史调用点而误删。
|
||||
func containsExecutePack(packs []string, target string) bool {
|
||||
return hasExecutePack(packs, target)
|
||||
}
|
||||
|
||||
func normalizeExecuteToolDomain(domain string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(domain)) {
|
||||
case "schedule":
|
||||
return "schedule"
|
||||
case "taskclass":
|
||||
return "taskclass"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteCoreMinPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackCoreMin,
|
||||
Content: strings.TrimSpace(fmt.Sprintf(`
|
||||
- 当前时间锚点:%s。涉及“今天/明天/本周”等相对时间时,先按该锚点换算。
|
||||
- 用户意图优先:只推进用户当前明确要求;未明确部分优先 ask_user。
|
||||
- 先事实后动作:优先读工具补齐事实,再决定下一步。
|
||||
- 只要决定调用 place/move/swap/batch_move/unplace 这类写工具,就必须输出 action=confirm;continue + 写工具无效。
|
||||
- 输出格式固定:先 <SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>,再输出用户可见正文。`,
|
||||
buildExecuteNowAnchorLine())),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteNowAnchorLine() string {
|
||||
now := time.Now()
|
||||
weekdays := []string{"周日", "周一", "周二", "周三", "周四", "周五", "周六"}
|
||||
return fmt.Sprintf("%s(%s,%s)", now.Format("2006-01-02 15:04:05 -07:00"), weekdays[int(now.Weekday())], now.Format("MST"))
|
||||
}
|
||||
|
||||
func buildExecuteSafetyHardPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackSafetyHard,
|
||||
Content: strings.TrimSpace(`
|
||||
- 严禁伪造工具结果;若新结果与既有事实冲突,先重查一次再决定。
|
||||
- 工具参数必须严格使用 schema 字段名,禁止自造别名。
|
||||
- JSON 只保留当前 action 必需字段;不要输出空字符串、空对象、空数组或 null 占位。
|
||||
- P1 阶段禁止调用 min_context_switch。
|
||||
- 连续两轮同类读查询后,必须转执行 / ask_user / 明确说明阻塞,不能无限空转。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteContextProtocolPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackContextProtocol,
|
||||
Content: strings.TrimSpace(`
|
||||
- msg0 动态区初始仅保留 context_tools_add / context_tools_remove。
|
||||
- 需要业务工具前先 context_tools_add:排程用 domain="schedule",任务类写入用 domain="taskclass"。
|
||||
- schedule 可选 packs=["mutation","analyze","detail_read","deep_analyze","queue","web"];core 固定注入,不要显式传 core。
|
||||
- 只在业务方向切换时再 remove;done 后的动态区清理由系统自动完成,不必手动 remove。
|
||||
- 如果目标工具当前不在可用列表,先 add 对应 domain / packs,再继续执行。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteModePlanPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackModePlan,
|
||||
Content: strings.TrimSpace(`
|
||||
- 当前为计划执行模式:必须围绕当前计划步骤推进。
|
||||
- 未满足 done_when 时,只能 continue / confirm / ask_user,禁止 next_plan。
|
||||
- next_plan / done 时,goal_check 必须是字符串,并对照 done_when 给出完成证据。
|
||||
- 禁止跳步执行后续计划。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteModeReActPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackModeReAct,
|
||||
Content: strings.TrimSpace(`
|
||||
- 当前为自由执行(ReAct)模式:可自主决定 continue / confirm / ask_user / done / abort。
|
||||
- 如果关键事实无法通过工具补齐,优先 ask_user,不做猜测落库。
|
||||
- 自主推进时要小步快跑,优先闭合当前局部问题,不要发散成大范围开放搜索。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteSchedulePack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackDomainSchedule,
|
||||
Content: strings.TrimSpace(`
|
||||
- 当前业务域为 schedule:只处理当前目标任务类,不重排无关内容。
|
||||
- existing 只作事实参考;真正可调对象优先看 suggested。
|
||||
- 同任务类内部顺序必须保持,任何越过前驱/后继边界的移动都会被写工具拒绝。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteScheduleMutationPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackScheduleMutation,
|
||||
Content: strings.TrimSpace(`
|
||||
- mutation 包负责真正落日程写操作:place / move / swap / batch_move / unplace。
|
||||
- 写操作必须走 action=confirm;不要在 continue 里偷跑写工具。
|
||||
- 若是主动优化链路,优先在后端给出的合法候选中选择,不要自己再全窗搜索新坑位。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteQueueMicroPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackMicroQueue,
|
||||
Content: strings.TrimSpace(`
|
||||
- queue 包适合“按同一条件逐个处理一批任务”的场景,例如把所有早八任务依次挪走。
|
||||
- query_target_tasks 可结合 enqueue=true 先把候选任务入队,再用 queue_pop_head / queue_apply_head_move / queue_skip_head 顺序处理。
|
||||
- 当你需要连续处理多条相似任务时,优先走 queue,避免把整批任务细节长期堆在上下文里。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteScheduleWebPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackScheduleWeb,
|
||||
Content: strings.TrimSpace(`
|
||||
- web 包只用于补充通用学习资料或通识信息,不用于捏造个人时间、考试时间、DDL 或排程事实。
|
||||
- web_search 先粗搜,web_fetch 再抓正文;不确定时宁可不用,也不要把网页结果当成排程事实直接写入。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteTaskClassPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackDomainTaskClass,
|
||||
Content: strings.TrimSpace(`
|
||||
- taskclass 域只负责生成或修正任务类,不代表已经开始排程。
|
||||
- upsert_task_class 若返回 validation.ok=false,必须先处理 validation.issues,再考虑重试或 ask_user。
|
||||
- subject_type / difficulty_level / cognitive_intensity 是任务类语义画像必填项;优先静默推断,只有确实无法判断时再 ask_user。
|
||||
- excluded_slots 取值应与系统节次定义一致;excluded_days_of_week 使用 1~7 表示周一到周日。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteRoughDoneMicroPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackMicroRoughDone,
|
||||
Content: strings.TrimSpace(`
|
||||
- 已有 rough_build_done:本轮以微调为主,不要把任务重新当成“未排入”再全量 place。
|
||||
- 若当前问题已经可接受,应优先收口,不要为了追求完美继续反复局部打磨。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteTaskClassRetryMicroPack() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackMicroTaskRetry,
|
||||
Content: strings.TrimSpace(`
|
||||
- 最近一次 upsert_task_class 失败时,优先围绕 validation.issues 修补。
|
||||
- 问题未解决前,不要用 done 假装收口;要么重试,要么 ask_user 补关键信息。`),
|
||||
}
|
||||
}
|
||||
|
||||
func shouldInjectExecuteDiagLoopPack(state *newagentmodel.CommonState, ctx *newagentmodel.ConversationContext) bool {
|
||||
if state == nil || !hasExecuteRoughBuildDone(ctx) {
|
||||
return false
|
||||
}
|
||||
if normalizeExecuteToolDomain(readExecuteActiveToolDomain(state)) != "schedule" {
|
||||
return false
|
||||
}
|
||||
activePacks := readExecuteActiveToolPacks(state)
|
||||
return hasExecutePack(activePacks, newagenttools.ToolPackAnalyze) &&
|
||||
hasExecutePack(activePacks, newagenttools.ToolPackMutation)
|
||||
}
|
||||
27
backend/newAgent/prompt/execute_rule_packs_health.go
Normal file
27
backend/newAgent/prompt/execute_rule_packs_health.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package newagentprompt
|
||||
|
||||
import "strings"
|
||||
|
||||
func buildExecuteScheduleAnalyzePackV2() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackScheduleAnalyze,
|
||||
Content: strings.TrimSpace(`
|
||||
- analyze 包已激活:优先使用 analyze_health 判断“现在还值不值得继续主动优化”,不要把它当成全能体检表。
|
||||
- 若需要维度级细诊断(如 rhythm),再 add packs=["deep_analyze"],不要默认把所有分析都铺开。
|
||||
- 在主动优化专用模式里,analyze_health 会直接返回 decision.candidates:这些就是后端已经验证合法、并且复诊后确实变好的 move/swap 候选。
|
||||
- 一旦 decision.candidates 已经给出,下一步应直接从候选里选一个去执行;不要再自己搜索 query_target_tasks / query_available_slots。
|
||||
- 若 analyze_health 显示 should_continue_optimize=false,优先收口;不要因为“理论上还还能动”就继续局部修补。`),
|
||||
}
|
||||
}
|
||||
|
||||
func buildExecuteDiagLoopMicroPackV2() executeRulePack {
|
||||
return executeRulePack{
|
||||
Name: executeRulePackMicroDiagLoop,
|
||||
Content: strings.TrimSpace(`
|
||||
- 粗排后的主动优化允许多轮 execute,但每一轮都必须围绕“当前主问题”做局部、小范围、可解释的调整。
|
||||
- 在主动优化专用模式里,analyze_health 负责“出候选题”,你只负责在 decision.candidates 里做选择,不负责重新全窗搜点。
|
||||
- 若当前问题主要来自时间窗过紧,或所有合法候选都只是平移没有变轻,应接受局部不完美并收口。
|
||||
- 若连续两轮诊断没有明显改善,或当前 recommended_operation 已经是 close,应优先收口。
|
||||
- 主动优化优先在已有落位之间做选择:swap 优先,move 次之;不要做全窗口搜索。`),
|
||||
}
|
||||
}
|
||||
@@ -8,58 +8,46 @@ import (
|
||||
"github.com/cloudwego/eino/schema"
|
||||
)
|
||||
|
||||
const planSystemPrompt = `
|
||||
你是 SmartMate 的规划器。
|
||||
你的职责不是直接执行任务,而是先把用户意图拆成一组清晰、稳定、可逐步执行的自然语言计划,并严格按后端约定的 JSON 协议输出。
|
||||
const planSystemPromptCore = `
|
||||
你是 SmartMate 的规划器(Planner),只负责规划,不负责执行。
|
||||
|
||||
请遵守以下规则:
|
||||
1. 只负责规划,不要假装已经调用了工具,也不要伪造执行结果。
|
||||
2. 每一轮只推进一步规划;如果信息不足,应明确转成 ask_user,而不是继续硬猜。
|
||||
3. 若当前计划仍不完整,就继续围绕当前任务补全计划,不要跳去执行细节。
|
||||
4. 若你认为计划已经完整可执行,请返回 action=plan_done,并附带完整 plan_steps。
|
||||
5. plan_steps 必须使用自然语言,便于后端将完整 plan 重新注入到后续上下文顶部。
|
||||
6. 输出格式:先输出一行 <SMARTFLOW_DECISION>{JSON 决策}</SMARTFLOW_DECISION>,然后换行输出给用户看的自然语言正文。JSON 中不要包含 speak 字段——用户可见的话放在标签之后。
|
||||
7. 每次输出前先评估任务复杂度:simple(简单明确,无复杂依赖)、moderate(多步操作,需要一定推理)、complex(需要深度推理、多方案比较或复杂依赖关系)。
|
||||
8. 粗排识别规则:若满足以下两个条件,在 action=plan_done 时附加 needs_rough_build=true 和 task_class_ids:
|
||||
条件1:用户输入中存在"任务类 ID"字段(见上下文"任务类 ID"部分);
|
||||
条件2:用户意图明确是"批量安排/帮我排课/把任务类排进日程"等批量调度需求。
|
||||
满足时:后端会在用户确认计划后自动运行粗排算法(硬性约束已由算法保证,无需 LLM 校验)。
|
||||
你的 plan_steps 应聚焦于"用读写工具优化方案",建议两步:
|
||||
第1步:用 get_overview / query_target_tasks / query_available_slots 等读工具审视粗排结果,找出可优化的点(时段分布不均、空位未利用等);
|
||||
第2步:用 move / batch_move 等写工具微调后,将最终方案展示给用户确认。
|
||||
禁止安排任何"校验/验证约束"步骤——硬性约束由算法兜底,LLM 不需要操心。
|
||||
最高优先级规则:
|
||||
1. 意图边界:只规划用户当前明确要求,禁止擅自扩展后续动作。
|
||||
2. 事实边界:禁止伪造工具调用和执行结果。
|
||||
|
||||
你会看到:
|
||||
- 当前阶段与轮次信息
|
||||
- 已有完整 plan(如果之前已经规划过)
|
||||
- 当前步骤(如果已存在)
|
||||
- 置顶上下文块
|
||||
- 可用工具摘要
|
||||
- 历史对话
|
||||
|
||||
请基于这些输入继续规划,而不是重复忽略既有 plan。
|
||||
`
|
||||
规划规则:
|
||||
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。`
|
||||
|
||||
// BuildPlanSystemPrompt 返回规划阶段系统提示词。
|
||||
func BuildPlanSystemPrompt() string {
|
||||
return strings.TrimSpace(planSystemPrompt)
|
||||
parts := []string{
|
||||
strings.TrimSpace(planSystemPromptCore),
|
||||
BuildPlanDecisionContractText(),
|
||||
}
|
||||
return strings.TrimSpace(strings.Join(parts, "\n\n"))
|
||||
}
|
||||
|
||||
// BuildPlanMessages 组装规划阶段的 messages。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责把 state + context 收敛成统一 4 段式规划阶段模型输入;
|
||||
// 2. 不负责解析模型输出,也不负责判断规划质量;
|
||||
// 3. msg3 中的状态文本由本函数显式传入,确保统一骨架下仍能看到完整计划与阶段信息。
|
||||
// 1. 规划阶段只保留 Planner 专用规则,跳过通用人格底座,避免角色指令冲突。
|
||||
// 2. msg1 展示真实对话,msg2 展示规划工作区,msg3 仅给最小执行指令与用户本轮输入。
|
||||
// 3. 工具目录使用轻量版,仅提供“有什么工具”,不注入执行态大段参数示例。
|
||||
func BuildPlanMessages(state *newagentmodel.CommonState, ctx *newagentmodel.ConversationContext, userInput string) []*schema.Message {
|
||||
return buildUnifiedStageMessages(
|
||||
ctx,
|
||||
StageMessagesConfig{
|
||||
SystemPrompt: BuildPlanSystemPrompt(),
|
||||
Msg1Content: buildPlanConversationMessage(ctx),
|
||||
Msg2Content: buildPlanWorkspace(state),
|
||||
Msg3Suffix: BuildPlanUserPrompt(state, userInput),
|
||||
Msg3Role: schema.User,
|
||||
SystemPrompt: BuildPlanSystemPrompt(),
|
||||
Msg1Content: buildPlanConversationMessage(ctx),
|
||||
Msg2Content: buildPlanWorkspace(state),
|
||||
Msg3Suffix: BuildPlanUserPrompt(state, userInput),
|
||||
Msg3Role: schema.User,
|
||||
SkipBaseSystemPrompt: true,
|
||||
UseLiteToolCatalogMsg: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -68,9 +56,9 @@ func BuildPlanMessages(state *newagentmodel.CommonState, ctx *newagentmodel.Conv
|
||||
func BuildPlanUserPrompt(state *newagentmodel.CommonState, userInput string) string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("请继续当前任务的规划阶段,严格按 SMARTFLOW_DECISION 标签格式输出。\n")
|
||||
sb.WriteString("目标:围绕最近对话和规划工作区信息,产出一份稳定、可执行的自然语言计划;若关键信息不足,请明确 ask_user。\n\n")
|
||||
sb.WriteString(BuildPlanDecisionContractText())
|
||||
sb.WriteString("请继续当前任务规划,只输出一组 SMARTFLOW_DECISION 决策。\n")
|
||||
sb.WriteString("请基于最近对话与规划工作区推进,不要重复已有计划内容。\n")
|
||||
sb.WriteString("输出格式与字段约束严格按 msg0 协议执行。\n")
|
||||
|
||||
trimmedInput := strings.TrimSpace(userInput)
|
||||
if trimmedInput != "" {
|
||||
@@ -85,40 +73,30 @@ 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;标签之后换行输出给用户看的自然语言正文。
|
||||
决策标签格式:<SMARTFLOW_DECISION>{JSON}</SMARTFLOW_DECISION>
|
||||
|
||||
JSON 字段说明:
|
||||
JSON 字段:
|
||||
- action:只能是 %s / %s / %s
|
||||
- reason:给后端和日志看的简短说明
|
||||
- complexity:任务复杂度,只能是 simple / moderate / complex
|
||||
- plan_steps:仅当 action=%s 时允许返回;返回时必须是完整计划,不是增量
|
||||
- complexity:只能是 simple / moderate / complex
|
||||
- plan_steps:仅当 action=%s 时允许返回,且必须是完整计划
|
||||
- plan_steps[].content:步骤正文,必填
|
||||
- plan_steps[].done_when:可选,建议写"什么情况下算这一步做完"
|
||||
- needs_rough_build:仅当满足粗排识别规则时为 true,否则省略;为 true 时后端自动运行粗排算法
|
||||
- task_class_ids:needs_rough_build=true 时必填,从上下文"任务类 ID"字段读取
|
||||
- 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 字段。给用户看的话放在 </SMARTFLOW_DECISION> 标签之后。
|
||||
|
||||
合法示例:
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"当前信息已足够继续规划","complexity":"moderate"}</SMARTFLOW_DECISION>
|
||||
我先把计划再收束一下。
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"当前时间范围仍不明确","complexity":"simple"}</SMARTFLOW_DECISION>
|
||||
你更希望我优先安排今天,还是按整周来规划?
|
||||
|
||||
<SMARTFLOW_DECISION>{"action":"%s","reason":"当前计划已具备执行条件","complexity":"simple","plan_steps":[{"content":"先确认本周可用时间范围","done_when":"拿到明确的可用时间段列表"},{"content":"基于可用时间生成执行安排","done_when":"得到一份用户可确认的安排方案"}]}</SMARTFLOW_DECISION>
|
||||
计划已经整理好了,我先给你确认一下。
|
||||
`,
|
||||
注意:
|
||||
- JSON 中不要包含 speak 字段
|
||||
- 不要在 planning 阶段调用任何工具(包括 context_tools_add/remove)`,
|
||||
newagentmodel.PlanActionContinue,
|
||||
newagentmodel.PlanActionAskUser,
|
||||
newagentmodel.PlanActionDone,
|
||||
newagentmodel.PlanActionDone,
|
||||
newagentmodel.PlanActionContinue,
|
||||
newagentmodel.PlanActionAskUser,
|
||||
newagentmodel.PlanActionDone,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -127,7 +127,25 @@ func renderPlanTaskClassMeta(state *newagentmodel.CommonState) string {
|
||||
if tc.StartDate != "" || tc.EndDate != "" {
|
||||
line += fmt.Sprintf(";日期范围:%s ~ %s", tc.StartDate, tc.EndDate)
|
||||
}
|
||||
if len(tc.ExcludedDaysOfWeek) > 0 {
|
||||
line += fmt.Sprintf(";排除星期:%v", tc.ExcludedDaysOfWeek)
|
||||
}
|
||||
if tc.SubjectType != "" || tc.DifficultyLevel != "" || tc.CognitiveIntensity != "" {
|
||||
line += fmt.Sprintf(";语义画像:%s/%s/%s",
|
||||
planSemanticValue(tc.SubjectType),
|
||||
planSemanticValue(tc.DifficultyLevel),
|
||||
planSemanticValue(tc.CognitiveIntensity),
|
||||
)
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func planSemanticValue(value string) string {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return "未标注"
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package newagentprompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||||
@@ -45,6 +46,13 @@ type StageMessagesConfig struct {
|
||||
// Msg3Role 指定第 4 条消息的角色。
|
||||
// Execute 继续使用 system,其余节点一般使用 user。
|
||||
Msg3Role schema.RoleType
|
||||
|
||||
// SkipBaseSystemPrompt 为 true 时,msg0 只使用节点自己的 SystemPrompt,
|
||||
// 不再拼接 ConversationContext.SystemPrompt。
|
||||
SkipBaseSystemPrompt bool
|
||||
|
||||
// UseLiteToolCatalogMsg 为 true 时,msg0 工具目录采用轻量模式(仅名称与职责)。
|
||||
UseLiteToolCatalogMsg bool
|
||||
}
|
||||
|
||||
// buildUnifiedStageMessages 组装统一 4 段式消息骨架。
|
||||
@@ -58,7 +66,7 @@ func buildUnifiedStageMessages(
|
||||
ctx *newagentmodel.ConversationContext,
|
||||
config StageMessagesConfig,
|
||||
) []*schema.Message {
|
||||
msg0 := buildUnifiedMsg0(config.SystemPrompt, ctx)
|
||||
msg0 := buildUnifiedMsg0(config.SystemPrompt, ctx, config.SkipBaseSystemPrompt, config.UseLiteToolCatalogMsg)
|
||||
msg1 := buildUnifiedMsg1(config.Msg1Content)
|
||||
msg2 := buildUnifiedMsg2(config.Msg2Content)
|
||||
msg3 := buildUnifiedMsg3(ctx, config)
|
||||
@@ -85,19 +93,72 @@ func buildUnifiedMsg3Message(content string, role schema.RoleType) *schema.Messa
|
||||
// 1. 先合并基础系统提示与节点系统提示,保证模型身份稳定;
|
||||
// 2. 若当前节点注入了工具 schema,则附加紧凑工具目录;
|
||||
// 3. 若两部分都为空,则回退到最小兜底提示,避免出现空消息。
|
||||
func buildUnifiedMsg0(stageSystemPrompt string, ctx *newagentmodel.ConversationContext) string {
|
||||
base := strings.TrimSpace(mergeSystemPrompts(ctx, stageSystemPrompt))
|
||||
func buildUnifiedMsg0(stageSystemPrompt string, ctx *newagentmodel.ConversationContext, skipBaseSystemPrompt bool, useLiteToolCatalog bool) string {
|
||||
base := ""
|
||||
if skipBaseSystemPrompt {
|
||||
base = strings.TrimSpace(stageSystemPrompt)
|
||||
} else {
|
||||
base = strings.TrimSpace(mergeSystemPrompts(ctx, stageSystemPrompt))
|
||||
}
|
||||
if base == "" {
|
||||
base = "你是 SmartMate 助手,请继续当前阶段。"
|
||||
}
|
||||
|
||||
toolCatalog := renderExecuteToolCatalogCompact(ctx)
|
||||
toolCatalog := renderExecuteToolCatalogCompact(ctx, nil)
|
||||
if useLiteToolCatalog {
|
||||
toolCatalog = renderUnifiedToolCatalogLite(ctx)
|
||||
}
|
||||
if toolCatalog == "" {
|
||||
return base
|
||||
}
|
||||
return base + "\n\n" + toolCatalog
|
||||
}
|
||||
|
||||
// renderUnifiedToolCatalogLite 渲染统一阶段可用工具的轻量目录。
|
||||
//
|
||||
// 1. 只展示工具名和一句话职责,避免把 execute 的参数/返回示例污染到 plan/chat/deliver。
|
||||
// 2. 目录信息仅用于“能力边界感知”,不承担具体参数指导。
|
||||
// 3. 当工具数量过多时保留前若干项并给出省略提示,控制 msg0 体积。
|
||||
func renderUnifiedToolCatalogLite(ctx *newagentmodel.ConversationContext) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
schemas := ctx.ToolSchemasSnapshot()
|
||||
if len(schemas) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
const maxItems = 18
|
||||
lines := []string{"当前可用工具(轻量目录):"}
|
||||
added := 0
|
||||
|
||||
for _, item := range schemas {
|
||||
name := strings.TrimSpace(item.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
desc := strings.TrimSpace(item.Desc)
|
||||
if desc == "" {
|
||||
lines = append(lines, fmt.Sprintf("- %s", name))
|
||||
} else {
|
||||
lines = append(lines, fmt.Sprintf("- %s:%s", name, desc))
|
||||
}
|
||||
added++
|
||||
if added >= maxItems {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if added == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(schemas) > added {
|
||||
lines = append(lines, fmt.Sprintf("- 其余 %d 个工具已省略(按需再看)。", len(schemas)-added))
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// buildUnifiedMsg1 返回节点自行提供的历史视图。
|
||||
//
|
||||
// 说明:
|
||||
|
||||
Reference in New Issue
Block a user