Files
smartmate/backend/newAgent/prompt/unified_context.go
Losita 66c06eed0a 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永远只适合做选择题、判断题,不适合做开放创新题。
2026-04-27 01:09:37 +08:00

274 lines
9.1 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"
)
// 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
// SkipBaseSystemPrompt 为 true 时msg0 只使用节点自己的 SystemPrompt
// 不再拼接 ConversationContext.SystemPrompt。
SkipBaseSystemPrompt bool
// UseLiteToolCatalogMsg 为 true 时msg0 工具目录采用轻量模式(仅名称与职责)。
UseLiteToolCatalogMsg bool
}
// 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, config.SkipBaseSystemPrompt, config.UseLiteToolCatalogMsg)
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, 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, 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 返回节点自行提供的历史视图。
//
// 说明:
// 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
}