package newagentprompt import ( "encoding/json" "fmt" "sort" "strconv" "strings" newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model" "github.com/cloudwego/eino/schema" ) const ( // executeHistoryKindKey 用于在 history 里区分普通用户消息与后端注入的纠错提示。 // 这里负责“识别并过滤”,不负责写入该标记。 executeHistoryKindKey = "newagent_history_kind" executeHistoryKindCorrectionUser = "llm_correction_prompt" ) type executeToolSchemaDoc struct { Name string `json:"name"` Parameters map[string]any `json:"parameters"` } type executeLoopRecord struct { Thought string ToolName string ToolArgs string Observation string } type conversationTurn struct { Role string Content string } type executeLatestToolRecord struct { ToolName string Observation string } // buildExecuteStageMessages 组装 execute 阶段的四段式消息。 // // 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, state, ctx) msg1 := buildExecuteMessage1V3(ctx) msg2 := buildExecuteMessage2V3(ctx) msg3 := buildExecuteMessage3(state, ctx, runtimeUserPrompt) return []*schema.Message{ schema.SystemMessage(msg0), {Role: schema.Assistant, Content: msg1}, {Role: schema.Assistant, Content: msg2}, schema.SystemMessage(msg3), } } // 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 执行器,请继续当前执行阶段。" } rulePackSection, _ := renderExecuteRulePackSection(state, ctx) if rulePackSection != "" { base += "\n\n" + rulePackSection } toolCatalog := renderExecuteToolCatalogCompact(ctx, state) if toolCatalog != "" { base += "\n\n" + toolCatalog } return base } // buildExecuteMessage1V3 只渲染真实对话流,不混入 tool observation。 func buildExecuteMessage1V3(ctx *newagentmodel.ConversationContext) string { lines := []string{"历史上下文:"} if ctx == nil { lines = append(lines, "- 对话历史:暂无。", "- 阶段锚点:按当前工具事实推进执行。", ) return strings.Join(lines, "\n") } turns := collectExecuteConversationTurns(ctx.HistorySnapshot()) if len(turns) == 0 { lines = append(lines, "- 对话历史:暂无。") } else { turnLines := make([]string, 0, len(turns)+1) turnLines = append(turnLines, "对话历史:") for _, turn := range turns { turnLines = append(turnLines, turn.Role+": \""+turn.Content+"\"") } lines = append(lines, strings.Join(turnLines, "\n")) } if hasExecuteRoughBuildDone(ctx) { lines = append(lines, "- 阶段锚点:粗排已完成,本轮仅做微调,不重新 place。") } else { lines = append(lines, "- 阶段锚点:按当前工具事实推进,不做无依据操作。") } return strings.Join(lines, "\n") } // buildExecuteMessage2V3 只承载当轮 ReAct loop。 // // 1. 每条记录固定展示 thought / tool_call / observation,方便模型做局部闭环。 // 2. 如果当前还没有任何 tool loop,明确给“新一轮”占位,避免模型误判缺上下文。 func buildExecuteMessage2V3(ctx *newagentmodel.ConversationContext) string { lines := []string{"当轮 ReAct Loop 记录:"} if ctx == nil { lines = append(lines, "- 暂无可用 ReAct 记录。") return strings.Join(lines, "\n") } loops := collectExecuteLoopRecords(ctx.HistorySnapshot()) if len(loops) == 0 { lines = append(lines, "- 已清空(新一轮 loop 准备中)。") return strings.Join(lines, "\n") } for i, loop := range loops { lines = append(lines, fmt.Sprintf("%d) thought/reason:%s", i+1, loop.Thought)) lines = append(lines, fmt.Sprintf(" tool_call:%s", renderExecuteToolCallText(loop.ToolName, loop.ToolArgs))) lines = append(lines, fmt.Sprintf(" observation:%s", loop.Observation)) } 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 { maxRounds = state.MaxRounds } if state.HasPlan() { modeText = "计划执行(有预定义步骤)" } activeDomain = strings.TrimSpace(state.ActiveToolDomain) activePacks = readExecuteActiveToolPacks(state) } lines = append(lines, fmt.Sprintf("- 当前轮次:%d/%d", roundUsed, maxRounds), "- 当前模式:"+modeText, ) 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, "计划步骤锚点(强约束):") if step, ok := state.CurrentPlanStep(); ok { stepContent := strings.TrimSpace(step.Content) if stepContent == "" { stepContent = "(当前步骤内容为空)" } doneWhen := strings.TrimSpace(step.DoneWhen) if doneWhen == "" { doneWhen = "(未提供 done_when,需基于步骤目标给出可验证完成证据)" } 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, "- 当前计划步骤不可读;请先判断是否已完成全部计划。", "- 若已完成全部计划,输出 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:你可以根据工具调用记录自行判断。", "- 非目标:不重新粗排、不修改无关任务类。", ) if roughBuildDone { lines = append(lines, "- 阶段约束:粗排已完成,本轮只微调 suggested;existing 仅作已安排事实参考,不作为可移动目标。") } lines = append(lines, "- 参数纪律:工具参数必须严格使用 schema 字段;若返回“参数非法”,需先改参再继续。") if state != nil { if state.AllowReorder { lines = append(lines, "- 顺序策略:用户已明确允许打乱顺序,但当前主链不再提供顺序重排工具,请优先使用 move/swap 做局部调整。") } else { lines = append(lines, "- 顺序策略:默认保持 suggested 相对顺序,仅做局部 move/swap 调整。") } } 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 = "请继续当前任务执行阶段,严格按 SMARTFLOW_DECISION 标签格式输出。" } else { instruction = firstExecuteLine(instruction) } lines = append(lines, "本轮指令:"+instruction) return strings.Join(lines, "\n") } // renderExecuteToolCatalogCompact 将当前 tool schemas 渲染为紧凑简表。 // // 1. 这里只给模型最低必要的参数和返回值感知,不重复塞完整 schema JSON。 // 2. 对复杂工具额外给一条调用示例,降低“参数字段写错”的概率。 // 3. 这里只展示当前真实可用工具,避免历史残留能力继续污染工具面。 func renderExecuteToolCatalogCompact(ctx *newagentmodel.ConversationContext, state *newagentmodel.CommonState) string { if ctx == nil { return "" } schemas := ctx.ToolSchemasSnapshot() if len(schemas) == 0 { return "" } lines := []string{"可用工具(简表):"} index := 0 for _, schemaItem := range schemas { name := strings.TrimSpace(schemaItem.Name) if name == "" { continue } index++ desc := strings.TrimSpace(schemaItem.Desc) if desc == "" { 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) 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") } 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", "upsert_task_class": return true default: return false } } func renderExecuteToolCallHint(toolName string) string { switch strings.ToLower(strings.TrimSpace(toolName)) { case "upsert_task_class": 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 "" } } func renderExecuteToolReturnHint(toolName string) (returnType string, sample string) { returnType = "string(自然语言文本)" switch strings.ToLower(strings.TrimSpace(toolName)) { case "get_overview": return returnType, "规划窗口共27天...课程占位条目34个...任务清单(已过滤课程)..." case "get_task_info": 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": return "string(JSON字符串)", `{"tool":"query_target_tasks","count":6,"status":"suggested","enqueue":true,"enqueued":6,"queue":{"pending_count":6},"items":[{"task_id":35,"name":"示例任务","status":"suggested","slots":[{"day":3,"week":12,"day_of_week":1,"slot_start":5,"slot_end":6}]}]}` case "queue_pop_head": return "string(JSON字符串)", `{"tool":"queue_pop_head","has_head":true,"pending_count":5,"current":{"task_id":35,"name":"示例任务","status":"suggested","slots":[{"day":3,"week":12,"day_of_week":1,"slot_start":5,"slot_end":6}]}}` case "queue_status": return "string(JSON字符串)", `{"tool":"queue_status","pending_count":5,"completed_count":1,"skipped_count":0,"current_task_id":35,"current_attempt":1}` case "queue_apply_head_move": return "string(JSON字符串)", `{"tool":"queue_apply_head_move","success":true,"task_id":35,"pending_count":4,"completed_count":2,"result":"已将 [35]... 从第3天第5-6节移至第5天第3-4节。"}` case "queue_skip_head": return "string(JSON字符串)", `{"tool":"queue_skip_head","success":true,"skipped_task_id":35,"pending_count":4,"skipped_count":1}` case "query_range": return returnType, "第5天第3-6节:第3节空、第4节空..." case "place": return returnType, "已将 [35]... 预排到第5天第3-4节。" case "move": return returnType, "已将 [35]... 从第3天第5-6节移至第5天第3-4节。" case "swap": return returnType, "交换完成:[35]... ↔ [36]..." case "batch_move": return returnType, "批量移动完成,2 个任务全部成功。" case "unplace": return returnType, "已将 [35]... 移除,恢复为待安排状态。" case "web_search": 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 "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, "自然语言结果(成功/失败原因/关键数据摘要)。" } } func parseExecuteToolSchema(schemaText string) executeToolSchemaDoc { doc := executeToolSchemaDoc{Parameters: map[string]any{}} schemaText = strings.TrimSpace(schemaText) if schemaText == "" { return doc } if err := json.Unmarshal([]byte(schemaText), &doc); err != nil { return doc } if doc.Parameters == nil { doc.Parameters = map[string]any{} } return doc } func renderExecuteToolParamSummary(parameters map[string]any) string { if len(parameters) == 0 { return "{}" } keys := make([]string, 0, len(parameters)) for key := range parameters { keys = append(keys, key) } sort.Strings(keys) parts := make([]string, 0, len(keys)) for _, key := range keys { status := "可选" typeText := "" switch typed := parameters[key].(type) { case string: status = "必填" typeText = strings.TrimSpace(typed) case map[string]any: if required, ok := typed["required"].(bool); ok && required { status = "必填" } typeText = strings.TrimSpace(asExecuteString(typed["type"])) if enumRaw, ok := typed["enum"].([]any); ok && len(enumRaw) > 0 { enumText := make([]string, 0, len(enumRaw)) for _, item := range enumRaw { enumText = append(enumText, fmt.Sprintf("%v", item)) } if typeText == "" { typeText = "enum" } typeText += ":" + strings.Join(enumText, "/") } } if typeText == "" { parts = append(parts, fmt.Sprintf("%s(%s)", key, status)) continue } parts = append(parts, fmt.Sprintf("%s(%s,%s)", key, status, typeText)) } return strings.Join(parts, ";") } // collectExecuteLoopRecords 从 history 里提取 thought + tool_call + observation 三元组。 // // 1. 以 assistant tool_call 为主记录。 // 2. 用 ToolCallID 去关联 tool observation,保证同轮绑定。 // 3. thought 只向前取最近一条 assistant 纯文本消息,不跨越到更早的工具调用之前做复杂回溯。 func collectExecuteLoopRecords(history []*schema.Message) []executeLoopRecord { if len(history) == 0 { return nil } toolResultByCallID := make(map[string]*schema.Message, len(history)) for _, msg := range history { if msg == nil || msg.Role != schema.Tool { continue } callID := strings.TrimSpace(msg.ToolCallID) if callID == "" { continue } toolResultByCallID[callID] = msg } records := make([]executeLoopRecord, 0, len(history)) for i, msg := range history { 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 = "{}" } observation := "该工具调用尚未返回结果。" callID := strings.TrimSpace(call.ID) if callID != "" { if resultMsg, ok := toolResultByCallID[callID]; ok && resultMsg != nil { text := strings.TrimSpace(resultMsg.Content) if text != "" { observation = text } } } records = append(records, executeLoopRecord{ Thought: thought, ToolName: toolName, ToolArgs: toolArgs, Observation: observation, }) } } return records } func findExecuteThoughtBefore(history []*schema.Message, index int) string { for i := index - 1; i >= 0; i-- { msg := history[i] if msg == nil || msg.Role != schema.Assistant { continue } if len(msg.ToolCalls) > 0 { continue } content := compactExecuteText(msg.Content, 140) if content != "" { return content } } return "(未记录)" } func renderExecuteToolCallText(toolName, toolArgs string) string { toolName = strings.TrimSpace(toolName) if toolName == "" { toolName = "unknown_tool" } toolArgs = strings.TrimSpace(toolArgs) if toolArgs == "" { toolArgs = "{}" } return toolName + "(" + toolArgs + ")" } func hasExecuteRoughBuildDone(ctx *newagentmodel.ConversationContext) bool { if ctx == nil { return false } for _, block := range ctx.PinnedBlocksSnapshot() { if strings.TrimSpace(block.Key) == "rough_build_done" { return true } } return false } func renderExecuteLatestAnalyzeSummary(ctx *newagentmodel.ConversationContext) string { record, ok := findExecuteLatestToolRecord(ctx, map[string]struct{}{ "analyze_health": {}, "analyze_rhythm": {}, }) if !ok { return "" } return fmt.Sprintf("%s -> %s", record.ToolName, record.Observation) } func renderExecuteLatestMutationSummary(ctx *newagentmodel.ConversationContext) string { record, ok := findExecuteLatestToolRecord(ctx, map[string]struct{}{ "place": {}, "move": {}, "swap": {}, "batch_move": {}, "unplace": {}, "queue_apply_head_move": {}, }) 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. 过滤 correction prompt,避免把后端纠错提示伪装成用户真实意图。 // 2. 过滤 assistant tool_call 消息,避免 msg1 和 msg2 重复。 // 3. 保持原始顺序,不在这里裁剪长度。 func collectExecuteConversationTurns(history []*schema.Message) []conversationTurn { if len(history) == 0 { return nil } turns := make([]conversationTurn, 0, len(history)) for _, msg := range history { if msg == nil { continue } text := strings.TrimSpace(msg.Content) if text == "" { continue } switch msg.Role { case schema.User: if isExecuteCorrectionPrompt(msg) { continue } turns = append(turns, conversationTurn{Role: "user", Content: text}) case schema.Assistant: if len(msg.ToolCalls) > 0 { continue } turns = append(turns, conversationTurn{Role: "assistant", Content: text}) } } return turns } func isExecuteCorrectionPrompt(msg *schema.Message) bool { if msg == nil || msg.Role != schema.User { return false } if msg.Extra != nil { if kind, ok := msg.Extra[executeHistoryKindKey].(string); ok && strings.TrimSpace(kind) == executeHistoryKindCorrectionUser { return true } } content := strings.TrimSpace(msg.Content) return strings.Contains(content, "请重新分析当前状态,输出正确的内容。") } func compactExecuteText(content string, maxLen int) string { content = firstExecuteLine(content) content = strings.TrimSpace(content) if content == "" { return "" } runes := []rune(content) if len(runes) <= maxLen { return content } if maxLen <= 3 { return string(runes[:maxLen]) } return string(runes[:maxLen-3]) + "..." } func firstExecuteLine(content string) string { content = strings.TrimSpace(content) if content == "" { return "" } lines := strings.Split(content, "\n") return strings.TrimSpace(lines[0]) } func asExecuteString(value any) string { if text, ok := value.(string); ok { return text } return "" } func renderExecuteTaskClassIDs(state *newagentmodel.CommonState) string { if state == nil || len(state.TaskClassIDs) == 0 { return "" } parts := make([]string, len(state.TaskClassIDs)) for i, id := range state.TaskClassIDs { parts[i] = strconv.Itoa(id) } return fmt.Sprintf("task_class_ids=[%s]", strings.Join(parts, ",")) } // 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, "- 写前最少检查项: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") }