前言
随着大语言模型(LLM)的快速发展,越来越多的开发者希望将AI能力集成到自己的应用中。然而,直接调用LLM API面临着诸多工程挑战:如何保存聊天上下文?如何进行网络检索?如何加载本地数据?如何便捷管理Prompt?更重要的是,当需要切换不同的LLM时,模型的输入输出结构差异巨大,微小的需求变更可能导致大量代码修改。
LangChain应运而生,作为当前最热门的AI应用开发框架,它提供了一站式解决方案,让开发者能够像搭积木一样快速构建LLM应用。本文将从零开始,带你深入理解LangChain的核心组件与最佳实践。
一、为什么选择LangChain
1.1 为什么需要大模型应用框架
大模型一般有两种形态呈现给我们:一种是训练好的二进制文件,另一种是部署后暴露的接口。无论哪种形式,LLM只提供了非常基础的调用方式。当我们要构建复杂的Chat Bot时,需要考虑大量工程问题。
传统开发模式的痛点:
- 上下文管理复杂:需要自行处理聊天历史存储
- 模型切换成本高:不同LLM的接口差异巨大
- 缺乏可观测性:难以监控运行流程、Token消耗、错误追踪
- 功能重复开发:网络检索、本地数据加载等功能需要反复实现
以OpenAI和文心一言为例,两者的SDK调用方式截然不同:
python
# OpenAI调用方式
from openai import OpenAI
client = OpenAI()
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "你好"}]
)
content = completion.choices[0].message.content
# 文心一言调用方式
import qianfan
client = qianfan.ChatCompletion()
resp = client.do(messages=[{"role": "user", "content": "你好"}])
content = resp["body"]
1.2 LangChain的核心优势
| 优势维度 | 说明 |
|---|---|
| 认可度 | GitHub 86.3k+ star,最热门的AI应用开发框架 |
| 持续维护 | 累计融资数千万美金,10000+次commit更新 |
| 易用性 | 屏蔽不同LLM的接口差异,统一调用方式 |
| 可扩展性 | 近700种集成,涵盖LLM、向量存储、工具等 |
| 可观测性 | 提供LangSmith平台,实现全链路监控 |
| 稳定性 | 2024年1月发布0.1.0稳定版本 |
使用LangChain后的代码对比:
python
from langchain_openai import ChatOpenAI
from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
from langchain_core.output_parsers import StrOutputParser
# 切换LLM只需修改一行代码
llm = ChatOpenAI() # OpenAI
# llm = QianfanChatEndpoint() # 文心一言
chain = llm | StrOutputParser()
content = chain.invoke("你好")
二、LangChain核心概念
2.1 六大核心组件
LangChain提供了6大核心组件,帮助开发者更好地使用大语言模型:
┌─────────────────────────────────────────────────────────┐
│ LangChain 核心组件 │
├─────────────┬─────────────┬─────────────┬───────────────┤
│ Models │ Prompts │ Indexes │ Memory │
│ 模型组件 │ 提示组件 │ 索引组件 │ 记忆组件 │
├─────────────┼─────────────┼─────────────┼───────────────┤
│ Chains │ Agents │ │ │
│ 链组件 │ 代理组件 │ │ │
└─────────────┴─────────────┴─────────────┴───────────────┘
- Models(模型):封装不同类型的LLM,包括文本生成模型和聊天模型
- Prompts(提示):管理和优化提示词,支持模板化和动态变量
- Indexes(索引):处理文档索引和检索
- Memory(记忆):管理对话历史和状态
- Chains(链):组合多个组件形成处理流程
- Agents(代理):让LLM自主决策使用哪些工具
2.2 框架安装
bash
# 安装LangChain核心包
pip install langchain langchain-community
# 指定版本安装(课程使用0.2.1版本)
pip install langchain==0.2.1 langchain-community==0.2.1
安装后会自动包含以下扩展包:
langchain-core:基础抽象和表达式语言langchain-community:第三方集成langchain-text-splitters:文本分割器langsmith:调试、测试、监控平台
三、Prompt组件详解
3.1 Prompt组件的基本组成
Prompt是所有AI应用交互的起点。LangChain封装了高可移植性的Prompt组件,同一个Prompt可以支持各种LLM,切换模型时无需修改Prompt。
Prompt组件分类:
| 组件类型 | 说明 |
|---|---|
PromptTemplate |
文本消息提示模板 |
ChatPromptTemplate |
聊天消息提示模板 |
SystemMessagePromptTemplate |
系统消息模板 |
HumanMessagePromptTemplate |
用户消息模板 |
AIMessagePromptTemplate |
AI消息模板 |
MessagesPlaceholder |
消息占位符 |
3.2 基础用法示例
python
from datetime import datetime
from langchain_core.prompts import (
PromptTemplate,
ChatPromptTemplate,
HumanMessagePromptTemplate,
MessagesPlaceholder,
)
from langchain_core.messages import AIMessage
# 文本提示模板
prompt = PromptTemplate.from_template("请讲一个关于{subject}的冷笑话")
prompt_str = prompt.format(subject="程序员")
print(prompt_str) # 输出: 请讲一个关于程序员的冷笑话
# 聊天提示模板
chat_prompt = ChatPromptTemplate.from_messages([
("system", "你是OpenAI开发的聊天机器人,当前时间:{now}"),
MessagesPlaceholder("chat_history"),
HumanMessagePromptTemplate.from_template("请讲一个关于{subject}的冷笑话"),
]).partial(now=datetime.now())
# 调用模板
chat_prompt_value = chat_prompt.invoke({
"subject": "程序员",
"chat_history": [
("human", "我叫慕小课"),
AIMessage("我是ChatGPT, 有什么能帮到你的么?")
],
})
print(chat_prompt_value.to_string())
3.3 字符串提示拼接
LangChain支持使用+运算符拼接提示模板:
python
from langchain_core.prompts import PromptTemplate
prompt = (
PromptTemplate.from_template("请讲一个关于{subject}的冷笑话")
+ ",让我开心下"
+ "\n使用{language}语言。"
)
print(prompt.format(subject="程序员", language="中文"))
3.4 复用提示模板
使用PipelinePromptTemplate可以实现提示模板的复用:
python
from langchain_core.prompts import PipelinePromptTemplate, PromptTemplate
# 完整模板
full_template = """{instruction}
{example}
{start}"""
full_prompt = PromptTemplate.from_template(full_template)
# 子模板
instruction_prompt = PromptTemplate.from_template("你正在模拟{person}。")
example_prompt = PromptTemplate.from_template("""
下面是一个交互例子:
Q: {example_q}
A: {example_a}""")
start_prompt = PromptTemplate.from_template("""
现在,你是一个真实的人,请回答用户的问题!
Q: {input}
A:""")
# 组装管道提示
pipeline_prompt = PipelinePromptTemplate(
final_prompt=full_prompt,
pipeline_prompts=[
("instruction", instruction_prompt),
("example", example_prompt),
("start", start_prompt),
]
)
print(pipeline_prompt.format(
person="雷军",
example_q="你最喜欢的汽车是什么?",
example_a="小米su7",
input="你最喜欢的手机是什么?"
))
四、Model组件详解
4.1 Model组件概述
LangChain为两种类型的模型提供接口:
- LLM:使用纯文本作为输入输出的大语言模型
- Chat Model:使用聊天消息列表作为输入输出的聊天模型
核心调用方法:
| 方法 | 说明 |
|---|---|
invoke |
单次调用,生成内容 |
batch |
批量调用,一次生成多个内容 |
stream |
流式输出,实时返回生成内容 |
4.2 基础使用示例
python
import dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
# 1. 编排Prompt
prompt = ChatPromptTemplate.from_messages([
("system", "你是OpenAI开发的聊天机器人,现在的时间是{now}"),
("human", "{query}"),
]).partial(now=datetime.now())
# 2. 创建大语言模型
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")
# 3. 生成内容
prompt_value = prompt.invoke({"query": "请讲一个关于程序员的冷笑话"})
ai_message = llm.invoke(prompt_value)
# 4. 提取内容
print("type:", ai_message.type)
print("content:", ai_message.content)
print("response_metadata:", ai_message.response_metadata)
4.3 批处理与流式输出
python
# 批处理示例
prompt = ChatPromptTemplate.from_template("请讲一个关于{subject}的冷笑话")
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")
ai_messages = llm.batch([
prompt.invoke({"subject": "程序员"}),
prompt.invoke({"subject": "Python"}),
])
for ai_message in ai_messages:
print(ai_message.content)
# 流式输出示例
response = llm.stream(prompt.invoke({"subject": "LLM和LLMOps"}))
for chunk in response:
print(chunk.content, flush=True, end="")
五、OutputParser组件详解
5.1 为什么需要输出解析器
LLM的输出通常是字符串,但在实际应用中,我们往往需要结构化的数据:
python
llm = ChatOpenAI()
llm.invoke("告诉我3个动物的名字。")
# 输出: 好的,这里有三种动物的名字:\n\n1. 狮子\n2. 大熊猫\n3. 斑马
输出解析器可以将非结构化文本转换为结构化数据:
输出解析器 = 预设提示 + 解析功能
5.2 StrOutputParser
最简单的输出解析器,将内容原样返回:
python
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
result = parser.parse("程序员的梦工厂") # 输出: 程序员的梦工厂
5.3 JsonOutputParser
将LLM输出转换为JSON格式:
python
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field
# 定义输出结构
class Joke(BaseModel):
joke: str = Field(description="回答用户的冷笑话")
punchline: str = Field(description="冷笑话的笑点")
# 创建解析器
parser = JsonOutputParser(pydantic_object=Joke)
prompt = ChatPromptTemplate.from_template(
"回答用户的问题。\n{format_instructions}\n{query}\n"
).partial(format_instructions=parser.get_format_instructions())
# 构建链
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")
chain = prompt | llm | parser
# 调用
result = chain.invoke({"query": "请讲一个关于程序员的冷笑话"})
# 输出: {'joke': '为什么程序员总是冷静的?', 'punchline': '因为他们总是有一堆bug在身边。'}
六、LCEL表达式与Runnable协议
6.1 传统嵌套写法的问题
python
# 嵌套式写法(不推荐)
content = parser.invoke(
llm.invoke(
prompt.invoke({"query": req.query.data})
)
)
问题:
- 可读性差,维护困难
- 无法得知每步执行进度
- 组件增多时代码变成"一次性"代码
6.2 Runnable协议
LangChain实现了Runnable协议,所有组件支持统一的调用方式:
| 方法 | 说明 |
|---|---|
invoke |
单次调用 |
batch |
批量调用 |
stream |
流式输出 |
ainvoke |
异步单次调用 |
abatch |
异步批量调用 |
astream |
异步流式输出 |
6.3 使用管道操作符
python
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template("{query}")
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")
parser = StrOutputParser()
# 使用 | 操作符连接组件
chain = prompt | llm | parser
# 等价于
composed_chain = (
RunnableParallel({"query": RunnablePassthrough()})
.pipe(prompt)
.pipe(llm)
.pipe(parser)
)
# 调用
content = chain.invoke({"query": "你好,你是?"})
6.4 RunnableParallel并行运行
python
from langchain_core.runnables import RunnableParallel
# 创建两条链
joke_chain = joke_prompt | llm | parser
poem_chain = poem_prompt | llm | parser
# 并行执行
map_chain = RunnableParallel(joke=joke_chain, poem=poem_chain)
result = map_chain.invoke({"subject": "程序员"})
# 输出: {'joke': '...', 'poem': '...'}
6.5 RunnablePassthrough透传数据
python
from langchain_core.runnables import RunnablePassthrough
# 透传输入数据
chain = {"query": RunnablePassthrough()} | prompt | llm | StrOutputParser()
content = chain.invoke("你好,你是")
七、Callback回调机制
7.1 Callback功能介绍
Callback是LangChain提供的回调机制,允许在LLM应用程序的各个阶段使用钩子(hook),对于记录日志、监控、流式传输等任务非常有用。
主要事件:
| 事件 | 触发时机 | 方法 |
|---|---|---|
on_chat_model_start |
聊天模型启动时 | |
on_llm_start |
大语言模型启动时 | |
on_llm_new_token |
生成新token时 | |
on_llm_end |
模型结束时 | |
on_chain_start |
链开始运行时 | |
on_chain_end |
链结束时 | |
on_tool_start |
工具开始运行时 |
7.2 使用StdOutCallbackHandler
python
from langchain_core.callbacks import StdOutCallbackHandler
chain = {"query": RunnablePassthrough()} | prompt | llm | StrOutputParser()
# 使用回调
content = chain.stream(
"你好,你是?",
config={"callbacks": [StdOutCallbackHandler()]}
)
7.3 自定义回调处理器
python
from langchain_core.callbacks import BaseCallbackHandler
from typing import Dict, Any, List, Optional
from uuid import UUID
class LLMOpsCallbackHandler(BaseCallbackHandler):
"""自定义回调处理器"""
def on_llm_start(
self,
serialized: Dict[str, Any],
prompts: List[str],
*,
run_id: UUID,
**kwargs: Any,
) -> Any:
print("LLM开始执行:", prompts)
def on_llm_new_token(self, token: str, **kwargs) -> Any:
print("新token:", token)
def on_llm_end(self, response, **kwargs) -> Any:
print("LLM执行完成")
总结
本文深入介绍了LangChain框架的核心组件与使用技巧:
- Prompt组件:支持模板化、拼接、复用,提高提示词管理效率
- Model组件:统一不同LLM的调用方式,支持批处理和流式输出
- OutputParser组件:将非结构化输出转换为结构化数据
- LCEL表达式:使用管道操作符优雅地组合组件
- Runnable协议:提供统一的调用接口和并行执行能力
- Callback机制:实现全链路监控和调试