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)
- Java/Spring:Spring AI Alibaba Graph(包路径
- 安全基线:以 OWASP Top 10 for LLM Applications v1.1(2025 发布)为准
1. 为什么 AI 系统需要工作流?
单轮对话能回答问题,但很难稳定地交付结果。线上真实任务很少是"问一句答一句"就完事——检索信息、调用工具、输出结构化结果、校验格式、失败重试、不满意再来一轮,这些步骤串起来才叫交付。靠一段超长 Prompt 把所有逻辑塞进去,早晚会炸。你需要的是一种可分支、可循环、可观测的执行路径。
LLM 的特点是"能力强但不完全稳定"。它可能答非所问、格式错误、产生幻觉,或者在调用工具时失败。这就引出了三个核心问题:
- 下一步并不唯一,需要根据当前结果动态决策路径
- 结果不理想时,系统需要自动修正,而不是直接失败
- 中间状态必须被记录,否则难以调试、追踪与恢复
这正是工作流思维要解决的。
以"写一篇文章"为例:一次生成往往不够理想。直觉做法是手动复制结果再附加新要求继续提问,但这种方式既不高效也快速消耗上下文。如果将这一过程结构化为"审查 → 修改 → 再审查"的循环,并设定停止条件(如达到质量标准或触达迭代上限),稳定性会明显好很多。
说到底,工作流就是把一次性的生成过程,变成一个可迭代、可收敛、可控制的系统化流程。
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 的
SendAPI 可动态决定并行分支数) - 循环边:节点回到自身或前序节点
- 终止边:流程结束(
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 两种基本模式
- 固定次数循环(for 风格):最多重试 3 次
- 条件驱动循环(while 风格):评分低于 80 就继续修改
AI 场景里第二类更有代表性——"跑几次"往往不是先验确定的,而是由内容质量、工具执行结果、外部反馈共同决定。但实际开发中两者必须同时使用:因为 LLM 不确定性可能导致一直不合格,固定次数提供降级兜底。
4.3 嵌套循环
实际工程中经常遇到嵌套循环:
- 外层:质量迭代(生成 → 审核 → 修改)
- 内层:工具重试(节点内调用外部 API 失败后的指数退避重试)
两层的作用域、终止条件、计数器必须独立——内层重试耗尽不应影响外层迭代预算,外层退出也不意味着内层可无限制重试。设计时为每层明确独立的退出条件和安全边界。
4.4 三要素
可靠的 Loop 一定包含:
- 继续条件:为什么还要再来一轮
- 退出条件:什么时候已经足够好
- 安全边界:最大轮次、超时、预算、熔断条件
没有这些约束,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. 总结
工作流框架会更新换代,但"图结构 + 状态 + 可控循环"这层抽象基本不会变。几个正在发生的演进方向:
- Agent 化:节点从"固定脚本"变成"能自主选工具、拆子目标"的执行单元,但底层仍需要清晰的图与状态边界,否则难以观测与兜底
- 多智能体协作:多个角色分工、对话或委托;难点往往在共享 State 的权限与冲突解决
- 人机协同:把 HITL 当作一等公民写进图与状态机
- 更长上下文与记忆:与 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. 参考资料
- Spring AI Alibaba Graph 官方文档:https://java2ai.com/docs/frameworks/graph-core/quick-start
- LangGraph Graph API 官方文档:https://docs.langchain.com/oss/python/langgraph/graph-api
- OWASP Top 10 for LLM Applications v1.1:https://genai.owasp.org/llm-top-10/

浙公网安备 33010602011771号