一、什么是有限状态机 FSM(Finate State Machine)?
有限状态机(Finite State Machine,简称 FSM)是一种基于离散状态和明确规则的数学模型,核心定义为:系统在任意时刻仅处于有限个离散状态中的一个,通过接收事件(或输入) 触发状态间的转移,且转移过程遵循预定义的规则,最终可达到一个或多个终止状态。
简单来说,FSM 的本质是 “状态驱动的规则化流转”—— 用清晰的 “状态” 定义系统状态,用 “事件” 触发状态变化,用 “转移规则” 约束变化路径,替代冗余的 if-else 逻辑,让复杂流程变得可预测、可维护。
1.1、核心要素
任何 FSM 都由 5 个核心部分组成,缺一不可:
-
状态(State):系统的离散状态集合,是对系统行为模式的抽象。
- 示例:订单的 “待支付”“已支付”“已发货”“已完成”;交通灯的 “红灯”“绿灯”“黄灯”。
-
事件(Event):触发状态转移的外部输入或内部条件。
- 示例:订单的 “支付”“发货”“确认收货” 事件;交通灯的 “计时结束” 事件。
-
转移(Transition):从一个状态到另一个状态的映射关系,由 “当前状态 + 事件” 唯一确定。
- 示例:“待支付” + “支付” → “已支付”;“红灯” + “计时结束” → “绿灯”。
-
初始状态(Initial State):系统启动时的默认状态,是流程的起点。
- 示例:订单创建后的 “待支付”;交通灯启动后的 “红灯”。
-
终止状态(Final State):流程结束后不再转移的状态,是流程的终点(可多个)。
- 示例:订单的 “已完成”“已取消”;任务的 “成功”“失败”。
1.2、核心特征
-
有限性:状态数量是有限的,避免无限循环或不可控状态;
-
确定性:同一状态下接收同一事件,必定转移到唯一目标状态(确定性 FSM,最常用);
-
无记忆性(马尔可夫性):下一状态仅由当前状态和输入事件决定,与历史状态无关;
-
可预测性:所有状态转移都遵循预定义规则,系统行为可完全预判。
1.3、前端开发中的应用
单向数据流
-
状态描述了应用在特定时间点的状况
-
用户界面是基于该状态渲染的
-
当某件事发生时(例如用户点击按钮),状态会根据所发生的事情进行更新
-
用户界面会基于新的状态重新渲染
二、langgraph 多智能体协作
2.1、 多智能体协作:关系与通信结构探析
理解智能体间的交互与通信方式,是设计高效多智能体系统的基础。如下图 所示,智能体关系与通信模型从最简单的单智能体到复杂的定制协作结构,呈现多样化选择。每种模型有独特优势与挑战,影响系统整体效率、健壮性与适应性。
-
单智能体:最基础模型,智能体独立运行,无需与其他实体交互,适合可拆分为独立子问题的场景,但能力受限。
-
网络型:多个智能体以去中心化方式直接交互,点对点通信,信息、资源和任务共享,具备弹性,但通信管理和决策一致性较难。
-
监督者:专门智能体“监督者”协调下属智能体,负责通信、任务分配和冲突解决,层级结构清晰,易于管理,但存在单点故障和瓶颈风险。
-
工具型监督者:监督者不直接指挥,而是为其他智能体提供资源、指导或分析支持,赋能而非强制控制,提升灵活性。
-
层级型:多层监督者结构,高层监督者管理低层监督者,底层为操作智能体,适合复杂问题分层管理,便于扩展和分布式决策。
-
定制型:最灵活模型,针对具体问题或应用定制独特关系与通信结构,可混合前述模型或创新设计,适合优化特定性能、动态环境或领域知识集成。定制模型需深入理解多智能体原理,慎重设计通信协议、协调机制与涌现行为。
2.2、 langgraph state edges node
langgraph 中有 2 种给 node 添加 edge 的方法
add_edge 硬编码的路径
add_conditional_edges 由 llm 决定走哪条路径,代码需要根据 state 的状态栏判断走哪条路径。
2.2、 以 network 类型为例
2.2.1、多人协作拟写技术博客
“多人协作撰写技术博客”:3 个领域专家(前端、后端、DevOps)各自完成模块,同时可查看他人输出并补充交叉内容(如前端提到的框架需后端确认兼容性)。
因为去中心化,所有节点都可能走向另一个节点。
定义状态
class NetworkState(TypedDict):
user_query: str = "" # 原始需求
# 每个智能体的输出(其他智能体可读取)
frontend_content: Optional[str] = None # 前端专家内容
backend_content: Optional[str] = None # 后端专家内容
devops_content: Optional[str] = None # DevOps 专家内容
# 通信标记:记录是否需要其他智能体补充(点对点交互的核心)
need_frontend_supplement: bool = False
need_backend_supplement: bool = False
need_devops_supplement: bool = False
# 完成标记:所有智能体确认无需补充则结束
all_completed: bool = False
定义 graph
# 3. 构建网络型图(去中心化,点对点通信)
def build_network_graph() -> StateGraph:
agents = NetworkAgents(llm)
graph = StateGraph(NetworkState)
# 添加平等节点(无中心,都是操作智能体)
graph.add_node("frontend", agents.frontend_agent)
graph.add_node("backend", agents.backend_agent)
graph.add_node("devops", agents.devops_agent)
# 核心:去中心化通信规则(根据补充需求动态跳转,实现点对点交互)
def communication_branch(state: NetworkState) -> str:
# 前端需要后端补充 → 跳转到后端
if state.get("need_backend_supplement", False):
return "backend"
# 后端需要 DevOps 补充 → 跳转到 DevOps
elif state.get("need_devops_supplement", False):
return "devops"
# 全部完成 → 结束
elif state.get("all_completed", False):
return END
elif state.get("need_devops_supplement", False) and state.get("need_devops_supplement", False) and state.get("need_frontend_supplement", False)
return END
# 默认 → 前端(初始入口)
else:
return "frontend"
# 所有节点的出口都指向分支判断(实现动态点对点跳转)
graph.add_conditional_edges(
"frontend",
communication_branch,
{"backend": "backend", "devops": "devops", "frontend": "frontend", END: END},
)
graph.add_conditional_edges(
"backend",
communication_branch,
{"frontend": "frontend", "devops": "devops", "backend": "backend", END: END},
)
graph.add_conditional_edges(
"devops",
communication_branch,
{"frontend": "frontend", "backend": "backend", "devops": "devops", END: END},
)
# 初始入口:任意智能体(此处选前端)
graph.set_entry_point("frontend")
return graph
2.2.2 代码实现
# 网络型:多个智能体以去中心化方式直接交互,点对点通信,信息、资源和任务共享,具备弹性,但通信管理和决策一致性较难。
# 模式核心特性
# 无中心节点,所有智能体地位平等;
# 智能体直接点对点通信(通过共享状态交换信息);
# 任务和资源分布式共享,弹性强;
# 难点:需处理通信冲突和决策一致性。
# 案例场景
# “多人协作撰写技术博客”:3 个领域专家(前端、后端、DevOps)各自完成模块,同时可查看他人输出并补充交叉内容(如前端提到的框架需后端确认兼容性)。
# 关键逻辑讲解
# 点对点通信:通过 NetworkState 存储所有智能体输出,每个智能体可读取其他模块内容,实现 “信息共享”;
# 去中心化决策:无中心节点,通过 communication_branch 动态跳转,根据智能体的 “补充需求” 决定下一个工作节点;
# 弹性扩展:新增智能体(如 “测试专家”)只需添加节点和通信规则,不影响现有流程;
# 潜在问题:若多个智能体相互请求补充(如前端需要后端,后端需要前端),会陷入死循环,需在实际场景中添加 “最大重试次数” 限制。
from dotenv import load_dotenv
import os
# 1. crreate llm
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langgraph.types import Command
from typing import Literal
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from typing import Dict, List, Optional
from langgraph.checkpoint.memory import InMemorySaver
load_dotenv()
llm = ChatOpenAI(
model=os.getenv("ARK_CHAT_MODEL"),
api_key=os.getenv("ARK_API_KEY"),
base_url=os.getenv("ARK_API_BASE_URL"),
)
# 1. 共享状态(支持点对点通信:存储所有智能体输出,允许相互读取)
class NetworkState(TypedDict):
user_query: str = "" # 原始需求
# 每个智能体的输出(其他智能体可读取)
frontend_content: Optional[str] = None # 前端专家内容
backend_content: Optional[str] = None # 后端专家内容
devops_content: Optional[str] = None # DevOps 专家内容
# 通信标记:记录是否需要其他智能体补充(点对点交互的核心)
need_frontend_supplement: bool = False
need_backend_supplement: bool = False
need_devops_supplement: bool = False
# 完成标记:所有智能体确认无需补充则结束
all_completed: bool = False
# 2. 网络型智能体(平等地位,可读取他人输出并请求补充)
class NetworkAgents:
def __init__(self, llm: ChatOpenAI):
self.llm = llm
# 前端专家:撰写前端模块,可请求后端/DevOps 补充
def frontend_agent(self, state: NetworkState) -> NetworkState:
print("=== 前端专家工作中 ===")
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是前端专家,撰写技术博客的前端模块。可参考后端/DevOps 已输出内容(若有),若需要他们补充信息,将对应标记设为 True。",
),
(
"human",
"用户需求:{query}\n后端已输出:{backend}\nDevOps 已输出:{devops}",
),
]
)
chain = prompt | self.llm | StrOutputParser()
frontend_content = chain.invoke(
{
"query": state.get("user_query", ""),
"backend": state.get("backend_content", "未输出"),
"devops": state.get("devops_content", "未输出"),
}
)
# 模拟:若后端未输出,请求补充
need_backend_supplement = state.get("backend_content") is None
print(f"前端内容:{frontend_content[:100]}...")
print(f"是否需要后端补充:{need_backend_supplement}")
return {
"frontend_content": frontend_content,
"need_backend_supplement": need_backend_supplement,
"need_frontend_supplement": False,
}
# 后端专家:撰写后端模块,可请求前端/DevOps 补充
def backend_agent(self, state: NetworkState) -> NetworkState:
print("\n=== 后端专家工作中 ===")
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是后端专家,撰写技术博客的后端模块。可参考前端/DevOps 已输出内容(若有),若需要他们补充信息,将对应标记设为 True。",
),
(
"human",
"用户需求:{query}\n前端已输出:{frontend}\nDevOps 已输出:{devops}",
),
]
)
chain = prompt | self.llm | StrOutputParser()
backend_content = chain.invoke(
{
"query": state.get("user_query", ""),
"frontend": state.get("frontend_content", "未输出"),
"devops": state.get("devops_content", "未输出"),
}
)
# 模拟:若 DevOps 未输出,请求补充
need_devops_supplement = state.get("devops_content") is None
print(f"后端内容:{backend_content[:100]}...")
print(f"是否需要 DevOps 补充:{need_devops_supplement}")
return {
"backend_content": backend_content,
"need_devops_supplement": need_devops_supplement,
"need_backend_supplement": False,
}
# DevOps 专家:撰写部署模块,可请求前端/后端补充
def devops_agent(self, state: NetworkState) -> NetworkState:
print("\n=== DevOps 专家工作中 ===")
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你是 DevOps 专家,撰写技术博客的部署模块。可参考前端/后端已输出内容(若有),若需要他们补充信息,将对应标记设为 True。",
),
(
"human",
"用户需求:{query}\n前端已输出:{frontend}\n后端已输出:{backend}",
),
]
)
chain = prompt | self.llm | StrOutputParser()
devops_content = chain.invoke(
{
"query": state.get("user_query", ""),
"frontend": state.get("frontend_content", "未输出"),
"backend": state.get("backend_content", "未输出"),
}
)
# 模拟:所有内容已齐全,无需补充
need_frontend_supplement = False
need_backend_supplement = False
need_devops_supplement = False
all_completed = True
print(f"DevOps 内容:{devops_content[:100]}...")
print(f"是否全部完成:{all_completed}")
return {
"devops_content": devops_content,
"need_frontend_supplement": need_frontend_supplement,
"need_backend_supplement": need_backend_supplement,
"need_devops_supplement": need_devops_supplement,
"all_completed": all_completed,
}
# 3. 构建网络型图(去中心化,点对点通信)
def build_network_graph() -> StateGraph:
agents = NetworkAgents(llm)
graph = StateGraph(NetworkState)
# 添加平等节点(无中心,都是操作智能体)
graph.add_node("frontend", agents.frontend_agent)
graph.add_node("backend", agents.backend_agent)
graph.add_node("devops", agents.devops_agent)
# 核心:去中心化通信规则(根据补充需求动态跳转,实现点对点交互)
def communication_branch(state: NetworkState) -> str:
# 前端需要后端补充 → 跳转到后端
if state.get("need_backend_supplement", False):
return "backend"
# 后端需要 DevOps 补充 → 跳转到 DevOps
elif state.get("need_devops_supplement", False):
return "devops"
# 全部完成 → 结束
elif state.get("all_completed", False):
return END
elif state.get("need_devops_supplement", False) and state.get("need_devops_supplement", False) and state.get("need_frontend_supplement", False)
return END
# 默认 → 前端(初始入口)
else:
return "frontend"
# 所有节点的出口都指向分支判断(实现动态点对点跳转)
graph.add_conditional_edges(
"frontend",
communication_branch,
{"backend": "backend", "devops": "devops", "frontend": "frontend", END: END},
)
graph.add_conditional_edges(
"backend",
communication_branch,
{"frontend": "frontend", "devops": "devops", "backend": "backend", END: END},
)
graph.add_conditional_edges(
"devops",
communication_branch,
{"frontend": "frontend", "backend": "backend", "devops": "devops", END: END},
)
# 初始入口:任意智能体(此处选前端)
graph.set_entry_point("frontend")
return graph
def save_graph_image(graph_data: bytes):
import os
from IPython.display import Image, display
# display(Image(app.get_graph(xray=True).draw_mermaid_png()))
# image 存到本地,使用绝对路径并添加错误处理
try:
# 使用脚本所在目录的绝对路径
script_dir = os.path.dirname(os.path.abspath(__file__))
file_name = os.path.basename(__file__)
image_path = os.path.join(script_dir, file_name.replace(".py", ".png"))
# 尝试获取并保存图形
with open(image_path, "wb") as f:
f.write(graph_data)
print(f"图形已成功保存到: {image_path}")
except Exception as e:
print(f"保存图形时出错: {e}")
# 运行网络型协作
if __name__ == "__main__":
# 编译图(加入内存检查点)
checkpointer = InMemorySaver()
app = build_network_graph().compile(checkpointer=checkpointer)
graph_data = app.get_graph(xray=True).draw_mermaid_png()
save_graph_image(graph_data)
initial_state = NetworkState(
user_query="撰写一篇关于微服务架构的技术博客,分前端、后端、DevOps 三个模块,重点讲协作流程"
)
print("=== 网络型协作启动(去中心化点对点)===")
config = {
"configurable": {
"thread_id": "123123123",
}
}
final_state = app.invoke(initial_state, config=config)
print("\n=== 网络型协作结束 ===")
print("最终博客内容整合:")
print(f"前端模块:{final_state.get('frontend_content', '未生成')}")
print(f"后端模块:{final_state.get('backend_content', '未生成')}")
print(f"DevOps 模块:{final_state.get('devops_content', '未生成')}")
states = list(app.get_state_history(config))
for index, state in enumerate(states):
print(f"############### 状态 {index}: #################")
print(state)