系列文章导航: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系列文章导航目录-持续更新中

2811

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



