LangGraph构建多智能体

LangGraph

🦜🕸 LangGraph ⚡ 以图的方式构建语言代理 ⚡

官方文档地址:https://langchain-ai.github.io/langgraph/

LangGraph 是一个用于构建有状态的多参与者应用程序的库,利用 LLM 创建代理和多代理工作流。与

其他 LLM 框架相比,它提供了以下核心优势:循环性、可控性和持久性。LangGraph 允许您定义涉及

循环的流程,这对于大多数代理架构至关重要,使其与基于 DAG 的解决方案区别开来。作为一个非常底

层的框架,它提供了对应用程序流程和状态的细粒度控制,这对于创建可靠的代理至关重要。此外,

LangGraph 包含内置的持久性,支持先进的人机协作和记忆特性。

LangGraph 的灵感来源于 Pregel 和 Apache Beam。公共接口受 NetworkX 的启发。LangGraph 是由

LangChain Inc 开发的,它是 LangChain 的创建者,但可以在不使用 LangChain 的情况下使用。

LangGraph 平台是用于部署 LangGraph 代理的基础设施。它是一个商业解决方案,用于将代理应用程

序部署到生产环境,构建于开源的 LangGraph 框架之上。LangGraph 平台由多个组件组成,这些组件

协同工作以支持 LangGraph 应用程序的开发、部署、调试和监控:LangGraph 服务器(API)、

LangGraph SDK(API 客户端)、LangGraph CLI(构建服务器的命令行工具)、LangGraph

Studio(用户界面/调试器)。

主要功能

  • 循环和分支:在您的应用程序中实现循环和条件语句。
  • 持久性:在图中的每个步骤之后自动保存状态。在任何时候暂停和恢复图执行以支持错误恢复、 "人机交互"工作流、时间旅行等等。
  • "人机交互":中断图执行以批准或编辑代理计划的下一个动作。
  • 流支持:在每个节点产生输出时流式传输输出(包括令牌流式传输)。
  • 与 LangChain 集成:LangGraph 与 LangChain 和 LangSmith 无缝集成(但不需要它们)。

LangGraph 平台

LangGraph 平台是一个商业解决方案,用于将代理应用程序部署到生产环境,构建于开源的,LangGraph 框架之上。以下是一些在复杂部署中常见的问题,LangGraph 平台解决了这些问题:

  • 流式支持:LangGraph 服务器提供多种流式模式,优化以满足各种应用需求
  • 后台运行:在后台异步运行代理
  • 支持长时间运行的代理:能够处理长时间运行过程的基础设施
  • 双重文本:处理用户在代理回复之前收到两条消息的情况
  • 处理突发性 :任务队列以确保请求在高负载下也能始终如一地处理,不会丢失。

Graph(图)

LangGraph 的核心是将代理工作流建模为图。你可以使用三个关键组件来定义代理的行为

  1. 状态:一个共享的数据结构,表示应用程序的当前快照。它可以是任何 Python 类型,但通常是
    Type
    dDict 或 Pydantic BaseModel。
  2. 节点:编码代理逻辑的 Python 函数。它们接收当前 状态 作为输入,执行一些计算,并返回一个
    更新的 状态。
  3. 边:Python 函数,根据当前 状态 确定要执行的下一个 节点。它们可以是条件分支或固定转换。
    通过组合 节点 和 边,你可以创建复杂的循环工作流,随着时间的推移发展 状态。但是,真正的力量来
    自于 LangGraph 如何管理 状态。需要强调的是:节点 和 边 不再是 Python 函数------它们可以包含
    LLM 或简单的 Python 代码。

简而言之:节点完成工作。边指示下一步要做什么。

LangGraph 的底层图算法使用消息传递来定义一个通用程序。当一个节点完成其操作时,它会沿着一条或多条边向其他节点发送消息。这些接收节点然后执行其函数,将结果消息传递给下一组节点,并且该过程继续进行。受到 Google 的 Pregel 系统的启发,该程序以离散的"超级步骤"进行。

超级步骤可以被认为是图节点上的单个迭代。并行运行的节点属于同一个超级步骤,而顺序运行的节点则属于不同的超级步骤。在图执行开始时,所有节点都处于 inactive 状态。当节点在任何传入边(或"通道")上收到新消息(状态)时,它将变为 active 状态。然后,活动节点运行其函数并响应更新。在每个超级步骤结束时,没有传入消息的节点通过将其标记为 inactive 来投票 halt。当所有节点都处于inactive 状态且没有消息在传输中时,图执行终止。

StateGraph

stateGraph 类是使用的主要图类。它由用户定义的状态对象参数化。

python 复制代码
#示例:state_graph.py
# 从langgraph.graph模块导入START和StateGraph
from langgraph.graph import START, StateGraph
# 定义一个节点函数my_node,接收状态和配置,返回新的状态
def my_node(state, config):
    return {"x": state["x"] + 1,"y": state["y"] + 2}
# 创建一个状态图构建器builder,使用字典类型作为状态类型
builder = StateGraph(dict)
# 向构建器中添加节点my_node,节点名称将自动设置为'my_node'
builder.add_node(my_node) # node name will be 'my_node'
# 添加一条边,从START到'my_node'节点
builder.add_edge(START, "my_node")
# 编译状态图,生成可执行的图
graph = builder.compile()
# 调用编译后的图,传入初始状态{"x": 1}
print(graph.invoke({"x": 3,"y":2}))

# 输出:
# {'x': 2,'y': 4}

Compiling your graph(编译你的图)

要构建你的图,你首先定义状态,然后添加节点和边,最后进行编译。编译图究竟是什么,为什么需要它?

编译是一个非常简单的步骤。它对图的结构进行一些基本检查(没有孤立的节点等等)。它也是你可以指定运行时参数的地方,例如 检查点 和 断点。你只需调用 .compile 方法即可编译你的图。

python 复制代码
#你必须在使用图之前编译它。

graph = graph_builder.compile(...)

编译结果

State(状态)

定义图时,你做的第一件事是定义图的 状态 状态 包含图的 模式 以及 归约器函数,它们指定如何将

更新应用于 状态 状态 的模式将是图中所有 节点 的输入模式,可以是 TypedDict 或者

Pydantic 模型。所有 节点 将发出对 状态 的更新,这些更新然后使用指定的 归约器 函数进行应用。

Nodes(节点)

在 LangGraph 中,节点通常是 Python 函数(同步或 async),其中第一个位置参数是状态,(可选地),第二个位置参数是"配置",包含可选的可配置参数(例如 thread_id)。类似于 NetworkX,您可以使用add_node方法将这些节点添加到图形中

python 复制代码
#示例:node_case.py
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph, START
from langgraph.graph import END
# 初始化 StateGraph,状态类型为字典
graph = StateGraph(dict)
# 定义节点
def my_node(state: dict, config: RunnableConfig):
    print("In node: ", config["configurable"]["user_id"])
    return {"results": f"Hello, {state['input']}!"}
def my_other_node(state: dict):
    return state
# 将节点添加到图中
graph.add_node("my_node", my_node)
graph.add_node("other_node", my_other_node)
# 连接节点以确保它们是可达的
graph.add_edge(START, "my_node")
graph.add_edge("my_node", "other_node")
graph.add_edge("other_node", END)
# 编译图
print(graph.compile())

在幕后,函数被转换为RunnableLambda,它为您的函数添加了批处理和异步支持,以及本地跟踪和调试。

如果您在没有指定名称的情况下将节点添加到图形中,它将被赋予一个默认名称,该名称等同于函数名称。

python 复制代码
graph.add_node(my_node)
# You can then create edges to/from this node by referencing it as
`"my_node"`

START 节点

START节点是一个特殊节点,它代表将用户输入发送到图形的节点。引用此节点的主要目的是确定哪些节点应该首先被调用。

python 复制代码
from langgraph.graph import START

graph.add_edge(START,"my_node")
graph.add_edge("my_node","other_node")

END 节点

END节点是一个特殊节点,它代表一个终端节点。当您想要指定哪些边在完成操作后没有动作时,会引用此节点。

python 复制代码
from langgraph.graph import END

graph.add_edge("other_node", END)

Edges(边)

边定义了逻辑如何路由以及图形如何决定停止。这是您的代理如何工作以及不同节点如何相互通信的重要部分。有一些关键类型的边

  • 普通边:直接从一个节点到下一个节点。
  • 条件边:调用一个函数来确定下一个要转到的节点。
  • 入口点:用户输入到达时首先调用的节点。
  • 条件入口点:调用一个函数来确定用户输入到达时首先调用的节点。

一个节点可以有多个输出边。如果一个节点有多个输出边,则所有这些目标节点将在下一个超级步骤中并行执行。

普通边

如果您总是想从节点 A 到节点 B,您可以直接使用add_edge方法。

python 复制代码
#示例:edges_case.py
graph.add_edge("node_a","node_b")

条件边

如果您想选择性地路由到一个或多个边(或选择性地终止),您可以使用add_conditional_edges方法。此方法接受节点的名称和一个"路由函数",该函数将在该节点执行后被调用

python 复制代码
graph.add_conditional_edges("node_a", routing_function)

类似于节点, routing_function接受图形的当前 state并返回一个值。

默认情况下,返回值 routing_function用作要将状态发送到下一个节点的节点名称(或节点列表)。

所有这些节点将在下一个超级步骤中并行运行。

您可以选择提供一个字典,该字典将 routing_function的输出映射到下一个节点的名称。

python 复制代码
graph.add_conditional_edges("node_a"}), routing_function, {True: "node_b", False:"node_c")

入口点

入口点是图形启动时运行的第一个节点。您可以从虚拟的START节点使用add_edge方法到要执行的第一个节点,以指定进入图形的位置。

python 复制代码
from langgraph.graph import START

graph.add_edge(START,"my_node")

条件入口点

条件入口点允许您根据自定义逻辑从不同的节点开始。您可以从虚拟的START节点使用add_conditional_edges来实现这一点。

python 复制代码
from langgraph.graph import START

graph.add_conditional_edges(START, routing_function)

您可以选择提供一个字典,该字典将 routing_function的输出映射到下一个节点的名称。

python 复制代码
graph.add_conditional_edges(START, routing_my,{True: "my_node", False:"other_node"}) 

准备 :

python 复制代码
pip install modelscope

mkdir -p /root/autodl-tmp/demo/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
cd /root/autodl-tmp/demo/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
modelscope download --model deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --local_dir ./

# 创建Imdeploy隔离环境
conda create -n lmdeploy python=3.10 -y

# 激活环境
conda activate lmdeploy

# source activate lmdeploy
# 果是退出shell ,再次进入base环境,需要先加载环境
pip install lmdeploy

CUDA_VISIBLE_DEVICES=1 lmdeploy serve api_server /root/autodl-tmp/demo/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --model-format=hf --dtype=bfloat16  --tp=1 --server-port=23333
python 复制代码
from typing import Literal, Annotated
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_core.pydantic_v1 import BaseModel, Field
import re

# ================== 强化状态类 ==================
class AgentState(BaseModel):
    messages: Annotated[list, Field(default_factory=list)]
    next_agent: Literal["researcher", "calculator", "writer", "done"] = "researcher"
    step_count: int = 0
    is_finalized: bool = False

    @classmethod
    def from_raw(cls, raw):
        """统一状态转换方法"""
        if isinstance(raw, cls):
            return raw
        if isinstance(raw, dict):
            # 确保处理旧版状态格式
            return cls(
                messages=raw.get('messages', []),
                next_agent=raw.get('next_agent', 'researcher'),
                step_count=raw.get('step_count', 0),
                is_finalized=raw.get('is_finalized', False)
            )
        raise ValueError(f"无效状态类型: {type(raw)}")

    def to_safe_dict(self):
        """安全转换为字典(用于异常处理)"""
        return {
            "messages": [msg.dict() for msg in self.messages],
            "next_agent": self.next_agent,
            "step_count": self.step_count,
            "is_finalized": self.is_finalized
        }

# ================== 消息转换增强 ==================
def convert_messages(raw_messages):
    converted = []
    for msg in raw_messages:
        if isinstance(msg, dict):
            try:
                role = msg.get("role", "assistant")
                content = msg.get("content", "")
                msg_type = {
                    "user": HumanMessage,
                    "system": SystemMessage,
                    "assistant": AIMessage
                }[role]
                converted.append(msg_type(content=content))
            except KeyError:
                converted.append(AIMessage(content=str(msg)))
        elif hasattr(msg, 'content'):
            converted.append(msg)
        else:
            converted.append(AIMessage(content=str(msg)))
    return converted

# ================== 模型配置 ==================
model = ChatOpenAI(
    openai_api_base="http://localhost:23333/v1",
    model_name="/root/autodl-tmp/demo/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",
    openai_api_key="EMPTY",
    max_tokens=300,
    temperature=0.2
)

# ================== 核心Agent模板 ==================
def create_agent(role: str, prompt: str):
    def agent(raw_state):
        state = AgentState.from_raw(raw_state)
        if state.is_finalized:
            return state

        try:
            messages = convert_messages(state.messages)
            messages.append(SystemMessage(
                content=f"{prompt}\n最后必须用'建议下一步:选项'格式结尾(选项只能是researcher/calculator/writer/done)"
            ))

            response = model.invoke(messages)
            print(f"\n[{role}输出] {response.content[:200]}...")  # 限制日志长度

            # 强制Writer添加END标识
            if role == "writer" and "[END]" not in response.content:
                response.content += "\n[END]"
                print(f"[{role}修正] 已添加[END]标识")

            # 解析下一步建议
            next_agent = "done"
            if match := re.search(r'建议下一步\s*[::]\s*(\w+)', response.content):
                suggestion = match.group(1).lower()
                if suggestion in {"researcher", "calculator", "writer", "done"}:
                    next_agent = suggestion

            return AgentState(
                messages=messages + [response],
                next_agent=next_agent,
                step_count=state.step_count + 1,
                is_finalized="[END]" in response.content
            )
        except Exception as e:
            print(f"[{role}错误] {str(e)}")
            return AgentState(
                messages=state.messages,
                next_agent="done",
                step_count=state.step_count + 1,
                is_finalized=True
            )
    return agent

# ================== Supervisor节点强化 ==================
def supervisor(raw_state):
    state = AgentState.from_raw(raw_state)
    if state.is_finalized:
        return state

    try:
        print(f"\n[Supervisor] 当前步数: {state.step_count}")
        
        # 强制流程控制
        if state.step_count >= 8:
            print("[强制终止] 达到最大步数限制")
            return AgentState(
                messages=state.messages,
                next_agent="writer",
                step_count=state.step_count + 1,
                is_finalized=False
            )

        messages = convert_messages(state.messages)
        control_prompt = '''请严格选择下一步:
1. 需要更多数据 → researcher
2. 需要计算 → calculator 
3. 生成报告 → writer
4. 完成 → done'''
        
        response = model.invoke(messages + [SystemMessage(content=control_prompt)])
        decision = re.search(r'\b(researcher|calculator|writer|done)\b', response.content.lower())
        
        new_agent = decision.group(1).lower() if decision else "done"
        # 强制done前必须生成报告
        if new_agent == "done" and not any("[END]" in msg.content for msg in messages):
            new_agent = "writer"

        return AgentState(
            messages=state.messages,
            next_agent=new_agent,
            step_count=state.step_count + 1,
            is_finalized=False
        )
    except Exception as e:
        print(f"[Supervisor错误] {str(e)}")
        return AgentState(
            messages=state.messages,
            next_agent="writer",
            step_count=state.step_count + 1,
            is_finalized=False
        )

# ================== 工作流配置 ==================
builder = StateGraph(AgentState)
builder.add_node("supervisor", supervisor)
builder.add_node("researcher", create_agent("研究员", "收集经济数据并分析趋势"))
builder.add_node("calculator", create_agent("计算器", "执行GDP增长率计算"))
builder.add_node("writer", create_agent("作家", "生成包含[END]标识的最终报告"))

# builder.add_edge("supervisor","researcher")
builder.add_conditional_edges(
    "supervisor",
    lambda state: state.next_agent,
    {
        "researcher": "researcher",
        "calculator": "calculator", 
        "writer": "writer",
        "done": END
    }
)

for agent in ["researcher", "calculator", "writer"]:
    builder.add_edge(agent, "supervisor")

builder.set_entry_point("supervisor")
workflow = builder.compile()

# ================== 执行入口强化 ==================
if __name__ == "__main__":
    try:
        # 初始化状态时确保类型正确
        initial_state = AgentState(messages=[HumanMessage(content="成都市GDP增长率分析")])
        
        for step in workflow.stream(initial_state):
            node_name, raw_state = step.popitem()
            current_state = AgentState.from_raw(raw_state)
            
            print(f"\n[系统状态] 当前节点: {node_name}")
            print(f"下一步: {current_state.next_agent}")
            print(f"步数: {current_state.step_count}")
            print(f"完成状态: {current_state.is_finalized}")

            # 检查终止条件
            if current_state.is_finalized or node_name == "__end__":
                print("\n====== 流程完成 =====")
                if any("[END]" in msg.content for msg in current_state.messages):
                    print("最终报告内容:")
                    print(next(msg.content for msg in reversed(current_state.messages) if "[END]" in msg.content))
                else:
                    print("警告:未检测到完整报告")
                break
                
    except Exception as e:
        print(f"\n!!! 流程异常终止: {str(e)}")
        if 'current_state' in locals():
            print("最后状态:", current_state.to_safe_dict())

输出:
# python test03.py 
sys:1: LangChainDeprecationWarning: As of langchain-core 0.3.0, LangChain uses pydantic v2 internally. The langchain_core.pydantic_v1 module was a compatibility shim for pydantic v1, and should no longer be used. Please update the code to import from Pydantic directly.

For example, replace imports like: `from langchain_core.pydantic_v1 import BaseModel`
with: `from pydantic import BaseModel`
or the v1 compatibility namespace if you are working in a code base that has not been fully upgraded to pydantic 2 yet.         from pydantic.v1 import BaseModel


[Supervisor] 当前步数: 0

[系统状态] 当前节点: supervisor
下一步: writer
步数: 1
完成状态: False

[作家输出] 嗯,用户让我分析成都市GDP增长率,还特别提到要包含[END]标识的最终报告,最后用建议下一步:选项的格式结尾,选项只能是researcher、calculator、writer、done。首先,我得弄清楚用户的需求是什么。他们可能是一个经济研究者,或者是一个学生,或者是政府工作人员,需要一份详细的分析报告。

用户提到的"包含[END]标识的最终报告",可能意味着他们希望报告结构清晰,有明确的开...

[系统状态] 当前节点: writer
下一步: done
步数: 2
完成状态: True

====== 流程完成 =====
最终报告内容:
嗯,用户让我分析成都市GDP增长率,还特别提到要包含[END]标识的最终报告,最后用建议下一步:选项的格式结尾,选项只能是researcher、calculator、writer、done。首先,我得弄清楚用户的需求是什么。他们可能是一个经济研究者,或者是一个学生,或者是政府工作人员,需要一份详细的分析报告。

用户提到的"包含[END]标识的最终报告",可能意味着他们希望报告结构清晰,有明确的开始和结束部分。而最后的建议选项,可能是在完成报告后,他们需要知道下一步该做什么,比如继续研究、计算更多数据,或者已经完成任务了。

接下来,我需要考虑分析的内容。GDP增长率分析通常包括历史趋势、影响因素、比较分析和预测。所以,我应该从这些方面入手。首先,收集成都市近年来的GDP数据,计算增长率,然后分析这些增长是否稳定,是否有波动。然后,找出影响GDP增长的主要因素,比如经济结构、政策、投资等。接着,比较成都市与其他城市的GDP增长率,看看其在全球或区域中的位置。最后,预测未来几年的GDP增长情况,这可能对政策制定者有帮助。

在分析过程中,我需要确保数据的准确性和来源的可靠性。可能需要查阅国家统计局的数据,或者相关的经济研究论文。同时,分析时要保持客观,指出增长的亮点和挑战,比如经济增长是否过于依赖某些

进行流程优化

python 复制代码
from typing import Literal, Annotated
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
from langchain_core.pydantic_v1 import BaseModel, Field
import re

# ================== 状态类定义 ==================
class AgentState(BaseModel):
    messages: Annotated[list, Field(default_factory=list)]
    next_agent: Literal["researcher", "calculator", "writer", "done"] = "researcher"
    step_count: int = 0

    def __getitem__(self, key):
        return getattr(self, key)
    
    def get(self, key, default=None):
        return getattr(self, key, default)

# ================== 消息转换函数 ==================
def convert_messages(raw_messages):
    return [
        msg if not isinstance(msg, dict) else 
        {
            "user": HumanMessage,
            "system": SystemMessage,
            "assistant": AIMessage
        }[msg["role"]](content=msg["content"])
        for msg in raw_messages
    ]

# ================== 模型配置 ==================
model = ChatOpenAI(
    openai_api_base="http://localhost:23333/v1",
    model_name="/root/autodl-tmp/demo/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",
    openai_api_key="EMPTY",
    max_tokens=150,
    temperature=0.2
)

# ================== 核心函数:create_agent ==================
def create_agent(role: str, prompt: str):
    """通用Agent创建函数"""
    def agent(state: AgentState):
        current_state = AgentState(**state) if isinstance(state, dict) else state
        
        messages = convert_messages(current_state.messages)
        messages.append(SystemMessage(
            content=f"{prompt}\n请用'建议下一步:选项'格式结尾(选项只能是researcher/calculator/writer/done)"
        ))
        
        try:
            response = model.invoke(messages)
            print(f"[{role}输出] {response.content}")
            
            # 解析下一步建议
            next_agent = "done"
            if match := re.search(r'建议下一步:\s*(\w+)', response.content, re.IGNORECASE):
                suggestion = match.group(1).lower()
                if suggestion in {"researcher", "calculator", "writer", "done"}:
                    next_agent = suggestion
            
            return AgentState(**{
                "messages": messages + [response],
                "next_agent": next_agent,
                "step_count": current_state.step_count + 1
            })
        except Exception as e:
            print(f"[{role}错误] {str(e)}")
            return AgentState(**{
                "messages": current_state.messages,
                "next_agent": "done",
                "step_count": current_state.step_count + 1
            })
    return agent

# ================== Supervisor节点 ==================
def supervisor(state: AgentState):
    current_state = AgentState(**state) if isinstance(state, dict) else state
    
    print(f"\n[Supervisor] 当前步数: {current_state.step_count}")
    if current_state.step_count >= 10:
        return AgentState(**{
            "messages": current_state.messages,
            "next_agent": "done",
            "step_count": current_state.step_count + 1
        })
    
    messages = convert_messages(current_state.messages)
    control_prompt = '''请严格选择并直接回复以下选项(仅单词):
researcher - 需要进一步研究
calculator - 需要数学计算
writer - 生成最终报告
done - 结束流程'''
    messages.append(SystemMessage(content=control_prompt))
    
    try:
        response = model.invoke(messages)
        raw_output = response.content.strip().lower()
        print(f"[Supervisor决策] 原始输出: {raw_output}")
        
        # 增强解析逻辑
        if "writer" in raw_output:
            next_agent = "writer"
        elif "calculator" in raw_output:
            next_agent = "calculator"
        elif "researcher" in raw_output:
            next_agent = "researcher"
        else:
            next_agent = "done"
        
        return AgentState(**{
            "messages": current_state.messages,
            "next_agent": next_agent,
            "step_count": current_state.step_count + 1
        })
    except Exception as e:
        print(f"[Supervisor错误] {str(e)}")
        return AgentState(**{
            "messages": current_state.messages,
            "next_agent": "done",
            "step_count": current_state.step_count + 1
        })

# ================== 初始化各Agent ==================
researcher_agent = create_agent("研究员", "分析GDP趋势")
calculator_agent = create_agent("计算器", "执行增长率计算")
writer_agent = create_agent("作家", "生成最终报告(完成后必须添加[END]标识)")

# ================== 工作流配置 ==================
builder = StateGraph(AgentState)

builder.add_node("supervisor", supervisor)
builder.add_node("researcher", researcher_agent)
builder.add_node("calculator", calculator_agent)
builder.add_node("writer", writer_agent)

builder.add_conditional_edges(
    "supervisor",
    lambda state: state.next_agent,
    {
        "researcher": "researcher",
        "calculator": "calculator",
        "writer": "writer",
        "done": END
    }
)

for agent in ["researcher", "calculator", "writer"]:
    builder.add_edge(agent, "supervisor")

builder.set_entry_point("supervisor")
workflow = builder.compile()

# ================== 执行测试 ==================
if __name__ == "__main__":
    test_input = AgentState(
        messages=[{"role": "user", "content": "成都市GDP增长率分析"}]
    )
    
    try:
        for step in workflow.stream(test_input):
            node_name = list(step.keys())[0]
            state = step[node_name]
            
            print(f"\n[系统状态] 当前节点: {node_name}")
            print(f"[状态] next_agent: {state.get('next_agent')}")
            print(f"[状态] step_count: {state.get('step_count')}")
            
            if node_name == "__end__":
                print("\n====== 流程正常终止 ======")
                if state.messages:
                    print("最终报告:")
                    print(state.messages[-1].content)
                break
                
    except Exception as e:
        print(f"\n!!! 流程异常终止: {str(e)}")

输出:

root@autodl-container-01074f9e9d-c86e91af:~/autodl-tmp/model/demo_26# python test04.py

sys:1: LangChainDeprecationWarning: As of langchain-core 0.3.0, LangChain uses pydantic v2 internally. The langchain_core.pydantic_v1 module was a compatibility shim for pydantic v1, and should no longer be used. Please update the code to import from Pydantic directly.

For example, replace imports like: from langchain_core.pydantic_v1 import BaseModel

with: from pydantic import BaseModel

or the v1 compatibility namespace if you are working in a code base that has not been fully upgraded to pydantic 2 yet. from pydantic.v1 import BaseModel

Supervisor\] 当前步数: 0 \[Supervisor决策\] 原始输出: 嗯,我现在需要完成一个关于成都市gdp增长率分析的任务。首先,我得理解什么是gdp增长率。gdp是gross domestic product的缩写,代表一个地区在一定时期内生产的最终产品和服务的总值。增长率则是gdp在一段时间内的增长百分比,通常用来衡量经济的增长情况。 接下来,我需要收集成都市近年来的gdp数据。这可能包括过去几年的gdp数值,以及对应的年份。然后,我需要计算这些数据的增长率。计算方法通常是用当前年份的gdp减去前一年的gdp,然后除以前一年的gdp,最后乘以100%得到增长率。例如,如果2020年的g \[系统状态\] 当前节点: supervisor \[状态\] next_agent: done \[状态\] step_count: 1 root@autodl-container-01074f9e9d-c86e91af:\~/autodl-tmp/model/demo_26# python test04.py sys:1: LangChainDeprecationWarning: As of langchain-core 0.3.0, LangChain uses pydantic v2 internally. The langchain_core.pydantic_v1 module was a compatibility shim for pydantic v1, and should no longer be used. Please update the code to import from Pydantic directly. For example, replace imports like: `from langchain_core.pydantic_v1 import BaseModel` with: `from pydantic import BaseModel` or the v1 compatibility namespace if you are working in a code base that has not been fully upgraded to pydantic 2 yet. from pydantic.v1 import BaseModel \[Supervisor\] 当前步数: 0 \[Supervisor决策\] 原始输出: 好的,我现在需要分析用户的问题:"成都市gdp增长率分析"。首先,我要理解这个问题的主要目标是什么。gdp增长率分析通常涉及到收集和分析过去几年的gdp数据,计算增长率,然后进行趋势分析和预测。这可能包括收集经济数据、进行统计计算、建立模型、撰写报告以及最终的结论和建议。 接下来,我需要将这些步骤与用户提供的选项对应起来。用户提供的选项有四个:researcher、calculator、writer、done。我的任务是将每个步骤匹配到正确的选项。 首先,收集和分析gdp数据属于研究阶段,因此对应的是"researcher"。然后,计算增长率需要数学计算,所以是"calculator"。撰写最终报告则是"writer"的 \[系统状态\] 当前节点: supervisor \[状态\] next_agent: writer \[状态\] step_count: 1 \[作家输出\] 好的,我现在需要分析成都市GDP增长率,并撰写一份报告。首先,我应该了解成都市的经济情况,包括GDP的基本数据和趋势。接下来,我需要收集相关的经济指标,比如GDP增长率、GDP组成部分、投资数据和就业情况。然后,分析这些数据,找出经济增长的关键驱动因素,比如产业升级、科技创新或基础设施建设。同时,也要关注潜在的风险,如经济结构的单一性或外部环境的影响。最后,根据分析结果,提出可行的建议,比如优化产业结构、加大科技创新投入或促进消费市场发展。完成后,记得添加\[END\]标识。 ```plaintext 建议下一步:researcher ``` \[系统状态\] 当前节点: writer \[状态\] next_agent: researcher \[状态\] step_count: 2 \[Supervisor\] 当前步数: 2 \[Supervisor决策\] 原始输出: 好的,我现在需要分析用户提供的查询,并生成一个适当的回复。首先,用户给出的查询是:"成都市gdp增长率分析"。然后,用户提供了三个选项,分别是"researcher"、"calculator"、"writer"和"done",并要求选择一个单词作为回复。 接下来,我要理解每个选项的含义和适用场景。第一个选项"researcher"指的是研究人员,通常用于需要深入研究和分析的情况。第二个选项"calculator"则是指进行数学计算的工具或过程,适用于需要数值计算的情况。第三个选项"writer"则是指生成报告或文档,适用于需要撰写或整理结果的情况。最后一个选项"done"则是表示流程已经完成,可以结束。 现在,分析用户的需求。用户提到 \[系统状态\] 当前节点: supervisor \[状态\] next_agent: writer \[状态\] step_count: 3 \[作家输出\] 嗯,用户让我分析成都市GDP增长率,并且给出了一个示例回复。看起来用户可能需要一份详细的分析报告,或者至少是了解成都经济发展的趋势。首先,我需要明确用户的需求是什么。他们可能是一位研究人员,或者是需要经济数据支持的决策者,也可能是学生或分析师。 用户提供的示例回复中,我建议下一步是"researcher",这可能意味着他们希望我继续深入分析,或者可能需要更多的数据或方法。所以,我需要考虑用户可能需要哪些具体的信息或步骤。 接下来,我应该考虑分析GDP增长的几个关键因素,比如经济结构、投资领域、就业情况、科技创新、区域协同发展、政策支持、国际影响力、人口结构变化以及未来 \[系统状态\] 当前节点: writer \[状态\] next_agent: done \[状态\] step_count: 4 \[Supervisor\] 当前步数: 4 \[Supervisor决策\] 原始输出: 好,我现在需要帮助用户分析成都市gdp增长率,并给出下一步的建议。用户之前已经给出了一个示例回复,其中建议下一步是"researcher",这可能意味着他们希望进一步深入研究或需要更多的数据支持。 首先,我应该回顾一下用户的需求。他们可能是一位研究人员,或者是需要经济数据支持的决策者,也可能是学生或分析师。他们需要了解成都市的经济表现,特别是gdp增长的情况,可能还涉及到未来的趋势预测。 接下来,我需要考虑分析gdp增长的关键因素。这可能包括经济结构的变化,比如传统产业升级情况;投资领域,如基础设施、房地产、科技产业等的投资情况;就业结构,看看是否有新的产业或行业的兴起,或者传统行业的衰退 \[系统状态\] 当前节点: supervisor \[状态\] next_agent: researcher \[状态\] step_count: 5 \[研究员输出\] 好,用户之前让我分析成都市的GDP增长率,并且我给出了一个示例回复,建议下一步是"researcher"。现在,我需要进一步分析用户的需求和可能的深层意图。 首先,回顾之前的对话,用户可能是一位研究人员或者经济分析师,他们需要详细的数据和深入的分析来支持他们的研究或决策。用户可能不仅仅想要表面的GDP增长率,而是希望了解背后的原因和未来的趋势,以便做出更全面的分析。 接下来,我需要考虑用户可能需要哪些具体的信息。GDP增长率受多种因素影响,包括经济结构、投资领域、就业情况、科技创新、区域协同发展、政策支持、国际影响力、人口结构变化等。因此,我应该提供一个全面的分析 \[系统状态\] 当前节点: researcher \[状态\] next_agent: done \[状态\] step_count: 6 \[Supervisor\] 当前步数: 6 \[Supervisor决策\] 原始输出: 好,我现在需要帮助用户分析成都市的gdp增长率,并给出一个详细的分析报告。用户之前已经让我分析过,现在我需要进一步细化内容,确保涵盖所有关键因素。 首先,我应该回顾之前的对话,了解用户的需求。用户可能是一位研究人员,或者是需要经济数据支持的决策者,也可能是学生或分析师。他们可能需要详细的数据和深入的分析来支持他们的研究或决策。 接下来,我需要考虑gdp增长率的几个关键因素。经济结构、投资领域、就业情况、科技创新、区域协同发展、政策支持、国际影响力、人口结构变化等都是影响gdp增长的重要因素。因此,我应该在分析中逐一探讨这些方面,提供具体的例子和数据支持。 \[系统状态\] 当前节点: supervisor \[状态\] next_agent: done \[状态\] step_count: 7 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/db568931924543dda269888540729c98.png) #### 换成更大模型试试 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/2ff39488384c4eef9e39045fcbbc7489.png) ```python from typing import Literal, Annotated from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, START, END from langchain_core.messages import HumanMessage, SystemMessage, AIMessage from langchain_core.pydantic_v1 import BaseModel, Field import re import json # ================== 强化状态类 ================== class AgentState(BaseModel): messages: Annotated[list, Field(default_factory=list)] next_agent: Literal["researcher", "calculator", "writer", "done"] = "researcher" step_count: int = 0 is_finalized: bool = False search_data: dict = Field(default_factory=dict) # 新增搜索数据存储 @classmethod def from_raw(cls, raw): """统一状态转换方法""" if isinstance(raw, cls): return raw if isinstance(raw, dict): return cls( messages=raw.get('messages', []), next_agent=raw.get('next_agent', 'researcher'), step_count=raw.get('step_count', 0), is_finalized=raw.get('is_finalized', False), search_data=raw.get('search_data', {}) ) raise ValueError(f"无效状态类型: {type(raw)}") def to_safe_dict(self): """安全转换为字典""" return { "messages": [msg.dict() for msg in self.messages], "next_agent": self.next_agent, "step_count": self.step_count, "is_finalized": self.is_finalized, "search_data": self.search_data } # ================== 模型配置 ================== # # https://bigmodel.cn/usercenter/proj-mgmt/apikeys ,在这里申请 api key model = ChatOpenAI( openai_api_base="https://open.bigmodel.cn/api/paas/v4", model_name="glm-4", openai_api_key="xxxxxxxxxxxxxxxxxxxxxxxx", max_tokens=400, temperature=0.2 ) # ================== 核心节点定义 ================== def create_researcher(): def agent(raw_state): state = AgentState.from_raw(raw_state) if state.is_finalized: return state try: # 获取搜索查询 search_query = next( msg.content for msg in reversed(state.messages) if isinstance(msg, HumanMessage) ) messages = convert_messages(state.messages) response = model.invoke(messages) print(f"\n[研究员输出] {response.content[:300]}...") # 解析下一步建议 next_agent = "supervisor" if match := re.search(r'建议下一步\s*[::]\s*(\w+)', response.content): suggestion = match.group(1).lower() if suggestion in {"researcher", "calculator", "writer", "done"}: next_agent = suggestion return AgentState( messages=state.messages + [response], next_agent=next_agent, step_count=state.step_count + 1, is_finalized=False ) except Exception as e: print(f"[研究员错误] {str(e)}") return AgentState( messages=state.messages, next_agent="supervisor", step_count=state.step_count + 1, is_finalized=True ) return agent def create_calculator(): def agent(raw_state): state = AgentState.from_raw(raw_state) if state.is_finalized: return state try: messages = convert_messages(state.messages) messages.append(SystemMessage( content="请执行GDP增长率计算,需包含完整计算过程和公式,最后用'建议下一步:选项'格式结尾" )) response = model.invoke(messages) print(f"\n[计算器输出] {response.content[:200]}...") return AgentState( messages=state.messages + [response], next_agent=re.search(r'建议下一步\s*[::]\s*(\w+)', response.content).group(1).lower(), step_count=state.step_count + 1, is_finalized=False ) except Exception as e: print(f"[计算器错误] {str(e)}") return AgentState( messages=state.messages, next_agent="supervisor", step_count=state.step_count + 1, is_finalized=True ) return agent def create_writer(): def agent(raw_state): state = AgentState.from_raw(raw_state) if state.is_finalized: return state try: messages = convert_messages(state.messages) messages.append(SystemMessage( content="请生成包含[END]标识的最终报告,要求:\n" "1. 包含数据来源说明\n" "2. 使用Markdown格式\n" "3. 最后必须添加[END]标识\n" "4. 必须引用搜索数据中的具体内容" )) response = model.invoke(messages) if "[END]" not in response.content: response.content += "\n[END]" return AgentState( messages=state.messages + [response], next_agent="done", step_count=state.step_count + 1, is_finalized=True ) except Exception as e: print(f"[作家错误] {str(e)}") return AgentState( messages=state.messages, next_agent="supervisor", step_count=state.step_count + 1, is_finalized=True ) return agent # ================== 辅助函数 ================== def convert_messages(raw_messages): converted = [] for msg in raw_messages: if isinstance(msg, dict): try: msg_type = { "user": HumanMessage, "system": SystemMessage, "assistant": AIMessage }[msg["role"]] converted.append(msg_type(content=msg.get("content", ""))) except KeyError: converted.append(AIMessage(content=str(msg))) elif hasattr(msg, 'content'): converted.append(msg) else: converted.append(AIMessage(content=str(msg))) return converted # ================== Supervisor节点 ================== def supervisor(raw_state): state = AgentState.from_raw(raw_state) if state.is_finalized: return state try: print(f"\n[Supervisor] 当前步数: {state.step_count}") # 智能流程控制 if state.step_count >= 8: print("[强制终止] 达到最大步数限制") return AgentState( messages=state.messages, next_agent="writer", step_count=state.step_count + 1, is_finalized=False ) messages = convert_messages(state.messages) control_prompt = '''请严格选择下一步: 1. 需要补充数据 → researcher 2. 需要计算 → calculator 3. 生成报告 → writer 4. 完成 → done 注意:必须优先选择需要完成的最关键步骤''' response = model.invoke(messages + [SystemMessage(content=control_prompt)]) decision = re.search(r'\b(researcher|calculator|writer|done)\b', response.content.lower()) new_agent = decision.group(1).lower() if decision else "done" # 强制writer前必须存在搜索数据 if new_agent == "writer" and not state.search_data: new_agent = "researcher" # 强制done前必须生成报告 if new_agent == "done" and not any("[END]" in msg.content for msg in messages): new_agent = "writer" return AgentState( messages=state.messages, next_agent=new_agent, step_count=state.step_count + 1, is_finalized=False ) except Exception as e: print(f"[Supervisor错误] {str(e)}") return AgentState( messages=state.messages, next_agent="writer", step_count=state.step_count + 1, is_finalized=False ) # ================== 工作流配置 ================== builder = StateGraph(AgentState) builder.add_node("supervisor", supervisor) builder.add_node("researcher", create_researcher()) builder.add_node("calculator", create_calculator()) builder.add_node("writer", create_writer()) # 定义条件边 def route_supervisor(state): return state.next_agent builder.add_conditional_edges( "supervisor", route_supervisor, { "researcher": "researcher", "calculator": "calculator", "writer": "writer", "done": END } ) # 添加节点到supervisor for agent in ["researcher", "calculator", "writer"]: builder.add_edge(agent, "supervisor") builder.set_entry_point("supervisor") workflow = builder.compile() # ================== 执行入口 ================== if __name__ == "__main__": try: initial_state = AgentState( messages=[HumanMessage(content="成都市2025年GDP增长率分析")] ) final_report = None for step in workflow.stream(initial_state): node_name, raw_state = step.popitem() current_state = AgentState.from_raw(raw_state) print(f"\n[系统状态] 当前节点: {node_name}") print(f"下一步: {current_state.next_agent}") print(f"步数: {current_state.step_count}") print(f"完成状态: {current_state.is_finalized}") # 捕获最终报告 if node_name == "writer": final_report = next( (msg.content for msg in reversed(current_state.messages) if "[END]" in msg.content), None ) if current_state.is_finalized or node_name == "__end__": print("\n====== 流程完成 =====") if final_report: print(final_report) else: print("警告:未检测到完整报告") break except Exception as e: print(f"\n!!! 流程异常终止: {str(e)}") if 'current_state' in locals(): print("最后状态:", current_state.to_safe_dict()) ``` ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/dbbd65e9ba674faebbdff18bebf711e9.png)

相关推荐
程序员爱钓鱼17 分钟前
Python编程实战 · 基础入门篇 | 数据类型简介:数字、字符串、布尔值
后端·python
Juchecar24 分钟前
如何理解“AI token 大宗商品化”?
人工智能
文火冰糖的硅基工坊26 分钟前
[人工智能-大模型-29]:大模型应用层技术栈 - 第二层:Prompt 编排层(Prompt Orchestration)
人工智能·大模型·prompt·copilot
大模型真好玩28 分钟前
LangGraph实战项目:从零手搓DeepResearch(三)——LangGraph多智能体搭建与部署
人工智能·langchain·mcp
飞哥数智坊31 分钟前
DeepSeek-OCR:用“看图”代替“读文”,一种更像人类的上下文压缩方式
人工智能·deepseek
Python图像识别44 分钟前
73_基于深度学习的水面漂浮垃圾检测系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)
python·深度学习·yolo
L.fountain1 小时前
强化学习2.2 MDP实践——Frozen lake
人工智能·强化学习
JJJJ_iii1 小时前
【机器学习06】神经网络的实现、训练与向量化
人工智能·笔记·深度学习·神经网络·学习·机器学习·线性回归
倔强的石头1061 小时前
AI协作天花板!CherryStudio让多模型协同像搭积木
人工智能·cpolar
IT_陈寒1 小时前
Vite 3.0 性能优化实战:5个技巧让你的构建速度提升200% 🚀
前端·人工智能·后端