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;
    };
}

注意 messages 用 AppendStrategy(对话历史持续追加),current_draft 用 ReplaceStrategy(每次修改覆盖旧版本)。

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_node、iteration_count、review_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 循环实现区别? 前者有 LoopMode(count / condition),后者需自维护计数器
  • 工作流特有的安全风险? State 污染影响路由、Loop 放大攻击消耗 Token

14. 参考资料

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