一、前言
最近系统学习了 LangChain 框架,并通过 5 个由浅入深的 Demo 完成了实战练习。本文记录完整的实践过程、核心概念理解以及踩坑经验,希望能帮助同样在学习 LangChain 的读者快速上手。
本文所有代码已开源:[GitHub 仓库链接](https://github.com/binbin3828/langchain_demo.git "GitHub 仓库链接")
技术选型
| 组件 | 选型 | 说明 |
|---|---|---|
| LLM 框架 | LangChain | 当前最流行的 LLM 应用开发框架 |
| 模型 | DeepSeek V4 Pro | 国产大模型,性价比高,兼容 OpenAI API |
| 向量库 | ChromaDB | 轻量级本地向量数据库 |
| 嵌入模型 | sentence-transformers | 本地运行,无需 API Key |
二、项目概览
整个项目包含 5 个 Demo,覆盖了 LangChain 最核心的功能:
| Demo | 核心功能 | 关键组件 |
|---|---|---|
| demo_translation | 翻译链 | ChatPromptTemplate, ChatOpenAI, StrOutputParser |
| demo_getjson | 结构化 JSON 输出 | PydanticOutputParser, BaseModel |
| demo_getlist | 列表输出 | CommaSeparatedListOutputParser |
| demo_talk | 多轮对话记忆 | MessagesPlaceholder, RunnableWithMessageHistory |
| rag | 知识库问答(RAG) | Chroma, HuggingFaceEmbeddings, TextLoader |
三、环境准备
3.1 安装依赖
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install langchain langchain-openai langchain-community langchain-text-splitters
pip install chromadb sentence-transformers pydantic python-dotenv httpx
3.2 配置 API Key
在项目根目录创建 .env 文件:
DEEPSEEK_API_KEY=sk-your-key-here
3.3 初始化模型
所有 Demo 共用同一个模型初始化逻辑:
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="deepseek-v4-pro",
openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
)
因为 DeepSeek 兼容 OpenAI 的 API 格式,所以可以直接用
langchain-openai包,只需改base_url和api_key即可。
四、Demo 详解
Demo 1:翻译链 --- 理解 LCEL 核心
文件 :demo_translation.py
这是最简单的入门示例,展示了 LangChain 最核心的 LCEL(LangChain Expression Language) 管道操作。
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
system_template = "你是一个专业的翻译官,请将用户输入的文本从 {source_lang} 翻译成 {target_lang}。"
human_template = "{text}"
prompt_template = ChatPromptTemplate.from_messages([
("system", system_template),
("human", human_template)
])
parser = StrOutputParser()
# LCEL 管道:Prompt → Model → Parser
translation_chain = prompt_template | model | parser
result = translation_chain.invoke({
"source_lang": "中文",
"target_lang": "英文",
"text": "你好,今天天气不错。"
})
print(result) # Hello, the weather is nice today.
关键理解
LCEL 的 | 运算符 是 LangChain 的灵魂。a | b | c 意味着数据从左到右依次流过每个组件:
输入字典 → ChatPromptTemplate(填充模板)→ ChatOpenAI(调用 LLM)→ StrOutputParser(提取文本)→ 最终结果
StrOutputParser 的作用是把 LLM 返回的 AIMessage 对象转成纯字符串,省去了手动提取的麻烦。
Demo 2:结构化 JSON 输出
文件 :demo_getjson.py
很多场景下我们需要 LLM 输出结构化数据(JSON),而不是只有文本。LangChain 提供了 PydanticOutputParser,配合 Pydantic 模型定义,可以自动解析和校验输出。
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
# 1. 用 Pydantic 定义想要的 JSON 结构
class MovieInfo(BaseModel):
title: str = Field(description="电影名")
year: int = Field(description="上映年份")
director: str = Field(description="导演")
parser = PydanticOutputParser(pydantic_object=MovieInfo)
# 2. 提示词中加入格式说明
prompt = ChatPromptTemplate.from_template(
"请用 JSON 提供电影《{movie_name}》的基本信息。\n{format_instructions}"
)
chain = prompt | model | parser
result = chain.invoke({
"movie_name": "盗梦空间",
"format_instructions": parser.get_format_instructions()
})
print(result) # title='盗梦空间' year=2010 director='克里斯托弗·诺兰'
print(result.model_dump_json(indent=2, ensure_ascii=False))
运行结果
{
"title": "盗梦空间",
"year": 2010,
"director": "克里斯托弗·诺兰"
}
关键理解
parser.get_format_instructions() 会自动生成一段格式说明文本,插入到提示词中告诉 LLM 应该输出什么结构。比如它会生成类似这样的指令:
你输出的内容应该格式化为符合以下 JSON 模式的 JSON 实例...
{
"title": "电影名 (string)",
"year": "上映年份 (int)",
"director": "导演 (string)"
}
这就是 "Format Instructions Injection" 模式------在提示词中嵌入格式约束,让 LLM 按照预期输出。
Demo 3:列表输出
文件 :demo_getlist.py
from langchain_core.output_parsers import CommaSeparatedListOutputParser
parser = CommaSeparatedListOutputParser()
prompt = ChatPromptTemplate.from_template(
"列出5个关于{topic}的著名电影,中文回答。\n{format_instructions}"
)
chain = prompt | model | parser
result = chain.invoke({
"topic": "人工智能",
"format_instructions": parser.get_format_instructions()
})
print(result) # ['银翼杀手', '黑客帝国', '人工智能', '她', '机械姬']
print(type(result)) # <class 'list'>
CommaSeparatedListOutputParser 会把 LLM 输出的逗号分隔文本自动解析成 Python 列表,适合标签生成、关键词提取等场景。
Demo 4:多轮对话记忆
文件 :demo_talk.py
没有记忆的 LLM 是"金鱼"------每次对话都是独立的。LangChain 提供了多种记忆机制,这里使用 RunnableWithMessageHistory + InMemoryChatMessageHistory 实现多轮对话。
from langchain_core.prompts import MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 1. 提示词中预留历史消息位置
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个友好的AI助手。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
])
chain = prompt | model | StrOutputParser()
# 2. 会话历史存储
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
# 3. 包装链,注入记忆能力
chain_with_memory = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
# 4. 对话
session_id = "user123"
while True:
user_input = input("你: ")
if user_input.lower() in ["退出", "quit", "exit"]:
break
response = chain_with_memory.invoke(
{"input": user_input},
config={"configurable": {"session_id": session_id}}
)
print(f"AI: {response}")
关键理解
- MessagesPlaceholder:就像一个占位符,运行时会用真正的历史消息列表替换掉它
- session_id:支持多用户/多会话隔离,每个 session_id 拥有独立的聊天记录
- InMemoryChatMessageHistory :存在内存中,重启即丢失。生产环境可以替换为数据库持久化方案(如
PostgresChatMessageHistory)
对话效果
你: 我叫小明
AI: 你好小明!有什么我可以帮你的吗?
你: 我刚才说了什么?
AI: 你告诉我你叫小明。
看到没?AI 记得上一轮对话的内容,这就是记忆的作用。
Demo 5:RAG 知识库问答
文件 :rag.py
RAG(Retrieval-Augmented Generation)是目前最实用的 LLM 应用模式之一。核心思想:先检索相关知识,再让 LLM 基于检索结果作答,有效解决 LLM 知识过时和幻觉问题。
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
# 1. 加载文档
loader = TextLoader("knowledge.txt", encoding="utf-8")
documents = loader.load()
# 2. 文本分块
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=0)
docs = text_splitter.split_documents(documents)
# 3. 向量化并存入向量库
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vectorstore = Chroma.from_documents(docs, embeddings)
retriever = vectorstore.as_retriever()
# 4. 构建 RAG 链
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
answer = rag_chain.invoke("小明的猫叫什么名字?")
print(answer) # 旺财
RAG 完整流程
┌─────────────┐
│ knowledge.txt│
└──────┬──────┘
│ TextLoader
▼
┌─────────────┐
│ Documents │
└──────┬──────┘
│ CharacterTextSplitter
▼
┌─────────────┐
│ Chunks │
└──────┬──────┘
│ HuggingFaceEmbeddings
▼
┌─────────────┐
│ ChromaDB │
└──────┬──────┘
│ 相似度检索
▼
┌──────────────────────────┐
│ 检索到的相关文档片段 │
└────────────┬─────────────┘
│ 注入到 Prompt 的 {context}
▼
┌──────────────────────────┐
│ Prompt + Context + Q │
└────────────┬─────────────┘
│ ChatOpenAI
▼
┌──────────────────────────┐
│ 最终答案 │
└──────────────────────────┘
关键理解
这里最值得学习的是 LCEL 的并行输入写法:
{"context": retriever | format_docs, "question": RunnablePassthrough()}
retriever | format_docs:检索文档 → 格式化为字符串,作为contextRunnablePassthrough():直接把用户输入原样传递,作为question
这两个分支并行执行,结果合并成一个字典传入 Prompt。这是 LCEL 非常优雅的地方。
关于嵌入模型
HuggingFaceEmbeddings 使用 sentence-transformers/all-MiniLM-L6-v2,这是一个轻量级模型(~80MB),第一次运行时会自动下载。优点是完全本地运行,不需要任何 API Key,适合学习和开发阶段。
五、踩坑与最佳实践
坑 1:SSL 验证问题
# ❌ 错误做法:全局禁用 SSL 验证
http_client = httpx.Client(verify=False)
# ✅ 正确做法:使用默认客户端(移除 http_client 参数)
model = ChatOpenAI(
model="deepseek-v4-pro",
openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
# 不要传 http_client=xxx,使用默认安全配置
)
除非你的网络环境确实有 SSL 拦截(如公司代理),否则不应该禁用证书验证。
坑 2:RAG 脚本覆写知识文件
# ❌ 每次运行都会清空 knowledge.txt
with open("knowledge.txt", "w", encoding="utf-8") as f:
f.write("写死的知识...")
# ✅ 只读不写,保持文件内容稳定
loader = TextLoader("knowledge.txt", encoding="utf-8")
坑 3:忘记添加 .gitignore 就提交
提交后再想移除已跟踪的文件会比较麻烦:
git rm --cached .env # 从 Git 跟踪中移除但不删除本地文件
git rm --cached -r venv/ # 移除虚拟环境目录
最佳实践 :创建项目后第一步就配置 .gitignore,然后再写代码。
六、项目目录结构
langchain_demo/
├── demo_translation.py # 翻译链(入门)
├── demo_getjson.py # 结构化JSON输出
├── demo_getlist.py # 列表输出
├── demo_talk.py # 多轮对话记忆
├── rag.py # RAG 知识库问答
├── knowledge.txt # RAG 知识文件
├── requirements.txt # 依赖清单
├── .env # API Key(不提交)
├── .gitignore # Git 忽略规则
└── README.md # 项目说明
七、总结与收获
学到的核心概念
| 概念 | 理解 |
|---|---|
| LCEL(管道操作) | LangChain 提供的一种声明式编程语法 ,使用 ` |
| Prompt Template | 模板 + 变量的模式,运行时注入具体值,复用提示词 |
| Output Parser | 将 LLM 原始输出解析为结构化数据(str/list/dict/Pydantic) |
| Message History | 通过 MessagesPlaceholder + RunnableWithMessageHistory 实现多轮记忆 |
| RAG 架构 | 检索增强生成:Embedding → 向量检索 → LLM 生成,解决知识过时问题 |
下一步可以继续探索的方向
- Agent(智能代理):让 LLM 自主决定调用哪些工具(搜索、计算器、API)
- Callback(回调):监控和追踪 LLM 调用过程
- 流式输出:实现打字机效果,提升用户体验
- 多模态:LangChain 已支持图片、音频等多模态输入
- 部署:用 FastAPI + LangServe 将链部署为 API 服务
给读者的建议
- 先跑通再理解:不要试图一次性搞懂所有概念,先让代码跑起来,看到输出再去理解原理
- 多看官方文档:LangChain 官方文档更新很快,很多国内博客已经过时
- 善用调试 :用
langchain.debug = True可以看到 LLM 调用细节 - 小步迭代:每次只加一个组件,理解透了再加下一个
八、参考资料
本文首发于 CSDN,转载请注明出处。