后端: 1. Prompt 层从 execute 专属骨架重构为全节点统一四段式 buildUnifiedStageMessages - 新增 unified_context.go:定义 StageMessagesConfig + buildUnifiedStageMessages 统一骨架,所有节点(Chat/Plan/Execute/Deliver/DeepAnswer)共用同一套 msg0~msg3 拼装逻辑 - 新增 conversation_view.go:通用对话历史渲染 buildConversationHistoryMessage,各节点复用,不再各自维护提取逻辑 - 新增 chat_context.go / plan_context.go / deliver_context.go:各节点自行渲染 msg1(对话视图)和 msg2(工作区),统一层只负责"怎么拼",不再替节点决定"放什么" - Chat/Plan/Deliver/Execute 的 BuildXXXMessages 全部从 buildStageMessages 切到 buildUnifiedStageMessages,移除旧路径 - 删除 execute_pinned.go:execute 记忆渲染合并到统一层 renderUnifiedMemoryContext - Plan prompt 不再在 user prompt 中拼装任务类 ID 列表和 renderStateSummary,改为依赖 msg2 规划工作区;Chat 粗排判断从"上下文有任务类 ID"改为"批量调度需求" - Deliver prompt 新增 IsAborted/IsExhaustedTerminal 区分,支持粗排收口和主动终止场景 2. Execute ReAct 上下文简化——移除归档搬运、窗口裁剪和重复工具压缩 - 移除 splitExecuteLoopRecordsByBoundary、findLatestExecuteBoundaryMarker、tailExecuteLoops、compressExecuteLoopObservationsByTool、buildEarlyExecuteReactSummary、trimExecuteMessage1ByBudget 等六个函数 - 移除 executeLoopWindowLimit / executeConversationTurnLimit / executeMessage1MaxRunes 等预算常量 - msg1 不再从历史中归档上一轮 ReAct 结果,只保留真实对话流(user + assistant speak),全量注入 - msg2 不再按 loop_closed / step_advanced 边界切分"归档/活跃",直接全量注入全部 ReAct Loop 记录 - token 预算由统一压缩层兜底,prompt 层不再做提前裁剪 3. 压缩层从 Execute 专属提升为全节点通用 UnifiedCompact - 删除 execute_compact.go(Execute 专属压缩文件) - 新增 unified_compact.go:UnifiedCompactInput 参数化,各节点(Plan/Chat/Deliver/Execute)构造时从自己的 NodeInput 提取公共字段,消除对 Execute 的直接依赖 - CompactionStore 接口扩展 LoadStageCompaction / SaveStageCompaction,各节点按 stageKey 独立维护压缩状态互不覆盖 - 非 4 段式消息时退化成按角色汇总统计,确保 context_token_stats 仍然刷新 4. Retry 重试机制全面下线 - dao/agent.go:saveChatHistoryCore / SaveChatHistory / SaveChatHistoryInTx 移除 retry_group_id / retry_index / retry_from_user_message_id / retry_from_assistant_message_id 四个参数,修复乱码注释 - dao/agent-cache.go:移除 ApplyRetrySeed 和 extractMessageHistoryID 两个方法 - conv/agent.go:ToEinoMessages 不再回灌 retry_* 字段到运行期上下文 - service/agentsvc/agent.go:移除 chatRetryMeta 及 resolveRetryGroupID / buildRetrySeed 等全部重试逻辑 - service/agentsvc/agent_quick_note.go:整个文件删除(retry 快速补写路径已无用) - service/events/chat_history_persist.go:移除 retry 参数传递 5. 节点层瘦身 + 可见消息逐条持久化 - agent_nodes.go 大幅简化:Chat/Plan/Execute/Deliver 节点方法移除 ToolSchema 注入、状态摘要渲染等逻辑,只做参数转发和状态落盘 - 新增 visible_message.go:persistVisibleAssistantMessage 统一处理可见 assistant speak 的实时持久化,失败仅记日志不中断主流程 - 新增 llm_debug.go:logNodeLLMContext 统一打印 LLM 上下文调试日志 - graph_run_state.go 新增 PersistVisibleMessageFunc 类型 + AgentGraphDeps.PersistVisibleMessage 字段 - service/agentsvc/agent_newagent.go 精简主循环,注入 PersistVisibleMessage 回调;agent_history.go 精简历史构建 - token_budget.go 移除 Execute 专属预算检查,统一到通用预算 前端: 1. 移除 retry 相关 UI 和类型 - agent.ts 移除 retry_group_id / retry_index / retry_total 字段及 normalize 逻辑 - AssistantPanel.vue 移除 retry 相关 UI 和交互代码(约 700 行精简) - dashboard.ts 移除 retry 相关类型定义 - AssistantView.vue 微调 2. ContextWindowMeter 压缩次数展示和数值格式优化 - 新增 formatCompactCount 工具函数,千位以上用 k 单位压缩(如 80k) - 新增压缩次数显示 3.修复了新对话发消息时,user和assistant消息被自动调换的bug 仓库:无
213 lines
7.3 KiB
Go
213 lines
7.3 KiB
Go
package newagentprompt
|
||
|
||
import (
|
||
"strings"
|
||
|
||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||
"github.com/cloudwego/eino/schema"
|
||
)
|
||
|
||
// ConversationTurn 表示对话历史中的一轮自然语言交互。
|
||
//
|
||
// 职责边界:
|
||
// 1. 这里只承载 user 与 assistant speak,不承载 tool_call 和 tool observation;
|
||
// 2. 供 chat / plan / deliver 等节点复用,避免各节点重复写一套提取逻辑;
|
||
// 3. 不负责裁剪长度,长度预算统一交给压缩层处理。
|
||
type ConversationTurn struct {
|
||
Role string
|
||
Content string
|
||
}
|
||
|
||
// StageMessagesConfig 描述统一四段式骨架下,各节点自行提供的内容块。
|
||
//
|
||
// 设计目标:
|
||
// 1. 统一层只负责“四条消息怎么拼”,不再替节点决定“每条消息里该放什么”;
|
||
// 2. Msg1 / Msg2 / Msg3Prefix / Msg3Suffix 都由节点自己渲染,避免 chat / plan / deliver 继续套 execute 的内容模板;
|
||
// 3. memory_context 仍由统一层单入口注入到 msg3,避免多处重复注入。
|
||
type StageMessagesConfig struct {
|
||
// SystemPrompt 是节点自己的系统提示词。
|
||
SystemPrompt string
|
||
|
||
// Msg1Content 是第 2 条 assistant 消息,通常放“节点想看的历史视图”。
|
||
Msg1Content string
|
||
|
||
// Msg2Content 是第 3 条 assistant 消息,通常放“节点自己的工作区/补充约束”。
|
||
Msg2Content string
|
||
|
||
// Msg3Prefix 是第 4 条消息中位于 memory_context 之前的内容。
|
||
// 常见放法:阶段状态、规划工作区摘要、交付收口约束等。
|
||
Msg3Prefix string
|
||
|
||
// Msg3Suffix 是第 4 条消息中位于 memory_context 之后的内容。
|
||
// 对 user-role 节点来说,这里通常放最终用户指令,保证“用户输入收尾”。
|
||
Msg3Suffix string
|
||
|
||
// Msg3Role 指定第 4 条消息的角色。
|
||
// Execute 继续使用 system,其余节点一般使用 user。
|
||
Msg3Role schema.RoleType
|
||
}
|
||
|
||
// buildUnifiedStageMessages 组装统一 4 段式消息骨架。
|
||
//
|
||
// 固定布局:
|
||
// 1. msg0(system):系统规则 + 阶段规则 + 工具简表;
|
||
// 2. msg1(assistant):节点自定义的历史视图;
|
||
// 3. msg2(assistant):节点自定义的工作区;
|
||
// 4. msg3(user/system):节点自定义前后缀 + 统一 memory_context。
|
||
func buildUnifiedStageMessages(
|
||
ctx *newagentmodel.ConversationContext,
|
||
config StageMessagesConfig,
|
||
) []*schema.Message {
|
||
msg0 := buildUnifiedMsg0(config.SystemPrompt, ctx)
|
||
msg1 := buildUnifiedMsg1(config.Msg1Content)
|
||
msg2 := buildUnifiedMsg2(config.Msg2Content)
|
||
msg3 := buildUnifiedMsg3(ctx, config)
|
||
|
||
return []*schema.Message{
|
||
schema.SystemMessage(msg0),
|
||
{Role: schema.Assistant, Content: msg1},
|
||
{Role: schema.Assistant, Content: msg2},
|
||
buildUnifiedMsg3Message(msg3, config.Msg3Role),
|
||
}
|
||
}
|
||
|
||
// buildUnifiedMsg3Message 根据配置决定第 4 条消息的角色。
|
||
func buildUnifiedMsg3Message(content string, role schema.RoleType) *schema.Message {
|
||
if role == schema.User {
|
||
return schema.UserMessage(content)
|
||
}
|
||
return schema.SystemMessage(content)
|
||
}
|
||
|
||
// buildUnifiedMsg0 合并系统提示 + 工具简表,生成 msg0。
|
||
//
|
||
// 步骤化说明:
|
||
// 1. 先合并基础系统提示与节点系统提示,保证模型身份稳定;
|
||
// 2. 若当前节点注入了工具 schema,则附加紧凑工具目录;
|
||
// 3. 若两部分都为空,则回退到最小兜底提示,避免出现空消息。
|
||
func buildUnifiedMsg0(stageSystemPrompt string, ctx *newagentmodel.ConversationContext) string {
|
||
base := strings.TrimSpace(mergeSystemPrompts(ctx, stageSystemPrompt))
|
||
if base == "" {
|
||
base = "你是 SmartMate 助手,请继续当前阶段。"
|
||
}
|
||
|
||
toolCatalog := renderExecuteToolCatalogCompact(ctx)
|
||
if toolCatalog == "" {
|
||
return base
|
||
}
|
||
return base + "\n\n" + toolCatalog
|
||
}
|
||
|
||
// buildUnifiedMsg1 返回节点自行提供的历史视图。
|
||
//
|
||
// 说明:
|
||
// 1. 统一层不再内置 execute 风格的 ReAct 摘要;
|
||
// 2. 节点若未传入内容,则回退到最小占位,保证四段结构稳定;
|
||
// 3. 压缩层仍会统一统计和压缩这条消息。
|
||
func buildUnifiedMsg1(content string) string {
|
||
content = strings.TrimSpace(content)
|
||
if content != "" {
|
||
return content
|
||
}
|
||
return "历史上下文:暂无。"
|
||
}
|
||
|
||
// buildUnifiedMsg2 返回节点自行提供的工作区。
|
||
//
|
||
// 说明:
|
||
// 1. 非 execute 节点也允许有自己的 msg2,不再被统一层硬塞“暂无”语义;
|
||
// 2. 若节点暂时没有额外工作区,则回退到最小占位,保证结构稳定。
|
||
func buildUnifiedMsg2(content string) string {
|
||
content = strings.TrimSpace(content)
|
||
if content != "" {
|
||
return content
|
||
}
|
||
return "阶段工作区:暂无。"
|
||
}
|
||
|
||
// buildUnifiedMsg3 统一拼装 msg3:前缀 + memory_context + 后缀。
|
||
//
|
||
// 步骤化说明:
|
||
// 1. 前缀由节点决定,适合放轻量状态或阶段约束;
|
||
// 2. memory_context 只在这里注入一次,避免 pinned block 多入口重复出现;
|
||
// 3. 后缀由节点决定。对于 user-role 节点,通常把最终用户指令放在这里,保证消息末尾仍是用户输入。
|
||
func buildUnifiedMsg3(ctx *newagentmodel.ConversationContext, config StageMessagesConfig) string {
|
||
var sections []string
|
||
|
||
if prefix := strings.TrimSpace(config.Msg3Prefix); prefix != "" {
|
||
sections = append(sections, prefix)
|
||
}
|
||
if memoryText := renderUnifiedMemoryContext(ctx); memoryText != "" {
|
||
sections = append(sections, "相关记忆(仅在确有帮助时参考,不要机械复述):\n"+memoryText)
|
||
}
|
||
if suffix := strings.TrimSpace(config.Msg3Suffix); suffix != "" {
|
||
sections = append(sections, suffix)
|
||
}
|
||
|
||
if len(sections) == 0 {
|
||
return "请继续当前阶段。"
|
||
}
|
||
return strings.Join(sections, "\n\n")
|
||
}
|
||
|
||
// renderUnifiedMemoryContext 提取需要补充到 msg3 的记忆文本。
|
||
//
|
||
// 步骤化说明:
|
||
// 1. 只消费 memory_context,避免把 execution_context / current_step 等阶段专属块混回 prompt;
|
||
// 2. block 不存在或正文为空时直接返回空串;
|
||
// 3. 这里只读取 agentsvc 已经产出的最终文本,不在这里重新拼装记忆。
|
||
func renderUnifiedMemoryContext(ctx *newagentmodel.ConversationContext) string {
|
||
if ctx == nil {
|
||
return ""
|
||
}
|
||
|
||
block, ok := ctx.PinnedBlockByKey("memory_context")
|
||
if !ok {
|
||
return ""
|
||
}
|
||
content := strings.TrimSpace(block.Content)
|
||
if content == "" {
|
||
return ""
|
||
}
|
||
return content
|
||
}
|
||
|
||
// CollectConversationTurns 从历史消息中提取 user + assistant speak 对话流。
|
||
//
|
||
// 提取规则:
|
||
// 1. 只保留 user 消息(排除 correction prompt)和 assistant 纯文本消息;
|
||
// 2. assistant tool_call 消息与 tool observation 消息不纳入“真实对话”;
|
||
// 3. 返回顺序保持与原始 history 一致。
|
||
func CollectConversationTurns(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:
|
||
// 1. 跳过后端注入的 correction prompt,避免把纠错文案误判为用户真实意图。
|
||
if isExecuteCorrectionPrompt(msg) {
|
||
continue
|
||
}
|
||
turns = append(turns, ConversationTurn{Role: "user", Content: text})
|
||
case schema.Assistant:
|
||
// 2. 跳过工具调用消息,只保留真正面向用户的 speak/答复。
|
||
if len(msg.ToolCalls) > 0 {
|
||
continue
|
||
}
|
||
turns = append(turns, ConversationTurn{Role: "assistant", Content: text})
|
||
}
|
||
}
|
||
|
||
return turns
|
||
}
|