最近被问得最多的一个问题,就是“LangChain快速入门到底怎么搞”。很多人其实已经看过一遍官方文档,也复制过几段示例代码,但一到自己写AI Agent,就发现跑通一个demo和让Agent真正干活完全是两码事。对着文档抄出来的Agent,要么只会回“我无法访问外部工具”,要么反复调用同一个工具就是不给你最终答案,还有的聊上三轮就开始把聊天记录里的旧内容当事实,逻辑全乱。
这篇文章我不想做那种“一句话介绍LangChain”的科普,而是直接把从0到1搭一个能实际干活的AI Agent这条路走一遍,重点放在工具调用、状态管理和错误恢复这三件事上。适合刚打算入门LangChain的人,也适合已经跑过几个demo、但是被工程化问题卡住的人。建议按章节顺序看,每段都有能直接抄走的思路和代码。
1. 动手之前,先想清楚LangChain到底帮你省了什么
1.1 没有LangChain的时候,你写Agent要面对什么
很多人对LangChain的第一印象是“一个LLM封装库”,这个理解不能算错,但容易把格局看小了。LLM应用开发真正的复杂度,不在“调用模型”那一步,而在把模型接到真实业务逻辑里之后,所有边界问题都会冒出来。
举一个最简单的场景:你想做一个能查订单状态的助手。用户说“帮我看看A1001到哪了”,你第一反应可能是直接调API拿数据,再拼进Prompt让模型回答。但真正的Agent不是这种一次性的“取数-回答”流程,它是一个反复决策的循环:模型先判断“用户要查订单”,接着决定“应该调用get_order_status这个工具”,然后你的代码执行工具,把结果作为新的上下文丢回给模型,模型再判断“信息够不够,够了就给出最终回复”。
问题马上来了:谁来解析模型的输出里那一段JSON格式的工具调用指令?谁来维护多轮对话里已经产生的中途消息?如果模型调用工具时少传了参数,你是重试一次还是直接放弃?如果工具连续调用了八次还在循环,怎么强制停下来?没有框架的话,这些逻辑都要自己手写,而且很容易在某个角落里漏掉一种边界情况。
我经常用“带实习生”来类比这件事。你交给实习生一个任务,他不光要干活,还会在干活过程中产生一堆疑问、会出错、会拿回一些没用的中间结果。你需要一直盯着他:告诉他下一步做什么、他做完之后把结果汇报给你、你判断对不对、不对就让他重做。LLM应用里的模型就是这个实习生,LangChain等工具就是帮你把“盯人”这个过程结构化、可维护化的脚手架。
1.2 LangChain的核心抽象:模型层、工具层、记忆层、编排层
LangChain真正有长期价值的,不是某个具体类,而是它提供的一组抽象。我按自己的理解把它们分成四层:
| 抽象层 | 解决什么问题 | 核心组件 |
|---|---|---|
| 模型输入输出层 | 把Prompt构造、模型调用、输出解析标准化 | ChatPromptTemplate、ChatOpenAI/ChatOllama、StrOutputParser |
| 工具层 | 把普通Python函数包装成模型能理解、能调用的“说明书” | @tool装饰器、args_schema |
| 记忆层 | 管理多轮对话历史,避免无脑拼接 | trim_messages、LangGraph Checkpointer |
| 编排层 | 控制Agent的决策循环、分支、恢复 | LangGraph StateGraph、create_react_agent |
这四层不是LangChain发明的概念,而是LLM应用天然需要的能力。LangChain做的是把每个环节都给你一个通用的接口,让你换模型、换工具、换记忆策略的时候,不用把整个应用推倒重来。
但这里有一个特别容易踩的认知误区:框架不能消除模型的“不可靠”。LangChain能帮你规范地组织Prompt、解析输出、管理状态,但模型该幻觉还是幻觉、该传错参数还是传错参数。指望“用了LangChain Agent就稳定了”的人,基本都会失望。框架只是给你一套更顺手的问题排查工具,真正让Agent变“能干”的,是你在工具设计、流程约束、错误恢复上做的功夫。
1.3 不是所有项目都需要LangChain
这话放前面说清楚,免得你对框架抱有不切实际的期望。如果你的需求就是“调用一次模型,让它根据固定Prompt输出一段文本”,那不需要LangChain,直接调SDK反而更轻。我见过不少项目为了用LangChain而用LangChain,结果把简单流程拆成一堆组件,排查问题反而更费劲。
LangChain的舒适区是:需要多步决策、多个工具、多轮状态管理的场景。你要做问答机器人、代码生成助手、支持联网搜资料再总结的Agent、或者企业内部知识库助手,这类“模型+工具+状态”的组合型应用,才是它发挥价值的地方。
2. 搭好第一个能“听指挥”的Agent骨架
2.1 环境准备与版本选型,别被老教程带偏
再强调一次:LangChain的版本迭代非常快,网上大量教程还停留在0.0.x时代,里面的接口和最新版本已经对不上了。2024年到2025年之间,官方把很多核心功能从LangChain迁到了LangGraph,Agent的执行层主要走LangGraph,因此“只装langchain不够,还要装langgraph”逐渐成了标配。
我先给一个我自己比较常用的环境配置,新项目直接抄:
mkdir my-agent-demo cd my-agent-demo python3 -m venv venv source venv/bin/activate pip install "langchain>=0.3,<0.4" \ "langchain-core>=0.3,<0.4" \ "langchain-openai>=0.3,<0.4" \ "langgraph>=0.4" \ "langchain-ollama>=0.3"如果你用的是OpenAI兼容接口,langchain-openai里的ChatOpenAI也可以直接对接兼容服务,不需要额外改代码。想本地调试的话,langchain-ollama是个很好的选择,配合Ollama跑一个支持工具调用的小模型,在开发阶段能省不少事。
提示:项目里一定要锁版本,尤其是LangChain生态这种大版本之间接口不兼容的库。我建议在requirements.txt里写上具体版本号,至少也要限制大版本范围,否则过三个月重新装环境,代码跑不起来非常常见。
2.2 从一条最简单的Chain开始
在碰Agent之前,先把最基础的一条链跑通。这一步的意义是确认模型连接、Prompt构造、输出解析这几个环节都正常工作,后面出问题就知道不是底层连接挂了。
from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个严谨的技术助手,回答要简洁、准确。"), ("human", "{question}") ]) model = ChatOpenAI(model="gpt-4o-mini", temperature=0) chain = prompt | model answer = chain.invoke({"question": "用一句话解释LangChain是什么"}) print(answer.content)这段代码里最有LangChain味的是prompt | model这个管道操作。它把“构造Prompt”和“调用模型”拼成了一条流水线:输入一个包含question的字典,先被prompt格式化成消息列表,再交给model调用。输出是一个AIMessage对象,所以打印的时候要取.content属性。
关于模型选择,我建议开发阶段用一个小模型,gpt-4o-mini或者本地跑一个7B级别的小参数模型都行。小模型虽然推理能力弱一些,但胜在响应快、成本低,适合跑通流程。等到要验证高阶agent能力时再换大模型,免得每次调试都要等半天、烧一堆token。
2.3 给模型配上第一个工具,Agent的骨架就出来了
接下来进入正题:让模型学会调用工具。我拿“订单状态查询”举例,因为这个场景逻辑简单,一个工具函数、一个假数据库就能说明问题。
from langchain_core.tools import tool @tool def get_order_status(order_id: str) -> str: """根据订单ID查询订单当前状态,返回物流信息文本。 Args: order_id: 订单编号,例如"A1001"。 """ mock_db = { "A1001": "已发货,预计明天送达", "A1002": "正在拣货,预计后天发货", } return mock_db.get(order_id, "未找到该订单,请确认订单号是否正确")这个函数本身没有任何“AI”成分,但装饰器@tool会把它包装成一个模型能识别的工具对象。模型并不直接运行这个函数,它只是看到了这个工具的名称、描述、参数说明,然后决定“我要调用它”,并生成一段包含参数值的调用指令。最终真正执行这个函数的是你的代码,不是模型。
使用LangGraph prebuilt的create_react_agent来组装Agent:
from langgraph.prebuilt import create_react_agent agent = create_react_agent(model=model, tools=[get_order_status]) result = agent.invoke({ "messages": [{"role": "user", "content": "帮我查一下订单A1001现在到哪了"}] }) for message in result["messages"]: print(message.type, ":", message.content) if hasattr(message, "tool_calls") and message.tool_calls: print(" 调用了工具:", message.tool_calls)运行后,你会在输出里看到一条很有意思的过程:模型先返回一个带有tool_calls的AIMessage,然后系统执行get_order_status,再把工具返回结果以ToolMessage的形式追加到消息列表,最后模型基于这个结果生成最终回复。这个“模型决策→执行工具→反馈结果→再决策”的循环,就是AI Agent的核心运行逻辑。
我在这一步想特别强调一个观点:第一步不要一上来就设计一堆工具、搞复杂的LangGraph图结构。先让一个Agent只带一个工具、只解决一个问题,然后把过程中的messages一个不落地打印出来,亲眼看看模型是怎么做决策的。我看过太多人一上来就搭了六个工具三条分支,结果模型调用哪个工具都犹豫,最终效果一塌糊涂。地基只打了一个点的时候,你反而能看清这个点的问题,等复杂度上来再调试就难了。
2.4 从Chain到Agent,中间发生了什么
把2.2和2.3对比一下,你会发现本质区别不是“多了一个函数”,而是执行路径变了。Chain是单向管道:输入直接经过Prompt、模型、输出解析,一步到位。Agent是循环式决策:模型可能需要和工具“来回对话”好几次,才能给出最终答案。这个循环就是Agent能力的来源,也是风险来源——它意味着中间任何一步都可能出错,而且错误会在后续循环中被放大。
不过你不一定每次都要自己写这个循环。“create_react_agent”就是LangGraph premade的ReAct实现,它帮你把“模型→工具→再模型”这个循环封装好了。你先用它跑通第一个Agent,等你对运行逻辑有感觉了,再去用LangGraph自定义更复杂的状态流。
3. 让Agent真正“干活”:工具调用与记忆设计的核心细节
3.1 工具不是“注册函数”,而是给模型写说明书
我见过特别多Agent不好用,问题根本不是模型不行,而是工具定义写得太糊。模型看不到你的函数内部实现,它只能看到:函数名、函数描述、参数名、参数类型和参数描述。这四样东西决定了它能不能在正确的时候、用正确的参数调用你的工具。
举个例子,同样是查天气的工具:
@tool def get_weather(city: str) -> str: """获取天气。""" ...模型看到这个工具,只知道“传一个城市名进去”,但这个city是中文名还是英文名?是“北京”还是“Beijing”?温度单位是什么?城区天气还是整座城市天气?它全靠猜。稍微复杂一点的任务,猜错的概率就会很高。
更好的定义:
from pydantic import BaseModel, Field class WeatherInput(BaseModel): city: str = Field(description="城市中文名,例如北京、上海、广州") units: str = Field( default="celsius", description="温度单位,取值为 celsius 或 fahrenheit" ) @tool(args_schema=WeatherInput) def get_weather(city: str, units: str = "celsius") -> str: """查询指定城市的当前天气,返回天气状况、气温和风力。 Args: city: 城市中文名。 units: 温度单位。 """ # 这里接真实天气API return f"{city}当前晴,25摄氏度,风力3级"关键点是:描述里要把“正确用法”和“常见取值”写清楚。你可以在描述里写“例如北京、上海”,帮模型建立正确的参数空间。pydantic模型里的Field description也很有用,它最终会成为模型看到的部分。可以说,工具定义的质量直接决定了Agent的“手”准不准。
3.2 用LangGraph把Agent当成状态机来看
create_react_agent虽然方便,但它是一个固定结构的Agent,你只能配置模型和工具。如果你想自己控制“什么情况下必须调用工具”“工具失败后怎么恢复”“到达什么条件就强制结束”,就需要理解LangGraph。
LangGraph和LangChain的关系,可以简单这么看:LangChain提供各种能力组件,LangGraph提供把这些组件串成“图状流程”的编排能力。Chat模型、工具、Memory这些是零件,LangGraph用来画“流水线图纸”并执行它。
Graph这个词听起来唬人,核心只有三个概念:
- State:全局数据,通常是消息列表或其他自定义字段,每个节点都能读和写。
- Node:一个处理步骤,接收State,返回State的更新。
- Edge:节点之间的连接,可以是有条件的,例如“判断模型是否调用了工具,如果调用了就去工具节点,否则结束”。
下面是一个理解用的极简框架:
from typing import Literal, TypedDict from langgraph.graph import StateGraph, END class AgentState(TypedDict): messages: list def call_model(state: AgentState) -> AgentState: response = model.invoke(state["messages"]) return {"messages": [response]} def call_tool(state: AgentState) -> AgentState: last_message = state["messages"][-1] # 这里要解析tool_calls并执行工具,简化处理省略 return {"messages": [tool_result_message]} 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__" graph = StateGraph(AgentState) graph.add_node("agent", call_model) graph.add_node("tools", call_tool) graph.set_entry_point("agent") graph.add_conditional_edges( "agent", should_continue, {"tools": "tools", "__end__": END} ) graph.add_edge("tools", "agent") app = graph.compile()这个例子仅供参考理解,生产环境我建议直接用LangGraph prebuilt里的ToolNode,不要自己解析tool_calls,容易在边界条件上翻车。但你要通过这个骨架理解一个核心要点:Agent的每一步决策都发生在节点与节点的转移之间,而图结构本身允许你插入各种“关卡”——比如统计工具调用次数、超过阈值就跳到兜底节点,或者某个工具抛异常后走重试分支。
LangGraph和LangChain的“区别”本质上就是编排粒度的区别。LangChain关心“怎么把Prompt和模型拼起来”,LangGraph关心“这个流程什么时候循环、什么时候分支、什么时候终止”。用LangGraph写Agent,与其说是写代码,不如说是在设计一个业务状态机。
3.3 记忆设计:别把整个对话历史都塞给模型
Agent的“记忆”在技术上没有魔法,它就是把历史消息作为上下文传给模型。于是很多人图省事,直接把所有聊天记录一股脑全塞进去。短期对话还行,一旦聊了二三十轮,问题就全来了:上下文变长导致响应变慢、费用变高、而且模型容易被太久远又无关紧要的细节带偏。
我通常把记忆策略分成三层,按需组合:
第一层:短期滑动窗口。只保留最近N轮消息,比如最近6到10轮。这是性价比最高的策略,大多数应用场景根本不需要更早的记忆。
from langchain_core.messages import trim_messages trimmer = trim_messages( max_tokens=2000, strategy="last", token_counter=model, include_system=True, ) trimmed_messages = trimmer.invoke(current_messages)第二层:对话摘要。当对话超过窗口上限时,把早期的消息用模型生成一段摘要,用摘要替换掉那些原始消息。这样用户上一周聊过的偏好信息还在,但不会占太多上下文空间。
第三层:向量检索。类似RAG的思路,把历史消息按语义切片、向量化存储。用户提到“上次我让你帮我整理的Python学习计划”,系统先去向量库里召回最相关的几条历史记录,再拼进上下文。这一层适合知识库类、长期陪伴类产品,对个人小项目来说可以先不做,成本收益不一定划算。
如果你用的是LangGraph,记忆会变得更简单。LangGraph的Checkpointer机制可以直接把对话状态持久化到内存或数据库里,通过thread_id区分不同会话:
from langgraph.checkpoint.memory import MemorySaver checkpointer = MemorySaver() app = graph.compile(checkpointer=checkpointer) config = {"configurable": {"thread_id": "user_session_001"}} # 下次再传相同的thread_id,Agent能恢复之前的会话状态 result = app.invoke({"messages": [...]}, config=config)这个方案比手动在外部拼历史消息要干净得多,也是我推荐你尽早习惯的写法。因为你在业务里区分“哪个用户在哪个会话”是一种刚需,与其自己在外面维护全局状态,不如把这个复杂度交给LangGraph的状态层。
4. 从Demo到能上线的Agent,最难的是这五个问题
4.1 工具调用结果不稳定,先学会把中间过程打出来
很多人在Agent接上工具的初期,都会遇到一个很迷的现象:模型明明在一次消息里产生了tool_calls,但工具执行结果返回后,模型下一轮却像失忆一样说“抱歉,我无法获取订单信息”。这种问题十有八九是消息结构不对,工具结果没有以ToolMessage的形式正确插入消息列表。
排查这类问题,第一步永远是“把中间过程打出来”。不要让Agent变成一个黑盒,你必须看到每一步模型到底收到了什么、产出了什么:
for msg in result["messages"]: print(f"[{msg.type}] content={msg.content}") if hasattr(msg, "tool_calls") and msg.tool_calls: for call in msg.tool_calls: print(" tool:", call["name"], "args:", call["args"])看到的过程越多,你对模型行为的直觉就越准。说实话,做过三个以上Agent项目之后,我养成了一个习惯:每次调试都先看messages轨迹,而不是猜“是不是Prompt写错了”。大多数“Agent不稳定”问题,最终都能在这条轨迹里找到线索。
4.2 上下文污染:工具返回什么,模型就会信什么
另一个高频坑是工具返回内容过多、太杂。模型不会主动区分“这段信息是权威数据,那段信息是界面噪音”,你给什么它就可能用什么。比如一个天气工具返回了整段HTML页面,模型可能把“页面底部版权信息里的城市名字”当成你要的答案。
解决思路就八个字:工具返回尽量干净。能返回结构化JSON,就不要塞一长段散文;能只返回三个字段,就不要返回三十个字段。我在写工具函数时,最后一步往往是一个精简字典的序列化,把跟用户问题无关的中间字段全部丢掉。还有一点,如果工具内容特别长,可以先让模型或规则做一次摘要,只把关键结果放回上下文。
4.3 无限循环调用工具:必须加“强制刹车”
这是Agent最容易让人破防的问题,没有之一。模型调用工具,工具返回一个让它不满意的结果,它再调用一次同样的工具,反复循环,直到把重试次数跑光。你盯着日志看到二十条一模一样的“get_order_status”调用,血压很难不升高。
给定两个非常实用的刹车:
一是给执行器设置最大迭代数。LangGraph里可以在invoke时传recursion_limit:
try: result = agent.invoke(input, config={"recursion_limit": 15}) except GraphRecursionError: print("Agent递归太深,已强制终止")二是从源头约束模型。在系统Prompt里直接写一条策略:如果同一工具连续两次返回相同或类似的结果,就不要再重复调用,直接基于现有信息向用户说明无法解决。虽然这不能保证模型100%遵守,但配合轮数限制,能让大多数循环在早期就停下来。
我个人的经验是:单纯靠Prompt限制效果一般,必须在执行层也有硬性兜底。就像你不能只靠“自觉”来约束一个实习生,管理层级和自动刹车机制是必须的。
4.4 API超时、限流、单点故障,Agent必须能优雅失败
生产环境里,模型API不可能永远稳定。超时、限流、网络抖动都不可避免。Agent和普通脚本不一样的是,它可能在一个决策循环里调用多次模型,任何一次失败都可能让整个任务失败。所以一定要给“模型调用失败”设计兜底。
LangChain原生支持降级模型:
from langchain_openai import ChatOpenAI from langchain_ollama import ChatOllama primary_model = ChatOpenAI( model="gpt-4o-mini", timeout=30, max_retries=1 ) fallback_model = primary_model.with_fallbacks([ ChatOllama(model="qwen2.5:7b") ])这段代码的含义是:主模型调用失败或超时后,自动切换到一个本地模型。很多团队会在关键Agent服务里配一个本地模型做降级,优先保证系统“还能动”,而不是直接给用户报错。当然降级模型的回答质量可能明显下降,但你可以在返回头里标记“当前是降级模式,结果可能不精准”,让用户有心理预期。
另外工具层的异常也要处理。工具函数内部不该轻易把异常抛给执行器,尤其是不该把原始堆栈信息直接放进上下文,模型看到一堆英文报错可能会编出更离谱的回答。建议工具函数统一捕获异常,返回给模型一段“可理解的错误说明”,例如“订单服务暂时不可用,请稍后再试”。
4.5 可观测性:Agent上线前,先给自己留一条“后路”
Agent是循环系统,排查难度比普通接口高一个数量级。普通接口出错了,看一个请求日志就够了。Agent出错了,你得看“模型第一步决定了什么、为什么会决定这个、工具返回了什么、模型看到工具结果后又怎么想”的整条链路。
所以我建议无论项目多小,都要把Agent的关键运行轨迹记录下来。最笨也最有效的方式,就是写JSONL日志,把每一步messages、tool_calls、时间戳、thread_id、以及最终回复全部落盘。
{"ts": "2025-01-01T10:00:00Z", "thread_id": "session_001", "step": "agent", "type": "ai", "content": "", "tool_calls": [{"name": "get_order_status", "args": {"order_id": "A1001"}}]} {"ts": "2025-01-01T10:00:01Z", "thread_id": "session_001", "step": "tool", "type": "tool", "name": "get_order_status", "content": "已发货,预计明天送达"}如果项目团队有预算,LangSmith是官方配套的可观测平台,可视化程度高很多。但对小团队和个人项目来说,自建JSONL日志已经完全够用。重点不是工具,而是“必须要有”。等出问题那天你就能体会到,能回放的Agent和不能回放的Agent,排查速度差距是十倍的。
4.6 常见问题速查表:照着查能省半天时间
| 现象 | 可能原因 | 排查思路 | 解决建议 |
|---|---|---|---|
| 模型调用工具后,下一轮像“失忆” | 工具结果消息没有正确插入上下文 | 打印result["messages"],看是否有类型为tool的消息 | 检查执行器组装消息逻辑,或改用prebuilt的ToolNode |
| 工具参数总传错 | 工具描述太模糊,模型靠猜 | 看tool_calls里的args实际传了什么 | 重写工具描述,增加参数枚举和“例如”样例 |
| Agent反复调用同一工具不终止 | 缺少循环制动机制 | 查看日志里同一工具被调了几次 | 设置recursion_limit;在Prompt中约束失败后不再重试 |
| 模型输出频繁不是JSON格式 | 没有用结构化输出 | 看原始model输出是否符合预期格式 | 使用with_structured_output绑定pydantic模型 |
| 聊天一长,模型错误变多 | 历史消息过多、信息噪声大 | 看发给模型的最终消息有多长 | trim_messages裁剪窗口;较旧消息用摘要压缩 |
| 本地模型工具调用始终失败 | 模型本身不支持tool calling | 查看模型是否拒生成tool_calls | 换支持工具调用的模型,或走提示词型ReAct路线 |
| 一个工具一段长文本,把上下文塞满 | 工具返回内容未裁剪 | 查看ToolMessage的字符数 | 工具只返回必要字段,长文本先摘要 |
这张表是我在实际项目中遇到频率最高的七类问题。你会发现大部分都不是LangChain接口不会用,而是对“模型在循环中的行为”理解不够。每一条背后都对应一个实打实的调试经历,把这些记下来,后续开发会顺很多。
5. 最后我的几点体会
如果只能在这篇文章里留下一句话,我想说:LangChain和LangGraph入门最快的方式,不是把所有概念都研究透彻再动手,而是立刻写一个只有一个工具的极简Agent,然后把每一步消息从头打印到尾,亲眼看着模型怎么决策、工具怎么执行、结果怎么反馈。这个最小闭环一旦在你的直觉里建立起来,后面很多抽象概念会自动变得清晰。
几个我自己踩出来的建议,你直接拿去用:
第一,版本能锁就锁。LangChain生态迭代太快,今天能跑的代码三个月后可能就因为接口变更报错,所以项目里的requirements.txt一定要写清楚版本范围。
第二,永远让模型能看到“足够的证据”而不是“全部的信息”。工具结果该精简就精简,历史消息该裁剪就裁剪,别把上下文当作无限容量的垃圾桶。
第三,Agent上线前,至少把“无限循环”和“模型API挂了”这两种情况演练一遍。你不一定要把系统做得极其复杂,但至少要保证用户在Agent摆烂时,得到的是一个友好的兜底回复,而不是一个超时报错。
说到底,LangChain只是一个工具,真正的Agent有没有用,还是看你怎么定义工具、怎么设计流程、怎么处理失败。希望这篇能帮你少走一些我走过的弯路,早点写出那个“真正能干活的Agent”。后面我还会再写几篇关于LangGraph自定义流程和RAG落地的实战文章,有具体想看的场景也可以留言告诉我。