📌《LangChain + RAG 全链路学习笔记》系列 · 第 1 篇(共 13 篇)
作者:AvA
环境:Python 3.11 +LangChain 1.x⚠️看之前先注意一件事:网上大量 LangChain 教程还是 0.3.x 版本,导入路径已经变了,照抄会直接
ModuleNotFoundError: No module named 'langchain'。本系列每一处都标注了新旧路径差异,代码全部本机实测跑通。
这一章解决什么问题
不解决任何业务问题,只回答一件事:
怎么用一行invoke把一句话发给大模型,并把回答拿出来。
听起来简单,但这里有一个国内开发者绕不开的问题:模型厂商这么多,难道每换一家就要换一套 SDK、改一遍代码?
这一章给出了答案。
核心结论:OpenAI 兼容协议
国内主流厂商——DeepSeek、通义、月之暗面、智谱——都提供了「OpenAI 兼容接口」:它们的 API 形态和 OpenAI 一模一样,只是域名不同。
所以只要改一个base_url,同一个ChatOpenAI类就能调所有厂商,不需要为每家装一个专用包。
importosfromlangchain_openaiimportChatOpenAI llm=ChatOpenAI(model="deepseek-chat",base_url="https://api.deepseek.com/v1",# ← 精髓就这一行api_key=os.environ.get("DEEPSEEK_API_KEY"),temperature=0,)课程里演示了四种接法,结论如下:
| 接法 | 写法 | 评价 |
|---|---|---|
| 直连 OpenAI | ChatOpenAI(model=...) | 最标准 |
| 厂商专用包 | langchain_deepseek.ChatDeepSeek | 要额外装包,且只认一家 |
| 社区包 | langchain_community的ChatTongyi | 需要dashscope依赖 |
| 兼容协议 | ChatOpenAI+base_url | ⭐推荐,一个类通吃 |
最小可运行代码
importosfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAI load_dotenv()# ① 必须在创建客户端之前llm=ChatOpenAI(model="deepseek-chat",base_url="https://api.deepseek.com/v1",api_key=os.environ["DEEPSEEK_API_KEY"],# 用中括号:拼错会当场 KeyErrortemperature=0,)response=llm.invoke("你是谁?")# ② 返回 AIMessage 对象print(response.content)# ③ 要 .content 才是正文两个必须记住的约定:
load_dotenv()必须在创建客户端之前调用—— 客户端在构造的那一刻就去读环境变量。- 拿到的是
AIMessage对象,不是字符串—— 要.content取正文。
⚠️ 我踩到的坑
1.load_dotenv()放错位置,报错却和.env无关
放到ChatOpenAI(...)后面 → key 读不到 → 报鉴权错误(AuthenticationError),而不是「找不到 .env 文件」。报错信息会把你往错误的方向带。
2.os.environ.get("XXX")拼错变量名不报错
它会静默返回None,一路传到 API 那里才炸,表现为鉴权失败。
想立刻发现,用方括号:os.environ["DEEPSEEK_API_KEY"]—— 键不存在直接KeyError,当场暴露。
3.import成功 ≠ 能用
ChatTongyi的dashscope是在构造时才 import 的。所以:
fromlangchain_community.chat_modelsimportChatTongyi# ✅ 成功llm=ChatTongyi(...)# ❌ 这里才 ImportError这是个思维陷阱:别人说「装了这个包」,不代表你 import 一下就能用。
4.model=还是model_name=?
ChatOpenAI两个都能用(字段真名是model_name,model是别名),但ChatDeepSeek/ChatTongyi只认model。
👉统一写model=最安全。
5. 忘了.content
直接print(response)会打出一坨:
content='...' additional_kwargs={} response_metadata={} ...⚠️ 1.x 和 0.3.x 的导入路径差异
这条是本篇相对网上教程最大的不同:
# 网上 0.3.x 教程:fromlangchain.chat_modelsimportChatOpenAI# 本机 1.x 实际:fromlangchain_openaiimportChatOpenAI# ⭐ 单独成包了照抄老教程会直接ModuleNotFoundError: No module named 'langchain'。
补充 ①:我为什么选了 DeepSeek
主要还是控制成本。我平时用的 Claude Code,里面接的就是我自己配的deepseek-v4-flash,加上最近 DeepSeek 出了 v4.1,价格也往下调了(截至 2026 年 9 月)。
补充 ②:我实际遇到的第一个报错
第一个错误就是这个ModuleNotFoundError: No module named 'langchain',这个是因为课程使用的是老的版本,他和新版本之间的导入不同。
第二个错误是我导入的时候,包的大小写错了一处。这个错误其实会报错,但从报错信息里看不出是大小写的问题 —— 我盯着代码看了半天就是没发现,最后问 AI 才找出来。人眼对大小写是真的不敏感。
补充 ③:为什么改一个base_url就能调所有厂商
我个人觉得这个东西可能和我现在使用的这个 Claude 比较像,就是这个 openai 设置的是一个通用接口,所以只需要提供模型商的网址,就是 base_url 就可以了,然后每个模型商会给自己的模型提供专用的接口,这个可能是比这个统一的接口更加适配,包括速度快,损耗少啥的。
✅ 自测三问
ChatOpenAI(...).invoke("你好")返回什么类型?要打印纯文本该怎么写?AIMessage是一个消息对象,不是字符串。正文在它的.content里,所以打印纯文本要写print(response.content)。我一开始以为
print(response)会报错,其实不会—— 它会把content、response_metadata、usage_metadata等 9 个字段一股脑全打出来。不报错反而更坑:报错你会去查,不报错你只会纳闷「这什么玩意儿」。顺带一提,那一坨不是 JSON,是 Python 对象的 repr —— 看引号就能分:单引号
'...'是 Python,双引号"..."才是 JSON。真要存它或者传给前端,才需要json.dumps(response.model_dump())。想用
ChatOpenAI调 DeepSeek,最少传哪几个参数?model、base_url、api_key。把
load_dotenv()挪到ChatOpenAI(...)后面,报错信息为什么看起来跟.env无关?因为那时候环境变量还没来得及加载,key 读到的是
None,一路传到 API 那里才以「鉴权失败」的形式炸出来。
📖本系列共 13 篇,下一篇:02 模型与消息 ——LLM和ChatModel到底差在哪
笔记同步更新在 GitHub:
github.com/MaDai66/langchain-rag-notes
代码基于 LangChain 1.x 实测,有问题欢迎评论区交流。