一、先说结论:为什么现在值得学 LangGraph
如果你只是做一个“一问一答”的聊天机器人,直接调模型接口就够了。
但一旦进入真实项目,你很快会遇到这些问题:
- 对话多轮之后,状态越来越乱。
- 一个任务里既有固定流程,又需要模型动态决策。
- 工具调用失败后,没法优雅恢复。
- 调试时只知道“回答错了”,却不知道错在检索、路由、提示词,还是工具。
LangGraph 的价值就在这里。
LangGraph 的定位已经非常清晰:它更偏底层编排运行时,强调的是 durable execution、streaming、human-in-the-loop、persistence 这些“把 Agent 跑成系统”的能力,而不只是“让模型能调用几个工具”。
换句话说:
- LangChain 更像能力组件层。
- LangGraph 更像运行编排层。
- 你写的不是一次函数调用,而是一张“可追踪、可循环、可恢复”的执行图。
这也是为什么 LangChain 官方现在会明确说,LangChain 的 Agent 能力本身就是建立在 LangGraph 之上的。
二、本文基于的两个案例
search_ai_agent.py:一个支持搜索工具调用的 LangGraph Agent。rag_agent.py:一个带本地知识库检索、问题重写、答案约束的 RAG Agent。
分别代表了 LangGraph 落地中非常典型的两条路线:
- 路线 1:工具型 Agent
- 路线 2:知识型 Agent
把这两个案例看懂,你对 LangGraph 的核心工作方式基本就有感觉了。
三、案例一:支持搜索工具调用的 Agent
1. 这个案例解决了什么问题
这个案例的目标很直接:
- 用户先提问。
- 大模型先判断要不要调用工具。
- 如果需要,就执行搜索工具。
- 工具结果回填消息列表。
- 大模型继续思考,直到给出最终答案。
这就是最经典的 Agent 闭环:
2. 核心状态是怎么设计的
这个案例里定义了一个 SearchState:
class SearchState(TypedDict):
messages: Annotated[list[AnyMessage], operator.add]
llm_calls: int
这个设计有两个重点:
messages负责承载整个对话和工具交互历史。llm_calls用来记录模型调用次数,方便调试循环次数和成本。
3. 图是怎么跑起来的
这个案例实际上只有两个节点:
call_llmcall_tool
以及一个条件判断函数:
has_tool_call
图结构可以简化理解为:
这套结构的价值在于:
- 图天然支持循环。
- 不是写死“一次调用模型 + 一次调用工具”。
- 而是让模型自己决定什么时候停。
这比普通链式调用更接近真实 Agent。
4. 这个案例最值得学习的点
亮点 1:Agent 的本质不是“能调工具”,而是“能循环决策”
很多人第一次做 Agent,容易把重点放在 bind_tools() 上。
其实真正关键的是:
- 工具只是能力。
- 图里的“条件边 + 回环”才是 Agent 的行为骨架。
也就是说,bind_tools() 解决的是“模型能不能提工具请求”,而 add_conditional_edges() 才解决“系统下一步往哪走”。
亮点 2:把调试信息显式放进状态
llm_calls 这个字段非常实用。
在简单 Demo 里,它只是计数器;但在正式项目里,完全可以扩展成:
tool_calls_countretry_countcost_estimatetrace_id
只要状态设计得好,后面做可观测性、限流、预算控制都会轻松很多。
5·源码
# 基于deepseek作为llm的支持搜索的智能体
import operator
from typing import Annotated, TypedDict
from langchain.chat_models import init_chat_model
from langchain_core.messages import AnyMessage, SystemMessage, ToolMessage, HumanMessage
from langchain_tavily import TavilySearch
from langgraph.graph import END, START, StateGraph
# 初始化模型:
llm = init_chat_model(
model="deepseek-chat",
model_provider="deepseek",
temperature=0,
)
# 绑定工具;
search_tool = TavilySearch()
# 定义工具列表
tools = [search_tool]
model_with_tools = llm.bind_tools(tools)
# 定义LangGraph状态:
class SearchState(TypedDict):
# 消息历史:
messages: Annotated[list[AnyMessage], operator.add]
llm_calls: int
# 定义大模型调用节点
def call_llm(state: SearchState) -> SearchState:
"""
调用大模型,根据返回的message决定是否调用工具!
"""
ai_message = model_with_tools.invoke(
[SystemMessage(content="请你是一个智能体,你可以自主选择使用工具来回答用户的问题。")]
+ state["messages"]
)
return {
"messages": [ai_message],
"llm_calls": state["llm_calls"] + 1,
}
# 搞一个hash完成名字映射的工具:
tool_name_map = {tool.name: tool for tool in tools}
# 定义工具调用节点
def call_tool(state: SearchState) -> SearchState:
"""
执行工具
"""
# 判断最后一条ai的消息是否包含tool_call
if state["messages"][-1].tool_calls:
# 遍历对应的ai发的tool_call,如果本地存在这个工具就进行调用
# 如果不存在,就返回状态
tool_messages = []
for tool_call in state["messages"][-1].tool_calls:
if tool_name_map.get(tool_call["name"]):
tool_result = tool_name_map.get(tool_call["name"]).invoke(tool_call["args"])
tool_messages.append(
ToolMessage(
content=str(tool_result),
tool_call_id=tool_call["id"],
)
)
if tool_messages:
return {
"messages": tool_messages,
"llm_calls": state["llm_calls"],
}
return state
# 构建图:
graph = StateGraph(SearchState)
# 添加节点:
graph.add_node("call_llm", call_llm)
graph.add_node("call_tool", call_tool)
# 添加边:
graph.add_edge(START, "call_llm")
# 添加条件边:
def has_tool_call(state: SearchState):
if state["messages"][-1].tool_calls:
return "need to call tool"
return "return"
graph.add_conditional_edges(
"call_llm",
has_tool_call,
# 搞一个映射的map
{
"need to call tool": "call_tool",
"return": END,
},
)
graph.add_edge("call_tool", "call_llm")
# 图编译
final_agent = graph.compile()
# print(final_agent)
# 测试-制作一个用例
# 不调用工具:
messages1= [
HumanMessage(content="你好")
]
# 传给图:
result1 = final_agent.invoke({"messages": messages1, "llm_calls": 0})
print(result1["messages"][-1].content)
print(result1["llm_calls"])
# 调用工具:
message2 = [
HumanMessage(content="今天南昌天气怎么样")
]
# 传给图:
result2 = final_agent.invoke({"messages": message2, "llm_calls": 0})
print(result2["messages"][-1].content)
print(result2["llm_calls"])
# 绘制mermaid图:
# mermaid_code = final_agent.get_graph(xray=True).draw_mermaid()
# print(mermaid_code)
# graph TD;
# __start__([<p>__start__</p>]):::first
# call_llm(call_llm)
# call_tool(call_tool)
# __end__([<p>__end__</p>]):::last
# __start__ --> call_llm;
# call_llm -. return .-> __end__;
# call_llm -. need to call tool .-> call_tool;
# call_tool --> call_llm;
# classDef default fill:#f2f0ff,line-height:1.2
# classDef first fill-opacity:0
# classDef last fill:#bfb6fc
# 你好!很高兴见到你。我是一个智能助手,可以帮你搜索信息、回答问题。有什么我可以帮助你的吗?
# 1
# 根据搜索结果,我为您整理了今天南昌的天气情况:
# ## 今天南昌天气情况
# **天气状况:** 阴天
# **温度范围:** 11°C ~ 16°C
# **风力风向:** 东北风2级
# **空气质量:** 良(AQI 96)
# ## 详细天气信息
# 1. **温度:** 今天最高温度16°C,最低温度11°C
# 2. **天气:** 全天以阴天为主
# 3. **风力:** 东北风2级,风力较小
# 4. **湿度:** 预计湿度较高,体感温度可能比实际温度稍低
# ## 生活建议
# 1. **穿衣建议:** 建议穿大衣类服装,注意保暖
# 2. **出行建议:** 天气阴沉,外出可携带雨具以防万一
# 今天南昌天气以阴天为主,温度适中但偏凉,建议您外出时适当增添衣物,注意保暖。
# 5 -->搜索了多次,由大模型自主决策层
四、案例二:带问题重写的 RAG Agent
1. 这个案例比上一个进了一步
第一个案例解决的是“如何调工具”。
第二个案例开始解决更真实的问题:
- 用户的问题不一定提得准。
- 本地知识库不一定一次就能检索到。
- 检索到了,也不一定和问题强相关。
- 生成答案时,还要限制模型不能胡说。
所以这个图不只是“检索 + 生成”,而是做了三层治理:
- 检索前:让模型决定是否使用检索工具。
- 检索后:判断检索结果是否相关。
- 生成前:如果不相关,先重写问题再重试。
这就不是普通 RAG 了,而是一个带反馈回路的 RAG Agent。
2. 这个案例的完整流程
这个结构已经很接近正式项目里的知识问答系统了。
3. 每个模块在做什么
模块 1:知识库准备
知识库部分用了这一套流程:
UnstructuredMarkdownLoader加载 Markdown 文档CharacterTextSplitter.from_tiktoken_encoder()按 token 切块GoogleGenerativeAIEmbeddings生成向量InMemoryVectorStore存入内存向量库as_retriever()生成检索器create_retriever_tool()把检索器包装成工具
这一步非常关键,因为它把“知识库”转换成了“Agent 可调用的工具”。
从 LangGraph 视角看,检索器不是特殊能力,它和搜索工具本质一样,都是图里的一个可调节点。
模块 2:generate_answerOr_toolcall
这个节点负责第一轮判断:
- 如果问题适合直接回答,就直接答。
- 如果需要知识库,就发起工具调用。
这一步是 Agent 和普通 RAG 的分水岭。
普通 RAG 往往是“每个问题都先检索一次”,而这里是“让模型先判断要不要检索”。这样更灵活,但也更依赖提示词和工具描述。
模块 3:retriever_node
这里用了官方预构建能力 ToolNode([retriever_tool])。
这是一个明显升级点,因为它比手写工具执行逻辑更稳,尤其适合:
- 多工具场景
- 统一错误处理
- 后续扩展状态注入
模块 4:is_rewrite_required
这是整个案例里最有实战价值的部分之一。
它没有在检索后立刻生成答案,而是先让模型做一次“相关性裁判”:
- 如果检索内容和问题相关,进入
generate_answer - 如果不相关,进入
rewrite_question - 最多重写 3 次,避免无限循环
很多 RAG 失败,并不是生成能力差,而是“拿错料了”。
模块 5:rewrite_question
这个节点做的是查询改写。
它的作用不是回答问题,而是把原始问题变成更适合检索的问题,比如:
- 补充关键词
- 消除歧义
- 把口语问题改成知识库更容易命中的表达
这一步经常能显著提高召回率。
模块 6:generate_answer
这里用了一个很硬的提示词约束:
- 必须完全基于 Context
- Context 没答案就只回复“不知道”
- 有答案也只允许简洁输出
这是典型的 RAG 防幻觉做法。
它未必让回答更“好看”,但会让系统更“可信”。
4. 这个案例真正体现出的工程价值
这个案例最有代表性的地方,不是“用了向量库”,而是它已经具备了 Agent 系统的几个关键特征:
- 有状态:消息会随着流程推进不断累积。
- 有分支:检索后会根据相关性分流。
- 有循环:问题改写后重新发起检索。
- 有边界:最多重写 3 次,不无限递归。
- 有约束:最终生成受 Context 限制。
这已经不是“写个 RAG Demo”,而是在搭一个“可控的知识问答工作流”。
5·源码
# 基于deepseek作为llm的支持搜索的智能体
import operator
from typing import Annotated, Literal, TypedDict
from langchain.chat_models import init_chat_model
#导入langchain_core的消息类型
from langchain_core.messages import AnyMessage, SystemMessage, ToolMessage, HumanMessage
from langchain_tavily import TavilySearch
# 导入langgraph的图
from langgraph.graph import END, START, StateGraph
# 导入langgraph的工具节点
from langgraph.prebuilt import ToolNode, tools_condition
# 导入谷歌的嵌入模型
from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_community.vectorstores import Chroma
from pydantic import BaseModel, Field
import tiktoken
from langchain_text_splitters import CharacterTextSplitter
# 导入markdown加载器
from langchain_community.document_loaders.markdown import UnstructuredMarkdownLoader
# 导入内存向量存储
from langchain_core.vectorstores import InMemoryVectorStore
# 导入检索器变成工具的函数库
from langchain_core.tools import create_retriever_tool
# 加载文档 (让 Markdown 按结构解析(标题、段落、列表分开))
loader = UnstructuredMarkdownLoader("langgraph_concept.md", mode="elements")
documents = loader.load()
# 设置基于token的文本分割器
splitter = CharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base",
chunk_size=200,
chunk_overlap=50, # 文档重叠长度
)
# 原始文本(一个document的page_content) → 编码成 token 序列 → 滑动窗口切分 → 解码成 chunk
# [0,1,...,119][70,...,189][140,...,299] → "chunk1" "chunk2" "chunk3"
# 分割文档
chunks = splitter.split_documents(documents)
embeddings = GoogleGenerativeAIEmbeddings(model="gemini-embedding-001")
# 创建内存向量存储
vectorstore = InMemoryVectorStore(embeddings)
# 先把切分后的文档写入向量库,否则检索器会连到空知识库
vectorstore.add_documents(chunks)
# 创建检索器:
retriever = vectorstore.as_retriever()
# 封装成agent可调用的工具;
retriever_tool = create_retriever_tool(
retriever,
name="langgraph_concept_retriever",
# 绑定工具后,会给ai限制住,日常回答可以不调用工具,直接回答,但是一旦涉及到非绑定工具描述之外的内容,llm就直接返回类似无法查询之类的不再进行调用工具!
description="介绍langgraph的所有相关信息,只要关于它的都可以检索!",
)
# 初始化模型:
llm = init_chat_model(
model="deepseek-chat",
model_provider="deepseek",
temperature=0,
)
# 直接导入LangGraph定义好的消息专状态
from langgraph.graph import MessagesState
# 节点:产生答案或者调用工具
def generate_answerOr_toolcall(state: MessagesState) -> MessagesState:
messages = state["messages"]
# 通过知识库限制ai只能回答知识库检索器工具描述的问题相关的!
response = llm.bind_tools([retriever_tool]).invoke(messages)
state["messages"].append(response)
return state
# 封装检索器节点:
retriever_node = ToolNode([retriever_tool])
# 产生答案节点
GENERATE_PROMPT = (
"你是一个严格的过滤助手。你的回答必须完全基于 Context。\n"
"1. 如果 Context 中没有直接提到答案,你必须仅回复'不知道',禁止解释,禁止道歉。\n"
"2. 如果有答案,请用中文简洁回答,严禁超过两句话。\n"
"Context: {context}\n"
"Question: {question}"
)
def generate_answer(state: MessagesState) -> MessagesState:
messages = state["messages"]
question=messages[0].content
context=messages[-1].content
response = llm.invoke(GENERATE_PROMPT.format(question=question, context=context))
state["messages"].append(response)
return state
# 重写问题节点
# 美化问题的提示词
REWRITE_PROMPT = (
"查看输⼊并尝试推断潜在的语义意图/含义。\n"
"这是最初的问题:"
"\n ------- \n"
"{question}"
"\n ------- \n"
"提出⼀个改进后的问题:"
)
def rewrite_question(state: MessagesState) -> MessagesState:
messages = state["messages"]
question=messages[0].content
response = llm.invoke(REWRITE_PROMPT.format(question=question))
state["messages"].append(HumanMessage(content=response.content))
return state
# 编译图:
graph = StateGraph(MessagesState)
# 添加节点:
graph.add_node("generate_answerOr_toolcall", generate_answerOr_toolcall)
graph.add_node("rewrite_question", rewrite_question)
graph.add_node("generate_answer", generate_answer)
graph.add_node("retriever_node", retriever_node)
# 添加边:
graph.add_edge(START, "generate_answerOr_toolcall")
# 条件边:
graph.add_conditional_edges("generate_answerOr_toolcall",
tools_condition,# 判断最后一条ai消息是否调用了工具
{"tools": "retriever_node", "__end__": END}
)
graph.add_edge("rewrite_question", "generate_answerOr_toolcall")
# 检测是否需要重写问题;
# 定义输出结构化,让大模型自己检测如果答案和问题相关就返回str的yes,否则返回no
judge_rewrite_prompt = (
"你是⼀个评分员,评估检索到的⽂档与⽤⼾问题的相关性。 \n "
"以下是检索到的⽂档: \n\n {context} \n\n"
"以下是⽤⼾的问题: {question} \n"
"如果⽂档包含与⽤⼾问题相关的关键字或语义,则将其评为相关。 \n"
"给出⼀个⼆元分数“yes”或“no”,以表明该⽂档是否与问题相关。"
)
class IsRequiredRewrite(BaseModel):
score: str = Field(description="返回yes或no")
# 让大模型自己判断(如果走了三次都是no,那就直接返回产生答案)
def is_rewrite_required(state: MessagesState) -> Literal["rewrite_question", "generate_answer"]:
messages = state["messages"]
question = messages[0].content
context = messages[-1].content
response = llm.with_structured_output(IsRequiredRewrite).invoke(
judge_rewrite_prompt.format(question=question, context=context)
)
score = response.score.strip().lower()
# 统计重写次数
rewrite_count = sum(isinstance(message, HumanMessage) for message in messages) - 1
# 检索结果相关时直接回答;不相关时最多重写三次,超过后直接生成答案
if score == "yes":
return "generate_answer"
if rewrite_count >= 3:
return "generate_answer"
return "rewrite_question"
graph.add_conditional_edges("retriever_node",
is_rewrite_required,
{"generate_answer": "generate_answer", "rewrite_question": "rewrite_question"}
)
graph.add_edge("generate_answer", END)
# 编译图:
rag_agent = graph.compile()
# 测试用例;你好,你是;
# result = rag_agent.invoke({"messages": [HumanMessage(content="你好,你是")]})
# print(result["messages"][-1].content)
# print("-----------------------------------------------------------------")
# # 测试用例;LangGraph 是什么?
# result = rag_agent.invoke({"messages": [HumanMessage(content="LangGraph 到底是什么?")]})
# print(result["messages"][-1].content)
# print("-----------------------------------------------------------------")
# # 为什么普通的“聊天式 AI”不够用了?
# result = rag_agent.invoke({"messages": [HumanMessage(content="为什么普通的“聊天式 AI”不够用了?")]})
# print(result["messages"][-1].content)
# print("-----------------------------------------------------------------")
# Agent 是什么?
# result = rag_agent.invoke({"messages": [HumanMessage(content="Agent、Workflow 与 LangGraph 的关系")]})
# print(result["messages"][-1].content)
# print("-----------------------------------------------------------------")
# 你好!我是DeepSeek,一个由深度求索公司开发的AI助手。我很高兴为你提供帮助!
# 我可以协助你处理各种问题,比如:
# - 回答问题和解释概念
# - 帮助分析和解决问题
# - 协助写作和翻译
# - 提供学习建议和指导
# - 以及其他许多任务
# 有什么我可以帮助你的吗?
# -----------------------------------------------------------------
# LangGraph是一个将智能体从"聊天演示"推进到"可运行系统"的基础设施。它可以独立使用,不依赖LangChain。简言之,LangGraph是构建可运行智能体系统的基础框架。
# -----------------------------------------------------------------
# 我主要专注于介绍LangGraph相关的概念和知识。关于"为什么普通的'聊天式 AI'不够用了"这个问题,涉及到更广泛的AI发展趋势和应 用场景分析,这超出了我当前的专业范围。
# 不过,我可以告诉您,LangGraph是一个用于构建复杂、有状态的AI应用程序的框架,它特别适合需要多步骤推理、状态管理和复杂工作流的场景。如果您想了解LangGraph如何解决传统聊天式AI的局限性,或者想了解它在构建更复杂AI系统方面的优势,我很乐意为您详细介绍。
# 您是否对LangGraph的具体应用场景或技术特点感兴趣呢?
# -----------------------------------------------------------------
# 根据检索到的上下文,Agent、Workflow与LangGraph的关系如下:
# LangGraph是一个图结构编排运行时,既能表达固定Workflow,也能承载动态Agent,为两者提供状态管理、节点执行等能力。它是面向 长时运行、有状态Agent的底层编排框架,解决Agent系统中状态流转、流程控制和中断恢复等问题。LangGraph作为系统编排层,负责将各个组件串成可运行的Agent系统。
# -----------------------------------------------------------------
# 制作一个找不到的例子
result = rag_agent.stream({"messages": [HumanMessage(content="langgraph是谁发明的? ")]})
for chunk in result:
for role, updated_messages in chunk.items():
print("节点角色:", role)
updated_messages["messages"][-1].pretty_print()
print("-----------------------------------------------------------------")
# mermaid_code = rag_agent.get_graph(xray=True).draw_mermaid()
# print(mermaid_code)
# graph TD;
# __start__([<p>__start__</p>]):::first
# generate_answerOr_toolcall(generate_answerOr_toolcall)
# rewrite_question(rewrite_question)
# generate_answer(generate_answer)
# retriever_node(retriever_node)
# __end__([<p>__end__</p>]):::last
# __start__ --> generate_answerOr_toolcall;
# generate_answerOr_toolcall -.-> __end__;
# generate_answerOr_toolcall -. tools .-> retriever_node;
# retriever_node -.-> generate_answer;
# retriever_node -.-> rewrite_question;
# rewrite_question --> generate_answerOr_toolcall;
# generate_answer --> __end__;
# classDef default fill:#f2f0ff,line-height:1.2
# classDef first fill-opacity:0
# classDef last fill:#bfb6fc
# 节点角色: generate_answerOr_toolcall
# ================================== Ai Message ==================================
# 我来帮您查询LangGraph的发明者信息。
# Tool Calls:
# langgraph_concept_retriever (call_00_JY0YtQgXDiESjyn8KMSnoZ1m)
# Call ID: call_00_JY0YtQgXDiESjyn8KMSnoZ1m
# Args:
# query: langgraph 发明者 创始人 谁创建的
# -----------------------------------------------------------------
# 节点角色: retriever_node
# ================================= Tool Message =================================
# Name: langgraph_concept_retriever
# 为什么需要 LangGraph?
# 二、LangGraph 是什么?
# LangGraph 可独立使用,不依赖 LangChain。
# 三、LangGraph 与 LangChain 的关系
# -----------------------------------------------------------------
# 节点角色: rewrite_question
# ================================ Human Message =================================
# 改进后的问题:
# **“LangGraph 是由哪个团队或个人开发的?其背后的主要设计理念或目标是什么?”**
# **改进说明:**
# 1. **明确开发主体**:原问题“谁发明的”可能指向个人、团队或公司,改进后明确询问“团队或个人”,避免歧义。
# 2. **补充背景信息**:增加对“设计理念或目标”的追问,可引导回答者提供更全面的背景,例如是否与特定框架(如LangChain)关联,或为解决什么问题而设计。
# 3. **提升信息深度**:原问题仅关注发明者,改进后同时关注动机,有助于理解工具的技术定位与应用场景。
# 若需进一步调整问题方向(如聚焦技术细节、应用案例等),可继续优化提问角度。
# -----------------------------------------------------------------
# 节点角色: generate_answerOr_toolcall
# ================================== Ai Message ==================================
# 我来帮您查询LangGraph的开发团队和设计理念信息。
# Tool Calls:
# langgraph_concept_retriever (call_00_eECSKIp1TwA9PyaDeaoHzHXL)
# Call ID: call_00_eECSKIp1TwA9PyaDeaoHzHXL
# Args:
# query: LangGraph 开发团队 创建者 设计理念 目标
# -----------------------------------------------------------------
# 节点角色: retriever_node
# ================================= Tool Message =================================
# Name: langgraph_concept_retriever
# 为什么需要 LangGraph?
# 二、LangGraph 是什么?
# 简言之,LangGraph 是把 Agent 从“聊天演示”推进到“可运行系统”的基础设施。
# LangGraph 可独立使用,不依赖 LangChain。
# -----------------------------------------------------------------
# 节点角色: rewrite_question
# ================================ Human Message =================================
# 改进后的问题:
# **“LangGraph 是由哪个团队或个人开发的?其背后的主要设计理念或目标是什么?”**
# **改进说明:**
# 1. **明确开发主体**:原问题“谁发明的”可能指向个人、团队或公司,改进后明确询问“团队或个人”,避免歧义。
# 2. **补充背景信息**:增加对“设计理念或目标”的追问,可引导回答者提供更全面的背景,例如是否与特定框架(如LangChain)关联,或为解决什么问题而设计。
# 3. **提升信息深度**:原问题仅关注发明者,改进后同时关注动机,有助于理解工具的技术定位与应用场景。
# 若需进一步调整问题方向(如聚焦技术细节、应用案例等),可继续优化提问角度。
# -----------------------------------------------------------------
# 节点角色: generate_answerOr_toolcall
# ================================== Ai Message ==================================
# 让我尝试更具体的查询来获取LangGraph的开发背景信息。
# Tool Calls:
# langgraph_concept_retriever (call_00_jS4fKRFejUtVP07VDmUEtwjV)
# Call ID: call_00_jS4fKRFejUtVP07VDmUEtwjV
# Args:
# query: LangGraph 创建者 创始人 开发公司 设计目标
# -----------------------------------------------------------------
# 节点角色: retriever_node
# ================================= Tool Message =================================
# Name: langgraph_concept_retriever
# 为什么需要 LangGraph?
# 二、LangGraph 是什么?
# 简言之,LangGraph 是把 Agent 从“聊天演示”推进到“可运行系统”的基础设施。
# LangGraph 可独立使用,不依赖 LangChain。
# -----------------------------------------------------------------
# 节点角色: rewrite_question
# ================================ Human Message =================================
# 改进后的问题:
# **“LangGraph 是由哪个团队或个人开发的?其背后的主要设计理念或目标是什么?”**
# **改进说明:**
# 1. **明确开发主体**:原问题“谁发明的”可能指向个人、团队或公司,改进后明确询问“团队或个人”,避免歧义。
# 2. **补充背景信息**:增加对“设计理念或目标”的追问,可引导回答者提供更全面的背景,例如是否与特定框架(如LangChain)关联,或为解决什么问题而设计。
# 3. **提升信息深度**:原问题仅关注发明者,改进后同时关注动机,有助于理解工具的技术定位与应用场景。
# 若需进一步调整问题方向(如聚焦技术细节、应用案例等),可继续优化提问角度。
# -----------------------------------------------------------------
# 节点角色: generate_answerOr_toolcall
# ================================== Ai Message ==================================
# 让我尝试查询LangGraph与LangChain的关系,这可能有助于了解其开发背景。
# Tool Calls:
# langgraph_concept_retriever (call_00_tHk1XIdtGqWoCEItDa9O0MlD)
# Call ID: call_00_tHk1XIdtGqWoCEItDa9O0MlD
# Args:
# query: LangGraph LangChain 关系 开发团队 背景
# -----------------------------------------------------------------
# 节点角色: retriever_node
# ================================= Tool Message =================================
# Name: langgraph_concept_retriever
# 三、LangGraph 与 LangChain 的关系
# LangGraph 可独立使用,不依赖 LangChain。
# 为什么需要 LangGraph?
# 四、Agent、Workflow 与 LangGraph 的关系
# -----------------------------------------------------------------
# 节点角色: generate_answer
# ================================== Ai Message ==================================
# 不知道
# -----------------------------------------------------------------
五、把两个案例放在一起,会看懂 LangGraph 的核心
如果把这两个案例合起来看,LangGraph 的核心思路其实就三句话:
1. 先定义状态,再定义节点
不要上来就写 Prompt。
在 LangGraph 里,真正的第一步是先想清楚:
- 我这个系统要记住什么?
- 哪些数据要跨节点传递?
- 哪些值是累积的,哪些值是覆盖的?
状态设计对了,图才不会越写越乱。
2. 节点负责“做事”,边负责“决定往哪走”
节点是动作。
边是控制流。
很多初学者会把判断逻辑、流程控制、工具执行、答案生成全塞进一个函数,最后代码表面像“Agent”,本质还是一团脚本。
LangGraph 最重要的训练,是逼你把“行为”和“路由”拆开。
3. Agent 不是神秘概念,本质就是“状态机 + LLM 决策”
这一点是很多人学 LangGraph 后最大的认知升级。
所谓 Agent,不是一个抽象词,它往往就是:
- 用状态存上下文
- 用节点执行动作
- 用条件边做分支
- 用循环边做自我修正
- 用工具补外部能力
模型只是其中一个节点,不是全部。
六、如果你要写一套 LangGraph 实战,推荐按这个步骤来
第 1 步:先写状态,不要先写 Prompt
最少先回答这几个问题:
- 需要保存消息历史吗?
- 需要记录重试次数吗?
- 需要记录工具调用次数吗?
- 需要保存检索结果或中间结论吗?
推荐思路:
- 简单聊天图:直接用
MessagesState - 稍复杂图:继承
MessagesState增加业务字段 - 不要一开始就把状态塞得过大
第 2 步:先画流程图,再写节点
哪怕只是纸上画,也建议先明确:
- 入口节点是谁
- 哪些节点会回环
- 哪些节点会结束
- 哪些节点是条件分流点
如果流程图都画不清,代码大概率也会越写越歪。
第 3 步:把“模型调用”和“工具执行”分开
建议保持这个习惯:
- 一个节点专门负责让模型思考
- 一个节点专门负责执行工具
- 一个函数专门负责条件路由
这样后面排查问题时非常省心。
第 4 步:给循环加预算
只要图里有回环,就要提前设计:
- 最大 LLM 调用次数
- 最大工具调用次数
- 最大重写次数
- 超限后的兜底响应
这是防止 Agent 跑飞的底线。
第 5 步:给生成答案加约束
尤其是 RAG 场景,建议明确写进 Prompt:
- 只能基于 Context 回答
- 不知道就直接说不知道
- 是否允许总结、推断、扩写
- 输出格式是否固定
不然很容易出现“检索没命中,但模型照样组织了一段像真话的话”。
第 6 步:尽早接入可观测性
最少建议做两件事:
- 保留流式调试输出,便于看每个节点发生了什么
- 接 LangSmith 或其他 tracing 方案
第 7 步:从 Demo 走向生产时,优先补持久化
官方现在把 persistence 讲得很重,不是没原因。
一旦进入真实场景,你很快就会需要:
- 多轮记忆
- 中断恢复
- 人工审核后继续执行
- 错误后从上一步恢复
这时就该把 checkpointer 接上,而不是靠内存状态硬撑。
七、总结
LangGraph 最值得学的地方,不是 API 本身,而是它逼着你用“系统思维”去写 Agent。
通过这两个案例,已经走完了一条很标准的成长路径:
- 从“模型会不会调工具”
- 到“图能不能形成闭环”
- 再到“知识检索能不能被控制”
- 最后过渡到“系统是否可追踪、可恢复、可持续运行”
如果只用一句话总结本文,我会这么说:
LangGraph 的核心不是让大模型更聪明,而是让你的 Agent 更像一个真正可运行的软件系统。