新手也能懂!手把手教你用 LangChain 搭建第一个 AI Agent
读完这篇博客,你将能自己动手搭建一个会计算、会查天气、会统计文本的 AI 智能助手。不需要任何 Agent 开发经验,只需要会 Python 基础。
开篇:ChatGPT 能聊天,但"Agent"能干活
你可能用过 ChatGPT——问它问题,它回答。但你有没有发现一个问题:ChatGPT 不会算数(经常算错)、不知道实时天气、不能操作文件。
为什么?因为大语言模型(LLM)本质上只是一个"文字接龙机"——它只会一个字一个字地生成文本,没有手脚去执行实际操作。
而 Agent(智能体) 解决了这个问题:
┌──────────────────────────────────────────────────────┐ │ Agent (智能体) │ │ │ │ ┌───────────┐ 决策循环 ┌───────────┐ │ │ │ LLM │ ◄─────────────────► │ Tools │ │ │ │ (大脑) │ 思考→行动→观察 │ (手脚) │ │ │ └───────────┘ └───────────┘ │ │ │ │ │ │ ▼ ▼ │ │ 理解语言、推理 算数、查天气、调API │ └──────────────────────────────────────────────────────┘
Agent = LLM(大脑)+ Tools(手脚)+ 决策循环
当用户问"北京今天多少度?",Agent 的 LLM 推理出"我需要查天气",然后调用天气工具获取数据,再告诉用户结果。它不是凭空编造——是真的去查了。
一、先认识两个主角:LangChain 和 LangGraph
很多新手会被这两个名字搞晕。其实关系很简单:
🔗 LangChain —— 搭积木
LangChain 让你像串糖葫芦一样把 LLM 操作串起来:
用户输入 → 写提示词 → 调 LLM → 解析结果 → 输出
一条线走到底,顺序执行。适合简单任务,比如"翻译一段文字"。
🕸️ LangGraph —— 画流程图
LangGraph 更进一步——不再是一条直线,而是有循环、有分支的图:
┌── 需要工具?──是──→ 调工具 ──┐ │ │ 用户输入 → LLM 思考 ←───── 工具结果 ──────┘ │ └── 不需要?───→ 直接回答 → 输出
Agent 的核心就是反复循环:"思考→需要工具→调用→拿到结果→再思考→..."。这种循环正是 LangGraph 擅长的。
🎯 一句话区分
LangChain 是链——从头跑到尾。LangGraph 是图——可以循环、分叉、暂停。LangGraph 更高级。
二、项目实战:搭建你的第一个 Agent
项目结构
python/ ├── .env.example # API Key 配置模板 ├── requirements.txt # 依赖 ├── tools.py # 自定义工具(Agent 的手) ├── agent_demo.py # 主程序 └── README.md # 说明文档
Step 1:安装依赖
cd python pip install -r requirements.txt
requirements.txt 内容:
langchain>=0.3.0 langchain-openai>=0.2.0 langchain-community>=0.3.0 langgraph>=0.2.0 python-dotenv>=1.0.0
Step 2:配置 API Key
复制 .env.example 为 .env,填入你的 Key:
cp .env.example .env
推荐用 DeepSeek(性价比高,国内即用):
DEEPSEEK_API_KEY=sk-你的key DEEPSEEK_BASE_URL=https://api.deepseek.com/v1 LLM_PROVIDER=deepseek
🔗 获取 Key:DeepSeek
Step 3:定义工具(Agent 的手)
# tools.py
from langchain_core.tools import tool
import math
from datetime import datetime
@tool
def calculator(expression: str) -> str:
"""执行数学表达式计算。支持 + - * / sqrt 等。"""
try:
allowed = {k: v for k, v in math.__dict__.items()
if not k.startswith("__")}
result = eval(expression, {"__builtins__": {}}, allowed)
return f"计算结果: {result}"
except Exception as e:
return f"计算出错: {e}"
@tool
def get_current_datetime(format_str: str = "%Y-%m-%d %H:%M:%S") -> str:
"""获取当前日期和时间。"""
return f"当前时间: {datetime.now().strftime(format_str)}"
@tool
def text_stats(text: str) -> str:
"""统计文本的字数、字符数、行数。"""
lines = text.split("\n")
words = text.split()
return f"字符数: {len(text)}, 字数: {len(words)}, 行数: {len(lines)}"
@tool
def get_weather(city: str) -> str:
"""查询指定城市的天气(模拟数据,仅供学习)。"""
mock_db = {
"北京": "☀️ 晴天, 32°C",
"上海": "🌧️ 小雨, 28°C",
"深圳": "⛅ 多云, 30°C",
}
return mock_db.get(city, f"暂无 {city} 的天气数据")
# ⭐ 汇总所有工具——Agent 只会用这个列表里的工具
ALL_TOOLS = [calculator, get_current_datetime, text_stats, get_weather]
关键点:函数的
docstring(三引号里的描述)就是告诉 LLM "这个工具能干什么"。LLM 靠读这段文字来决定什么时候用哪个工具。
Step 4:创建 Agent(两种方式)
方式一:LangChain 经典模式(推荐新手)
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# ① 写提示词
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个乐于助人的 AI 助手。回答请用中文。"),
MessagesPlaceholder(variable_name="chat_history"), # 对话历史
("human", "{input}"), # 用户输入
MessagesPlaceholder(variable_name="agent_scratchpad"), # 思考过程
])
# ② 创建 agent
agent = create_tool_calling_agent(llm=llm, tools=ALL_TOOLS, prompt=prompt)
# ③ 包装成执行器(负责"思考→行动→观察"的循环)
agent_executor = AgentExecutor(
agent=agent,
tools=ALL_TOOLS,
verbose=True, # 打印内部思考过程(建议新手开启)
max_iterations=10, # 最多调 10 轮工具,防止死循环
)
方式二:LangGraph 现代模式(高级)
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(
model=llm,
tools=ALL_TOOLS,
state_modifier=SystemMessage(content="你是一个乐于助人的 AI 助手。"),
)
一行就搞定!循环、容错、历史管理全自动。
Step 5:运行
python agent_demo.py
看到的菜单:
╔══════════════════════════════════════════════════════════════╗ ║ 🚀 LangChain Agent 入门 Demo ║ ║ ║ ║ 请选择运行模式: ║ ║ 1. 交互模式 - 经典 Tool Calling Agent (推荐初学者) ║ ║ 2. 交互模式 - 现代 LangGraph ReAct Agent ║ ║ 3. 一次性示例 - 经典模式 ║ ║ 4. 一次性示例 - ReAct 模式 ║ ║ 5. 调试模式 - 查看 Agent 内部思考过程 ║ ║ 0. 退出 ║ ╚══════════════════════════════════════════════════════════════╝
选 1 就能在终端跟 Agent 聊天了:
👤 你: 北京天气怎么样?
🔍 Agent 思考中...
----------------------------------------
> 进入新的 AgentExecutor 链...
> 思考:用户问北京天气,我需要调用 get_weather 工具
> 调用工具: get_weather
参数: {"city": "北京"}
> 工具返回: 北京 天气: ☀️ 晴天, 32°C
> 思考:我已经拿到天气数据,可以回答用户了
🤖 Agent: 北京今天是晴天,气温 32°C,适合出门但注意防晒哦!
三、两种方式的代码对比
| 方式一:LangChain 经典 | 方式二:LangGraph 现代 | |
|---|---|---|
| 创建 agent | 3 步(提示词→agent→executor) | 1 步(create_react_agent) |
| 提示词 | 手动写 3 个占位槽 | 直接传 SystemMessage |
| 对话历史 | 自己维护 list,每轮手动 append | 传 thread_id,自动管理 |
| 调用方式 | invoke({"input": ..., "chat_history": ...}) |
invoke({"messages": [...]}, config=...) |
| 取结果 | result["output"] |
result["messages"][-1].content |
| 灵活性 | 低——黑盒循环 | 高——可插节点、加分叉 |
| 代码量 | 多 | 少 |
| 适合 | 学习原理 | 实际干活 |
四、记忆(Memory)—— Agent 怎么记住聊过什么
想象你跟一个人聊天,每一句话他都会忘记你上一句说了什么——这聊不下去。Agent 也一样,需要记忆来支持多轮对话。
LangChain 经典模式:自己手动管,存内存
在方式一中,"记忆"就是一个普通的 Python 列表,存在进程内存里:
# 就是一个 list,你手动维护
chat_history = []
# 每轮对话结束后,自己往里塞
chat_history.append(HumanMessage(content=user_input))
chat_history.append(AIMessage(content=result["output"]))
# 下一轮调用时,手动传进去
result = agent_executor.invoke({
"input": user_input,
"chat_history": chat_history, # ← 不传就失忆
})
特点:
-
存在进程内存里,程序一关就没了
-
你得手动 append、手动传,忘了传就"失忆"
-
只有一个全局列表,没法同时服务多个用户
LangGraph 默认:自动管,但也存在内存
方式二(LangGraph)默认也是存内存,但管理是全自动的——你不需要手动维护列表:
config = {"configurable": {"thread_id": "demo-session"}}
# ↑ 靠这个 ID 自动追踪上下文
result = agent.invoke(
{"messages": [HumanMessage(content=user_input)]},
config=config, # ← 自动读写历史,不用你管
)
但注意:默认情况下,重启程序记忆还是丢了——因为它也是纯内存。
LangGraph + Checkpointer:一行代码,记忆永久保存
LangGraph 真正强的地方在于可以挂载持久化存储,改一行代码就行:
from langgraph.checkpoint.memory import MemorySaver # 内存(重启就丢)
from langgraph.checkpoint.sqlite import SqliteSaver # SQLite(存硬盘,重启还在)
# 创建 agent 时挂上 checkpointer
agent = create_react_agent(
model=llm,
tools=tools,
checkpointer=SqliteSaver.from_conn_string("memory.db"), # ← 持久化!
)
# 重启后,同一个 thread_id 的历史还在!
不同 thread_id 的记忆互相隔离,天然支持多用户:
# 用户 A 的记忆
agent.invoke({"messages": [HumanMessage("我叫小明")]},
config={"configurable": {"thread_id": "user-a"}})
# 用户 B 的记忆(完全独立)
agent.invoke({"messages": [HumanMessage("我叫小红")]},
config={"configurable": {"thread_id": "user-b"}})
一张表看清三种记忆方案
| LangChain 经典 | LangGraph 默认 | LangGraph + Checkpointer | |
|---|---|---|---|
| 存储位置 | 内存 list | 内存 | 内存 / SQLite / Postgres |
| 谁管理 | 你手动 append | 自动 | 自动 |
| 重启后还在? | ❌ 丢 | ❌ 丢 | ✅ 在(用 SQLite 等) |
| 多用户隔离 | ❌ 做不到 | ✅ 靠 thread_id |
✅ 靠 thread_id |
| 适合场景 | 学习、单次对话 | 学习、单次会话 | 生产环境 |
一句话:LangChain 经典是你拿个 list 自己记,程序关了就忘;LangGraph 自带记忆框架,默认也是内存,但改一行代码就能把记忆写到硬盘上,永久保存。
五、Agent 决策过程详解
当你问"3 的平方根加 5 等于多少?",Agent 内部发生了什么:
Step 1 — LLM 分析
"用户问的是数学题,我需要用 calculator 工具来计算"
Step 2 — 工具调用
→ calculator("sqrt(3) + 5")
Step 3 — 工具返回
← "计算结果: 6.732050807568877"
Step 4 — LLM 综合
"结果是 6.732...,我告诉用户"
Step 5 — 输出 ✅
"3 的平方根加 5 的结果约等于 6.732"
整个过程并不神秘——就是一个反复的"思考→行动"循环,直到 LLM 觉得能回答了为止。
六、进阶:如何扩展你的 Agent
添加新工具,只需 3 步
# ① 在 tools.py 里定义
@tool
def translate(text: str, target_lang: str = "中文") -> str:
"""将文本翻译成目标语言"""
# 你的翻译逻辑
return translated_text
# ② 注册到工具列表
ALL_TOOLS.append(translate)
Agent 自动就能用新工具了,不用改 Agent 本身的任何代码。
还有现成的社区工具
from langchain_community.tools import DuckDuckGoSearchRun # 网络搜索 from langchain_community.tools import WikipediaQueryRun # 维基百科
七、常见问题
Q:API Key 要钱吗? DeepSeek 新用户有免费额度,国内可以直接用,不用担心。
Q:能在 PyCharm 里跑吗? 完全可以。PyCharm 直接打开项目文件夹,装好依赖后右键 agent_demo.py → Run,在 Run 窗口就能交互。
Q:verbose=True 输出太多怎么办? 改成 verbose=False 就行。但建议新手先开着,能看到 Agent 的"思考过程"非常有助于理解原理。
Q:Agent 调工具总出错怎么办? 每个工具函数里用 try/except 包住,返回友好的错误信息。Agent 会读错误信息然后自己调整重试。
八、学习路线图
✅ 第一步:理解 Agent = LLM + Tools + 循环 (你现在在这里) ✅ 第二步:跑通 Demo,跟 Agent 聊几句 📖 第三步:自己写一个新工具加进去 📖 第四步:改系统提示词,换 Agent 的"人设" 📖 第五步:用 LangGraph 构建更复杂的工作流 📖 第六步:学 RAG(检索增强生成)——让 Agent 能读文件 📖 第七步:多 Agent 协作
九、常见面试题
如果你去面试 AI Agent 开发相关的岗位,下面这些题大概率会碰到。
Q1:什么是 Agent?它和直接调用 LLM 有什么区别?
回答:
Agent = LLM + Tools + 决策循环。直接调用 LLM 只能做文本生成——你问它"今天天气怎么样",它只能编一个答案。Agent 则会在 LLM 推理出"我需要查天气"之后,真的去调用天气 API 获取实时数据,再综合返回。
核心区别:LLM 只有脑子,Agent 有脑子 + 手脚。
Q2:LangChain 和 LangGraph 是什么关系?什么时候用哪个?
回答:
都来自同一家公司(LangChain Inc),定位不同:
-
LangChain:链式调用框架。适合"一次性"任务(翻译→摘要→分类),流程是线性的。
-
LangGraph:图式状态机框架。适合需要循环、分支、条件判断的任务(如 Agent 的"思考→行动→观察"循环)。
简单项目用 LangChain 就够了;需要复杂工作流、多步推理、人机交互时用 LangGraph。
Q3:Agent 怎么知道该调用哪个工具?
回答:
靠工具的描述(description)。当你用 @tool 装饰器定义工具时,函数的 docstring 会被当作工具的"说明书"注入到 LLM 的提示词里。LLM 读取所有工具的说明书,根据用户的问题判断该用哪个、参数填什么。
例如用户问"北京天气?",LLM 看到 get_weather 的描述是"查询指定城市的天气",就决定调用它,并填入参数 city="北京"。
Q4:Tool Calling Agent 和 ReAct Agent 有什么区别?
回答:
| Tool Calling | ReAct | |
|---|---|---|
| 决策方式 | LLM 原生 function calling | 强制 LLM 按"思考→行动→观察"模板输出 |
| 提示词 | 不需要特殊格式 | 需要 ReAct 格式的提示词 |
| 灵活性 | 依赖模型能力 | 格式固定,兼容性更好 |
| 底层实现 | create_tool_calling_agent (LangChain) |
create_react_agent (LangGraph) |
Tool Calling 依赖模型自身支持 function calling(如 GPT-4、DeepSeek),更简洁。ReAct 模式通过提示词工程强制推理链,对模型要求更低,且过程可读性更强。
Q5:Agent 的记忆是怎么实现的?如何持久化?
回答:
分三层:
-
没有记忆:每次调用都是独立的,Agent 不知道上一轮聊了什么
-
内存记忆:用 Python list 或 LangGraph 默认的 MemorySaver,存进程内存里。程序重启就丢
-
持久化记忆:LangGraph 挂载 SqliteSaver / PostgresSaver 等 Checkpointer,历史存硬盘,重启不丢。通过
thread_id隔离不同用户
# 持久化记忆示例
from langgraph.checkpoint.sqlite import SqliteSaver
agent = create_react_agent(
model=llm, tools=tools,
checkpointer=SqliteSaver.from_conn_string("memory.db"),
)
Q6:Agent 陷入死循环怎么办?有哪些防护手段?
回答:
三种防护:
-
max_iterations:LangChain 的
AgentExecutor可以设最大迭代次数,超过就强制停止 -
超时控制:给整个 invoke 加超时,避免无限等待
-
工具设计兜底:工具函数内部做好异常捕获,确保每次都返回有效结果(哪怕是报错信息),让 Agent 能继续往下走而不是卡住
AgentExecutor(
agent=agent, tools=tools,
max_iterations=10, # ← 最多调 10 轮工具
)
LangGraph 内置了递归次数限制,默认 25 步。
Q7:什么是 RAG?它和 Agent 怎么结合?
回答:
RAG(检索增强生成) = 先从知识库检索相关内容,再把检索结果和用户问题一起给 LLM,让 LLM 基于真实资料回答。
和 Agent 结合后,Agent 不再是"凭空回答"或"只能调计算/天气工具",而是多了一个能查资料的工具。流程变成:
用户提问 → Agent 分析 → 需要查资料?→ 调用检索工具查知识库
→ 需要计算? → 调用计算器
→ 不需要工具?→ 直接回答
这被称为 RAG Agent,能处理私域知识 + 实时操作。
Q8:@tool 装饰器背后做了什么?
回答:
@tool 装饰器做了三件事:
-
读取函数的 docstring,作为工具的描述信息
-
读取函数的参数签名,自动生成 JSON Schema,告诉 LLM 工具需要哪些参数、什么类型
-
包装成
BaseTool子类,赋予invoke()/ainvoke()等 Agent 能调用的接口
本质上就是把一个普通 Python 函数,变成 LLM 能理解和调用的标准化工具。
@tool
def calculator(expression: str) -> str:
"""执行数学表达式计算。"""
...
# 等价于手动写:
class CalculatorTool(BaseTool):
name = "calculator"
description = "执行数学表达式计算。"
args_schema = {"expression": {"type": "string"}}
def _run(self, expression: str) -> str:
...
Q9:temperature 参数对 Agent 有什么影响?应该设多少?
回答:
-
temperature = 0:LLM 输出完全确定性。同一输入永远同一输出。Agent 场景推荐设为 0,因为 Agent 需要稳定地判断"用哪个工具、传什么参数",不能发挥创意
-
temperature = 0.7~1.0:输出变随机。适合写作、头脑风暴等创意任务,但 Agent 可能"想歪"——该调计算器时却自己瞎编一个数
面试标准答案:Agent / 工具调用场景设 0 或接近 0;创意生成类场景设 0.7 以上。
Q10:如果要做一个能操作数据库的 Agent,你会怎么设计?
回答:
-
定义数据库工具:用
@tool包装 SQL 查询函数,描述清楚工具能查什么表、返回什么字段 -
安全第一:工具内部做 SQL 注入防护(参数化查询)、限制只读操作(SELECT 不放行 DROP/DELETE)
-
结果截断:查询结果太多时截断,避免撑爆 LLM 上下文窗口
-
用 LangGraph:复杂多步查询(先查A表 → 根据结果查B表)用 LangGraph 的图和 checkpointer 管理状态
-
记忆持久化:挂 SqliteSaver/PostgresSaver,保证多轮分析不丢上下文
@tool
def query_database(sql: str) -> str:
"""执行 SELECT 查询。可用表: users(id, name), orders(id, user_id, amount)"""
# 安全检查:只允许 SELECT
if not sql.strip().upper().startswith("SELECT"):
return "错误:只允许 SELECT 查询"
# 参数化查询、结果截断...
cursor.execute(sql)
return str(cursor.fetchmany(100)) # 最多返回 100 行
写在最后
Agent 开发听起来高大上,但核心思想非常简单:
-
LLM 负责理解和推理
-
Tools 负责干活
-
一个循环把它们串起来
LangChain 帮你快速上手,LangGraph 帮你走得更远。希望这篇博客帮你迈出了第一步。
📂 完整代码在文中的
python/目录下,复制就能跑。🔗 资源链接:
写于 2026 年 7 月,一个还不错的开始。
更多推荐


所有评论(0)