深入理解 AI Agent · 多 Agent 编排 #03:Supervisor 与 Orchestrator——Dream-SaaS 双层编排架构拆解

编排系列收官篇。前两篇分别讲了多 Agent 编排的四种核心模式(串行/并行/条件/竞争)和 Agent 间的通信、状态共享与冲突解决。本篇从概念落地到真实代码,以 Dream-SaaS 项目为例,拆解 Supervisor + Orchestrator 双层编排架构的设计思路与关键实现。

一、开篇:为什么需要两层编排?

单 Agent 应用通常不需要编排------一个 ChatClient 调一次 LLM,拿到结果返回,完事。但当系统里出现多个 Agent、多种工具、多种任务类型时,"谁先执行、走哪条路、结果怎么汇总"就成了必须回答的问题。

很多团队的第一反应是用一个 StateGraph 把所有逻辑塞进去:路由、工具调用、安全检查、记忆管理、Provider 选择......节点越加越多,边越连越密,最终变成一个没人敢改的"上帝图"。

Dream-SaaS 在实践中选择了双层编排

  • 外层 Orchestrator(AgentOrchestratorService):负责请求级编排------会话管理、PreLlm 规则链拦截、状态准备(history/retrieval/provider)、调用 Graph、结果提取、记忆维护。管的是"一次对话的生命周期"。
  • 内层 StateGraph(dreamAgentGraph):负责推理级编排------route 路由 → tools 工具执行/plan 计划生成 → synthesize 汇总回答。管的是"一次推理内部的节点流转"。

两层的边界很清晰:

复制代码
Client
  │
  ▼
AgentOrchestratorService(外层:请求级编排)
  ├── 1. 会话ID & Provider 选择
  ├── 2. PreLlmRuleChain 规则链拦截(第一层路由)
  ├── 3. 准备状态:history / retrieval / provider
  ├── 4. 调用 dreamAgentGraph ──────────────────────┐
  ├── 5. 提取 final answer & tool outputs           │
  └── 6. 维护记忆                                   │
                                                    ▼
                              dreamAgentGraph(内层:推理级编排)
                                START
                                  │
                                route ──┬── WITH_TOOLS ──→ tools ──┐
                                        │                          ├──→ synthesize → END
                                        └── PLAN_THEN_ANSWER → plan ┘

外层管"请求生命周期",内层管"推理流转"。外层不关心 Graph 内部有几个节点、怎么走的条件边;内层不关心会话记忆怎么存、Provider 怎么选。混淆两层的后果就是 Graph 膨胀成上帝对象------安全检查、记忆管理、Provider 路由全做成 Graph 节点,图的拓扑复杂度指数级增长。

这个划分不是拍脑袋决定的。外层的 PreLlm 规则链需要在 Graph 启动前拦截请求(安全合规场景不能让请求进入推理流程),记忆维护需要在 Graph 执行完后统一写入(避免节点内部并发写记忆),这些天然属于"请求级"关注点。而工具路由、计划生成、答案合成是"推理级"关注点,它们需要共享 Graph 的 OverAllState、走条件边、可能循环------这些是 StateGraph 的强项。

二、Supervisor 层:三层路由的确定性梯度

路由是编排的核心决策------"这个请求该走哪条路"。Dream-SaaS 没有把所有路由决策都交给 LLM,而是设计了三层路由,从快到慢、从确定性到不确定性依次执行。

2.1 第一层:PreLlmRuleChain 规则链------零延迟零成本的安全门

规则链在 Graph 调用之前执行,完全不调 LLM。它按 @Order 排序遍历所有 PreLlmRule 实现,任一规则判定拦截,立即返回:

复制代码
// PreLlmRuleChain.java --- 按@Order排序执行全部PreLlmRule
public RuleEvaluationResult evaluate(PreLlmContext context) {
    if (!properties.isRulesBeforeLlm()) {
        return RuleEvaluationResult.allow();
    }
    for (PreLlmRule rule : rules) {
        RuleEvaluationResult r = rule.evaluate(context);
        if (!r.allowed()) {
            return r;  // 任一规则拦截,立即返回
        }
    }
    return RuleEvaluationResult.allow();
}

AgentOrchestratorService.chat() 中,规则链的拦截结果直接返回给客户端,不进入 Graph,不调 LLM

复制代码
PreLlmContext preCtx = new PreLlmContext(conversationId, userInput, provider);
RuleEvaluationResult gate = preLlmRuleChain.evaluate(preCtx);
if (!gate.allowed()) {
    // 直接返回规则提示,不进入Graph,不调LLM
    return new AgentChatResponse(conversationId, provider, gate.userVisibleMessage(),
        List.of("strategy:preLlmBlocked:" + gate.reasonCode()));
}

设计哲学就一句话:"能不调 LLM 就不调"。安全拦截、合规检查、高频简单意图------这些 100% 确定的场景用规则处理,零延迟、零 Token 成本、零幻觉风险。如果一个请求包含违禁词,规则链在毫秒级拦截并返回提示,而不是花 3 秒钟让 LLM 生成一段"我无法回答这个问题"。

2.2 第二层:关键词启发式路由------needsTools 的 80% 场景

通过了规则链的请求进入 Graph。Graph 的第一个节点是 route,它决定请求是走"工具执行"路径还是"计划+回答"路径。这个决策同样不调 LLM,而是用纯关键词匹配:

复制代码
// AgentToolRunner.java --- 纯关键词匹配
@Override
public boolean needsTools(String message) {
    String lower = message.toLowerCase(Locale.ROOT);
    return lower.contains("时间") || lower.contains("date") || lower.contains("time")
            || lower.contains("天气")
            || lower.contains("工单") || lower.contains("ticket");
}

在 Graph 的 route 节点中使用:

复制代码
graph.addNode("route", AsyncNodeAction.node_async(state -> {
    String userInput = state.value(AgentGraphKeys.USER_INPUT, "");
    String route = toolRunner.needsTools(userInput)
            ? AgentGraphKeys.ROUTE_WITH_TOOLS
            : AgentGraphKeys.ROUTE_PLAN_THEN_ANSWER;
    return Map.of(AgentGraphKeys.ROUTE, route);
}));

"查天气""现在几点""工单 TK-1024 什么状态"------这些意图明确包含工具触发词,关键词匹配的准确率足够高。不调 LLM 意味着省掉一次 2-5 秒的推理延迟和几千 Token 的成本。

当然,关键词匹配有盲区。"帮我看看外面要不要带伞"这种隐含天气查询的请求,needsTools 会返回 false,走 plan → synthesize 路径,最终由 LLM 在 synthesize 环节理解意图。这不是 bug------关键词路由的目标是覆盖 80% 的高确定性场景,剩下 20% 交给第三层。

2.3 第三层:LLM 语义路由------留给模糊场景

前两层处理完,剩下的请求进入 synthesize 节点,由 LLM 理解用户意图并生成回答。这是三层中最慢、最贵但也最灵活的一层。

值得注意的是,Dream-SaaS 没有把"路由判断"本身做成一个 LLM 调用节点。很多编排框架喜欢加一个 router 节点,让 LLM 输出 {"route": "tools"} 这样的结构化 JSON 来决定走向。这种做法的问题在于:路由判断本身需要一次 LLM 调用,增加了延迟和成本;而且 LLM 的路由输出不稳定,可能返回格式错误的 JSON,需要额外的解析和重试逻辑。

Dream-SaaS 的做法是:确定性的路由决策全部前置,LLM 只在最终回答环节做语义理解。route 节点用关键词,plan 节点用纯逻辑,只有 synthesize 节点真正调用 LLM。这样一次对话最多只调一次 LLM(工具路径下 tools 节点可能调外部 API 但不调 LLM),延迟和成本都可控。

2.4 UserHelpIntentRouter:纯函数路由器的另一种实现

除了通用对话 Graph 的 needsTools,Dream-SaaS 还有一个 UserHelpIntentRouter,基于配置化的关键词短语匹配,将用户问题路由到四个子图:权限查询、账号问题、操作指南、通用聊天。

它和 needsTools 的思路一致,但粒度更细------不是"要不要调工具"的二元判断,而是"属于哪个领域"的多元分类。配置化意味着新增意图类别不需要改代码,只需要在配置文件中添加关键词短语。

2.5 三层路由的本质是"确定性梯度"

三层路由的本质是"确定性梯度"------越确定的决策越靠前,越不确定的越靠后。规则链处理 100% 确定的安全/合规场景,关键词处理 80% 确定的工具触发场景,LLM 处理剩余需要语义理解的模糊场景。这不是过度设计,而是成本和延迟的必然选择。

很多人看到"三层路由"的第一反应是"有必要这么复杂吗"。算一笔账:假设一次 LLM 调用平均 3 秒、消耗 2000 Token。如果规则链每天拦截 10% 的请求(安全合规),关键词路由每天覆盖 60% 的请求(工具触发),只有 30% 的请求需要 LLM 做语义理解------那么规则链和关键词每天省下的 LLM 调用量是 70%。这不是"过度设计",是真金白银的成本节约和用户体验提升。

反过来看那些"所有路由都交给 LLM"的框架,每次对话都要先让 LLM 判断意图,再执行------延迟翻倍、成本翻倍,而且路由结果还可能不稳定。

三、Orchestrator 层:StateGraph DAG 编排

通过了规则链的请求进入内层 Graph。Dream-SaaS 的 dreamAgentGraph 拓扑非常精简:

复制代码
START → route → [条件分支]
                    ├─ WITH_TOOLS → tools → synthesize → END
                    └─ PLAN_THEN_ANSWER → plan → synthesize → END

3.1 Graph 拓扑:条件边驱动的 DAG

条件边是 Graph 的路由核心。addConditionalEdges 根据 route 节点写入的 ROUTE 状态值,决定下一个节点:

复制代码
graph.addConditionalEdges(
    "route",
    AsyncEdgeAction.edge_async(
        state -> state.value(AgentGraphKeys.ROUTE, AgentGraphKeys.ROUTE_PLAN_THEN_ANSWER)),
    Map.of(
        AgentGraphKeys.ROUTE_WITH_TOOLS, "tools",
        AgentGraphKeys.ROUTE_PLAN_THEN_ANSWER, "plan"));
graph.addEdge("tools", "synthesize");
graph.addEdge("plan", "synthesize");
graph.addEdge("synthesize", END);

两条分支最终汇聚到 synthesize 节点。这是一个典型的 DAG(有向无环图)------没有循环,每个节点只执行一次。循环场景(如 critique-revise)在领域专用 Graph 中实现,通用对话 Graph 刻意保持无环,降低调试复杂度。

3.2 KeyStrategyFactory:状态传递的契约

StateGraph 的节点之间通过 OverAllState 传递数据。每个 state key 需要注册一个合并策略(KeyStrategy),决定当多个节点写同一个 key 时值如何合并。

Dream-SaaS 的通用对话 Graph 中,所有 key 都使用 ReplaceStrategy------后写覆盖先写。这看起来简单,但背后有明确的设计意图:

  • 通用对话 Graph 的拓扑是 DAG,不存在并行写同一 key 的场景(tools 和 plan 是互斥分支),所以不需要 AppendStrategy 或 MergeStrategy。
  • ReplaceStrategy 语义最清晰:每个节点写入的值就是最终值,不存在"合并"带来的歧义。
  • 如果未来引入并行节点,再为需要追加的 key(如 toolOutputs)切换策略。

KeyStrategy 本质上是节点间的数据契约------它声明了"这个 key 的值由谁写、怎么合并"。和微服务中的 API 契约一样,应该最小化、显式化、避免隐式约定。

3.3 Plan 节点为什么不调 LLM

plan 节点是 Dream-SaaS 编排设计中最反直觉的部分。它的名字叫"plan",但完全不调用 LLM ,而是通过 AgentGraphPlanBuilder 用纯逻辑生成结构化计划文本:

复制代码
public static String buildPlanForDirectPath(String userInput, String retrievalSnippets) {
    boolean augmented = hasSubstantiveRetrieval(retrievalSnippets);
    String kind = augmented ? AgentGraphKeys.TASK_KIND_KNOWLEDGE
                            : AgentGraphKeys.TASK_KIND_GENERAL;
    StringBuilder sb = new StringBuilder();
    sb.append("【任务类型】").append(kind).append('\n');
    sb.append("1) 先理解用户目标;若问题含糊,可先澄清再作答。\n");
    if (augmented) {
        sb.append("2) 下方「检索补充」含可参考材料:事实与数字须与之对齐...\n");
    } else {
        sb.append("2) 当前无实质检索命中:可结合常识与历史对话作答...\n");
    }
    // ...
    return sb.toString();
}

它判断检索结果是否实质命中,决定任务类型是"知识问答"还是"通用对话",然后按模板生成指令文本。这个文本会作为 synthesize 节点的 prompt 上下文,引导 LLM 按正确的方式回答。

Plan 节点不一定要调 LLM。 很多编排框架把"planning"做成一个 LLM 调用节点,让模型输出一个结构化的任务计划。但 Dream-SaaS 的实践表明:当任务类型可以通过规则判断(有没有检索结果、是不是多步问题),用确定性逻辑生成计划模板更可靠------没有幻觉风险、没有额外延迟、没有额外 Token 消耗。LLM 应该用在真正需要语义理解的"synthesize"环节,而不是机械地组装指令。

这个设计还有一个好处:可测试。纯函数的 PlanBuilder 可以写单元测试,输入不同的 userInput 和 retrievalSnippets,断言输出的计划文本包含正确的指令。LLM 驱动的 plan 节点几乎无法做确定性单元测试。

3.4 synthesize 节点:上下文组装的艺术

synthesize 是 Graph 的最终节点,也是唯一调用 LLM 的节点。它组装所有上下文------history、plan、retrieval、toolOutputs、userInput------然后调用 ChatClient 生成回答。

上下文组装的顺序和格式直接影响 LLM 的回答质量。Dream-SaaS 的组装原则是:

  1. System Prompt 在前:定义角色和行为约束。
  2. Plan 指令紧随:告诉 LLM 当前任务类型和回答策略。
  3. 检索补充居中:如果有 RAG 结果,作为事实依据。
  4. 工具结果其后:如果走了工具路径,附上工具返回数据。
  5. 历史对话在后:最近 N 轮对话记录。
  6. 用户输入最后:当前问题。

这个顺序不是随意的------LLM 对 prompt 开头和结尾的内容更敏感( primacy 和 recency 效应)。角色定义和任务类型放在最前面设定基调,用户问题放在最后面确保不被忽略。

工具路径下还有一个特殊处理:如果工具返回空结果,PlanBuilder 会调用 buildPlanAfterTools() 检测工具结果是否为空,如果为空,在 plan 文本中明确写入"工具未返回有效数据,请告知用户暂时无法获取该信息,不要编造数据"。这行指令能显著降低 LLM 基于"工具被调用了"这个信号编造数据的概率。

"假成功"检测不能只靠输出端。 编排系列中篇提到了 AgentOutputFailureDetector 在输出端检测异常内容,但输入端的防护同样重要。空工具结果、降级提示、错误信息如果不做特殊处理就喂给 LLM,模型可能基于不完整信息"脑补"出合理但错误的回答。输入端显式告知 LLM"这个数据不可用",比在输出端检测幻觉要可靠得多。

四、领域专用 Graph 的两种模式

通用对话 Graph 解决日常问答,但 Dream-SaaS 还有两类需要特殊编排逻辑的领域场景。它们各自有独立的 Graph 实现。

4.1 Human-in-the-Loop:卖家入驻审核的 interruptBefore 模式

卖家入驻审核流程需要人工审批------系统自动初审后,在 review 节点暂停,等待运营人员审核,审核通过后继续执行后续步骤(如签约、开通支付)。

Spring AI Alibaba Graph 通过 interruptBefore 支持这种模式。核心机制是三件套:

① RunnableConfig + threadId 状态持久化

复制代码
RunnableConfig runConfig = RunnableConfig.builder()
    .threadId(graphThreadId)
    .build();
OverAllState state = sellerEnablementGraph.call(inputs, runConfig)
    .orElseThrow(() -> new IllegalStateException("Graph 返回空状态"));

每次 Graph 调用携带一个 threadId,Graph 的状态快照与 threadId 绑定。中断后状态被持久化,不会因为 JVM 重启丢失(前提是配置了持久化存储)。

② stateOf 检测中断点

复制代码
String nextNode = sellerEnablementGraph.stateOf(runConfig)
    .map(StateSnapshot::next)
    .orElse("");
boolean interrupted = "review".equalsIgnoreCase(nextNode);

Graph 在 interruptBefore 指定的节点前暂停。通过 stateOf 查询当前状态快照的 next 字段,可以判断流程是否停在了 review 节点。如果 next 是 review,说明流程已暂停等待人工输入。

③ updateState 注入人工决策恢复执行

运营人员做出审核决定后,调用 resumeReview() 方法,通过 updateState 将审核结果注入 Graph 状态,然后恢复执行:

复制代码
// 伪代码:注入人工决策后恢复
sellerEnablementGraph.updateState(runConfig, Map.of(
    "reviewDecision", approved ? "APPROVED" : "REJECTED",
    "reviewComment", comment
));
sellerEnablementGraph.call(resumeInputs, runConfig);  // 从断点继续

Human-in-the-Loop 不是"加个审批"。 它需要状态持久化(threadId)、中断点检测(stateOf)、恢复机制(updateState)三件套,缺一不可。很多团队以为 HITL 就是"在流程中插一个审批节点",但没有状态持久化,审批人一刷新页面流程就丢了;没有中断点检测,不知道流程是在等待审批还是已经异常退出;没有恢复机制,审批结果无法回注到 Graph 中继续执行。

此外,SellerFlowOrchestratorService 还有一个 paymentMaxRetries 参数(取值范围 1-10),限制支付环节的最大重试次数,防止因支付网关临时故障导致无限重试。

4.2 Critique-Revise 闭环:内容生成的质量门控

内容生成场景(如商品描述、营销文案)需要质量保证。Dream-SaaS 的 EnhancedContentGraphConfig 实现了 Critique-Revise 闭环:

复制代码
draft → critique → qualityGate
                      ├── PASS → merge → END
                      └── REVISE → revise → critique(循环)

关键设计:

条件属性开关 :通过 @ConditionalOnProperties 控制 enhanced-enabled,开关关闭时退化为简单的 draft → END 流程,便于灰度发布和回退。

质量门控QualityGateNode 读取 critique 的评审结果,判断是否通过。不通过则路由到 revise 节点修订,修订后重新进入 critique 评审。

循环次数控制 :Graph 维护两个计数器------REVISION_COUNT(修订总次数)和 GATE_RETRY_COUNT(门控重试次数),任一达到上限即跳出循环。这是必须的,否则 critique 永远不满意,Graph 会无限循环。

并行分支与合并 :支持 ParallelBranchNode 并行生成多个版本,通过 MergeNode 合并选优。并行节点使用独立的 state key 避免写冲突。

FallbackStrategy 降级:当闭环达到最大迭代次数仍未通过质量门控时,执行降级策略------返回当前最佳版本并标记"质量未达标",而不是直接报错。

ContentGraphMetrics 指标收集:记录每次闭环的迭代次数、通过率、降级率,用于监控内容质量和评估 prompt 效果。

4.3 选型:什么时候用哪种模式?

维度 通用对话 Graph HITL Graph Critique-Revise Graph
拓扑 DAG(无环) DAG + 中断点 有环(循环)
人工介入 不需要 必须(审核节点) 不需要
LLM 调用次数 1 次 0-多次 多次(critique+revise)
延迟要求 秒级 分钟/小时级 十秒级
质量保障 LLM 自检 人工审核 自动评审+门控
典型场景 日常问答、工具调用 入驻审核、合规审批 文案生成、报告撰写

选型原则很简单:需要人做决策的用 HITL,需要机器反复打磨质量的用 Critique-Revise,一次性问答用通用 Graph。不要在通用对话 Graph 里加循环------那会让简单场景的延迟和成本不可控。

五、状态管理:请求生命周期

外层 Orchestrator 的 chat() 方法是整个请求生命周期的编排入口。完整的 chat 流程:

复制代码
public AgentChatResponse chat(AgentChatRequest request) {
    // 1. 准备会话ID和Provider
    String conversationId = resolveConversationId(request);
    String provider = clientSelector.normalizeProvider(request.provider());

    // 2. PreLlm规则链拦截(第一层路由)
    PreLlmContext preCtx = new PreLlmContext(conversationId, request.userInput(), provider);
    RuleEvaluationResult gate = preLlmRuleChain.evaluate(preCtx);
    if (!gate.allowed()) {
        return new AgentChatResponse(conversationId, provider,
            gate.userVisibleMessage(),
            List.of("strategy:preLlmBlocked:" + gate.reasonCode()));
    }

    // 3. 准备Graph输入状态
    String history = renderHistory(conversationId);
    Map<String, Object> inputs = new HashMap<>();
    inputs.put(AgentGraphKeys.CONVERSATION_ID, conversationId);
    inputs.put(AgentGraphKeys.USER_INPUT, request.userInput());
    inputs.put(AgentGraphKeys.PROVIDER, provider);
    inputs.put(AgentGraphKeys.HISTORY_TEXT, history);
    inputs.put(AgentGraphKeys.RETRIEVAL_SNIPPETS,
        retrievalSnippetProvider.retrieve(preCtx).orElse(""));

    // 4. 执行Graph
    Optional<OverAllState> finished = dreamAgentGraph.call(inputs);
    OverAllState state = finished.orElseThrow(...);

    // 5. 提取结果
    String answer = state.value(AgentGraphKeys.FINAL_ANSWER, "");
    List<String> executedTools = extractExecutedTools(state);

    // 6. 维护记忆
    appendMemory(conversationId, "user", request.userInput());
    appendMemory(conversationId, "assistant", answer);

    return new AgentChatResponse(conversationId, provider, answer, executedTools);
}

记忆管理使用 ConcurrentHashMap<String, Deque<String>>,每个会话 ID 对应一个双端队列,保留最近 12 轮(MAX_TURNS=12),超出自动淘汰最早轮次。

六、可观测性:让黑盒变透明

多 Agent 编排的可观测性比单 Agent 更难。Graph 内部的节点跳转对外部是黑盒------Controller 层只看到调用了 dreamAgentGraph.call(),返回了一个结果,但中间 route 走了哪条路、tools 调了什么 API、synthesize 耗时多久,全部不可见。

6.1 为什么 Trace 必须在编排层捕获

如果只在 Controller 层打日志,看到的是:

复制代码
10:00:01 POST /api/chat → 200 (3.2s)

仅此而已。3.2 秒花在哪里?route 判定用了多久?tools 节点调用外部 API 耗时多少?synthesize 调 LLM 用了几次?完全不知道。

Trace 必须在编排层捕获,因为只有编排层知道 Graph 内部的节点流转。外层 Orchestrator 在 chat() 入口生成 conversationId 作为 traceId,内层 Graph 通过 OverAllState 的 CONVERSATION_ID 传递这个 traceId,每个节点在入口和出口记录耗时和状态。

6.2 conversationId 作为 traceId 贯穿全链路

复制代码
// 外层生成traceId
String conversationId = resolveConversationId(request);
// 写入Graph状态,内层节点可读取
inputs.put(AgentGraphKeys.CONVERSATION_ID, conversationId);
// 日志MDC
MDC.put("traceId", conversationId);

这样从 Controller → Orchestrator → Graph 节点 → 工具调用 → LLM 请求,所有日志都可以通过同一个 traceId 串联。排查问题时,用 conversationId 一搜,整条链路的日志按时间线排列,哪个节点慢、哪里报错一目了然。

6.3 ToolTrace:工具级调用记录

各领域 Agent 有独立的 ToolTrace 实现:SellerToolTraceFulfillmentToolTraceTicketMgmtToolTrace。它们记录每次工具调用的:

  • 工具名称和输入参数
  • 调用开始/结束时间和耗时
  • 返回状态(成功/失败/超时)
  • 错误信息(如有)

这些 Trace 数据与 conversationId 关联,可以回溯"这个回答用了哪些工具、工具返回了什么"。

6.4 @WithAgentTrace AOP + Span 树(E1 模块)

E1 模块(已交付 Prompt)引入了 @WithAgentTrace 注解和 AgentTraceAspect 切面,自动为标注的方法创建 Span,形成调用树:

复制代码
chat() [3.2s]
 ├── PreLlmRuleChain.evaluate() [2ms]
 ├── retrievalSnippetProvider.retrieve() [180ms]
 └── dreamAgentGraph.call() [3.0s]
      ├── route [1ms]
      ├── tools [850ms]
      │    ├── WeatherAPI.call() [420ms]
      │    └── TicketAPI.query() [380ms]
      └── synthesize [2.1s]
           └── ChatClient.call() [2.0s]

Span 树让耗时分析从"猜"变成"看"------synthesize 占了 65% 的时间(主要是 LLM 推理),tools 占了 27%(外部 API),route 和规则链几乎可以忽略。优化方向一目了然。

6.5 跨层深链:Graph 内部节点 → Orchestrator → Controller

可观测性的最终目标是"从一次用户请求能追溯到每一个内部步骤"。Dream-SaaS 的跨层链路是:

  1. Controller 层:记录 HTTP 请求/响应、conversationId、总耗时。
  2. Orchestrator 层:记录规则链拦截结果、检索命中情况、Graph 执行结果、记忆写入状态。
  3. Graph 层:每个节点记录输入 state 摘要、输出 state 摘要、节点耗时。
  4. 工具层:ToolTrace 记录外部 API 调用详情。
  5. LLM 层:记录 model、prompt tokens、completion tokens、调用耗时。

所有层的日志通过 conversationId/traceId 串联。当用户反馈"回答不对"时,可以完整复盘:规则链是否放行了?route 走了工具还是 plan?工具返回了什么?LLM 收到了什么 prompt、输出了什么?这种级别的可观测性是多 Agent 系统在生产环境稳定运行的基础。

七、结语:编排架构的设计取舍

三篇编排系列文章,从四种模式到通信与冲突,再到本篇的双层架构拆解,覆盖了多 Agent 编排的核心议题。几个关键结论:

  1. 双层编排的核心是边界划分------外层管"请求生命周期"(安全/记忆/检索/Provider 选择),内层管"推理流转"(节点路由/工具执行/答案合成)。混淆两层会导致 Graph 膨胀成上帝对象,维护成本指数级增长。
  2. 路由决策遵循"确定性梯度"------规则链处理 100% 确定的场景,关键词启发式覆盖 80% 的工具触发,LLM 只在需要语义理解的模糊场景介入。越确定越靠前,这是延迟和成本的必然选择,不是过度设计。
  3. Plan 节点不一定要调 LLM------能用确定性逻辑生成的计划模板就不用 LLM。纯函数 PlanBuilder 可测试、无幻觉、零额外延迟。LLM 是稀缺资源,应该用在真正需要语义理解的 synthesize 环节。
  4. Human-in-the-Loop 需要三件套------状态持久化(threadId)、中断点检测(stateOf)、恢复机制(updateState),缺一不可。"加个审批节点"不是 HITL,只是一个需要人填的表单。
  5. 可观测性必须在编排层建设------Graph 内部的节点跳转对 Controller 层是黑盒。conversationId 贯穿全链路 + Span 树 + ToolTrace,让"回答不对"的排查从玄学变成工程。

编排系列到此收官。但 Agent 工程化的挑战远未结束------当系统跑起来后,下一个必须面对的问题是安全:Prompt 注入、工具滥用、数据泄露、越权操作,每一项都可能让精心构建的编排体系在攻击面前不堪一击。

相关推荐
逻辑君12 分钟前
ANNA 7.6 知识智能引擎白皮书
人工智能·机器学习
叠层归一研究院13 分钟前
基于区分本源的此刻存在唯一性本体论推演
人工智能·算法
维核科技15 分钟前
三个业务共用一台服务器,显存不够用了
大数据·服务器·人工智能
听我哔哔16 分钟前
AI 漫剧制作步骤全流程分享:从剧本到成片的个人实操记录
人工智能·ai漫剧·漫剧制作
木井巳16 分钟前
【JavaEE】Spring Web MVC 入门
java·spring boot·spring·servlet·java-ee
Bode_200217 分钟前
AI工具/大模型学习
人工智能·学习
桐盛科技20 分钟前
什么是边缘计算网关?桐盛科技拆解智慧公厕与能碳管理选型指南
人工智能·边缘计算
lailai041021 分钟前
AI远程控制的技术原理与实现路径
大数据·人工智能
科创致远22 分钟前
芜湖新能源汽车配套企业的多工厂命题:复制一家工厂,复制的是标准esop
大数据·人工智能·汽车·精益工程