WorkFlow介绍
工作流是 LLM 和工具通过预定义的代码路径进行编排的系统。另一方面,智能体是 LLM 动态指导其自身流程和工具使用的系统,它们都是构建复杂的大模型应用系统的核心组件。
LangGraph的工作流通过有向图(Directed Graph)定义,由节点(Node)、边(Edge)、状态(State)构成,支持条件分支、循环、并行执行。通过 状态图(StartGraph) 将多个智能体(Agent)和任务节点(Node)组织成可动态调整的流程结构,实现 有状态、可扩展的任务编排。
LangGraph通过 状态图 将Agent和工作流解耦,让开发者能以“搭积木”的方式构建复杂AI系统:
- Agent 是“乐高积木”,负责实现具体功能;
- 工作流 是“搭建规则”,定义积木如何组合、何时执行。
这种设计既保留了灵活性,又能通过状态管理实现大规模系统的可靠运行。
一句话总结:Agent 是一种特殊的节点(Node),而工作流是整个图的运行流程(Graph)。Agent 负责"思考+行动”,工作流负责”编排+调度”。
相关概念
StateGraph
LangGraph 的核心是将 Agent 和 WorkFlow 建模为图。可以使用三个关键组件来定义一个Graph:
State:表示应用程序当前快照的共享数据结构。它可以是任何 Python 类型,但通常是TypedDict或Pydantic BaseModel。Nodes:编码代理逻辑的 Python 函数。它们接收当前的State作为输入,执行一些计算或副作用,并返回更新后的State。Edges:根据当前State决定接下来执行哪个Node的 Python 函数。它们可以是条件分支或固定转换。
构建流程
先定义状态,再添加节点和边,最后编译成图。
关键特性
节点和边可包含 LLM 或纯 Python 代码,支持构建复杂、循环的工作流,状态随时间演进。
State(状态)
表示应用当前快照的共享数据结构,通常是 TypedDict 或 Pydantic 模型。包含 schema 和 reducer 函数(决定如何应用更新)。
Reducer 函数
指定状态更新的方式(默认覆盖,也可自定义合并逻辑,如 add_messages 用于消息列表的智能更新)。
MessagesState
预构建的常用状态,包含消息列表,支持子类化扩展字段。
Node(节点)
执行具体逻辑的 Python 函数,接收状态(和可选配置)作为输入,返回状态更新。
特殊节点
START:用户输入的入口节点。END:终止节点。
Edge(边)
决定节点间的路由逻辑。
- 普通边:固定从一个节点到另一个节点。
- 条件边:根据路由函数的返回值动态决定下一个节点(可并行执行多个节点)。路由函数接收当前状态,返回节点名称或映射值。
案例:评估器
使用 LangGraph(LangChain 的一个扩展库)构建了一个 循环式工作流(workflow),用于 自动生成并评估笑话,如果笑话不够好笑,就基于反馈重新生成,直到达到“有趣”的标准为止。
一个典型的 LLM Agent + 反馈循环(Feedback Loop) 架构。
大致流程如下:
- 用户提供一个主题(topic)
- 系统生成一个笑话
- LLM 对笑话进行评估(是否有趣 + 改进建议)
- 如果不有趣 → 带着反馈重新生成
- 如果有趣 → 结束流程

代码实现
创建项目
创建项目参考:第三版:1、LangGraph之基本介绍+项目生成
定义大模型
import os
import dotenv
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
llm = ChatOpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL"),
model="Qwen/Qwen2.5-72B-Instruct",
)
核心代码实现
graph.py
from typing import TypedDict, Literal
from langchain_core.output_parsers import StrOutputParser
from langgraph.constants import START, END
from langgraph.graph import StateGraph
from pydantic import BaseModel, Field
from agent.ai_model import llm
class State(TypedDict):
joke: str # 笑话内容
topic: str # 笑话主题
feedback: str # 评估反馈
funny_or_not: str # 评定是否为笑话
# 结构化输出模型(用于LLM评估反馈)
class Feedback(BaseModel):
"""使用此工具来结构化响应内容"""
grade: Literal["funny", "not_funny"] = Field(
examples=["funny", "not_funny"],
description="笑话是否有趣"
)
feedback: str = Field(
description="若不幽默,提供改进建议",
examples=["可以加入双关语或意外结局"]
)
# 节点函数
def generator_joke(state: State):
"""生成一个笑话"""
prompt = (
f"根据反馈改进笑话:{state['feedback']}\n,主题:{state['topic']}"
if state.get("feedback", None)
else f"根据主题生成笑话:{state['topic']}"
)
chain = llm | StrOutputParser()
return {"joke": chain.invoke(prompt)}
def evaluate_joke(state: State):
"""评估笑话是否 有趣"""
# 第一种写法(只适用于gpt等少量模型):
# chain = llm.with_structured_output(Feedback)
# resp: Feedback = chain.invoke(
# f"评估此笑话的幽默程度:\n{state['joke']}\n"
# "注意:幽默应包含意外性或巧妙措辞"
# )
# return {"feedback": resp.feedback, "funny_or_not": resp.grade}
# 第二种写法(通用):
chain = llm.bind_tools([Feedback])
evaluation = chain.invoke(
f"评估此笑话的幽默程度:\n{state['joke']}\n"
"注意:幽默应包含意外性或巧妙措辞"
)
evaluation = evaluation.tool_calls[-1]["args"]
print(f"evaluation: {evaluation}")
return {"feedback": evaluation.get("feedback", ""), "funny_or_not": evaluation.get("grade", "not funny")}
# 条件边的路由函数
def route_func(state: State):
"""动态路由决策函数"""
return (
"Accept"
if state.get("funny_or_not", None) == "funny"
else "Reject + Feedback"
)
# 构建工作流
builder = StateGraph(State)
# 添加节点
builder.add_node("joke_generator", generator_joke)
builder.add_node("joke_evaluator", evaluate_joke)
# 添加边
builder.add_edge(START, "joke_generator")
builder.add_edge("joke_generator", "joke_evaluator")
builder.add_conditional_edges("joke_evaluator",
route_func,
{
"Accept": END,
"Reject + Feedback": "joke_generator"
})
# 编译工作流
graph = builder.compile()
核心代码解析
1. 类型定义:State
class State(TypedDict):
joke: str # 当前生成的笑话
topic: str # 用户提供的主题
feedback: str # 上一轮评估给出的改进建议
funny_or_not: str # "funny" 或 "not_funny"
State是整个工作流的 状态容器,所有节点共享这个状态。- 每次节点执行后,会返回一个字典(如
{"joke": "..."}),更新State中的部分字段。
2. 结构化输出模型:Feedback
class Feedback(BaseModel):
grade: Literal["funny", "not_funny"] = Field(...)
feedback: str = Field(...)
- 这是一个 Pydantic 模型,用于强制 LLM 输出结构化的评估结果。
grade只能是"funny"或"not_funny"(通过Literal限制)。feedback字段用于提供改进建议(如“加入双关语”)。
💡 为什么需要结构化?
因为 LLM 默认输出是自由文本,但我们需要程序能可靠地提取“是否有趣”和“建议”,所以用工具调用(tool calling)或结构化输出来约束格式。
3. 节点函数
(1) generator_joke(state: State)
def generator_joke(state: State):
prompt = (
f"根据反馈改进笑话:{state['feedback']}\n,主题:{state['topic']}"
if state.get("feedback", None)
else f"根据主题生成笑话:{state['topic']}"
)
chain = llm | StrOutputParser()
return {"joke": chain.invoke(prompt)}
- 返回
{"joke": "..."},更新状态中的joke字段。
(2) evaluate_joke(state: State)
def evaluate_joke(state: State):
chain = llm.bind_tools([Feedback])
evaluation = chain.invoke(
f"评估此笑话的幽默程度:\n{state['joke']}\n"
"注意:幽默应包含意外性或巧妙措辞"
)
evaluation = evaluation.tool_calls[-1]["args"]
return {
"feedback": evaluation.get("feedback", ""),
"funny_or_not": evaluation.get("grade", "not funny")
}
- 更新状态中的
feedback和funny_or_not。
✅ 为什么用
bind_tools而不是with_structured_output?
with_structured_output仅支持部分模型(如 GPT-4o、Claude 3.5+ 等原生支持 JSON schema 的)。bind_tools是更通用的方式,几乎所有支持 function/tool calling 的模型都能用(包括开源模型如 Llama 3.1 + function calling 微调版)。
4. 路由函数:route_func
def route_func(state: State):
return "Accept" if state.get("funny_or_not") == "funny" else "Reject + Feedback"
- 根据评估结果决定下一步:
- 如果
funny_or_not == "funny"→ 走向END(接受) - 否则 → 回到
joke_generator(拒绝并反馈)
- 如果
注意:返回的字符串必须与
add_conditional_edges中的 key 一致。
5. 构建工作流图(StateGraph)
builder = StateGraph(State)
# 添加节点
builder.add_node("joke_generator", generator_joke)
builder.add_node("joke_evaluator", evaluate_joke)
# 边连接
builder.add_edge(START, "joke_generator")
builder.add_edge("joke_generator", "joke_evaluator")
# 条件边(核心!实现循环)
builder.add_conditional_edges(
"joke_evaluator",
route_func,
{
"Accept": END,
"Reject + Feedback": "joke_generator"
}
)
graph = builder.compile()
6. 图结构
START
↓
joke_generator → joke_evaluator
↘ (if not funny)
↖___________ (loop back)
↘ (if funny)
→ END
add_conditional_edges是实现 动态路由/循环 的关键。- 整个工作流可以执行多次迭代,直到笑话被判定为“funny”。
7. 潜在改进点
-
防止无限循环:
- 可添加最大重试次数(如最多3次),避免 LLM 一直生成不好笑的笑话。
- 方法:在
State中加attempt_count,在route_func中判断。
-
初始状态简化:
- 调用时只需传
topic,其他字段可设默认值(可通过__init__或预处理实现)。
- 调用时只需传
效果演示

代码仓地址
自此,本文分享到此结束!!!

854

被折叠的 条评论
为什么被折叠?



