Files
smartmate/backend/services/agent/node/confirm.go
Losita d7184b776b Version: 0.9.75.dev.260505
后端:
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 迁移面
2026-05-05 16:00:57 +08:00

209 lines
6.3 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 agentnode
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
agentmodel "github.com/LoveLosita/smartflow/backend/services/agent/model"
agentstream "github.com/LoveLosita/smartflow/backend/services/agent/stream"
)
const (
confirmStageName = "confirm"
confirmStatusBlockID = "confirm.status"
)
// ConfirmNodeInput 描述确认节点单轮运行所需的最小依赖。
//
// 职责边界:
// 1. 不需要 LLM Client — 确认内容由已有状态机械格式化,不调模型;
// 2. RuntimeState 提供计划步骤和待确认工具快照;
// 3. ChunkEmitter 负责推送确认事件到前端。
type ConfirmNodeInput struct {
RuntimeState *agentmodel.AgentRuntimeState
ConversationContext *agentmodel.ConversationContext
ChunkEmitter *agentstream.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 *agentmodel.AgentRuntimeState,
flowState *agentmodel.CommonState,
emitter *agentstream.ChunkEmitter,
) error {
summary := buildPlanSummary(flowState.PlanSteps)
interactionID := generateConfirmInteractionID(flowState)
if err := emitter.EmitConfirmRequest(
ctx, confirmStatusBlockID, confirmStageName,
interactionID,
"计划确认",
summary,
agentstream.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 *agentmodel.AgentRuntimeState,
flowState *agentmodel.CommonState,
emitter *agentstream.ChunkEmitter,
) error {
pendingTool := runtimeState.PendingConfirmTool
summary := buildToolConfirmSummary(pendingTool)
interactionID := generateConfirmInteractionID(flowState)
if err := emitter.EmitConfirmRequest(
ctx, confirmStatusBlockID, confirmStageName,
interactionID,
"操作确认",
summary,
agentstream.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 []agentmodel.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 *agentmodel.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 *agentmodel.CommonState) string {
prefix := flowState.TraceID
if prefix == "" {
prefix = "confirm"
}
return fmt.Sprintf("%s-%d", prefix, time.Now().UnixMilli())
}
// prepareConfirmNodeInput 校验并准备确认节点的运行态依赖。
func prepareConfirmNodeInput(input ConfirmNodeInput) (
*agentmodel.AgentRuntimeState,
*agentmodel.ConversationContext,
*agentstream.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 = agentmodel.NewConversationContext("")
}
if input.ChunkEmitter == nil {
input.ChunkEmitter = agentstream.NewChunkEmitter(
agentstream.NoopPayloadEmitter(), "", "", time.Now().Unix(),
)
}
return input.RuntimeState, input.ConversationContext, input.ChunkEmitter, nil
}