从 FSM(有限状态机)理解多智能体协作

29 阅读11分钟

一、什么是有限状态机 FSM(Finate State Machine)?

有限状态机(Finite State Machine,简称 FSM)是一种基于离散状态和明确规则的数学模型,核心定义为:系统在任意时刻仅处于有限个离散状态中的一个,通过接收事件(或输入) 触发状态间的转移,且转移过程遵循预定义的规则,最终可达到一个或多个终止状态

简单来说,FSM 的本质是 “状态驱动的规则化流转”—— 用清晰的 “状态” 定义系统状态,用 “事件” 触发状态变化,用 “转移规则” 约束变化路径,替代冗余的 if-else 逻辑,让复杂流程变得可预测、可维护。

1.1、核心要素

任何 FSM 都由 5 个核心部分组成,缺一不可:

  1. 状态(State):系统的离散状态集合,是对系统行为模式的抽象。

    • 示例:订单的 “待支付”“已支付”“已发货”“已完成”;交通灯的 “红灯”“绿灯”“黄灯”。
  2. 事件(Event):触发状态转移的外部输入或内部条件。

    • 示例:订单的 “支付”“发货”“确认收货” 事件;交通灯的 “计时结束” 事件。
  3. 转移(Transition):从一个状态到另一个状态的映射关系,由 “当前状态 + 事件” 唯一确定。

    • 示例:“待支付” + “支付” → “已支付”;“红灯” + “计时结束” → “绿灯”。
  4. 初始状态(Initial State):系统启动时的默认状态,是流程的起点。

    • 示例:订单创建后的 “待支付”;交通灯启动后的 “红灯”。
  5. 终止状态(Final State):流程结束后不再转移的状态,是流程的终点(可多个)。

    • 示例:订单的 “已完成”“已取消”;任务的 “成功”“失败”。

1.2、核心特征

  1. 有限性:状态数量是有限的,避免无限循环或不可控状态;

  2. 确定性:同一状态下接收同一事件,必定转移到唯一目标状态(确定性 FSM,最常用);

  3. 无记忆性(马尔可夫性):下一状态仅由当前状态和输入事件决定,与历史状态无关;

  4. 可预测性:所有状态转移都遵循预定义规则,系统行为可完全预判。

1.3、前端开发中的应用

单向数据流

  • 状态描述了应用在特定时间点的状况

  • 用户界面是基于该状态渲染的

  • 当某件事发生时(例如用户点击按钮),状态会根据所发生的事情进行更新

  • 用户界面会基于新的状态重新渲染

二、langgraph 多智能体协作

2.1、 多智能体协作:关系与通信结构探析

理解智能体间的交互与通信方式,是设计高效多智能体系统的基础。如下图 所示,智能体关系与通信模型从最简单的单智能体到复杂的定制协作结构,呈现多样化选择。每种模型有独特优势与挑战,影响系统整体效率、健壮性与适应性。

  1. 单智能体:最基础模型,智能体独立运行,无需与其他实体交互,适合可拆分为独立子问题的场景,但能力受限。

  2. 网络型:多个智能体以去中心化方式直接交互,点对点通信,信息、资源和任务共享,具备弹性,但通信管理和决策一致性较难。

  3. 监督者:专门智能体“监督者”协调下属智能体,负责通信、任务分配和冲突解决,层级结构清晰,易于管理,但存在单点故障和瓶颈风险。

  4. 工具型监督者:监督者不直接指挥,而是为其他智能体提供资源、指导或分析支持,赋能而非强制控制,提升灵活性。

  5. 层级型:多层监督者结构,高层监督者管理低层监督者,底层为操作智能体,适合复杂问题分层管理,便于扩展和分布式决策。

  6. 定制型:最灵活模型,针对具体问题或应用定制独特关系与通信结构,可混合前述模型或创新设计,适合优化特定性能、动态环境或领域知识集成。定制模型需深入理解多智能体原理,慎重设计通信协议、协调机制与涌现行为。

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)