AI 工作流中的 Workflow、Graph 与 Loop:从概念到双引擎实现

AI 工作流中的 Workflow、Graph 与 Loop:从概念到双引擎实现

本文系统梳理 AI 工作流中三个核心概念——Workflow、Graph、Loop,并基于 Spring AI Alibaba Graph(Java)与 LangGraph(Python)两个当前最主流的框架给出完整实现。所有 API 名称、类名基于官方文档(截至 2026 年 6 月)核对。

一句话结论

AI 系统真正的难点不是"生成内容",而是"在不确定的输出上构建可收敛的执行路径"。 这就是工作流(Workflow)、图(Graph)、循环(Loop)三件事要解决的问题。


0. 本文约定

  • 范围:AI 系统内部的工作流编排(Graph/Loop),不是简单的 if-else + Prompt 串接
  • 生态
    • Java/Spring:Spring AI Alibaba Graph(包路径 com.alibaba.cloud.ai.graph
    • Python:LangGraph(langgraph,文档已迁移至 docs.langchain.com
  • 安全基线:以 OWASP Top 10 for LLM Applications v1.1(2025 发布)为准

1. 为什么 AI 系统需要工作流?

单轮对话能回答问题,但很难稳定地交付结果。线上真实任务很少是"问一句答一句"就完事——检索信息、调用工具、输出结构化结果、校验格式、失败重试、不满意再来一轮,这些步骤串起来才叫交付。靠一段超长 Prompt 把所有逻辑塞进去,早晚会炸。你需要的是一种可分支、可循环、可观测的执行路径。

LLM 的特点是"能力强但不完全稳定"。它可能答非所问、格式错误、产生幻觉,或者在调用工具时失败。这就引出了三个核心问题:

  1. 下一步并不唯一,需要根据当前结果动态决策路径
  2. 结果不理想时,系统需要自动修正,而不是直接失败
  3. 中间状态必须被记录,否则难以调试、追踪与恢复

这正是工作流思维要解决的。

以"写一篇文章"为例:一次生成往往不够理想。直觉做法是手动复制结果再附加新要求继续提问,但这种方式既不高效也快速消耗上下文。如果将这一过程结构化为"审查 → 修改 → 再审查"的循环,并设定停止条件(如达到质量标准或触达迭代上限),稳定性会明显好很多。

说到底,工作流就是把一次性的生成过程,变成一个可迭代、可收敛、可控制的系统化流程。


2. 传统工作流 vs AI 工作流

维度 传统 Workflow(BPMN/Camunda/Temporal/Airflow) AI Workflow(Graph/Loop 驱动)
路径选择时机 设计时确定 运行时由 LLM 评估决定
同一节点多次执行 通常不需要 经常需要(生成 → 评估 → 修正)
节点输出 确定性强 不确定,需运行时评估
失败处理 抛错或回滚 循环回去让 LLM 看着调整
节点间传递 参数 上下文、草稿、评分、错误、历史轮次
调试方式 日志 + 追踪 日志 + 状态快照 + 可重放

关键差异:AI 工作流的路径选择依赖于运行时生成内容的质量评估,同一节点可能因输出不确定而反复执行。BPMN 2.0 虽支持事件驱动与 DMN 决策表,但分支条件通常在设计时确定;AI 场景中"生成结果是否达标"本身就是运行时评估。


3. Graph:工作流的结构

Graph 是工作流的"形状"。一个图里有三类元素:Node、Edge、State。

3.1 Node(节点)

执行单元:读状态 → 执行逻辑 → 写回状态。例如:

  • DraftNode:生成初稿
  • ReviewNode:质量审核
  • ReviseNode:按反馈修改
  • SearchNode:检索外部知识
  • FormatNode:格式校验
  • HumanNode:人工审核(HITL)

3.2 Edge(边)

控制流抽象,决定节点之间的执行路径:

  • 顺序边:A → B,固定走向
  • 条件边:根据运行时状态在预定义候选路径中选择(Spring AI Alibaba 的 addConditionalEdges(),LangGraph 的 add_conditional_edges()
  • 动态路由:候选节点在运行时才确定(LangGraph 的 Send API 可动态决定并行分支数)
  • 循环边:节点回到自身或前序节点
  • 终止边:流程结束(END
  • 并行边:一个节点同时分发到多个下游节点并行执行

条件边与动态路由其实是一个连续谱系——条件边候选集在设计时确定但选择逻辑可依赖运行时状态,动态路由的候选集本身在运行时才确定。多数场景下条件边已够用;动态路由适用于 map-reduce 等需要运行时决定并行分支数的场景。

3.3 State(状态)

节点间共享的"工作记忆"。通常以键值对实现(Java 的 Map<String, Object>、Python 的 dict / TypedDict)。

3.4 State 更新策略

State 设计不仅是"存什么",还涉及"怎么更新":

策略 语义 适用场景 Spring AI Alibaba LangGraph
覆盖 Replace 新值替换旧值 单值字段(分类结果、当前草稿) ReplaceStrategy 默认行为
追加 Append 新值追加到列表 累积字段(对话历史) AppendStrategy Annotated[list, operator.add]
自定义合并 自定义函数决定 消息 ID 追加/更新、冲突解决 自定义 KeyStrategy add_messages reducer

关键约束:多个并行节点同时用"覆盖"语义写同一字段会出现竞态——LangGraph 会抛 INVALID_CONCURRENT_GRAPH_UPDATE。设计 State 时要提前规划哪些字段可能被并行写入,并为它们选择合适的更新策略。

3.5 实际项目常用 State 字段

  • input:用户输入,全流程保留
  • messages:对话历史,追加策略
  • retrieval_result:RAG 检索结果
  • tool_result:工具调用结果
  • llm_response:LLM 原始输出
  • intermediate_steps:执行步骤记录
  • next_step / next_node:路由控制字段(Spring 模式通过此字段配合条件边;LangGraph 用条件边函数返回值,不需要这个字段)
  • output:最终输出

4. Loop:Graph 上的回溯

4.1 与 Agent Loop 的区别

  • Agent Loop(外层):整个 Agent 在 while 循环中反复执行"推理 → 行动 → 观察"
  • Graph Loop(内层):特定节点子集通过回边(Back Edge)形成的迭代修正循环

两者关系:Agent Loop 是外层,Graph Loop 可以嵌套在某个节点或子图内。

4.2 两种基本模式

  1. 固定次数循环(for 风格):最多重试 3 次
  2. 条件驱动循环(while 风格):评分低于 80 就继续修改

AI 场景里第二类更有代表性——"跑几次"往往不是先验确定的,而是由内容质量、工具执行结果、外部反馈共同决定。但实际开发中两者必须同时使用:因为 LLM 不确定性可能导致一直不合格,固定次数提供降级兜底。

4.3 嵌套循环

实际工程中经常遇到嵌套循环:

  • 外层:质量迭代(生成 → 审核 → 修改)
  • 内层:工具重试(节点内调用外部 API 失败后的指数退避重试)

两层的作用域、终止条件、计数器必须独立——内层重试耗尽不应影响外层迭代预算,外层退出也不意味着内层可无限制重试。设计时为每层明确独立的退出条件和安全边界。

4.4 三要素

可靠的 Loop 一定包含:

  1. 继续条件:为什么还要再来一轮
  2. 退出条件:什么时候已经足够好
  3. 安全边界:最大轮次、超时、预算、熔断条件

没有这些约束,Loop 很容易从"自我修正"变成"无限打转"。


5. Workflow、Graph、Loop 的关系

一句话:Workflow 是目标与过程,Graph 是结构与载体,Loop 是图上的控制模式。

继续沿用"写文章并审核"的例子:

视角 例子
Workflow "先生成初稿,再审核,不达标就修改,直到达标后输出"
Graph 把 生成 → 审核 → 修正 画成节点与连线,共享一份 State
Loop "审核不通过就回到修改,直到评分达标或上限"

三者是同一件事的三个观察角度。


6. 框架实现一:Spring AI Alibaba Graph

Spring AI Alibaba 是阿里云推出的 Spring AI 实现,Graph 模块提供了完整的工作流编排能力(参考其官方 Quick Start 文档)。

6.1 引入依赖

<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-graph-core</artifactId>
    <version>1.0.0.2</version>
</dependency>

6.2 定义状态与更新策略

import com.alibaba.cloud.ai.graph.KeyStrategy;
import com.alibaba.cloud.ai.graph.KeyStrategyFactory;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;
import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy;
import java.util.HashMap;

public static KeyStrategyFactory createKeyStrategyFactory() {
    return () -> {
        HashMap<String, KeyStrategy> strategies = new HashMap<>();
        strategies.put("input",            new ReplaceStrategy());
        strategies.put("messages",         new AppendStrategy());   // 对话历史追加
        strategies.put("current_draft",    new ReplaceStrategy());  // 草稿覆盖
        strategies.put("review_score",     new ReplaceStrategy());
        strategies.put("review_feedback",  new ReplaceStrategy());
        strategies.put("iteration_count",  new ReplaceStrategy());
        strategies.put("output",           new ReplaceStrategy());
        strategies.put("next_node",        new ReplaceStrategy());  // 路由控制
        return strategies;
    };
}

注意 messagesAppendStrategy(对话历史持续追加),current_draftReplaceStrategy(每次修改覆盖旧版本)。

6.3 实现节点

import com.alibaba.cloud.ai.graph.action.NodeAction;
import com.alibaba.cloud.ai.graph.OverAllState;
import org.springframework.ai.chat.client.ChatClient;

// 1) 生成初稿
public static class DraftNode implements NodeAction {
    private final ChatClient chatClient;
    public DraftNode(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        String input = state.value("input").map(v -> (String) v).orElse("");
        String draft = chatClient.prompt()
            .user(String.format("请根据以下要求撰写文章:%s", input))
            .call().content();
        return Map.of("current_draft", draft, "next_node", "review");
    }
}

// 2) 质量审核
public static class ReviewNode implements NodeAction {
    private final ChatClient chatClient;
    public ReviewNode(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        String draft = state.value("current_draft").map(v -> (String) v).orElse("");
        int count    = state.value("iteration_count").map(v -> (int) v).orElse(0);

        String prompt = String.format(
            "请评估以下文章质量,给出 0-100 的评分和改进建议。\n" +
            "以JSON格式返回:{\"score\": 85, \"feedback\": \"...\"}\n\n%s", draft);
        String response = chatClient.prompt().user(prompt).call().content();

        // 实际项目用 Jackson/Gson 解析
        double score    = parseScore(response);
        String feedback = parseFeedback(response);

        // 评分达标 或 达到最大轮次 → 退出;否则 → 修改
        String nextNode = (score >= 80 || count >= 3) ? "exit" : "revise";
        return Map.of(
            "review_score",    score,
            "review_feedback", feedback,
            "iteration_count", count + 1,
            "next_node",       nextNode
        );
    }
}

// 3) 根据反馈修正
public static class ReviseNode implements NodeAction {
    private final ChatClient chatClient;
    public ReviseNode(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        String draft    = state.value("current_draft").map(v -> (String) v).orElse("");
        String feedback = state.value("review_feedback").map(v -> (String) v).orElse("");

        String revised = chatClient.prompt()
            .user(String.format("请根据反馈修改文章。\n\n原文:%s\n\n反馈意见:%s",
                draft, feedback))
            .call().content();

        return Map.of("current_draft", revised, "next_node", "review");
    }
}

// 4) 输出
public static class ExitNode implements NodeAction {
    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        String draft = state.value("current_draft").map(v -> (String) v).orElse("");
        return Map.of("output", draft);
    }
}

6.4 组装 Graph

import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.CompileConfig;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;
import com.alibaba.cloud.ai.graph.saving.SaverConfig;
import com.alibaba.cloud.ai.graph.saving.MemorySaver;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.client.ChatClient;

import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async;
import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async;
import static com.alibaba.cloud.ai.graph.StateGraph.END;
import static com.alibaba.cloud.ai.graph.StateGraph.START;

public static CompiledGraph buildWorkflow(ChatModel chatModel) throws GraphStateException {
    ChatClient.Builder builder = ChatClient.builder(chatModel);

    var draft  = node_async(new DraftNode(builder));
    var review = node_async(new ReviewNode(builder));
    var revise = node_async(new ReviseNode(builder));
    var exit   = node_async(new ExitNode());

    StateGraph workflow = new StateGraph(createKeyStrategyFactory())
        .addNode("draft",  draft)
        .addNode("review", review)
        .addNode("revise", revise)
        .addNode("exit",   exit);

    // 顺序边
    workflow.addEdge(START, "draft");

    // 条件边:根据 next_node 字段决定路由
    workflow.addConditionalEdges("draft",
        edge_async(state -> (String) state.value("next_node").orElse("review")),
        Map.of("review", "review"));

    workflow.addConditionalEdges("review",
        edge_async(state -> (String) state.value("next_node").orElse("exit")),
        Map.of(
            "revise", "revise",   // 审核不通过 → 修改
            "exit",   "exit"      // 审核通过或达到上限 → 输出
        ));

    // 修改后回到审核节点 → 形成 Loop
    workflow.addConditionalEdges("revise",
        edge_async(state -> (String) state.value("next_node").orElse("review")),
        Map.of("review", "review"));

    workflow.addEdge("exit", END);

    // 持久化:生产环境建议 RedisSaver 或数据库 Saver
    var saver = new MemorySaver();
    var compileConfig = CompileConfig.builder()
        .saverConfig(SaverConfig.builder().register(saver).build())
        .build();

    return workflow.compile(compileConfig);
}

在这个实现中:每个 Node 只做自己名字说的事,Edge(条件边)控制路由,State(next_nodeiteration_countreview_score)驱动决策。Loop 通过 review → revise → review 的回边实现,安全边界由 iteration_count >= 3 保证。

持久化的关键意义:确保流程中断后可以从最近的 checkpoint 恢复。已迭代 2 轮的审核在第 3 轮中断后,应继续第 3 轮而不是从第 1 轮重新开始。


7. 框架实现二:LangGraph

LangGraph 是 LangChain 团队推出的图工作流库。文档已从 langchain-ai.github.io/langgraph 迁移到 docs.langchain.com/oss/python/langgraph/

7.1 安装

pip install -U langgraph langchain-openai

7.2 定义状态

from typing import TypedDict, Annotated, List
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage

class ArticleState(TypedDict):
    messages:        Annotated[List[BaseMessage], add_messages]  # 消息类型自带 reducer
    input:           str
    current_draft:   str
    review_score:    float
    review_feedback: str
    iteration_count: int
    output:          str

LangGraph 的 reducer 机制:

  • 默认(无 reducer):覆盖
  • Annotated[list, operator.add]:追加
  • add_messages:按消息 ID 智能合并(追加新消息,更新已有消息)

7.3 实现节点

from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
import json

llm = ChatOpenAI(model="gpt-4o-mini")

def draft_node(state: ArticleState):
    resp = llm.invoke(f"请根据以下要求撰写文章:{state['input']}")
    return {"current_draft": resp.content, "messages": [resp]}

def review_node(state: ArticleState):
    prompt = (
        "请评估以下文章质量,给出 0-100 的评分和改进建议。\n"
        "以JSON格式返回:{\"score\": 85, \"feedback\": \"...\"}\n\n"
        f"{state['current_draft']}"
    )
    resp = llm.invoke(prompt)
    parsed = json.loads(resp.content)
    count = state.get("iteration_count", 0)
    return {
        "review_score":    parsed["score"],
        "review_feedback": parsed["feedback"],
        "iteration_count": count + 1,
        "messages":        [resp],
    }

def revise_node(state: ArticleState):
    prompt = (
        f"请根据反馈修改文章。\n\n原文:{state['current_draft']}\n\n"
        f"反馈意见:{state['review_feedback']}"
    )
    resp = llm.invoke(prompt)
    return {"current_draft": resp.content, "messages": [resp]}

def exit_node(state: ArticleState):
    return {"output": state["current_draft"]}

7.4 组装 Graph + 条件边

def should_revise(state: ArticleState) -> str:
    if state["review_score"] >= 80 or state["iteration_count"] >= 3:
        return "exit"
    return "revise"

workflow = StateGraph(ArticleState)
workflow.add_node("draft",  draft_node)
workflow.add_node("review", review_node)
workflow.add_node("revise", revise_node)
workflow.add_node("exit",   exit_node)

workflow.add_edge(START, "draft")
workflow.add_edge("draft", "review")
workflow.add_conditional_edges(
    "review",
    should_revise,
    {"revise": "revise", "exit": "exit"},
)
workflow.add_edge("revise", "review")  # Loop
workflow.add_edge("exit", END)

# 持久化(生产环境用 SqliteSaver / PostgresSaver)
from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)

# 运行
config = {"configurable": {"thread_id": "article-001"}}
result = app.invoke({"input": "介绍 Java 21 虚拟线程"}, config=config)
print(result["output"])

7.5 Send API:动态并行

当并行分支数在运行时才能确定(如 map-reduce):

from langgraph.types import Send

def continue_to_jokes(state):
    # 根据 state 动态决定生成几个 joke
    return [Send("generate_joke", {"subject": s}) for s in state["subjects"]]

workflow.add_conditional_edges("node_a", continue_to_jokes, ["generate_joke"])

7.6 人机协同(Interrupt)

from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import ToolNode

memory = MemorySaver()
app = workflow.compile(
    checkpointer=memory,
    interrupt_before=["tools"],  # 调用工具前暂停
)

# 第一次运行:跑到 interrupt 处停住
config = {"configurable": {"thread_id": "1"}}
result = app.invoke({"input": "..."}, config=config)

# 人工审核/改写后,从 checkpoint 恢复
result = app.invoke(None, config=config)

8. 框架对照表

概念 Spring AI Alibaba LangGraph
状态 OverAllState + KeyStrategyFactory TypedDict + Annotated[type, reducer]
覆盖 ReplaceStrategy 默认行为
追加 AppendStrategy Annotated[list, operator.add]
节点 NodeAction 接口 普通函数
顺序边 addEdge(src, dst) add_edge(src, dst)
条件边 addConditionalEdges(src, fn, map) add_conditional_edges(src, fn)
固定次数循环 LoopMode.count(N) 自维护计数器
条件驱动循环 LoopMode.condition(predicate) 条件边 + while 逻辑
持久化 MemorySaver / RedisSaver MemorySaver / SqliteSaver / PostgresSaver
人机协同 interruptBefore + updateState interrupt_before + update_state
编译 StateGraph.compile(CompileConfig) StateGraph.compile()

9. 工作流抽象能力

很多初学者设计工作流时,容易把每一步都写成具体动作——"调用模型生成文案;检查标题长度;检查语气;判断是否补资料;再调用模型修改"。短期可用,但流程越来越碎,复用性差。

更成熟的方式是把流程抽象到稳定的结构层

  • Node 抽象职责边界:在这个节点中产出的结果该是什么,必须出现哪些信息。而不是抽象"这一次调了哪个 API"。
  • Edge 抽象流转规则:在什么状态下允许去哪、何时结束。用条件边表达分支与循环,而不是在图外写满 if-else。
  • State 抽象推进任务时必须持久记住的信息:工单快照、审核结论、重试次数、错误码等。

例如在"生成并审核文章"里,与其设计十几个零散节点检查标题、字数、语气,不如抽象为四个稳定职责的节点:

  • DraftNode:产出当前版本内容
  • ReviewNode:评估当前结果是否达标
  • ReviseNode:根据反馈修正内容
  • ExitNode:满足条件时输出最终结果

好的工作流关键看 Node、Edge、State 的抽象能否经得起复用与扩展,和步骤多少关系不大。


10. 落地时常见的坑

真正把工作流落地时,问题往往不出在"图不会画",而出在细节没有提前设计好。

10.1 State 设计的粒度

  • 太粗:所有东西塞进一个大对象,谁改了哪个字段不好查
  • 太细:字段拆得特别散,每个节点都要拼来拼去
  • 建议:按业务含义分几块——用户原始输入、当前生成结果、审核/评分结论、流程控制字段(如当前步骤、重试次数)

10.2 循环终止条件

不要只写"如果不满意就继续优化",要明确:

  • 最大轮次是多少?
  • 评分阈值是多少?
  • 超时或成本超限时怎么办?
  • 连续失败后是否要 fallback?

10.3 错误处理与降级

AI 工作流不是只处理"成功路径"。工具异常、模型超时、格式校验失败、外部接口限流,都应在图上有明确边:重试、降级、转人工、输出"当前最优 + 错误说明",而不是只靠外围 try-catch 吞掉。

Spring AI Alibaba 官方把错误分成四类

错误类型 谁来修复 策略 示例
瞬时错误 系统(自动) 指数退避重试 网络超时、API 限流
LLM 可恢复错误 LLM 把错误塞到 State 里,循环回去 工具调用失败、输出格式异常
用户可修复错误 人工 interruptBefore 暂停 缺少必要信息、指令不明确
意外错误 开发者 让异常冒泡 未知异常

这些策略与分布式系统的弹性模式很接近:

  • 指数退避重试:1s、2s、4s 递增间隔,最多 5 次
  • 熔断器:连续 N 次 LLM 输出格式校验失败就熔断,降级到模板输出或换更简单的模型
  • 舱壁隔离:给不同外部 API 设独立并发上限
  • 补偿事务(Saga):多步骤操作某步挂了,按反序执行回滚

10.4 Token 与成本控制

Loop 会自然放大 Token 与延迟。设计时要提前思考:

  • 哪些节点必须调用大模型,哪些可以用代码替代
  • 是否可以先粗筛再精修
  • 是否需要在达到"足够好"时就提前结束,而不是追求"理论最优"

10.5 节点间数据传递

节点之间传什么、字段名怎么定义、结构化输出采用什么 schema,都应该尽早统一(统一用 JSON Schema 或 Pydantic 模型)。否则图一旦复杂,调试成本会急剧上升。


11. 安全考量

工作流为 LLM 输出引入结构和约束,但也带来新的攻击面。参考 OWASP Top 10 for LLM Applications v1.1(2025),需要重点关注:

11.1 State 污染(工作流特有)

恶意用户输入通过节点处理后写入 State 的路由控制字段(如 next_node),可能影响后续条件边路由,跳过审核节点直接到达输出

防御

  • 对 State 中的路由控制字段做白名单校验
  • 不要让 LLM 输出直接写 next_node,而是写一个语义化字段(如 intent),由边缘函数翻译成路由
  • 关键节点(审核、计费)无论路由结果如何都强制执行

11.2 Loop 放大攻击(工作流特有)

恶意输入构造使 ReviewNode 永远返回低分,导致 Loop 达到最大轮次才退出,消耗大量 Token。

防御

  • iteration_count 上限
  • 独立的 Token 消耗预算(不依赖 LLM 自报)
  • 评分分布异常检测(如连续 N 轮评分方差 < 1 视为"评分卡死")
  • 超时熔断(最大 wall-clock 时间)

11.3 提示注入的级联影响(OWASP LLM01)

OWASP LLM01 Prompt Injection:恶意用户输入可能覆盖系统提示,在工作流中逐节点传播放大。

防御

  • 输入过滤(黑名单 + 长度限制)
  • 系统提示与用户输入严格分隔(XML 标签或独立 channel)
  • 对 LLM 输出做安全检测后再传递给下游节点
  • 高风险节点独立审计日志

11.4 工具调用的权限边界(OWASP LLM08)

OWASP LLM08 Excessive Agency:高风险操作(删除、发送)需通过人机协同节点确认。遵循最小权限原则。

防御

  • 每个节点只声明任务所需的工具(白名单)
  • 工具调用前独立鉴权
  • 高风险操作强制 HITL

11.5 输出内容安全过滤(OWASP LLM02)

OWASP LLM02 Insecure Output Handling:LLM 输出在进入下游系统(数据库、前端渲染、Shell 命令)前必须经过校验。

防御

  • Schema 校验(Pydantic / JSON Schema)
  • 内容安全分类器
  • Shell/HTML/SQL 注入专用过滤

12. 总结

工作流框架会更新换代,但"图结构 + 状态 + 可控循环"这层抽象基本不会变。几个正在发生的演进方向:

  1. Agent 化:节点从"固定脚本"变成"能自主选工具、拆子目标"的执行单元,但底层仍需要清晰的图与状态边界,否则难以观测与兜底
  2. 多智能体协作:多个角色分工、对话或委托;难点往往在共享 State 的权限与冲突解决
  3. 人机协同:把 HITL 当作一等公民写进图与状态机
  4. 更长上下文与记忆:与 RAG、会话记忆结合时,要特别注意 State 里哪些该进向量库、哪些只该留在本轮任务上下文

理解图结构、状态流转和可控循环这几层抽象,比追某个框架的 API 变化更有长期价值。具体语言和框架跟着团队技术栈走就行。


13. 面试准备要点

高频问题

  • 为什么 AI 系统需要工作流? LLM 输出不确定,需要动态决策、自动修正和可控收敛
  • Workflow、Graph、Loop 什么关系? Workflow 是目标与过程,Graph 是结构与载体,Loop 是图上的控制模式
  • Graph Loop 和 Agent Loop 区别? Agent Loop 是顶层 while 循环(推理→行动→观察),Graph Loop 是图内回边,可嵌套
  • Loop 如何防死循环? 三要素:继续条件、退出条件、安全边界(最大轮次 + 超时 + Token 预算)
  • State 更新策略怎么选? 单值用 Replace,累积用 Append,并行写入必须用 Reducer
  • 条件边和动态路由区别? 条件边候选集设计时确定、运行时选;动态路由候选集运行时确定
  • 怎么理解 Graph 抽象设计? Node 抽象职责边界,Edge 抽象流转规则,State 抽象必须持久记住的信息

追问准备

  • 工作流中断后怎么恢复? 持久化 + checkpoint 机制
  • 节点内错误怎么处理? 瞬时重试、LLM 可恢复循环回去、用户可修复转人工、意外冒泡
  • Spring AI Alibaba 和 LangGraph 循环实现区别? 前者有 LoopModecount / condition),后者需自维护计数器
  • 工作流特有的安全风险? State 污染影响路由、Loop 放大攻击消耗 Token

14. 参考资料

posted @ 2026-06-11 15:14  AJun816  阅读(8)  评论(0)    收藏  举报