返回学习路线

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:

python
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:

python
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:

python
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:

python
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:

python
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:

python
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:

python
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 成果)

python
# 如果上面 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:

python
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:

python
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:

python
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-你的key

Subsection 2 · 跑联网搜索 cmd:pip install langchain-community tavily-python 新建 lc_tavily.py:

python
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的手写代码):

python
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 合体:

python
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 深入)。