后端: 1.新建Deliver节点:LLM生成任务总结,失败降级到机械格式化,伪流式输出 2.新建Confirm节点:确认卡片推送与状态持久化 3.新建Interrupt节点:追问/确认/默认中断三种处理路径 4.实现状态持久化体系:model层定义AgentStateStore接口+AgentStateSnapshot快照,dao/cache.go新增Redis CRUD,agent_nodes层每节点自动存快照、Deliver完成后清理 5.所有model struct补充JSON tags,支持Redis序列化/反序列化 前端:无 仓库:无
209 lines
6.4 KiB
Go
209 lines
6.4 KiB
Go
package newagentnode
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
newagentmodel "github.com/LoveLosita/smartflow/backend/newAgent/model"
|
||
newagentstream "github.com/LoveLosita/smartflow/backend/newAgent/stream"
|
||
)
|
||
|
||
const (
|
||
confirmStageName = "confirm"
|
||
confirmStatusBlockID = "confirm.status"
|
||
)
|
||
|
||
// ConfirmNodeInput 描述确认节点单轮运行所需的最小依赖。
|
||
//
|
||
// 职责边界:
|
||
// 1. 不需要 LLM Client — 确认内容由已有状态机械格式化,不调模型;
|
||
// 2. RuntimeState 提供计划步骤和待确认工具快照;
|
||
// 3. ChunkEmitter 负责推送确认事件到前端。
|
||
type ConfirmNodeInput struct {
|
||
RuntimeState *newagentmodel.AgentRuntimeState
|
||
ConversationContext *newagentmodel.ConversationContext
|
||
ChunkEmitter *newagentstream.ChunkEmitter
|
||
}
|
||
|
||
// RunConfirmNode 执行一轮确认节点逻辑。
|
||
//
|
||
// 核心职责:
|
||
// 1. 判断确认来源:有 PendingConfirmTool → 工具确认;有 PlanSteps → 计划确认;
|
||
// 2. 机械格式化确认内容(不需要 LLM 调用);
|
||
// 3. 推送确认事件 EmitConfirmRequest → 前端渲染确认卡片;
|
||
// 4. 调用 OpenConfirmInteraction 固化中断快照,Phase 自动变为 interrupted。
|
||
//
|
||
// 设计原则:
|
||
// 1. 不等待用户响应 — 等待是 interruptNode 的职责;
|
||
// 2. 不执行任何工具 — 只固化"意图",执行留给恢复后的 Execute;
|
||
// 3. Confirm 是图里唯一负责"生成确认事件 + 固化快照"的地方,上游节点只设 Phase。
|
||
func RunConfirmNode(ctx context.Context, input ConfirmNodeInput) error {
|
||
runtimeState, _, emitter, err := prepareConfirmNodeInput(input)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
flowState := runtimeState.EnsureCommonState()
|
||
|
||
// 优先处理工具确认(Execute 发起的写操作确认)。
|
||
if runtimeState.PendingConfirmTool != nil {
|
||
return handleToolConfirm(ctx, runtimeState, flowState, emitter)
|
||
}
|
||
|
||
// 其次处理计划确认(Plan 完成后的整体验收)。
|
||
if flowState.HasPlan() {
|
||
return handlePlanConfirm(ctx, runtimeState, flowState, emitter)
|
||
}
|
||
|
||
// 既没有工具也没有计划 → 异常状态,不应到达此处。
|
||
return fmt.Errorf("confirm node: 没有可确认的内容(无计划、无待确认工具)")
|
||
}
|
||
|
||
// handlePlanConfirm 处理计划确认。
|
||
//
|
||
// 流程:
|
||
// 1. 从 flowState.PlanSteps 格式化可读摘要;
|
||
// 2. 推送确认事件到前端;
|
||
// 3. 调用 OpenConfirmInteraction 固化快照(无 PendingTool)。
|
||
func handlePlanConfirm(
|
||
ctx context.Context,
|
||
runtimeState *newagentmodel.AgentRuntimeState,
|
||
flowState *newagentmodel.CommonState,
|
||
emitter *newagentstream.ChunkEmitter,
|
||
) error {
|
||
summary := buildPlanSummary(flowState.PlanSteps)
|
||
interactionID := generateConfirmInteractionID(flowState)
|
||
|
||
if err := emitter.EmitConfirmRequest(
|
||
ctx, confirmStatusBlockID, confirmStageName,
|
||
interactionID,
|
||
"计划确认",
|
||
summary,
|
||
newagentstream.DefaultPseudoStreamOptions(),
|
||
); err != nil {
|
||
return fmt.Errorf("计划确认事件推送失败: %w", err)
|
||
}
|
||
|
||
runtimeState.OpenConfirmInteraction(
|
||
interactionID,
|
||
summary,
|
||
"plan",
|
||
nil,
|
||
)
|
||
|
||
_ = emitter.EmitStatus(
|
||
confirmStatusBlockID, confirmStageName,
|
||
"plan_confirm", "计划已生成,等待用户确认。", false,
|
||
)
|
||
return nil
|
||
}
|
||
|
||
// handleToolConfirm 处理工具确认。
|
||
//
|
||
// 流程:
|
||
// 1. 从 PendingConfirmTool 构建确认摘要;
|
||
// 2. 推送确认事件到前端;
|
||
// 3. 调用 OpenConfirmInteraction 固化快照(含 PendingTool);
|
||
// 4. 清空 PendingConfirmTool 临时邮箱。
|
||
func handleToolConfirm(
|
||
ctx context.Context,
|
||
runtimeState *newagentmodel.AgentRuntimeState,
|
||
flowState *newagentmodel.CommonState,
|
||
emitter *newagentstream.ChunkEmitter,
|
||
) error {
|
||
pendingTool := runtimeState.PendingConfirmTool
|
||
summary := buildToolConfirmSummary(pendingTool)
|
||
interactionID := generateConfirmInteractionID(flowState)
|
||
|
||
if err := emitter.EmitConfirmRequest(
|
||
ctx, confirmStatusBlockID, confirmStageName,
|
||
interactionID,
|
||
"操作确认",
|
||
summary,
|
||
newagentstream.DefaultPseudoStreamOptions(),
|
||
); err != nil {
|
||
return fmt.Errorf("工具确认事件推送失败: %w", err)
|
||
}
|
||
|
||
runtimeState.OpenConfirmInteraction(
|
||
interactionID,
|
||
summary,
|
||
"execute",
|
||
pendingTool,
|
||
)
|
||
|
||
// 确认快照已固化到 PendingInteraction,清空临时邮箱。
|
||
runtimeState.PendingConfirmTool = nil
|
||
|
||
_ = emitter.EmitStatus(
|
||
confirmStatusBlockID, confirmStageName,
|
||
"tool_confirm", "操作等待确认。", false,
|
||
)
|
||
return nil
|
||
}
|
||
|
||
// buildPlanSummary 把 PlanSteps 格式化成人类可读的确认摘要。
|
||
func buildPlanSummary(steps []newagentmodel.PlanStep) string {
|
||
var sb strings.Builder
|
||
sb.WriteString(fmt.Sprintf("共 %d 步:\n", len(steps)))
|
||
for i, step := range steps {
|
||
sb.WriteString(fmt.Sprintf("%d. %s", i+1, step.Content))
|
||
if step.DoneWhen != "" {
|
||
sb.WriteString(fmt.Sprintf("(完成条件:%s)", step.DoneWhen))
|
||
}
|
||
sb.WriteString("\n")
|
||
}
|
||
return strings.TrimSpace(sb.String())
|
||
}
|
||
|
||
// buildToolConfirmSummary 从工具快照构建确认摘要。
|
||
func buildToolConfirmSummary(tool *newagentmodel.PendingToolCallSnapshot) string {
|
||
if tool == nil {
|
||
return "待确认操作"
|
||
}
|
||
if tool.Summary != "" {
|
||
return tool.Summary
|
||
}
|
||
detail := fmt.Sprintf("即将执行工具:%s", tool.ToolName)
|
||
if tool.ArgsJSON != "" {
|
||
var args map[string]any
|
||
if json.Unmarshal([]byte(tool.ArgsJSON), &args) == nil && len(args) > 0 {
|
||
detail += fmt.Sprintf(",参数:%s", tool.ArgsJSON)
|
||
}
|
||
}
|
||
return detail
|
||
}
|
||
|
||
// generateConfirmInteractionID 生成确认交互的唯一标识。
|
||
func generateConfirmInteractionID(flowState *newagentmodel.CommonState) string {
|
||
prefix := flowState.TraceID
|
||
if prefix == "" {
|
||
prefix = "confirm"
|
||
}
|
||
return fmt.Sprintf("%s-%d", prefix, time.Now().UnixMilli())
|
||
}
|
||
|
||
// prepareConfirmNodeInput 校验并准备确认节点的运行态依赖。
|
||
func prepareConfirmNodeInput(input ConfirmNodeInput) (
|
||
*newagentmodel.AgentRuntimeState,
|
||
*newagentmodel.ConversationContext,
|
||
*newagentstream.ChunkEmitter,
|
||
error,
|
||
) {
|
||
if input.RuntimeState == nil {
|
||
return nil, nil, nil, fmt.Errorf("confirm node: runtime state 不能为空")
|
||
}
|
||
input.RuntimeState.EnsureCommonState()
|
||
if input.ConversationContext == nil {
|
||
input.ConversationContext = newagentmodel.NewConversationContext("")
|
||
}
|
||
if input.ChunkEmitter == nil {
|
||
input.ChunkEmitter = newagentstream.NewChunkEmitter(
|
||
newagentstream.NoopPayloadEmitter(), "", "", time.Now().Unix(),
|
||
)
|
||
}
|
||
return input.RuntimeState, input.ConversationContext, input.ChunkEmitter, nil
|
||
}
|