Files
smartmate/backend/newAgent/prompt/base.go
LoveLosita 32bb740b75 Version: 0.9.3.dev.260407
后端:
    1.Execute 上下文修复(无限循环 / 重复确认根治)
      - 更新node/execute.go:speak 写入历史(修复旧 TODO);confirm 动作 speak 不再丢失;
        continue 无工具调用时写 reason 保证上下文推进;区分 tool_call 数组/JSON损坏两种
        correction hint;goal_check hint 区分 plan/ReAct 模式
      - 更新node/execute.go:新增 AlwaysExecute 字段,extra.always_execute=true 时写工具
        跳过确认闸门直接执行并持久化
      - 更新model/graph_run_state.go:AgentGraphRequest 新增 AlwaysExecute;新增
        WriteSchedulePreviewFunc 类型和 WriteSchedulePreview Dep
      - 更新service/agentsvc/agent.go:新增 readAgentExtraBool 辅助

    2.粗排全链路修复
      - 更新service/agentsvc/agent_newagent.go:makeRoughBuildFunc 改用 HybridScheduleEntry
        而非 TaskClassItem.EmbeddedTime,普通时段放置不再被丢弃
      - 更新conv/schedule_provider.go:LoadScheduleState 从 task class 日期范围推算多周
        规划窗口,不再硬编码当前周 7 天;DayMapping 覆盖全部相关周,粗排跨周结果不再
        被 WeekDayToDay 静默丢弃
      - 更新node/rough_build.go:pinned block 区分有/无未覆盖 pending 任务两种情况,
        有 pending 时明确操作顺序(find_free→place)和完成判定,防止 LLM 重复调
        list_tasks;新增 countPendingTasks 辅助(只统计 Slots 为空的真正未覆盖任务)
      - 更新model/common_state.go:新增 StartDirectExecute(),Chat 直接路由 execute 时
        清空旧 PlanSteps,修复跨会话 HasPlan() 误判导致 ReAct 走 plan 模式的 bug
      - 更新node/chat.go:handleRouteExecute 改用 StartDirectExecute()

    3.排程预览缓存迁移至 Deliver 节点
      - 更新node/agent_nodes.go:Deliver 节点完成后调用 WriteSchedulePreview,只有任务
        真正完成才写预览缓存,中断路径不写中间态
      - 更新service/agentsvc/agent_newagent.go:注入 makeWriteSchedulePreviewFunc;移除
        graph 结束后的内联写入;makeRoughBuildFunc 注释修正
      - 更新conv/schedule_preview.go:ScheduleStateToPreview 补设 GeneratedAt
      - 更新model/agent.go:GetSchedulePlanPreviewResponse 新增 HybridEntries 字段
      - 更新service/agentsvc/agent_schedule_preview.go:GET handler Redis/MySQL 两条路径
        均透传 HybridEntries

    4.Execute thinking 模式修复
      - 更新newAgent/llm/ark_adapter.go:thinking 开启时强制 temperature=1,MaxTokens 自
        动托底至 16000,调用方与适配层行为对齐
      - 更新node/execute.go:调用参数同步改为 temperature=1.0 / MaxTokens=16000

    undo:
    1.流式推送换行未修复(undo)
    2.上下文依然待审视

前端:无
仓库:无
2026-04-07 12:10:56 +08:00

212 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package newagentprompt
import (
"fmt"
"strings"
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
"github.com/cloudwego/eino/schema"
)
// buildStageMessages 组装某个阶段通用的 messages。
//
// 消息排列策略(利用 LLM 近因效应):
// 1. system prompt角色 + 阶段规则)— 始终最顶部,定义基本身份;
// 2. tool schemas能力边界— 稳定参考信息,放在 history 前即可;
// 3. history对话历史、工具调用、修正反馈— 按时间顺序排列;
// 4. pinned blocks当前计划、当前步骤、粗排结果等最新约束— 紧贴 user prompt
// 利用近因效应让 LLM 优先关注本轮最相关的约束,而非被历史消息分散注意力;
// 5. user prompt阶段性指令— 始终在末尾,是本轮回答的核心触发。
func buildStageMessages(stageSystemPrompt string, ctx *newagentmodel.ConversationContext, runtimeUserPrompt string) []*schema.Message {
messages := make([]*schema.Message, 0, 4)
// 1. 合并 system prompt基础角色约束 + 阶段规则,始终在最顶部。
mergedSystemPrompt := mergeSystemPrompts(ctx, stageSystemPrompt)
if mergedSystemPrompt != "" {
messages = append(messages, schema.SystemMessage(mergedSystemPrompt))
}
// 2. 工具摘要:稳定参考信息,放在 history 前即可。
if toolText := renderToolSchemas(ctx); toolText != "" {
messages = append(messages, schema.SystemMessage(toolText))
}
// 3. 对话历史:按时间顺序,包含工具调用结果和修正反馈。
if ctx != nil {
history := ctx.HistorySnapshot()
if len(history) > 0 {
// 兼容旧快照:裸 Tool 消息(无 ToolCallID违反 OpenAI 兼容 API 格式约束,
// 会触发 API 拒绝请求导致连接断开。
// 这里将裸 Tool 消息降级为 User 消息,保证向后兼容。
for i, msg := range history {
if msg.Role == schema.Tool && msg.ToolCallID == "" {
history[i] = &schema.Message{
Role: schema.User,
Content: fmt.Sprintf("[工具执行结果]\n%s", msg.Content),
}
}
}
messages = append(messages, history...)
}
}
// 4. 置顶上下文块:当前计划、当前步骤、粗排结果等最新约束。
// 放在 history 之后、user prompt 之前,利用 LLM 近因效应提升对最新约束的注意力。
if pinnedText := renderPinnedBlocks(ctx); pinnedText != "" {
messages = append(messages, schema.SystemMessage(pinnedText))
}
// 5. 阶段性用户提示词:始终在末尾,是本轮回答的核心触发。
runtimeUserPrompt = strings.TrimSpace(runtimeUserPrompt)
if runtimeUserPrompt != "" {
messages = append(messages, schema.UserMessage(runtimeUserPrompt))
}
return messages
}
// renderStateSummary 把当前流程状态渲染成简洁文本。
func renderStateSummary(state *newagentmodel.CommonState) string {
if state == nil {
return "当前状态state 缺失,请先做兜底处理。"
}
var sb strings.Builder
current, total := state.PlanProgress()
sb.WriteString(fmt.Sprintf("当前阶段:%s\n", state.Phase))
sb.WriteString(fmt.Sprintf("当前轮次:%d/%d\n", state.RoundUsed, state.MaxRounds))
if !state.HasPlan() {
sb.WriteString("当前完整 plan暂无。\n")
} else {
sb.WriteString("当前完整 plan\n")
for i, step := range state.PlanSteps {
sb.WriteString(fmt.Sprintf("%d. %s\n", i+1, strings.TrimSpace(step.Content)))
if strings.TrimSpace(step.DoneWhen) != "" {
sb.WriteString(fmt.Sprintf(" 完成判定:%s\n", strings.TrimSpace(step.DoneWhen)))
}
}
if step, ok := state.CurrentPlanStep(); ok {
sb.WriteString(fmt.Sprintf("当前步骤进度:%d/%d\n", current, total))
sb.WriteString("当前步骤内容:\n")
sb.WriteString(strings.TrimSpace(step.Content))
sb.WriteString("\n")
if strings.TrimSpace(step.DoneWhen) != "" {
sb.WriteString("当前步骤完成判定:\n")
sb.WriteString(strings.TrimSpace(step.DoneWhen))
sb.WriteString("\n")
}
} else {
sb.WriteString("当前步骤进度:暂时无有效当前步骤。\n")
}
}
// 渲染任务类约束元数据(如有),帮助 LLM 了解排程范围和策略,避免追问已有信息。
if len(state.TaskClasses) > 0 {
sb.WriteString("\n本次排课涉及的任务类约束\n")
for _, tc := range state.TaskClasses {
line := fmt.Sprintf("- [ID=%d] %s策略=%s总时段预算=%d", tc.ID, tc.Name, tc.Strategy, tc.TotalSlots)
if tc.StartDate != "" || tc.EndDate != "" {
line += fmt.Sprintf(",日期范围=%s ~ %s", tc.StartDate, tc.EndDate)
}
if tc.AllowFillerCourse {
line += ",允许嵌入水课"
}
if len(tc.ExcludedSlots) > 0 {
line += fmt.Sprintf(",排除时段=%v", tc.ExcludedSlots)
}
sb.WriteString(line + "\n")
}
}
return sb.String()
}
// renderPinnedBlocks 把 ConversationContext 中的置顶块渲染成独立的 system 文本。
func renderPinnedBlocks(ctx *newagentmodel.ConversationContext) string {
if ctx == nil {
return ""
}
blocks := ctx.PinnedBlocksSnapshot()
if len(blocks) == 0 {
return ""
}
var sb strings.Builder
sb.WriteString("以下是后端置顶注入的上下文,请优先遵守:\n")
for _, block := range blocks {
title := strings.TrimSpace(block.Title)
if title == "" {
title = strings.TrimSpace(block.Key)
}
if title != "" {
sb.WriteString("【")
sb.WriteString(title)
sb.WriteString("】\n")
}
sb.WriteString(strings.TrimSpace(block.Content))
sb.WriteString("\n")
}
return strings.TrimSpace(sb.String())
}
// renderToolSchemas 把工具摘要渲染成独立文本块。
func renderToolSchemas(ctx *newagentmodel.ConversationContext) string {
if ctx == nil {
return ""
}
schemas := ctx.ToolSchemasSnapshot()
if len(schemas) == 0 {
return ""
}
var sb strings.Builder
sb.WriteString("以下是当前可用工具摘要,仅供你在规划时参考能力边界:\n")
for _, item := range schemas {
name := strings.TrimSpace(item.Name)
desc := strings.TrimSpace(item.Desc)
schemaText := strings.TrimSpace(item.SchemaText)
if name != "" {
sb.WriteString("- 工具名:")
sb.WriteString(name)
sb.WriteString("\n")
}
if desc != "" {
sb.WriteString(" 说明:")
sb.WriteString(desc)
sb.WriteString("\n")
}
if schemaText != "" {
sb.WriteString(" 参数摘要:")
sb.WriteString(schemaText)
sb.WriteString("\n")
}
}
return strings.TrimSpace(sb.String())
}
func mergeSystemPrompts(ctx *newagentmodel.ConversationContext, stageSystemPrompt string) string {
base := ""
if ctx != nil {
base = strings.TrimSpace(ctx.SystemPrompt)
}
stageSystemPrompt = strings.TrimSpace(stageSystemPrompt)
switch {
case base == "" && stageSystemPrompt == "":
return ""
case base == "":
return stageSystemPrompt
case stageSystemPrompt == "":
return base
default:
return base + "\n\n" + stageSystemPrompt
}
}