Day15:LangChain 基础入门:开启与大模型高效交互之旅

摘要:本文旨在帮助初学者轻松入门 LangChain,以通义千问大模型为例,详细介绍 LangChain 库的安装、核心概念及其使用场景,并通过示例代码展示如何使用 PromptTemplate 创建可复用的 Prompt 模板,结合大模型 API 实现简单的问答 Chain。

引言

很多初学者在学习大模型应用开发时,都会遇到一个问题:“我已经会调用OpenAI或通义千问的API了,但如何构建一个更复杂的应用,比如带记忆的聊天机器人、能检索文档的问答系统?”

LangChain 就是为了解决这个问题而生的。它就像一个 “AI应用的乐高积木” ,把各种复杂的功能模块化,让我们可以轻松地组合、串联,快速构建强大的LLM应用。

本文将带你从零开始,以阿里通义千问为例,手把手掌握LangChain最核心的几个概念:PromptTemplateChainModel 和 Memory。文章最后会提供一个完整的可运行代码,让你直观感受LangChain的魅力。

🧐 一、LangChain是什么?

简单来说,LangChain 是一个用于开发由大语言模型(LLM)驱动的应用程序的框架 。

它本身不提供模型,而是提供了一套标准接口和丰富组件,让我们可以:

  • 统一接口调用各种模型(OpenAI、通义千问、文心一言等)。

  • 通过提示词模板复用和管理Prompt 。

  • 将多个步骤组合成一个工作流(Chain)。

  • 给应用增加短期或长期记忆(Memory)。

  • 让AI调用外部工具(Tool),如搜索引擎、计算器、API等 。

🏗️ 二、LangChain四大核心概念

为了完成今天的任务,我们需要先了解这四个基础概念,它们就像乐高积木中的基础块。

PromptTemplate(提示词模板)
  • 作用:告别硬编码字符串!它允许你定义一个带变量的模板,在运行时再填充具体内容,实现Prompt的复用和管理 。

  • 场景:比如你需要让AI翻译,可以定义一个模板 “请将下面的中文翻译成英文:{text}”,每次只需要传入不同的 {text} 即可。

Model(模型)
  • 作用:LangChain对各类大模型进行了统一封装,无论是文本补全模型(LLM)还是对话模型(Chat Model),都可以用同样的方式调用 。

  • 场景:今天我们使用的 ChatTongyi 就是LangChain社区版中专为通义千问提供的Chat Model封装 。

Chain(链)
  • 作用:将多个组件组合成一个整体。最简单的 LLMChain 就是将 PromptTemplate 和 Model 连接起来:输入 -> PromptTemplate -> Model -> 输出 。

  • 场景:实现一个简单的问答、数据清洗后调用模型、或者一个多步骤的复杂任务流。

Memory(记忆)
  • 作用:默认情况下,大模型是无状态的,它记不住你上一轮说了什么。Memory 组件就是为了解决这个问题,它可以在链的内部维护一个缓冲区,自动存储和传递历史对话信息 。

  • 场景:多轮对话机器人、需要上下文的交互场景。

Tool(工具)
  • 作用:让LLM具备使用外部工具的能力,比如联网搜索、执行代码、查天气等 。本文将不展开,但它是Agent(智能体)的核心。

🛠️ 三、环境准备

在开始写代码前,我们需要先安装库和配置好API密钥。

安装必要的库

打开你的终端,使用pip安装以下库。建议在虚拟环境中进行。

# 安装核心langchain库和社区集成库(包含通义千问)
pip install langchain langchain-community

# 安装通义千问的SDK(阿里云百炼平台)
pip install dashscope

# 安装用于加载环境变量的库(可选,但推荐)
pip install python-dotenv

四、实战:从Prompt到Chain

我们将编写两个简单的示例,由浅入深。

场景一:无状态问答(基础Chain)

这个场景演示了最核心的流程:定义模板 -> 实例化模型 -> 构建Chain -> 运行

完整代码:**langchain_basic_demo.py**

import os
from dotenv import load_dotenv
from langchain_community.chat_models import ChatTongyi
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 加载 .env 文件中的环境变量
load_dotenv()

# 1. 初始化通义千问模型
#    使用 langchain-community 中的 ChatTongyi
#    模型可以选择 qwen-turbo, qwen-plus, qwen-max 等 [citation:6]
llm = ChatTongyi(
    model="qwen-plus",
    temperature=0.7,  # 控制创造性,0.7 是个平衡值
    # dashscope_api_key 会自动从环境变量 DASHSCOPE_API_KEY 读取,所以这里可以不传
)

# 2. 创建 PromptTemplate
#    定义两个变量:topic 和 style
template = "你是一位{style}专家。请用一句话向小学生解释什么是{topic}。"
prompt = PromptTemplate.from_template(template)

# 3. 构建 Chain (使用现代 LCEL 语法,更加简洁直观) [citation:5]
#    | 管道符将各个组件连接起来:输入 -> prompt -> llm -> 字符串解析器 -> 输出
chain = (
    {"topic": RunnablePassthrough(), "style": RunnablePassthrough()}  # 接收两个输入
    | prompt
    | llm
    | StrOutputParser()  # 将模型返回的 AIMessage 对象转换成字符串
)

# 4. 运行 Chain
print("="*50)
print("🚀 无状态问答演示")
print("="*50)

response = chain.invoke({"topic": "量子计算", "style": "科普"})
print(f"用户提问: 用科普风格解释量子计算")
print(f"AI回答: {response}\n")

response2 = chain.invoke({"topic": "神经网络", "style": "教育"})
print(f"用户提问: 用教育风格解释神经网络")
print(f"AI回答: {response2}\n")

# 测试模型是否有记忆(它应该不记得之前的事)
response3 = chain.invoke({"topic": "我刚刚问了你什么?", "style": "通用"})
print(f"用户提问: 我刚刚问了你什么?(测试记忆)")
print(f"AI回答: {response3}")
print("="*50)

输出示例:

==================================================
🚀 无状态问答演示
==================================================
用户提问: 用科普风格解释量子计算
AI回答: 量子计算就像是一台超级厉害的“魔法算盘”,它不用普通的0和1来思考,而是让信息像小精灵一样同时扮演0和1(叫“量子叠加”),还能让多个小精灵手拉手“心灵感应”(叫“量子纠缠”),所以能飞快解决普通电脑要算几百年的大难题!✨

用户提问: 用教育风格解释神经网络
AI回答: 神经网络就像一个超级聪明的“数字大脑”,它由很多像小灯泡一样会亮会灭的小单元(叫“神经元”)连在一起,通过不断看图片、听声音或读文字来学习规律——比如认出猫和狗的区别,就像你多看几次小猫小狗,慢慢就能分清一样!🐱→💡→🐶

用户提问: 我刚刚问了你什么?(测试记忆)
AI回答: 你刚刚问我的问题就是“我刚刚问了你什么?”,这其实是一个有趣的“自己问自己”的问题,就像照镜子时镜子也在照你一样——它在请我回头看看你上一句话说了什么!😊
==================================================

解析:

  • 我们使用了PromptTemplate动态生成了两个不同风格的提问。

  • 最后一问证实了模型确实没有记忆,因为它不知道历史。

场景二:带记忆的对话(Memory初探)

为了解决无状态的问题,我们需要引入Memory。LangChain新版本推荐使用 RunnableWithMessageHistory 配合 ChatMessageHistory 来实现 。

完整代码:在同一个文件中追加以下内容

from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# 创建一个字典来存储不同会话的历史记录(简单内存存储)
store = {}

def get_session_history(session_id: str) -> BaseChatMessageHistory:
    """根据 session_id 获取或创建历史记录"""
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

# 1. 使用适合对话的 ChatPromptTemplate
#    关键点:使用 MessagesPlaceholder 作为历史消息的占位符 [citation:6]
prompt_with_memory = ChatPromptTemplate.from_messages([
    ("system", "你是一个知识渊博、乐于助人的助手。请用简洁的中文回答。"),
    MessagesPlaceholder(variable_name="history"),  # 历史消息会填充到这里
    ("human", "{input}")  # 用户当前的输入
])

# 2. 构建基础链(无记忆)
chain_without_memory = prompt_with_memory | llm | StrOutputParser()

# 3. 用 RunnableWithMessageHistory 给链装上“记忆” [citation:6]
chain_with_memory = RunnableWithMessageHistory(
    chain_without_memory,
    get_session_history,  # 历史获取函数
    input_messages_key="input",  # 指定哪个是用户的输入
    history_messages_key="history"  # 指定哪个是历史占位符
)

# 4. 模拟多轮对话
print("\n" + "="*50)
print("🧠 带记忆的对话演示 (Session ID: user_001)")
print("="*50)

config = {"configurable": {"session_id": "user_001"}}

resp1 = chain_with_memory.invoke({"input": "你好,我是小明,我最喜欢猫!"}, config=config)
print(f"用户: 你好,我是小明,我最喜欢猫!")
print(f"AI: {resp1}\n")

resp2 = chain_with_memory.invoke({"input": "你能记住我的名字和爱好吗?我叫什么?喜欢什么?"}, config=config)
print(f"用户: 你能记住我的名字和爱好吗?我叫什么?喜欢什么?")
print(f"AI: {resp2}\n")

resp3 = chain_with_memory.invoke({"input": "那你能给我推荐一部关于动物的电影吗?"}, config=config)
print(f"用户: 那你能给我推荐一部关于动物的电影吗?")
print(f"AI: {resp3}\n")

# 测试会话隔离 (新建一个会话)
print("\n" + "="*50)
print("🧠 带记忆的对话演示 (Session ID: user_002 - 新用户)")
print("="*50)
config2 = {"configurable": {"session_id": "user_002"}}

resp4 = chain_with_memory.invoke({"input": "你好,我叫小红,我最喜欢宇宙探索。还记得我吗?"}, config=config2)
print(f"用户: 你好,我叫小红,我最喜欢宇宙探索。还记得我吗?")
print(f"AI: {resp4}\n")

resp5 = chain_with_memory.invoke({"input": "我刚才告诉你我叫什么?"}, config=config2)
print(f"用户: 我刚才告诉你我叫什么?")
print(f"AI: {resp5}")

输出示例(预期):

==================================================
🚀 无状态问答演示
==================================================
用户提问: 用科普风格解释量子计算
AI回答: 量子计算就像是一台超级厉害的“魔法算盘”,它不用普通的0和1来思考,而是用能同时是0又是1的“量子小精灵”(叫量子比特),所以它能在一瞬间试好多好多条路,帮我们解决普通电脑要算几百年才能搞定的难题!

用户提问: 用教育风格解释神经网络
AI回答: 神经网络就像一个超级聪明的“数字大脑”,它由很多像小灯泡一样会亮会灭的小单元(叫“神经元”)连在一起,通过不断看图片、听声音或读文字来学习规律——比如认出猫和狗的区别,就像你多看几次小猫小狗,慢慢就能分清一样!🐱→💡→🐶

用户提问: 我刚刚问了你什么?(测试记忆)
AI回答: 你刚刚问我的问题就是“我刚刚问了你什么?”,这其实是一个有趣的“自己问自己”的问题,就像照镜子时镜子也在照你一样——它在请我回头看看你上一句话说了什么!😊
==================================================

==================================================
🧠 带记忆的对话演示 (Session ID: user_001)
==================================================
用户: 你好,我是小明,我最喜欢猫!
AI: 你好,小明!很高兴认识一位爱猫的朋友~🐱  
猫确实超可爱:高冷又粘人,傲娇还治愈,连打个哈欠都像在演小短剧 😄  
你养猫了吗?还是有特别喜欢的猫品种/网红猫?欢迎分享!

用户: 你能记住我的名字和爱好吗?我叫什么?喜欢什么?
AI: 当然可以!你叫**小明**,最喜欢**猫**~ 🐾  
我已经记在小本本上了(🧠✨),以后聊猫、养猫、云吸猫,或者想听猫的冷知识、趣事、品种推荐……随时找我! 😺

用户: 那你能给我推荐一部关于动物的电影吗?
AI: 当然可以!既然你超爱猫,我特别推荐这部温暖又治愈的电影:

🎬 **《流浪猫鲍勃》(A Street Cat Named Bob,2016)**  
✅ 真人真事改编|✅ 主角是一只超有灵性的橘猫鲍勃|✅ 充满善意与希望  

故事讲的是伦敦街头一位陷入低谷的街头艺人詹姆斯,在最艰难时收留了一只受伤的流浪橘猫——鲍勃。没想到,这只戴围巾、爱坐肩头的猫不仅改变了他的人生,还帮他在街头重拾尊严与勇气……连猫的“演技”都自然到让人怀疑它是不是拿了奥斯卡提名!😂

✨ 亮点:  
- 鲍勃本人(真猫!)全程本色出演,慵懒、傲娇、关键时刻超暖心;  
- 没有刻意煽情,却让人笑着流泪;  
- 看完会忍不住摸摸身边猫主子的头说:“谢谢你也在”。

需要我再推荐一部风格不同的(比如动画、搞笑、或科普向)?或者想了解鲍勃后来的真实故事?随时告诉我~ 😺


==================================================
🧠 带记忆的对话演示 (Session ID: user_002 - 新用户)
==================================================
用户: 你好,我叫小红,我最喜欢宇宙探索。还记得我吗?
AI: 你好,小红!很高兴再次见到你~虽然作为AI我没有记忆功能,不记得之前的对话,但听到你最喜欢宇宙探索,真让人兴奋!✨  
你想聊黑洞、火星探测、中国空间站,还是最近的詹姆斯·韦布望远镜新发现?我随时陪你仰望星空 🌌🚀

用户: 我刚才告诉你我叫什么?
AI: 是的,你刚才告诉我——你叫**小红**!😊  
而且你还特别提到:**最喜欢宇宙探索** 🌟  
(这可是个超酷的爱好!)  
需要我帮你查某个航天任务、解释一个天文概念,或者一起畅想星际旅行?随时开始~ 🚀

解析:

  1. 历史填充MessagesPlaceholder 是关键,它会自动将 history 变量展开为多条历史消息。

  2. 自动记忆RunnableWithMessageHistory 会自动将每次对话的 (Human, AI) 对存入 store 中对应的 session_id

  3. 会话隔离user_002 的对话完全不受 user_001 的影响,这是多用户系统的基石。

📝 五、总结与展望

今天我们完成了LangChain的入门学习:

  1. 我们理解了LangChain的核心设计理念:模块化和链式组合。

  2. 我们亲手实践了 **PromptTemplate****Model****Chain** 和 **Memory** 这四个最基础的组件。

  3. 我们学会了如何通过ChatTongyi将通义千问集成到LangChain中。

  4. 我们成功搭建了一个带记忆的问答Chain,实现了真正的多轮对话。

这只是一个开始。LangChain的世界还有更多精彩等待你去探索:

  • RAG(检索增强生成):连接你私有的知识库或文档,让AI基于你的资料回答问题。

  • Agent(智能体):赋予AI调用工具的能力,让它能帮你查天气、订机票、执行代码。

  • LCEL(LangChain表达式语言):用更简洁、强大的方式构建复杂的链。

希望这篇教程能为你打开一扇门。如果你有任何问题,欢迎在评论区留言交流!

附录:完整代码

# langchain_qwen_tutorial.py
import os
from dotenv import load_dotenv
from langchain_community.chat_models import ChatTongyi
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# --- 0. 加载环境变量 ---
load_dotenv()

# --- 1. 初始化模型 (全局共用) ---
llm = ChatTongyi(
    model="qwen-turbo",
    temperature=0.7,
)

# ================== 场景一:无状态问答 ==================
def demo_stateless():
    print("="*50)
    print("🚀 无状态问答演示")
    print("="*50)

    template = "你是一位{style}专家。请用一句话向小学生解释什么是{topic}。"
    prompt = PromptTemplate.from_template(template)

    chain = (
        {"topic": RunnablePassthrough(), "style": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )

    response = chain.invoke({"topic": "量子计算", "style": "科普"})
    print(f"用户提问: 用科普风格解释量子计算")
    print(f"AI回答: {response}\n")

    response3 = chain.invoke({"topic": "我刚刚问了你什么?", "style": "通用"})
    print(f"用户提问: 我刚刚问了你什么?(测试记忆)")
    print(f"AI回答: {response3}")
    print("="*50)

# ================== 场景二:带记忆的对话 ==================
# 存储历史记录的字典
store = {}

def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

def demo_stateful():
    print("\n" + "="*50)
    print("🧠 带记忆的对话演示")
    print("="*50)

    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个知识渊博、乐于助人的助手。请用简洁的中文回答。"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}")
    ])

    chain = prompt | llm | StrOutputParser()
    chain_with_memory = RunnableWithMessageHistory(
        chain,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )

    # 会话1
    config1 = {"configurable": {"session_id": "user_001"}}
    resp1 = chain_with_memory.invoke({"input": "你好,我是小明,我最喜欢猫!"}, config=config1)
    print(f"用户1: 你好,我是小明,我最喜欢猫!")
    print(f"AI: {resp1}\n")

    resp2 = chain_with_memory.invoke({"input": "我叫什么名字?"}, config=config1)
    print(f"用户1: 我叫什么名字?")
    print(f"AI: {resp2}\n")

    # 会话2
    config2 = {"configurable": {"session_id": "user_002"}}
    resp3 = chain_with_memory.invoke({"input": "你好,我是小红。"}, config=config2)
    print(f"用户2: 你好,我是小红。")
    print(f"AI: {resp3}\n")

    resp4 = chain_with_memory.invoke({"input": "我叫什么名字?"}, config=config2)
    print(f"用户2: 我叫什么名字?")
    print(f"AI: {resp4}")
    print("="*50)

if __name__ == "__main__":
    demo_stateless()
    demo_stateful()
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐