后端: 1.收口阶段 6 agent 结构迁移,将 newAgent 内核与 agentsvc 编排层迁入 services/agent - 切换 Agent 启动装配与 HTTP handler 直连 agent sv,移除旧 service agent bridge - 补齐 Agent 对 memory、task、task-class、schedule 的 RPC 适配与契约字段 - 扩展 schedule、task、task-class RPC/contract 支撑 Agent 查询、写入与 provider 切流 - 更新迁移文档、README 与相关注释,明确 agent 当前切流点和剩余 memory 迁移面
274 lines
9.1 KiB
Go
274 lines
9.1 KiB
Go
package agentprompt
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
agentmodel "github.com/LoveLosita/smartflow/backend/services/agent/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 *agentmodel.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 *agentmodel.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 *agentmodel.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 *agentmodel.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. 这里只读取 agent/sv 已经产出的最终文本,不在这里重新拼装记忆。
|
||
func renderUnifiedMemoryContext(ctx *agentmodel.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
|
||
}
|