AI 学习路线 · 进阶
Part 9 · LangChain 框架
目标:用 LangChain 把"模型、提示词、检索、记忆、工具"串成可复用的链和 Agent。
Section 1 · LangChain 是什么 + 装环境
Subsection 1 · 概念
LangChain = 一个"乐高框架",把 LLM 应用常用的零件做成了标准积木: Prompt模板 / 模型封装 / 记忆 / 检索 / 工具 / 链LangGraph = LangChain 家的"图编排",做 Agent 状态机(站8用)- 一句话:它不给你模型,它帮你"组装"模型应用。
Subsection 2 · 装库 cmd:
pip install langchain langchain-openai langchain-community- 预期看到:三个 Successfully installed。
Subsection 3 · 跑第一个 LangChain 代码
新建 lc_first.py:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIload_dotenv()# 用 LangChain 包一层 DeepSeekllm = ChatOpenAI( model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com",)resp = llm.invoke("你好,用一句话介绍你自己")print(resp.content)- 预期看到:模型自我介绍。
- 注意:LangChain 把"返回"统一封装成
resp.content,这就是它的价值之一——换模型不用改业务代码。
过关:能跑通 lc_first.py = Section 1 完成。
Section 2 · ChatModels vs LLMs + 角色消息
Subsection 1 · 区别
LLM 老的纯文本接口,输入输出都是字符串ChatModel 现在的对话接口,输入是消息列表(system/user/assistant),更强大- 你现在用的 ChatOpenAI 就是 ChatModel,主流都走这个。
Subsection 2 · 跑多角色消息
新建 lc_roles.py:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")messages = [ {"role": "system", "content": "你是资深中文编辑,只改错别字和病句,不改变风格。"}, {"role": "user", "content": "我今天去了公司,发现会议室以经被别人占用了。"},]print(llm.invoke(messages).content)- 预期看到:修正后的句子(“已经”)。
过关:能跑通并理解 system 角色的作用 = Section 2 完成。
Section 3 · PromptTemplate(提示词模板化)
Subsection 1 · 跑模板
新建 lc_prompt.py:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIfrom langchain_core.prompts import ChatPromptTemplateload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")# 定义模板:变量用 {xxx} 占位prompt = ChatPromptTemplate.from_messages([ ("system", "你是{topic}专家,回答要专业且简短。"), ("user", "请解释:{question}"),])# 模板 + 模型 = 一条链chain = prompt | llm# 不同参数复用同一条链for t, q in [("健身", "深蹲怎么练"), ("编程", "什么是递归")]: print(t, "=>", chain.invoke({"topic": t, "question": q}).content, "\n")- 预期看到:同一个模板,换参数输出不同专家的回答。
-
prompt | llm这种|就是 LCEL 链式写法(Section 6 详讲)。
过关:能跑通模板复用 = Section 3 完成。
Section 4 · 输出解析器(让输出变成数据结构)
Subsection 1 · 跑结构化输出
新建 lc_output.py:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import JsonOutputParserload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")parser = JsonOutputParser() # 自动把输出解析成 dictprompt = ChatPromptTemplate.from_messages([ ("system", "提取信息,只输出 JSON,不要其他文字。字段:name, price, category。"), ("user", "{text}"),])chain = prompt | llm | parserresult = chain.invoke({"text": "这款机械键盘卖399元,属于外设类"})print(result)print("价格:", result["price"])- 预期看到:一个 dict,且能取到 price。
- 这比Part 3 手写 json.loads 更稳,解析器还能接 Pydantic 模型校验。
过关:能跑通并取到字段 = Section 4 完成。
Section 5 · Memory 记忆(多轮对话不"失忆")
Subsection 1 · 跑带记忆的对话
新建 lc_memory.py:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIfrom langchain_core.messages import HumanMessage, AIMessageload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")# 手动维护对话历史:这就是"记忆"的最原始形态history = [ HumanMessage("我叫小明"), AIMessage("你好小明,很高兴认识你!"),]history.append(HumanMessage("我叫什么名字?"))print(llm.invoke(history).content) # 应该记得"小明"# 清空历史再问print(llm.invoke([HumanMessage("我叫什么名字?")]).content) # 不知道了- 预期看到:第一个回答出"小明",第二个答不出。
- 关键:模型本身没有记忆,记忆 = 你把历史拼进 messages 再发给它。
Subsection 2 · 认识 Memory 组件
LangChain 有 ConversationBufferMemory 等现成组件帮你自动管理历史。知道有这东西即可,底层就是上面这个原理。
过关:能跑通并说清"记忆的原理" = Section 5 完成。
Section 6 · LCEL 链式组合(LangChain 的招牌语法)
Subsection 1 · 理解 |
LCEL(LangChain Expression Language)prompt | llm | parser= 数据像水流过管道:prompt 生成消息 → llm 生成回答 → parser 解析好处:好读、好组合、自带流式和重试Subsection 2 · 跑一条完整 LCEL 链
新建 lc_lcel.py:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIfrom langchain_core.prompts import ChatPromptTemplateload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")prompt = ChatPromptTemplate.from_messages([ ("system", "把用户输入翻译成英文,只输出翻译结果。"), ("user", "{text}"),])chain = prompt | llm# 流式输出(LCEL 自带)for chunk in chain.stream({"text": "你好,很高兴认识你"}): print(chunk.content, end="", flush=True)- 预期看到:英文翻译以打字机效果输出。
过关:能跑通 LCEL 流式 = Section 6 完成。
Section 7 · 用 LCEL 串起 RAG
Subsection 1 · 写 LangChain 版 RAG
新建 lc_rag.py:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAI, OpenAIEmbeddingsfrom langchain_community.vectorstores import Chromafrom langchain_core.prompts import ChatPromptTemplatefrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langchain_community.document_loaders import TextLoaderload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")# 用 DeepSeek 的 embedding 接口(或换 bge)。这里用 OpenAI 兼容方式:from langchain_openai import OpenAIEmbeddingsembeddings = OpenAIEmbeddings( model="text-embedding-v1", # 用 DashScope 的兼容接口时需要;简单起见先注释 api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com",)- DeepSeek 目前没有 embedding 接口。改用阿里 DashScope 的 OpenAI 兼容 embedding(Part 6 已注册):把 base_url 换成
https://dashscope.aliyuncs.com/compatible-mode/v1,api_key 换成 DashScope 的 Key,model 用text-embedding-v1。如果你拿 DashScope Key 麻烦,退回Part 5 的 bge 方案做向量(本步可暂缓,直接看下面用 bge 版本)。
Subsection 2 · 用 bge 的简化版(直接用Part 5 成果)
# 如果上面 DashScope 不顺,就用Part 5 的 rag_tool 继续,LangChain 只学"串链"这部分:prompt = ChatPromptTemplate.from_messages([ ("system", "根据资料回答,资料没有就说没有:\n{context}"), ("user", "{question}"),])chain = prompt | llm# context 从站5的检索结果来- 跑通这个简化版,理解"LCEL 串 RAG"的骨架:检索得 context → 模板拼 context → llm 生成。
- 预期看到:能正常回答。
过关:理解并跑通"检索+模板+生成"的 LCEL 骨架 = Section 7 完成(本日不追求生产级,Part 14 会完整重写)。
Section 8 · 工具 Tools(自定义函数)
Subsection 1 · 定义工具
新建 lc_tool.py:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIfrom langchain_core.tools import toolload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")@tooldef add(a: int, b: int) -> int: """两数相加。用这个工具做精确计算,避免模型算错。""" return a + bprint("工具名:", add.name)print("调用:", add.invoke({"a": 3, "b": 4}))- 预期看到:
工具名: add和调用: 7。 - 工具 = 一个带说明(docstring)的 Python 函数,模型看到说明才知道"什么时候该用它"。
过关:能定义并调用一个工具 = Section 8 完成。
Section 9 · Agent 雏形(让模型自己决定调用工具)
Subsection 1 · 跑工具调用 Agent
新建 lc_agent.py:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIfrom langchain_core.tools import toolfrom langchain.agents import create_agentload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")@tooldef multiply(a: int, b: int) -> int: """两数相乘。""" return a * btools = [multiply]# LangChain 1.x 用 create_agent,不再需要 create_tool_calling_agent + AgentExecutor# system_prompt 替代旧版的 ChatPromptTemplate# debug=True 打印工具调用过程(替代旧版 verbose=True)agent = create_agent( model=llm, tools=tools, system_prompt="你是计算助手,需要时调用工具。", debug=True,)result = agent.invoke({"messages": [{"role": "user", "content": "123 乘以 456 等于多少?"}]})print(result["messages"][-1].content)- 预期看到:debug 日志里出现工具调用,模型算出正确结果 56088。
- 这就是 Function Calling / 工具调用:模型自己判断"这题要调 multiply",把参数填好调用,再用结果回答。Agent 的地基。
- LangChain 1.x 变化:
create_tool_calling_agent+AgentExecutor已移除,统一用create_agent;输入从{"input": "..."}改成{"messages": [...]}。
过关:能跑通"模型自动调工具" = Section 9 完成。
Section 10 · LangGraph 初步(Agent 的图编排)
Subsection 1 · 装库
cmd:pip install langgraph
Subsection 2 · 跑一个最简状态机
新建 lg_first.py:
from typing import TypedDictfrom langgraph.graph import StateGraph, ENDclass State(TypedDict): total: intdef add_one(state: State): return {"total": state["total"] + 1}# 建图:两个节点顺序执行g = StateGraph(State)g.add_node("a", add_one)g.add_node("b", add_one)g.set_entry_point("a")g.add_edge("a", "b")g.add_edge("b", END)app = g.compile()print(app.invoke({"total": 0})) # 预期 {'total': 2}- 预期看到:
{'total': 2}(经过两次 +1)。 - LangGraph = 把 Agent 流程画成"状态图":节点是动作,边是流转。复杂 Agent 用它才不乱。现在先知道怎么建节点连边。
过关:能跑通并理解"节点+边" = Section 10 完成。
Section 11 · Tavily 搜索工具(让 Agent 联网)
Subsection 1 · 拿 Tavily Key
- 打开 https://tavily.com 注册,创建一个 API Key(免费额度够学习),存进
.env:
TAVILY_API_KEY=tvly-你的keySubsection 2 · 跑联网搜索
cmd:pip install langchain-community tavily-python
新建 lc_tavily.py:
import osfrom dotenv import load_dotenvfrom langchain_community.tools import TavilySearchResultsload_dotenv()tool = TavilySearchResults(max_results=3)result = tool.invoke("2026年大模型行业有什么大新闻")for r in result: print(r["title"], "|", r["url"])- 预期看到:3 条真实的网页搜索结果。
- 这就是"给 Agent 接上网":搜索工具返回实时信息,模型再基于它回答。
过关:能跑通联网搜索 = Section 11 完成。
Section 12 · 用 LangChain 完整重写站 5 的 RAG
Subsection 1 · 完整重写
新建 lc_kb.py(用 langchain 的文档加载/切分/向量库,替换站5的手写代码):
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIfrom langchain_community.document_loaders import TextLoaderfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langchain_community.vectorstores import Chromafrom langchain_community.embeddings import HuggingFaceEmbeddingsfrom langchain_core.prompts import ChatPromptTemplateload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")# 1. 加载 + 切分loader = TextLoader("rag_data/company.md", encoding="utf-8")docs = loader.load()splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=40)chunks = splitter.split_documents(docs)# 2. 向量化 + 入库(用 bge,免 DashScope Key)embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")db = Chroma.from_documents(chunks, embeddings, persist_directory="lc_db")# 3. 检索 + 生成retriever = db.as_retriever(search_kwargs={"k": 3})prompt = ChatPromptTemplate.from_messages([ ("system", "根据资料回答,没有就说没有:\n{context}"), ("human", "{question}"),])def ask(q): context = "\n".join(d.page_content for d in retriever.invoke(q)) return llm.invoke(prompt.format_messages(question=q, context=context)).contentprint(ask("迟到会怎么样?"))- 预期看到:基于公司.md 的正确回答。
- 对比Part 5 手写版:LangChain 把"加载/切分/入库/检索"全部标准化了。这就是框架的价值。
过关:重写跑通 = Section 12 完成。
Section 13 · 多轮对话知识库(记忆 + RAG 合体)
Subsection 1 · 组装
新建 lc_memory_rag.py:把 Section 5 的记忆思路 + Section 12 的 RAG 合体:
import osfrom dotenv import load_dotenvfrom langchain_openai import ChatOpenAIfrom langchain_community.vectorstores import Chromafrom langchain_community.embeddings import HuggingFaceEmbeddingsfrom langchain_core.messages import HumanMessage, AIMessageload_dotenv()llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")db = Chroma(persist_directory="lc_db", embedding_function=embeddings)retriever = db.as_retriever(search_kwargs={"k": 3})history = [] # 记忆def chat(user_input): # 检索 context = "\n".join(d.page_content for d in retriever.invoke(user_input)) # 拼历史 + 资料 + 问题 messages = [{"role": "system", "content": f"根据资料回答:\n{context}"}] messages += history messages.append({"role": "user", "content": user_input}) resp = llm.invoke(messages).content history.append(HumanMessage(user_input)) history.append(AIMessage(resp)) return respwhile True: q = input("你:") if q == "exit": break print("助手:", chat(q))- 连续问:“迟到怎么处理” → “那请假呢” → “我刚才问了什么” 感受记忆生效。
- 预期看到:多轮连贯,且能记住前面问过什么。
过关:多轮知识库跑通 = Section 13 完成。
Section 14 · 常见坑与测试
Subsection 1 · 记坑
坑1 DeepSeek 没有 embedding 接口 → 用 bge(HuggingFaceEmbeddings)坑2 Chroma persist 目录重名冲突 → 换目录名或删掉旧的坑3 模型"看不到工具说明" → 工具 docstring 必须写清楚坑4 LCEL 流式报错 → 检查模型是否支持 stream坑5 历史无限增长 → 超长截断/只保留最近 N 轮Subsection 2 · 测试
- 跑一遍 Section 13 的多轮对话,测 5 个不同问题 + 连续追问,记录是否都正常。
过关:坑笔记写完 + 测试通过 = Section 14 完成。
Section 15 · 站 7 验收
勾选
- lc_first.py 跑通,理解 ChatModel 封装
- 会写 PromptTemplate 并复用
- 会做结构化输出(JsonOutputParser)
- 理解"记忆=拼历史",lc_memory.py 跑通
- LCEL 链式 + 流式跑通
- 会定义工具,lc_agent.py 模型自动调工具
- LangGraph 最简状态机跑通
- Tavily 联网搜索跑通
- LangChain 版 RAG(lc_kb.py)跑通
- 多轮记忆知识库跑通(Section 13)
- 常见坑笔记写完
写 300 字Part 7 总结:LangChain/LangGraph 解决了什么,你最喜欢哪个能力。
全勾选 = Part 9 通过 → 进入下一站 Part 10 · Agent 基础,15 天:规划/记忆/工具/执行 + Function Calling 深入)。
