从一次诡异的“模型不响应”说起
上周帮团队排查一个 LangChain Agent 的 bug,现象很诡异:同样的代码,在 A 同事的 Mac 上跑得飞起,在 B 同事的 Windows 上就卡在 AgentExecutor 的 invoke 方法里,日志最后一行永远是 Entering new AgentExecutor chain...,然后就没有然后了。查了三天,最后发现是 openai 库版本不一致——A 同事用的是 0.28.0,B 同事是 1.6.0,而 LangChain 在 0.1.0 之后对 OpenAI 的调用接口做了大改。这种“环境地狱”问题,做 AI Agent 开发的人迟早都会遇到。所以这篇笔记,咱们就从最基础的环境搭建开始,把坑先踩一遍。
环境搭建:别信官方文档的“一行命令”
官方文档说 pip install langchain 就完事了,但实际开发中你至少需要装这些:
# 核心库,注意版本锁定
pip install langchain==0.1.12
pip install langchain-community==0.0.28
pip install langchain-openai==0.1.3
# 工具链,缺一个都可能报“ModuleNotFoundError”
pip install python-dotenv==1.0.0 # 管理 API Key,别硬编码在代码里
pip install numexpr==2.8.7 # 计算器工具依赖,踩过坑
pip install wikipedia==1.4.0 # 维基百科工具,测试用
这里踩过坑:langchain-community 和 langchain-openai 是后来拆分的包,如果你只装了 langchain,调用 from langchain.agents import load_tools 时会报 ImportError: cannot import name 'load_tools' from 'langchain.agents'。别问我怎么知道的,问就是 debug 到凌晨两点。
环境变量配置,我习惯在项目根目录建一个 .env 文件:
OPENAI_API_KEY=sk-你的key
OPENAI_BASE_URL=https://api.openai.com/v1 # 如果你用代理或国内中转,这里改地址
然后在代码里:
from dotenv import load_dotenv
load_dotenv() # 别这样写:os.environ["OPENAI_API_KEY"] = "xxx"
别这样写:把 API Key 硬编码在代码里,然后不小心 push 到 GitHub。我见过有人因此被 AI 服务商封号,还一脸无辜地说“我只是想分享代码”。
第一个 Agent:让 AI 学会用计算器
先别急着搞复杂的 ReAct 或 Plan-and-Execute,我们从最简单的“工具调用”开始。目标是让 LLM 能调用一个计算器工具,而不是自己瞎算数学题。
from langchain_openai import ChatOpenAI
from langchain.agents import initialize_agent, AgentType
from langchain_community.tools import Tool
# 初始化模型,这里用 gpt-3.5-turbo 就够了,别浪费 gpt-4
llm = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0, # 做 Agent 时 temperature 设 0,减少随机性
verbose=True # 打开调试日志,新手必开
)
# 自定义一个计算器工具
def calculator(expression: str) -> str:
"""计算数学表达式,例如 '2 + 3 * 4'"""
try:
# 这里用 eval 有安全风险,但演示够用
result = eval(expression, {"__builtins__": {}}, {})
return str(result)
except Exception as e:
return f"计算错误:{str(e)}"
calc_tool = Tool(
name="计算器",
func=calculator,
description="用于执行数学计算,输入应为数学表达式,如 '2 + 3 * 4'"
)
# 初始化 Agent
agent = initialize_agent(
tools=[calc_tool],
llm=llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
handle_parsing_errors=True # 这个参数很重要,后面说
)
# 测试
response = agent.invoke("计算 12345 * 67890 的结果是多少?")
print(response["output"])
运行后你会看到类似这样的日志:
> Entering new AgentExecutor chain...
我需要计算 12345 * 67890 的结果。
Action: 计算器
Action Input: 12345 * 67890
Observation: 838102050
Thought: 我得到了计算结果。
Final Answer: 12345 * 67890 的结果是 838,102,050。
这里踩过坑:第一次跑的时候,模型可能输出 Action: 计算器 但后面没有 Action Input,导致解析失败。handle_parsing_errors=True 就是用来兜底的——当模型输出格式不对时,Agent 会重试而不是直接崩溃。
深入理解 Agent 的“思考-行动-观察”循环
上面那个例子看起来简单,但背后是 LangChain 的 ReAct 模式在运作。你可以把 Agent 想象成一个“会思考的机器人”:
- 思考(Thought):模型分析当前问题,决定下一步做什么
- 行动(Action):调用某个工具,传入参数
- 观察(Observation):获取工具返回的结果
- 循环:根据观察结果,再次思考,直到得出最终答案
这个循环在日志里看得一清二楚。新手最容易犯的错误是:以为 Agent 是一次性输出答案。实际上,Agent 可能会调用多次工具,比如先查维基百科,再用计算器算结果,最后整合答案。
调试技巧:让 Agent 的“思考过程”可视化
verbose=True 是调试利器,但有时候日志太多,你只想看关键步骤。可以这样:
import logging
logging.basicConfig(level=logging.INFO)
# 只打印 INFO 级别以上的日志,忽略 DEBUG 级别的工具调用细节
更高级的调试方式是用 langchain.callbacks:
from langchain.callbacks import StdOutCallbackHandler
handler = StdOutCallbackHandler()
agent = initialize_agent(
...,
callbacks=[handler]
)
这样你可以自定义回调函数,比如把 Agent 的思考过程写入文件,方便复盘。
常见翻车现场及修复
1. 模型输出格式不对
ValueError: Could not parse LLM output: `I need to calculate...`
原因:模型没有按照 Action: ...\nAction Input: ... 的格式输出。
修复:加 handle_parsing_errors=True,或者换一个更稳定的模型(比如从 gpt-3.5-turbo 换成 gpt-4)。
2. 工具返回结果太长
Error: String too long for LLM context window
原因:工具返回了超长文本(比如维基百科全文),超过了模型的上下文窗口。
修复:在工具函数里做截断,或者用 max_tokens 限制输出。
3. Agent 陷入死循环
Agent stopped due to max iterations (15) reached.
原因:模型反复调用同一个工具,无法收敛到最终答案。
修复:减少 max_iterations 参数(默认15),或者优化工具的 description,让模型更清楚什么时候该停止。
个人经验:从“能用”到“好用”的三个建议
-
工具的 description 比工具本身更重要。模型是通过 description 来决定是否调用工具的。别写“这是一个计算器”,要写“用于执行数学计算,输入应为数学表达式,如 ‘2 + 3 * 4’”。描述越具体,模型调用越准确。
-
永远不要信任模型的输出格式。即使是最强的模型,偶尔也会输出不符合规范的 JSON 或 Action 格式。
handle_parsing_errors=True和max_retries是保命符。 -
从最简单的 Agent 开始,逐步增加复杂度。先跑通一个工具,再加第二个,最后才考虑多工具协作。我见过太多人一上来就搞 ReAct + 记忆 + 多工具,结果 debug 到怀疑人生。
最后说一句:LangChain 的版本迭代很快,这篇笔记基于 0.1.12 版本。如果你看到报错,先检查版本是否匹配。别问我为什么知道——就在写这篇文章的时候,LangChain 又发布了 0.2.0 的 beta 版,API 又变了。
3044

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



