19-大模型智能体开发:LangChain&LangGraph基础指南

系列文章导航:AI系列文章导航目录-持续更新中

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,麻烦大家帮点贡献下点击量支持一下,您的支持是我更新的动力。

第19课:Agent开发框架——LangChain & LangGraph 完全指南(二)

一、主流Agent开发框架全景

1.1 为什么需要框架

一句话理解:前面几节课你学了怎么"手搓"一个Agent(自己写循环、管理消息、处理工具调用)。框架就是把这些重复工作封装好,让你专注于业务逻辑。就像你不需要自己写HTTP服务器,用Gin/Flask框架就行。

手搓Agent(第12-14课的方式):
  ✅ 完全可控,理解每一步
  ❌ 重复造轮子(消息管理、工具调用循环、错误处理等)
  ❌ 生产级功能缺失(流式输出、并发、持久化等)

框架解决的问题:
  1. 标准化的Agent循环(ReAct等)    ← 不用自己写while循环
  2. 工具注册和调用管理              ← 不用自己写tool_map
  3. 消息历史和上下文管理            ← 不用自己管理messages列表
  4. 流式输出                        ← 用户体验必备
  5. 持久化和恢复                    ← Agent执行到一半崩了可以恢复
  6. 可观测性(日志、追踪)          ← 知道Agent在做什么
  7. 多Agent编排                     ← 多个Agent协作
  8. 错误处理和重试                  ← 生产环境必备
  9. 人机协作(Human-in-the-loop)   ← 关键决策需要人确认

类比:
  手搓Agent = 用原生net/http写Web服务
  用框架 = 用Gin/Flask/Spring Boot写Web服务
  → 能做的事一样,但效率差很多

1.2 主流框架一览

┌──────────────┬────────────┬──────────────────┬───────────┬─────────────────┐
│ 框架         │ 语言       │ 核心优势         │ 多Agent   │ 适合场景        │
├──────────────┼────────────┼──────────────────┼───────────┼─────────────────┤
│ LangChain    │ Python/TS  │ 生态最大,标准库 │ ✅        │ 通用Agent开发   │
│ LangGraph    │ Python/TS  │ 状态图,精细控制 │ ✅        │ 复杂流程Agent   │
│ LlamaIndex   │ Python/TS  │ RAG专精          │ 部分      │ 知识库应用      │
│ CrewAI       │ Python     │ 角色驱动,易用   │ ✅核心    │ 多Agent团队协作 │
│ AutoGen      │ Python     │ 对话式多Agent    │ ✅核心    │ 研究/探索       │
│ PydanticAI   │ Python     │ 类型安全         │ 部分      │ 结构化输出      │
│ OpenAI SDK   │ Python/TS  │ 轻量全能,100+模型│ ✅       │ 通用Agent       │
│ Google ADK   │ Python     │ A2A原生,云原生  │ ✅        │ 跨Agent协作     │
│ Anthropic SDK│ Python/TS  │ Claude原生       │ 部分      │ Claude生态      │
│ Dify         │ 低代码     │ 可视化编排       │ ✅        │ 快速原型        │
│ Coze         │ 低代码     │ 字节出品         │ ✅        │ 快速上线        │
└──────────────┴────────────┴──────────────────┴───────────┴─────────────────┘

1.3 各框架简述(含诞生背景)

LangChain + LangGraph(本文重点)

诞生背景:
  2022年10月,Harrison Chase 的周末项目。
  痛点:每个 AI 开发者都在重复写消息管理、工具调用循环、提示词拼接。
  愿景:做 AI 开发的"标准库",统一接口,减少重复代码。

定位: Agent开发的"标准库" + "状态图引擎"
关系: LangGraph是LangChain生态的核心组件,专门处理复杂Agent流程
优势: 生态最大、社区最活跃、集成最多、文档最全
劣势: 抽象层多,学习曲线较陡
GitHub: langchain-ai/langchain (139k+ stars), langchain-ai/langgraph (34k+ stars)

LlamaIndex

诞生背景:
  2022年11月,Jerry Liu 创建。
  痛点:LangChain 的 RAG 能力太通用,企业知识库场景需要更专精的数据摄入和索引策略。
  愿景:做"数据框架",让 LLM 能高效访问私有数据。

定位: RAG(检索增强生成)专精框架
优势: 数据摄入能力强,支持100+数据源,索引策略丰富
劣势: Agent能力不如LangChain全面
适合: 知识库问答、文档检索、企业知识管理

CrewAI

诞生背景:
  2023年10月,João Moura 创建。
  痛点:LangGraph 的图编排对非技术人员太复杂,多 Agent 协作应该像"组建团队"一样直觉化。
  愿景:让多 Agent 协作像写剧本一样简单——给每个 Agent 一个角色、目标、背景故事。

定位: 角色驱动的多Agent协作框架
核心思想: 每个Agent有角色(Role)、目标(Goal)、背景(Backstory)
优势: 上手极快,多Agent协作开箱即用
劣势: 流程控制不够精细,黑盒较多
适合: 内容创作团队、研究团队、数据分析团队

AutoGen (Microsoft)

诞生背景:
  2023年9月,微软研究院发布。
  痛点:复杂任务需要多个 AI 角色相互讨论、质疑、协作,就像人类团队开会一样。
  愿景:让 Agent 之间通过"对话"协作,而不是预定义的流程。

定位: 对话式多Agent框架
核心思想: Agent之间通过对话协作,像人类团队开会一样
优势: 灵活的对话模式,支持人类参与
劣势: 对话轮次不可控,token消耗大
适合: 研究探索、代码生成、复杂推理

PydanticAI

诞生背景:
  2024年11月,Pydantic 团队发布。
  痛点:现有框架的输出都是字符串,生产系统需要类型安全的结构化输出,且 IDE 无法提供代码补全。
  愿景:把 Python 类型系统引入 Agent 开发,让 Agent 的输入输出像普通 Python 函数一样类型安全。

定位: 类型安全的Agent框架(Pydantic团队出品)
核心思想: 用Pydantic模型定义Agent的输入输出,编译时类型检查
优势: 类型安全、结构化输出、IDE友好
劣势: 生态较小,集成不如LangChain多
适合: 需要严格类型约束的生产系统

OpenAI Agents SDK

定位: 轻量但全能的多Agent框架(OpenAI官方出品,⭐ 26.9K)
核心思想: Agent + Handoff + Tools,极简API但功能完整
优势: 支持100+模型(不再仅限OpenAI)、Sandbox Agents、Sessions、Realtime Agents
劣势: 不支持复杂图编排(不如LangGraph灵活)
适合: 需要快速构建多Agent系统,且不想引入重框架
安装: pip install openai-agents

Google ADK (Agent Development Kit)

定位: Google官方Agent开发工具包(⭐ 20K),A2A协议原生支持
核心思想: 代码优先(code-first),灵活控制,原生支持A2A跨Agent协作
优势: A2A协议原生集成、与Google Cloud深度绑定、多模型支持
劣势: 生态较新,社区资源不如LangChain丰富
适合: 需要A2A跨组织Agent协作、Google Cloud用户
安装: pip install google-adk

Dify / Coze

定位: 低代码/无代码Agent平台
优势: 可视化拖拽,非技术人员也能用
劣势: 定制能力有限,复杂逻辑难实现
适合: 快速验证、内部工具、标准化场景(客服、FAQ)

1.4 框架选择决策树

你的需求是什么?
│
├── 快速验证想法(1天内上线)
│   → Dify/Coze 低代码平台
│
├── 标准RAG应用(知识库问答)
│   → LlamaIndex(专精)或 LangChain(通用)
│
├── 通用Agent应用(工具调用、对话等)
│   → LangChain + LangGraph ⭐ 首选
│
├── 多Agent协作(团队模式)
│   ├── 简单协作 → CrewAI(快速上手)
│   ├── 轻量多Agent → OpenAI Agents SDK(Handoff模式)
│   └── 复杂协作 → LangGraph(精细控制)
│
├── 跨组织/跨框架Agent协作
│   → Google ADK(A2A协议原生支持)
│
├── 需要精细控制流程
│   → LangGraph自定义状态图
│
├── 类型安全 + 结构化输出
│   → PydanticAI
│
└── 生产级系统
    → LangGraph + LangSmith(可观测性 + 部署)
    → 持久化 + 错误处理 + 灰度发布

二、LangChain基础——从零开始

从这里开始,我们深入LangChain + LangGraph。假设你是零基础,我会把每个概念都讲清楚。

2.1 LangChain是什么

LangChain = AI Agent开发的标准库

类比:
  LangChain之于Agent开发 ≈ Spring之于Java后端 ≈ React之于前端
  → 不是唯一选择,但是最主流、生态最大的选择

一句话定义:
  LangChain是一个框架,帮你把LLM(大语言模型)和外部世界(工具、数据、API)连接起来,
  构建能"思考+行动"的AI应用。

发展历史:
  2022.10: Harrison Chase 的周末项目,最初只是把 LLM 调用和工具调用串起来的小工具
  2022.12: ChatGPT 发布,AI 开发者数量爆炸,LangChain 成为"标准配置"
  2023.01: GitHub stars 突破 10,000,成为增长最快的开源项目之一
  2023.02: 融资 1000 万美元(种子轮),Harrison Chase 全职做 LangChain
  2023.04: 融资 2500 万美元(A 轮)
  2023.06: 推出 LangSmith(可观测性平台)← 从工具转向平台的关键一步
  2023.08: 推出 LangGraph(状态图引擎)← 解决 Chain 无法表达复杂流程的问题
  2024.01: LangGraph 逐渐成为核心,传统 Chain/AgentExecutor 开始被弃用
  2024.06: 推出 LangGraph Platform(部署平台)← 从平台转向基础设施
  2025: 推出 Deep Agents(基于 LangGraph 的高级 Agent 包)
  2025-2026: 被 Klarna、Replit、Elastic 等企业大规模采用

  关键转折点:
  ① 2023.08 LangGraph 发布
     → 标志着 LangChain 从"链式思维"转向"图式思维"
     → 这是整个 Agent 开发范式的转变,不只是 LangChain 的内部升级
  
  ② 2024.06 LangGraph Platform 发布
     → 标志着 LangChain 从"开发框架"转向"Agent 基础设施"
     → 商业模式从卖工具变成卖服务
  
现状(2026):
  - LangChain: 提供基础抽象(Model、Message、Tool、Prompt等)
  - LangGraph: 低层编排框架,构建有状态的长时运行Agent(⭐ 34k+)
  - Deep Agents: 基于LangGraph的高级Agent包(支持规划、子Agent、文件系统)
  - LangSmith: 可观测性 + 部署平台(追踪、评估、监控、一键部署)
  - 四者配合 = 完整的Agent开发与运维平台
  - 已有JS/TS版本(LangGraph.js),支持全栈开发

2.2 安装和环境配置

# 基础安装
pip install langchain langchain-core langchain-openai langgraph

# 如果你用其他模型提供商
pip install langchain-anthropic    # Claude
pip install langchain-google-genai # Gemini
pip install langchain-community    # 社区集成(各种工具、向量库等)

# 可选:LangSmith(追踪和调试)
pip install langsmith

# 完整安装(推荐学习时用)
pip install langchain langchain-core langchain-openai langchain-community langgraph langsmith
# 环境变量配置
import os

# OpenAI(必须)
os.environ["OPENAI_API_KEY"] = "sk-..."

# LangSmith(可选,但强烈推荐,能看到Agent每一步在做什么)
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "ls-..."
os.environ["LANGCHAIN_PROJECT"] = "my-agent-project"

# 如果用Anthropic
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..."

2.3 LangChain包结构

langchain生态的包结构(理解这个很重要,不然会被各种import搞晕):

langchain-core          ← 核心抽象,所有其他包依赖它
  ├── messages          ← 消息类型(HumanMessage, AIMessage等)
  ├── prompts           ← 提示词模板
  ├── tools             ← 工具定义
  ├── output_parsers    ← 输出解析器
  ├── runnables         ← LCEL(LangChain表达式语言)
  └── language_models   ← LLM抽象接口

langchain               ← 高级抽象和链
  ├── agents            ← Agent相关(已逐渐被LangGraph替代)
  ├── chains            ← 链(已逐渐被LCEL替代)
  └── ...

langchain-openai        ← OpenAI集成
  ├── ChatOpenAI        ← GPT模型
  └── OpenAIEmbeddings  ← 嵌入模型

langchain-anthropic     ← Anthropic集成
  └── ChatAnthropic     ← Claude模型

langchain-community     ← 社区集成(100+)
  ├── vectorstores      ← 向量数据库(Chroma, Pinecone, FAISS等)
  ├── document_loaders  ← 文档加载器(PDF, Web, DB等)
  └── tools             ← 各种工具集成

langgraph               ← 状态图Agent引擎 ⭐ 最重要
  ├── graph             ← 图定义
  ├── prebuilt          ← 预构建组件
  ├── checkpoint        ← 持久化
  └── ...

记住这个原则:
  - 基础类型 → 从 langchain_core 导入
  - 模型 → 从 langchain_openai / langchain_anthropic 导入
  - Agent逻辑 → 从 langgraph 导入
  - 社区工具 → 从 langchain_community 导入

2.4 核心概念一:Chat Model(聊天模型)

from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

# ============ 创建模型实例 ============

# OpenAI
llm = ChatOpenAI(
    model="gpt-4o-mini",      # 模型名称
    temperature=0,             # 0=确定性输出,1=随机性最大
    max_tokens=1000,           # 最大输出token数
    timeout=30,                # 超时时间(秒)
    max_retries=2,             # 失败重试次数
)

# Anthropic
llm_claude = ChatAnthropic(
    model="claude-sonnet-4-20250514",
    temperature=0,
    max_tokens=1000,
)

# ============ 基本调用 ============

# 方式1: 直接传字符串(最简单)
response = llm.invoke("你好,请介绍一下自己")
print(response.content)       # AI的回复文本
print(type(response))         # <class 'langchain_core.messages.ai.AIMessage'>

# 方式2: 传消息列表(更精确控制)
messages = [
    SystemMessage(content="你是一个Python专家,回答要简洁"),
    HumanMessage(content="什么是装饰器?"),
]
response = llm.invoke(messages)
print(response.content)

# 方式3: 传元组列表(简写方式,推荐)
messages = [
    ("system", "你是一个Python专家"),
    ("human", "什么是装饰器?"),
]
response = llm.invoke(messages)
print(response.content)

# ============ 流式输出 ============

# stream() 返回一个生成器,逐token输出
for chunk in llm.stream("给我讲一个笑话"):
    print(chunk.content, end="", flush=True)
print()  # 换行

# ============ 批量调用 ============

# batch() 并发处理多个请求
responses = llm.batch([
    "1+1等于几?",
    "Python的创始人是谁?",
    "地球到月球的距离?",
])
for r in responses:
    print(r.content)

# ============ 异步调用 ============

import asyncio

async def main():
    response = await llm.ainvoke("异步调用示例")
    print(response.content)
    
    # 异步流式
    async for chunk in llm.astream("异步流式示例"):
        print(chunk.content, end="")

asyncio.run(main())

2.5 核心概念二:Messages(消息系统)

from langchain_core.messages import (
    HumanMessage,      # 用户消息
    AIMessage,         # AI回复
    SystemMessage,     # 系统提示
    ToolMessage,       # 工具返回结果
    AIMessageChunk,    # 流式AI消息片段
)

# ============ 消息类型详解 ============

# 1. SystemMessage: 设定AI的角色和行为规则
system = SystemMessage(content="你是一个严格的代码审查员,只关注代码质量问题")

# 2. HumanMessage: 用户输入
human = HumanMessage(content="帮我看看这段代码有什么问题")

# 3. AIMessage: AI的回复(可能包含工具调用)
ai = AIMessage(content="这段代码有以下问题...")

# 带工具调用的AIMessage
ai_with_tools = AIMessage(
    content="",  # 当AI决定调用工具时,content通常为空
    tool_calls=[
        {
            "id": "call_abc123",
            "name": "search_code",
            "args": {"query": "memory leak patterns"}
        }
    ]
)

# 4. ToolMessage: 工具执行结果返回给AI
tool_result = ToolMessage(
    content="找到3个潜在的内存泄漏模式...",
    tool_call_id="call_abc123"  # 必须对应AIMessage中的tool_call id
)

# ============ 完整对话流程示例 ============

conversation = [
    SystemMessage(content="你是一个天气助手"),
    HumanMessage(content="北京天气怎么样?"),
    AIMessage(content="", tool_calls=[{"id": "call_1", "name": "get_weather", "args": {"city": "北京"}}]),
    ToolMessage(content="北京: 晴,28°C,湿度45%", tool_call_id="call_1"),
    AIMessage(content="北京现在天气晴朗,气温28°C,湿度45%,很适合出门。"),
    HumanMessage(content="那上海呢?"),
    # ... 对话继续
]

# ============ 消息的元数据 ============

msg = HumanMessage(
    content="你好",
    name="user_123",           # 发送者名称
    id="msg_001",              # 消息ID
    additional_kwargs={},      # 额外参数
)

# AIMessage特有属性
ai_msg = llm.invoke("你好")
print(ai_msg.content)          # 文本内容
print(ai_msg.tool_calls)       # 工具调用列表(如果有)
print(ai_msg.usage_metadata)   # token使用情况
print(ai_msg.response_metadata)# 模型返回的元数据

2.6 核心概念三:Prompt Templates(提示词模板)

from langchain_core.prompts import (
    ChatPromptTemplate,
    MessagesPlaceholder,
    PromptTemplate,
)

# ============ 基础模板 ============

# 简单字符串模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个{role},请用{style}的风格回答问题"),
    ("human", "{question}"),
])

# 填充变量,生成消息列表
messages = prompt.invoke({
    "role": "Python教师",
    "style": "简洁易懂",
    "question": "什么是列表推导式?"
})
print(messages)  # 得到填充后的消息列表

# 直接和模型串联使用
chain = prompt | llm  # 这就是LCEL(后面详细讲)
response = chain.invoke({
    "role": "Python教师",
    "style": "简洁易懂",
    "question": "什么是列表推导式?"
})
print(response.content)

# ============ 带历史消息的模板 ============

prompt_with_history = ChatPromptTemplate.from_messages([
    ("system", "你是一个智能助手"),
    MessagesPlaceholder(variable_name="chat_history"),  # 历史消息占位符
    ("human", "{input}"),
])

# 使用时传入历史消息
from langchain_core.messages import HumanMessage, AIMessage

history = [
    HumanMessage(content="我叫张三"),
    AIMessage(content="你好张三!有什么可以帮你的?"),
]

messages = prompt_with_history.invoke({
    "chat_history": history,
    "input": "我叫什么名字?"
})

# ============ 多轮对话模板(Agent常用) ============

agent_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个智能客服助手。
    
规则:
1. 礼貌友好
2. 如果不确定,使用工具查询
3. 不要编造信息"""),
    MessagesPlaceholder(variable_name="messages"),  # 整个对话历史
])

2.7 核心概念四:Tools(工具)

from langchain_core.tools import tool, StructuredTool, Tool
from pydantic import BaseModel, Field
from typing import Optional

# ============ 方式1: @tool装饰器(最常用) ============

@tool
def get_weather(city: str) -> str:
    """获取指定城市的当前天气信息。
    
    Args:
        city: 城市名称,如"北京"、"上海"
    """
    # 实际项目中这里调用天气API
    weather_data = {"北京": "晴 28°C", "上海": "多云 25°C"}
    return weather_data.get(city, f"未找到{city}的天气信息")

@tool
def search_database(query: str, limit: int = 5) -> str:
    """在数据库中搜索相关信息。
    
    Args:
        query: 搜索关键词
        limit: 返回结果数量,默认5条
    """
    return f"搜索'{query}'的前{limit}条结果: ..."

# 查看工具信息
print(get_weather.name)          # "get_weather"
print(get_weather.description)   # "获取指定城市的当前天气信息..."
print(get_weather.args_schema.schema())  # JSON Schema

# 直接调用工具(测试用)
result = get_weather.invoke({"city": "北京"})
print(result)  # "晴 28°C"

# ============ 方式2: Pydantic模型定义参数(更精确) ============

class SearchInput(BaseModel):
    """搜索工具的输入参数"""
    query: str = Field(description="搜索关键词")
    category: str = Field(description="搜索类别", default="all")
    max_results: int = Field(description="最大结果数", default=10, ge=1, le=100)

@tool(args_schema=SearchInput)
def advanced_search(query: str, category: str = "all", max_results: int = 10) -> str:
    """高级搜索工具,支持分类和数量限制"""
    return f"在{category}类别中搜索'{query}',返回{max_results}条结果"

# ============ 方式3: StructuredTool(动态创建) ============

def my_function(x: int, y: int) -> int:
    """计算两个数的和"""
    return x + y

calculator = StructuredTool.from_function(
    func=my_function,
    name="calculator",
    description="计算两个整数的和",
)

# ============ 工具绑定到模型 ============

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# bind_tools: 告诉模型有哪些工具可用
llm_with_tools = llm.bind_tools([get_weather, search_database, advanced_search])

# 模型会根据用户输入决定是否调用工具
response = llm_with_tools.invoke("北京今天天气怎么样?")
print(response.tool_calls)
# [{'id': 'call_xxx', 'name': 'get_weather', 'args': {'city': '北京'}}]

response2 = llm_with_tools.invoke("你好")
print(response2.tool_calls)  # []  ← 不需要工具时返回空列表
print(response2.content)     # "你好!有什么可以帮你的?"

# ============ 工具的错误处理 ============

@tool
def divide(a: float, b: float) -> str:
    """除法计算
    
    Args:
        a: 被除数
        b: 除数
    """
    if b == 0:
        return "错误: 除数不能为0"
    return str(a / b)

# ============ 异步工具 ============

import httpx

@tool
async def fetch_url(url: str) -> str:
    """获取URL的内容
    
    Args:
        url: 要获取的URL地址
    """
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        return response.text[:500]  # 只返回前500字符

2.8 核心概念五:LCEL(LangChain表达式语言)

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_openai import ChatOpenAI

# ============ LCEL是什么 ============
"""
LCEL = LangChain Expression Language
用 | (管道符) 把多个组件串联起来,类似Unix管道

核心思想:
  input → 组件A → 组件B → 组件C → output
  等价于: chain = A | B | C
  
每个组件都实现了Runnable接口:
  - invoke(input)      同步调用
  - stream(input)      流式调用
  - batch(inputs)      批量调用
  - ainvoke(input)     异步调用
  - astream(input)     异步流式
"""

# ============ 基础链 ============

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# 最简单的链: 提示词 → 模型 → 输出解析
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个翻译专家,将中文翻译成{language}"),
    ("human", "{text}"),
])

chain = prompt | llm | StrOutputParser()

# 调用
result = chain.invoke({"language": "英文", "text": "今天天气真好"})
print(result)  # "The weather is really nice today"

# 流式调用
for chunk in chain.stream({"language": "日文", "text": "你好世界"}):
    print(chunk, end="")

# ============ 带分支的链 ============

from langchain_core.runnables import RunnablePassthrough, RunnableLambda, RunnableParallel

# RunnablePassthrough: 透传输入
chain = RunnablePassthrough() | llm
# 等价于直接调用llm

# RunnableLambda: 自定义函数
def format_output(text: str) -> str:
    return f"【翻译结果】{text}"

chain = prompt | llm | StrOutputParser() | RunnableLambda(format_output)

# RunnableParallel: 并行执行多个链
parallel_chain = RunnableParallel(
    translation=prompt | llm | StrOutputParser(),
    original=RunnablePassthrough(),
)
result = parallel_chain.invoke({"language": "英文", "text": "你好", "original_text": "你好"})
# result = {"translation": "Hello", "original": {"language": "英文", "text": "你好"}}

# ============ 条件路由 ============

from langchain_core.runnables import RunnableBranch

# 根据输入内容选择不同的处理链
branch_chain = RunnableBranch(
    # (条件函数, 对应的链)
    (lambda x: "翻译" in x["task"], translation_chain),
    (lambda x: "总结" in x["task"], summary_chain),
    default_chain,  # 默认链
)

# ============ 实际例子: 带工具的对话链 ============

from langchain_core.tools import tool

@tool
def get_stock_price(symbol: str) -> str:
    """获取股票价格"""
    prices = {"AAPL": 150.0, "GOOGL": 140.0, "MSFT": 380.0}
    price = prices.get(symbol.upper())
    if price:
        return f"{symbol}: ${price}"
    return f"未找到{symbol}的股价"

llm_with_tools = ChatOpenAI(model="gpt-4o-mini").bind_tools([get_stock_price])

# 这个链会让模型决定是否调用工具
chain = prompt | llm_with_tools
response = chain.invoke({"input": "苹果公司股价多少?"})

2.9 核心概念六:Output Parsers(输出解析器)

from langchain_core.output_parsers import (
    StrOutputParser,
    JsonOutputParser,
    PydanticOutputParser,
)
from pydantic import BaseModel, Field
from typing import List

# ============ StrOutputParser: 提取纯文本 ============

chain = prompt | llm | StrOutputParser()
result = chain.invoke(...)  # 返回str而不是AIMessage

# ============ JsonOutputParser: 解析JSON ============

json_parser = JsonOutputParser()

prompt = ChatPromptTemplate.from_messages([
    ("system", "请以JSON格式回答,包含name和age字段"),
    ("human", "{question}"),
])

chain = prompt | llm | json_parser
result = chain.invoke({"question": "介绍一下Python之父"})
# result = {"name": "Guido van Rossum", "age": 69}

# ============ PydanticOutputParser: 结构化输出(推荐) ============

class MovieReview(BaseModel):
    """电影评价的结构化输出"""
    title: str = Field(description="电影名称")
    rating: float = Field(description="评分,1-10分")
    pros: List[str] = Field(description="优点列表")
    cons: List[str] = Field(description="缺点列表")
    summary: str = Field(description="一句话总结")

parser = PydanticOutputParser(pydantic_object=MovieReview)

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个电影评论家。{format_instructions}"),
    ("human", "评价电影: {movie}"),
])

chain = prompt | llm | parser
result = chain.invoke({
    "movie": "星际穿越",
    "format_instructions": parser.get_format_instructions()
})
# result是MovieReview实例
print(result.title)   # "星际穿越"
print(result.rating)  # 9.2
print(result.pros)    # ["视觉效果震撼", "科学基础扎实", ...]

# ============ with_structured_output: 更简洁的方式(推荐) ============

# 直接让模型输出结构化数据,不需要额外的parser
structured_llm = llm.with_structured_output(MovieReview)
result = structured_llm.invoke("评价电影星际穿越")
# result直接就是MovieReview实例

三、LangGraph核心——状态图Agent引擎

LangGraph是LangChain生态中最重要的组件。它用"状态图"来定义Agent的行为流程,比传统的while循环灵活得多。从这里开始是重中之重。

3.0 LangGraph 的诞生故事(先理解,再学技术)

在看代码之前,先理解 LangGraph 为什么会存在。这会让你对后面的每一个设计决策都有"哦,原来如此"的感觉。

2023 年中,LangChain 团队收到了大量开发者的反馈:

"我在构建一个代码审查 Agent,它需要:
  1. 分析代码
  2. 如果发现安全问题,走安全审查流程
  3. 如果发现性能问题,走性能优化流程
  4. 两个流程都完成后,汇总报告
  5. 如果报告质量不够,重新生成
  
  但 AgentExecutor 只支持线性的 ReAct 循环,
  我没法表达这种分支+并行+循环的复杂流程!"

这个反馈代表了一类真实的生产需求:Agent 的执行流程,本质上是一个有向图,而不是一条直线。

LangChain 团队的工程师们开始思考:有没有一种抽象,能优雅地表达任意复杂的 Agent 流程?

答案来自计算机科学的一个经典概念:有向图(Directed Graph)

灵感来源:工作流引擎

工业界早就有"工作流引擎"的概念(Airflow、Prefect、Temporal):
  - 把复杂的数据处理流程表达为有向无环图(DAG)
  - 每个节点是一个处理步骤
  - 边定义了步骤之间的依赖关系

LangGraph 的核心洞察:
  Agent 的执行流程 ≈ 工作流
  但 Agent 需要"有环图"(因为 Agent 需要循环推理)
  
  所以 LangGraph = 支持循环的工作流引擎 + LLM 集成

为什么叫"状态图"而不是普通的"图"?

这是 LangGraph 最关键的设计决策:

普通工作流引擎的问题:
  节点之间通过"参数传递"通信
  → 节点 A 的输出 = 节点 B 的输入
  → 当流程复杂时,参数传递变得混乱

LangGraph 的解决方案:
  引入"共享状态"(Shared State)
  → 所有节点共享同一个 State 对象
  → 每个节点读取 State,处理后更新 State
  → 类似 Redux 的状态管理模式
  
  好处:
  ① 任何节点都能访问任何历史信息
  ② 状态可以持久化(保存到数据库,重启后恢复)
  ③ 状态变化可以追踪(知道每一步发生了什么)

这个设计决策,直接影响了 LangGraph 的所有 API 设计。 理解了这一点,后面的 State、Node、Edge 就都顺理成章了。

3.1 为什么需要LangGraph

传统Agent的问题:
  while True:
      response = llm(messages)
      if response.tool_calls:
          result = execute_tool(response.tool_calls)
          messages.append(result)
      else:
          break
  
  这种线性循环的局限:
  ❌ 无法实现分支逻辑(如:需要人工审批才能继续)
  ❌ 无法实现并行执行(如:同时搜索多个数据源)
  ❌ 无法实现子流程(如:复杂任务分解为子任务)
  ❌ 无法持久化(如:执行到一半,明天继续)
  ❌ 无法实现人机协作(如:关键步骤需要人确认)

LangGraph的解决方案:
  用"状态图"(State Graph)来定义Agent的行为
  → 图中的节点 = 处理步骤
  → 图中的边 = 步骤间的跳转(可以是条件跳转)
  → 图的状态 = 共享数据(所有节点都能读写)

类比:
  传统Agent = 一条直线(只能前进或循环)
  LangGraph Agent = 一张地图(可以走任何路线)

3.2 LangGraph四大核心概念

LangGraph只有4个核心概念,理解了就能用:

┌─────────────────────────────────────────────────────────────┐
│  1. State(状态)                                           │
│     - 整个图的"共享内存"                                    │
│     - 所有节点都能读取和修改                                │
│     - 通常包含: messages列表 + 自定义字段                   │
│     - 类比: 全局变量 / Redux Store                          │
├─────────────────────────────────────────────────────────────┤
│  2. Node(节点)                                            │
│     - 图中的每个"处理步骤"                                  │
│     - 本质就是一个函数: 输入State → 输出State的更新         │
│     - 类比: 流程图中的方块                                  │
│     - 例: "agent"节点、"tools"节点、"review"节点            │
├─────────────────────────────────────────────────────────────┤
│  3. Edge(边)                                              │
│     - 节点之间的"跳转规则"                                  │
│     - 普通边: A → B(无条件跳转)                           │
│     - 条件边: A → B或C(根据条件决定去哪)                  │
│     - 类比: 流程图中的箭头                                  │
├─────────────────────────────────────────────────────────────┤
│  4. Graph(图)                                             │
│     - 节点 + 边 组成的完整流程                              │
│     - 编译后成为可执行的应用                                │
│     - 类比: 完整的流程图                                    │
└─────────────────────────────────────────────────────────────┘

执行流程:
  1. 从START节点开始
  2. 按照边的定义,依次执行节点
  3. 每个节点读取State,处理后更新State
  4. 遇到条件边时,根据State决定下一步
  5. 到达END节点时结束

3.3 State详解——图的共享内存

from typing import TypedDict, Annotated, List, Optional
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage

# ============ 基础State定义 ============

class BasicState(TypedDict):
    """最简单的State: 只有消息列表"""
    messages: Annotated[list, add_messages]

"""
关键点: Annotated[list, add_messages]

add_messages是一个"reducer函数",它定义了如何更新messages字段:
  - 不是替换,而是追加
  - 如果新消息的id和已有消息相同,则更新(而非追加)
  
为什么需要reducer?
  因为每个节点返回的是"增量更新",不是完整State
  
  例: 节点返回 {"messages": [new_msg]}
  → add_messages会把new_msg追加到现有messages列表末尾
  → 而不是用[new_msg]替换整个列表
"""

# ============ 自定义State(实际项目常用) ============

class AgentState(TypedDict):
    """客服Agent的状态"""
    messages: Annotated[list, add_messages]  # 对话历史
    user_id: str                              # 用户ID
    order_info: Optional[dict]                # 查询到的订单信息
    needs_human_review: bool                  # 是否需要人工审核
    retry_count: int                          # 重试次数
    current_step: str                         # 当前步骤(用于调试)

# ============ 自定义Reducer ============

from operator import add

class ResearchState(TypedDict):
    """研究Agent的状态"""
    messages: Annotated[list, add_messages]
    sources: Annotated[list, add]       # 使用add作为reducer,新源追加到列表
    summary: str                         # 普通字段,直接覆盖
    iteration: int                       # 迭代次数

"""
Reducer规则:
  - Annotated[type, reducer_func]: 用reducer函数合并更新
  - 无Annotated的普通字段: 直接覆盖(最后写入的值生效)

常用reducer:
  - add_messages: 追加消息(智能处理重复id)
  - operator.add: 列表拼接
  - 自定义函数: def my_reducer(existing, new) -> merged
"""

# ============ State更新示例 ============

def my_node(state: AgentState) -> dict:
    """节点函数: 输入完整State,输出部分更新"""
    # 读取State
    last_message = state["messages"][-1]
    user_id = state["user_id"]
    
    # 处理逻辑...
    
    # 返回要更新的字段(只需要返回变化的部分)
    return {
        "messages": [AIMessage(content="处理完成")],  # 追加一条消息
        "current_step": "completed",                   # 覆盖current_step
        # 不返回user_id → user_id保持不变
    }

3.4 Node详解——处理步骤

from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import AIMessage, HumanMessage

# ============ 节点就是函数 ============

"""
节点函数的签名:
  def node_name(state: State) -> dict:
      # 读取state
      # 处理逻辑
      # 返回state的部分更新

规则:
  1. 输入: 完整的当前State
  2. 输出: dict,包含要更新的字段
  3. 不需要返回完整State,只返回变化的部分
"""

class State(TypedDict):
    messages: Annotated[list, add_messages]
    draft: str
    approved: bool

# 节点1: AI生成内容
def generate(state: State) -> dict:
    """生成节点: 调用LLM生成内容"""
    llm = ChatOpenAI(model="gpt-4o-mini")
    messages = state["messages"]
    response = llm.invoke(messages)
    return {
        "messages": [response],
        "draft": response.content,
    }

# 节点2: 审查内容
def review(state: State) -> dict:
    """审查节点: 检查生成的内容是否合规"""
    draft = state["draft"]
    # 简单审查逻辑(实际可能调用另一个LLM)
    is_approved = "违规" not in draft and len(draft) > 10
    return {"approved": is_approved}

# 节点3: 修改内容
def revise(state: State) -> dict:
    """修改节点: 根据审查意见修改"""
    llm = ChatOpenAI(model="gpt-4o-mini")
    messages = state["messages"] + [
        HumanMessage(content="请修改上面的内容,使其更加合规和专业")
    ]
    response = llm.invoke(messages)
    return {
        "messages": [response],
        "draft": response.content,
    }

# ============ 特殊节点: ToolNode ============

from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool

@tool
def search(query: str) -> str:
    """搜索互联网"""
    return f"搜索结果: {query}..."

@tool
def calculator(expression: str) -> str:
    """计算数学表达式"""
    return str(eval(expression))

# ToolNode: 预构建的工具执行节点
# 它会自动读取最后一条AIMessage中的tool_calls,执行工具,返回ToolMessage
tool_node = ToolNode([search, calculator])

# ============ 节点可以是类 ============

class ResearchNode:
    """研究节点(用类实现,可以保存配置)"""
    
    def __init__(self, llm, max_sources: int = 5):
        self.llm = llm
        self.max_sources = max_sources
    
    def __call__(self, state: State) -> dict:
        """类实例作为节点时,需要实现__call__"""
        # 处理逻辑
        return {"messages": [...]}

3.5 Edge详解——跳转规则

from langgraph.graph import StateGraph, START, END
from typing import Literal

# ============ 普通边: 无条件跳转 ============

graph = StateGraph(State)
graph.add_node("step1", step1_func)
graph.add_node("step2", step2_func)
graph.add_node("step3", step3_func)

# step1 → step2 → step3 → END
graph.add_edge(START, "step1")
graph.add_edge("step1", "step2")
graph.add_edge("step2", "step3")
graph.add_edge("step3", END)

# ============ 条件边: 根据State决定去哪 ============

def route_after_review(state: State) -> Literal["revise", "publish"]:
    """路由函数: 根据审查结果决定下一步"""
    if state["approved"]:
        return "publish"
    else:
        return "revise"

graph.add_conditional_edges(
    "review",              # 从哪个节点出发
    route_after_review,    # 路由函数
    {                      # 路由结果 → 目标节点的映射
        "revise": "revise",
        "publish": "publish",
    }
)

# 简写: 如果路由函数返回值就是节点名,可以省略映射
graph.add_conditional_edges("review", route_after_review)

# ============ Agent常用的条件边模式 ============

def should_continue(state: State) -> Literal["tools", "end"]:
    """判断Agent是否需要继续调用工具"""
    last_message = state["messages"][-1]
    
    # 如果最后一条消息有tool_calls,说明需要执行工具
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    # 否则Agent已经给出最终回答,结束
    return "end"

graph.add_conditional_edges(
    "agent",
    should_continue,
    {"tools": "tools", "end": END}
)

# ============ 多条件边(复杂路由) ============

def complex_router(state: State) -> Literal["search", "calculate", "ask_human", "respond"]:
    """复杂路由: 根据多个条件决定"""
    last_msg = state["messages"][-1]
    
    if state.get("needs_human_review"):
        return "ask_human"
    
    if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
        tool_name = last_msg.tool_calls[0]["name"]
        if tool_name == "search":
            return "search"
        elif tool_name == "calculator":
            return "calculate"
    
    return "respond"

3.6 完整示例:从零构建一个ReAct Agent

"""
这个例子展示如何用LangGraph从零构建一个完整的ReAct Agent。
等价于 langgraph.prebuilt.create_react_agent 的手动实现。
理解这个例子 = 理解LangGraph的核心。
"""

from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage

# ===== Step 1: 定义工具 =====

@tool
def get_weather(city: str) -> str:
    """获取城市天气信息
    
    Args:
        city: 城市名称
    """
    weather_db = {
        "北京": "晴天,28°C,湿度40%",
        "上海": "多云,25°C,湿度65%",
        "深圳": "小雨,30°C,湿度80%",
    }
    return weather_db.get(city, f"暂无{city}的天气数据")

@tool
def search_knowledge(query: str) -> str:
    """搜索知识库获取信息
    
    Args:
        query: 搜索关键词
    """
    # 模拟搜索
    return f"关于'{query}'的搜索结果: 这是一些相关信息..."

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """发送邮件
    
    Args:
        to: 收件人邮箱
        subject: 邮件主题
        body: 邮件正文
    """
    return f"邮件已发送给{to},主题: {subject}"

tools = [get_weather, search_knowledge, send_email]

# ===== Step 2: 定义State =====

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]

# ===== Step 3: 定义节点 =====

# 模型(绑定工具)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
llm_with_tools = llm.bind_tools(tools)

def agent_node(state: AgentState) -> dict:
    """Agent节点: 调用LLM决定下一步行动"""
    # 添加系统提示
    system_message = SystemMessage(content="""你是一个智能助手,可以使用以下工具:
    - get_weather: 查询天气
    - search_knowledge: 搜索知识库
    - send_email: 发送邮件
    
    请根据用户需求,决定是否需要使用工具。如果不需要工具,直接回答。""")
    
    messages = [system_message] + state["messages"]
    response = llm_with_tools.invoke(messages)
    return {"messages": [response]}

# 工具节点(预构建)
tool_node = ToolNode(tools)

# ===== Step 4: 定义路由 =====

def should_continue(state: AgentState) -> Literal["tools", "end"]:
    """判断是否需要继续执行工具"""
    last_message = state["messages"][-1]
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    return "end"

# ===== Step 5: 构建图 =====

graph = StateGraph(AgentState)

# 添加节点
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)

# 添加边
graph.add_edge(START, "agent")                    # 开始 → agent
graph.add_conditional_edges("agent", should_continue, {
    "tools": "tools",                              # 需要工具 → tools
    "end": END,                                    # 不需要 → 结束
})
graph.add_edge("tools", "agent")                  # 工具执行完 → 回到agent

# ===== Step 6: 编译 =====

app = graph.compile()

# ===== Step 7: 运行 =====

# 单轮对话
result = app.invoke({
    "messages": [HumanMessage(content="北京天气怎么样?")]
})
for msg in result["messages"]:
    print(f"{msg.__class__.__name__}: {msg.content[:200] if msg.content else '[tool_calls]'}")

print("\n" + "="*50 + "\n")

# 多步骤任务
result = app.invoke({
    "messages": [HumanMessage(content="查一下北京天气,然后发邮件告诉alice@example.com")]
})
for msg in result["messages"]:
    print(f"{msg.__class__.__name__}: {msg.content[:200] if msg.content else '[tool_calls]'}")

"""
执行流程:
  1. START → agent: LLM看到"查天气+发邮件",决定先调get_weather
  2. agent → tools: 执行get_weather("北京"),返回天气信息
  3. tools → agent: LLM看到天气结果,决定调send_email
  4. agent → tools: 执行send_email(...),返回发送成功
  5. tools → agent: LLM看到邮件已发送,生成最终回复
  6. agent → END: 没有更多tool_calls,结束

这就是ReAct模式:
  Thought(思考) → Action(行动) → Observation(观察) → Thought → ... → Final Answer
"""

3.7 图的可视化

# LangGraph支持将图导出为Mermaid图或PNG图片

# 方式1: 打印Mermaid格式(可以粘贴到Mermaid在线编辑器)
print(app.get_graph().draw_mermaid())

# 方式2: 保存为PNG图片(需要安装额外依赖)
# pip install pygraphviz
# app.get_graph().draw_png("agent_graph.png")

# 方式3: 在Jupyter Notebook中直接显示
# from IPython.display import Image, display
# display(Image(app.get_graph().draw_mermaid_png()))

"""
生成的图大概长这样:

    ┌───────────┐
    │   START   │
    └─────┬─────┘
          │
          ▼
    ┌───────────┐
    │   agent   │◄─────────┐
    └─────┬─────┘          │
          │                │
     ┌────┴────┐           │
     │ 条件边  │           │
     └────┬────┘           │
    ╱          ╲           │
   ▼            ▼          │
┌─────┐    ┌───────┐      │
│ END │    │ tools │──────┘
└─────┘    └───────┘
"""

下一篇文章见:AI系列文章导航目录-持续更新中

内容概要:本文介绍了一个关于三相桥式全控整流及有源逆变电路的实验仿真模型,重点研究三相整流器与逆变器在Simulink环境下的建模与仿真技术。内容涵盖电力电子变换器的工作原理、控制策略设计、系统动态响应分析,并进一步扩展至10kV配电网中不同中性点接地方式(中性点不接地、经小电阻接地、经消弧线圈接地)下的单相、两相短路接地及相间短路故障的仿真研究,全面呈现了电力系统典型故障的暂态特性。此外,文档还整合了丰富的科研资源,涵盖电力系统优化、新能源并网、故障诊断、微电网调度等多个前沿方向,充分体现了Matlab/Simulink在电气工程仿真中的核心地位和广泛应用价值。; 适合人群:电气工程、自动化、电力电子等相关专业的高校学生、科研人员及工程技术人员,具备一定的电路理论基础和仿真软件操作经验者更佳。; 使用场景及目标:①用于教学实验中帮助理解三相整流与逆变电路的工作机制;②支撑科研项目中对电力系统故障特性的建模与分析;③作为开发新型控制算法(如PWM控制、低电压穿越等)的仿真验证平台;④辅助完成毕业设计、课题研究或工程方案评估; 阅读建议:此资源以Simulink仿真实现为核心,强调理论与实践结合,建议读者在学习过程中同步搭建模型,动手调试参数,深入理解各模块功能与系统整体行为,同时可参考文中提供的完整资源链接拓展研究视野。
内容概要:本文介绍了一个关于风光制氢合成氨系统优化研究的论文复现资源,依托Cplex求解器在Matlab环境中实现系统建模与求解。该资源聚焦于新能源耦合系统,涵盖风能、太阳能发电制氢,并进一步合成氨的全流程能量管理与优化调度,通过数学建模与优化算法实现系统经济性与运行效率的最大化。内容不仅包括风光出力不确定性处理、电解水制氢、氢气储存与转化、氨合成工艺等关键环节的建模,还整合了多种智能优化算法与电力系统调度策略,如二阶锥规划、多目标优化与需求响应机制,旨在为科研人员提供一套完整的综合能源系统优化研究框架与代码实现范例。; 适合人群:具备一定电力系统、优化理论及Matlab编程基础的研究生、科研人员及工程技术人员,尤其适合从事新能源系统优化、综合能源系统规划、氢能与氨能转化等前沿方向的研究者。; 使用场景及目标:① 复现高水平期刊论文中的风光制氢合成氨系统优化模型,掌握Cplex在Matlab中的建模与求解流程;② 学习并应用二阶锥规划、多目标优化、需求响应等先进优化方法于综合能源系统科研项目中;③ 借助提供的完整Matlab代码案例,快速搭建仿真环境,加速科研进程,提升学术创新能力与工程实践水平。; 阅读建议:此资源以科研复现为核心,强调理论与实践深度融合,建议读者在学习过程中结合文档中的代码实例,逐步调试与理解模型构建逻辑,并尝试进行参数调整与模型拓展,以深化对综合能源系统多能耦合与优化调度机制的理解与应用能力。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值