一、什么是LangChain
LangChain 既是一个开源框架 ,也是一套工具和库 ,但它的核心定位是框架。以下是结合官方定义和技术架构的详细解析:
既然是开源框架和库,那么它具体有什么用,能做什么?
按照我个人理解来看,LangChain 的核心价值在于------它提供了一个模块化、可插拔的集成框架,将RAG(检索增强生成)流程中的各个关键环节标准化并串联起来,形成一个完整、高效的生产流水线。
-
数据准备时:xxLoader(PDF、txt、docs等)
-
数据处理时:xxSplitter(按字符、递归、标记等分割)
-
数据向量化:xxEmbeddings(OpenAI、智谱、百度千帆等)
-
数据存储与索引:xxVectorStore(Chroma、Pinecone、Milvus、ES等)
-
检索时:xxRetriever(向量检索、关键词检索、混合检索、重排序等)
-
与LLM交互时:xxLLM(各类大模型接口) + xxPrompt(提示词模板) + xxMemory(对话记忆)
-
流程编排与整合 :Chain(将以上所有环节组装成可执行的流水线,例如
RetrievalQAChain), LCEL(LangChain表达语言)用于更灵活、可靠的链式构建。
实际意义:
LangChain 的作用是为RAG(及更多LLM应用)提供了一套"标准组件"和"装配蓝图" 。它让开发者无需从零开始造轮子,可以像搭积木一样,快速选择合适的数据加载器、文本分割策略、嵌入模型、向量数据库、检索器和大模型,并通过 Chain 的概念将它们优雅地组装成一个稳定、可维护的应用程序。这极大地降低了构建复杂LLM应用的技术门槛和工程复杂度,实现了真正的"全流程赋能"。
我们来看一个简单的业务流程:
任务目标
构建一个支持多格式文档上传、语义检索和智能问答的知识库系统,实现以下功能:
- 自动解析 PDF / 文本 / 网页等格式的文档
- 对文档内容进行语义切分和向量存储
- 支持基于上下文的智能问答和多轮对话
- 提供工具调用能力(如计算器、代码解释器)
流程图说明
PDF 文本 网页 否 是 返回工具调用结果 用户上传文档 文档类型 PyPDFLoader TextLoader WebBaseLoader RecursiveCharacterTextSplitter OllamaEmbeddings FAISS向量存储 用户提问 ConversationalRetrievalChain 检索器 ChatOpenAI模型 是否需要工具 回答用户 PythonAstREPL工具 ConversationBufferMemory
关键组件说明
- 文档加载器 :
- PyPDFLoader:处理 PDF 文件
- TextLoader:处理纯文本文件
- WebBaseLoader:抓取网页内容
- 文本处理 :
- RecursiveCharacterTextSplitter:按 1000 字符分块,保留 200 字符重叠
- 向量存储 :
- FAISS:高性能本地向量数据库,支持余弦相似度搜索
- 核心链 :
- ConversationalRetrievalChain:结合检索器和 LLM,支持多轮对话
- 工具调用 :
- PythonAstREPLTool:执行 Python 代码,用于数据分析或计算
- 记忆模块 :
- ConversationBufferMemory:保存最近 5 轮对话历史
技术选型依据
- 向量数据库:FAISS 在本地部署场景下具有较高的检索效率,适合中小型知识库
- 文本分割:RecursiveCharacterTextSplitter 能在保持语义完整的前提下优化上下文窗口
- 工具集成:Python 解释器工具可以扩展系统能力,处理需要动态计算的复杂问题
- 模型选择:ChatOpenAI 提供稳定的生成能力,OllamaEmbeddings 支持本地低成本部署
1. 框架的本质:标准化的开发范式
LangChain 的核心价值在于提供了一套结构化的开发框架,帮助开发者高效构建基于大语言模型(LLM)的复杂应用。它通过以下方式实现这一目标:
-
模块化设计:将应用拆分为可复用的组件(如模型接口、提示模板、工具调用、内存管理等),开发者可像搭积木一样组合这些模块。
-
标准化接口 :定义统一的交互协议(如
Runnable接口),允许不同模型(如 OpenAI、Anthropic)、工具(如 SQL 数据库、搜索引擎)和数据源(如 PDF、Notion)无缝集成。 -
控制流管理 :通过 链(Chains) 和 代理(Agents) 实现多步骤任务编排。例如,链可以串联 "文档检索→信息提取→结果生成" 的流程,而代理能让 LLM 动态决定调用哪些工具(如调用 Wolfram Alpha 进行数学计算)。
-
全生命周期支持 :从开发阶段的快速原型构建(如用
create_agent模板生成聊天机器人),到生产阶段的部署(通过 LangServe 转为 API)和监控(通过 LangSmith 追踪模型行为),LangChain 覆盖了应用的完整生命周期。
2. 工具和库的集合:丰富的功能组件
LangChain 提供了大量开箱即用的工具和库,作为框架的 "血肉":
-
模型集成:支持 1000+ 模型和工具,包括 GPT-4、Gemini、Wolfram Alpha、Pinecone 向量数据库等18。
-
核心库:
-
langchain-core:定义基础抽象(如消息类型、工具接口)和表达式语言(LCEL),用于声明式构建复杂逻辑25。 -
langchain-community:社区贡献的扩展库,涵盖文件加载器、自定义工具等小众需求8。 -
langgraph:用于构建有状态的多智能体应用,支持持久化对话历史和复杂控制流25。
-
-
实用工具:如提示优化、输出解析、流式处理等,帮助开发者解决 LLM 应用中的常见痛点35。
3. 开源生态的核心价值
LangChain 采用 MIT 开源协议,允许自由使用和修改,其生态优势体现在:
-
快速迭代:社区持续贡献新组件(如支持最新向量数据库或 API),开发者无需等待官方更新即可接入前沿技术。
-
跨平台支持:同时提供 Python 和 JavaScript 版本,适配后端服务、前端应用、边缘计算等多种场景。
-
企业级落地:通过 LangSmith 进行模型监控和评估,LangServe 实现微服务化部署,降低了从实验到生产的门槛。
4. 与工具库的本质区别
LangChain 与普通工具库的关键差异在于框架的系统性:
-
工具库 (如 Requests、Pandas)解决单一问题(如 HTTP 请求、数据处理),而 LangChain 整合这些工具,形成完整的解决方案。例如,结合
langchain-openai(模型调用)、langchain-sqlite(数据库查询)和langchain-chains(流程编排),可快速构建一个能回答数据库问题的智能代理。 -
动态决策能力:LangChain 的代理机制允许 LLM 根据实时反馈调整工具调用策略,而工具库通常需要开发者预先编写固定逻辑
二、LangChain的核心模块
-
LLM和提示(Prompt):统一大模型访问API,同时提供了Prompt提示模板管理机制。
-
输出解析器(Output Parsers):LangChain接受大模型返回的文本内容之后,可以使用专门的输出解析器对文本内容进行格式化,例如解析json,或者将llm输出的内容转化为Python对象。
-
链(Chain):对一些常见的场景封装了一些现成的模块,如:基于上下文信息的问答系统,自然语言生成SQL查询等等,因为实现这些任务的过程就像工作流一样,一步步的执行,所以叫链。
-
表达式语言:LangChain Expression Language(LCEL),新版本特性,用于解决工作流编排问题,通过LCEL表达式,我们可以灵活的自定义AI任务处理流程,也就是灵活自定义链(Chain)。
-
数据增强生成(RAG):大模型不了解新的信息,为了避免它一本正经的瞎讲,我们将新的信息导入到LLM,用于增强LLM生成的内容质量,这种模式叫做RAG模式
-
Agents:是一种基于大模型的应用设计模式,利用LLM的自然语言理解和推理能力,更具用户的需求自动调用外部系统、设备共同去完成任务,例如:用户输入"明天请假一天",大模型(LLM)自动调用请假系统,发起一个请假申请。
-
模型记忆(memory):让大模型记住之前的对话内容,这种能力成为模型记忆。
使用LangChain进行大模型开发,需要安装相关包依赖,安装命令:
Bash
# LangChain框架安装
pip install langchain
# 版本查看
pip show langchain
LangChain V1.0 vs LangGraph V1.0:分工与定位
-
LangChain :构建 AI 智能体的 最快方式 。提供标准的工具调用架构、供应商无关设计和可插拔的中间件系统,让开发者高效构建通用 Agent。
-
LangGraph :一个 底层运行时框架 ,专为需要长期运行、可控且高定制化的生产级智能体设计。
三、使用LangChain框架,调用AI大模型
构建的工作流用途不同,调用的方式也会稍有区别:
1. 专注于生成单段文本(如回答问题或写一段文字)
Python
from config import Config
conf=Config()
#1、openai方式连接大模型
from openai import OpenAI
# 初始化Deepseek的API客户端
client = OpenAI(api_key = conf.API_KEY, base_url="https://api.deepseek.com")
# 调用Deepseek的API,生成回答
response =client.chat.completions.create(model="deepseek-chat'
messages=[
{"role": "system","content":"你是传智教育的助手传智小智,,请根据用户的问题给出回答"},
{"role": "user","content":"你好,请你介绍一下你自己。"}
]
# 打印模型最终的响应结果
print(response.choices[0].message.content)
2. 能聊天的应用(如客服机器人或问答系统)
2.1 OpenAI标准库调用:
Python
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage
# 初始化 ChatOpenAI,配置 DeepSeek API
llm = ChatOpenAI(
model=conf.MODEL,
api_key=conf.API_KEY,
base_url=conf.API_URL,
temperature=0.7,
max_tokens=150
)
messages = [SystemMessage(content = "你是一个从事大模型开发多年的工程师,请根据用户的问题给出回答。"),
HumanMessage(content = "请你用一句话介绍一下LangChain")
]
result = llm.invoke(message)
print(result.content)
2.2 LangChain-模型厂商调用:
Python
model = ChatDeepSeek(
model="...",
api_key="...",
api_base="https://DeepSeek.ai/api/v1",
temperature = 0.5
)
区别:在调用DeepSeek等有独立接口的厂商的深度思考模型的时候,使用厂商独立接口可以拿到深度思考的思考过程。
2.3 自定义调用
应用场景:OpenAI标准库不会把深度思考模型的思考过程给你,但你又想使用OpenAI标准库方式来调用大模型,又想获取这个思考过程(俗称犟种)。
问:这几种调用方式都用到了 invoke() 这个函数:这个函数可以传入几种数据类型?
答:三种:字符串、消息对象列表、字典列表。
问:消息对象有几种,都是什么?
答:四种message:AI_message、tool_message、human_message、system_message
四、提示词管理工具类
LangChain封装了一组专门用于提示词(Prompts)管理的工具类,方便我们格式化提示词(prompts)内容,目的是将提示的逻辑(固定结构)与提示的数据(动态变量)彻底分离,从而实现代码的简洁、复用、安全和可维护性。
| 模板类型 | 核心作用 | 输出格式 | 使用场景 |
|---|---|---|---|
| ChatPromptTemplate | 现代应用的主力。用于构建包含多个角色(系统、用户、AI)的对话式"剧本" | ChatPromptValue(本质是List[BaseMessage]) | 所有需要与聊天模型进行结构化、角色化交互的应用 |
| PromptTemplate | 基础的字符串模板,将变量填充到单个字符串中。 | StringPromptValue(本质是字符串) | 构建需要简单文本输入的工具提示。 |
| FewShotChatMessage PromptTemplate | 在提示中动态插入多个对话示例,引导模型进行"举一反三",极大提升复杂任务的准确性 | ChatPromptValue | 需要特定输出格式的复杂任务,如代码生成、简历解析、合同条款提取等等 |
五、Message
聊天模型(Chat Model)以聊天消息列表作为输入,这个聊天消息列表的消息内容也可以通过提示词模板进行管理。这些聊天消息与原始字符串不同,因为每个消息都与"角色(role)"相关联。
例如,在OpenAI的Chat Completion API中,Openai的聊天模型,给不同的聊天消息定义了三种角色类型分别是助手(assistant)、人类(human)或系统(system)角色:
-
AIMessagePromptTemplate:助手(Assistant)消息指的是当前消息是AI回答的内容。
-
HumanMessagePromptTemplate:人类(user)消息指的是你发给AI的内容。
-
SystemMessagePromptTemplate:系统(system)消息通常是用来给AI身份进行描述。
六、Chain
能被称作LangChain,可想而知,Chain的含金量有多高了吧。Chain,也就是链的意思,指的是按照某一种逻辑,按顺序组合成一个流水线的方式。
Python
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage
# 初始化 ChatOpenAI,配置 DeepSeek API
llm = ChatOpenAI(
model=conf.MODEL,
api_key=conf.API_KEY,
base_url=conf.API_URL,
temperature=0.7,
max_tokens=150
)
# 直接使用模型+ 输出解析器搭建一个链
basic_chain = llm | StrOutputParaser()
'''
这是一个最简单的链式结构,模型+结果解析器
'''
# 查看输出结果
question = "你是哪个?"
result = basic_chain.invoke(question)
print(result)
问:前面有提到LECL,Chain与其有何区别?
答:用最接地气的话讲,核心区别就一个:Chain 是 "固定好的流程模板",LCEL 是 "能自由拼搭的积木语法" ------ 前者帮你把 "做事步骤" 固定死,后者让你随便组合步骤,想怎么搭就怎么搭。
1. Chain:像"现成的组装玩具说明书"
比如你买了个 "乐高小车",Chain 就相当于说明书上写死的步骤:
-
先拼车轮(对应 "调用工具查数据");
-
再拼车身(对应 "把数据传给 AI 处理");
-
最后装车灯(对应 "AI 输出结果")。
它的特点是:
-
步骤固定死,不能乱改:想在 "拼车身" 后加个 "贴贴纸"(比如加个数据过滤步骤),要么找个带 "贴贴纸" 的新说明书(对应换个现成的 Chain),要么自己拆了原步骤改(代码麻烦);
-
不用想 "怎么组合":拿来就能用,但只能做说明书上写的事;
-
种类多但不通用:比如有 "拼小车的 Chain""拼房子的 Chain"(对应 LLMChain、SequentialChain、RouterChain),不同场景要换不同的 Chain,记起来麻烦。
2. LCEL(LangChain Expression Language):像"乐高积木的通用拼接规则"
还是乐高,但 LCEL 不管你想拼车、拼房子还是拼飞船,只给你一套 "怎么拼" 的通用逻辑(比如 "凸点对凹点""长积木可以接短积木"),然后让你随便拿零件组合:
-
零件就是 "功能模块":比如 "车轮模块""车身模块""贴贴纸模块""查天气模块""AI 生成模块";
-
拼接用 "简单符号":比如用 "|"(管道符)像 "传接力棒" 一样串步骤,比如:
-
查天气模块 | 过滤无效数据模块 | AI 推荐活动模块 | 输出结果模块
-
(意思是:先查天气→再筛掉没用的信息→再让 AI 推荐活动→最后告诉用户);
-
想改就改,想加就加:比如突然想加 "如果下雨就提醒带伞"(条件判断),直接插个 "条件模块" 进去就行;想让 "查天气" 和 "查温度" 同时做(并行),也能直接拼,不用换整套流程。
核心区别对比(一句话总结)
| 维度 | Chain(老方法) | LCEL(新方法) |
|---|---|---|
| 本质 | 固定的 "流程模板" | 灵活的 "积木拼接语法" |
| 用法 | 找现成模板,按模板做事 | 拿模块自由组合,自己定流程 |
| 灵活性 | 低:改步骤要换模板 / 改代码 | 高:随便加、减、换模块,支持条件 / 并行 |
| 学习成本 | 高:要记各种不同类型的 Chain | 低:一套语法通吃所有模块 |
| 适用场景 | 简单、固定的流程(比如 "输入→AI 输出") | 复杂、多变的流程(比如 "查数据→过滤→条件判断→AI 生成→多轮交互") |
最后补个大白话结论:
Chain 是 LangChain 早期的 "老工具",适合新手快速上手简单需求(比如直接让 AI 回答问题);
LCEL 是现在 LangChain 推荐的 "新玩法",相当于把原来 "固定的模板" 拆成了 "可自由组合的零件 + 通用拼接规则",能搞定更复杂的需求,而且用起来更简单(不用记一堆 Chain 类型,靠 "|" 就能串流程)。
那么好,讲完这个区别之后,我们应该来看看:
都有什么链、有什么特点···
1. 串行链
Python
# 初始化 ChatOpenAI,配置 DeepSeek APIconf=Config()
llm = ChatOpenAI(
model=conf.MODEL,
api_key=conf.API_KEY,
base_url=conf.API_URL,
temperature=0.7,
max_tokens=150
)
print("--- 1. 串行链 (Sequential Chain) 示例 ---")
# 定义流水线的三个"工位"
prompt = ChatPromptTemplate.from_template("写一句关于"{topic}"的七言绝句。")
# llm 在通用环境中已定义
parser = StrOutputParser()
# 使用 LCEL `|` 管道符,将三个工位连接成一条串行流水线
serial_chain = prompt | llm | parser
print(type(serial_chain))
# 启动流水线,投入原材料
input_data = {"topic": "月色"}
result = serial_chain.invoke(input_data)
print(f"【输入】: {input_data}")
print(f"【最终输出】: {result}")
2. 并行链
Python
# langchain_4_2_chain.py (Corrected)
from langchain.prompts import PromptTemplate
from config import Config
import json
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# Import the necessary class
from langchain_core.runnables import RunnableParallel
# 初始化 ChatOpenAI,配置 DeepSeek APIconf=Config()
llm = ChatOpenAI(
model=conf.MODEL,
api_key=conf.API_KEY,
base_url=conf.API_URL,
temperature=0.7,
max_tokens=150
)
print("\n--- 2. 并行链 (Parallel Chain) 示例 ---")
# 定义两条独立的子流水线
poem_chain = ChatPromptTemplate.from_template("写一首关于"{topic}"的诗。") | llm | StrOutputParser()
joke_chain = ChatPromptTemplate.from_template("讲一个关于"{topic}"的俏皮话。") | llm | StrOutputParser()
# 使用 RunnableParallel 将字典结构转换为一个可执行的并行链
parallel_chain = RunnableParallel({
"poem": poem_chain,
"joke": joke_chain
})
# 启动并行流水线
input_data = {"topic": "程序员"}
# Now .invoke() works because parallel_chain is a Runnable object, not a dict
result = parallel_chain.invoke(input_data)
print(f"【输入】: {input_data}")
print("【最终输出】:")
# 结果是一个字典,包含了所有子流水线的输出
print(json.dumps(result, indent=2, ensure_ascii=False))
3. 分支与RunnablePassthrough()
该函数可以保留一份原本的数据文件
Python
# 初始化 ChatOpenAI,配置 DeepSeek APIconf=Config()
llm = ChatOpenAI(
model=conf.MODEL,
api_key=conf.API_KEY,
base_url=conf.API_URL,
temperature=0.7,
max_tokens=150
)
print("\n--- 3. 分支与 RunnablePassthrough 示例 ---")
# 1. 模拟一个检索器
def fake_retriever(query: str) -> str:
"""一个模拟的检索器,根据查询返回固定的上下文。"""
return f"关于"{query}"的背景知识是:这是一个非常重要的概念。"
# 2. 定义需要同时接收 context 和 question 的 Promptrag_prompt = ChatPromptTemplate.from_template(
"根据以下上下文回答问题。\n上下文: {context}\n问题: {question}"
)
# 3. 构建包含 Passthrough 的并行链
# 这条链接收一个字符串(问题)作为输入
# rag_prompt
chain = {
# "context" 分支:对输入运行检索器
"context": fake_retriever,
# "question" 分支:直接"透传"原始输入
"question": RunnablePassthrough()
} | rag_prompt | llm | StrOutputParser()
print(type(chain))
# 4. 执行链
user_question = "LCEL"
result = chain.invoke(user_question)
4. RunnableLambda自定义
RunnableLambda是一个"适配器",它可以将任何普通的python函数包装成一个标准的LangChain组件,让它无缝接入到LCEL的"|"流水线中。
实现方式:
Python
# 初始化 ChatOpenAI,配置 DeepSeek APIconf=Config()
llm = ChatOpenAI(
model=conf.MODEL,
api_key=conf.API_KEY,
base_url=conf.API_URL,
temperature=0.7,
max_tokens=150
)
print("\n--- 4. RunnableLambda 示例 ---")
# 1. 定义一个普通的 Python 函数,它不是标准的 LangChain 组件
def add_comment(text: str) -> str:
"""百度官方网站"""
return text.strip() + "\n 关于更多信息,欢迎访问:https://www.baidu.com/"
# 2. 使用 RunnableLambda 将其包装成一个"标准工位"
custom_processor = RunnableLambda(add_comment)
# 3. 构建一条包含自定义工位的串行链
chain = (
ChatPromptTemplate.from_template("请解释一下"{concept}"是什么。最多100字")
| llm
| StrOutputParser()
| custom_processor # 在这里接入我们的自定义函数
)
# 4. 执行链
result = chain.invoke({"concept": "大模型"})
print("【最终输出】:")
print(result)
七、输出解析器(output Parsers)
输出解析器是LangChain"模型I/O"模块中的关键组件。他的核心职责是扮演一个翻译官。
应用程序需要精确、结构化的数据,OutputParser接收LLM返回的文本以后,根据我们预先设定好的规则,将其解析转换为程序可以使用的、干净的结构化数据
| 解析器名称 | 核心功能 | 输出的 Python 类型 | 工业级应用场景 |
|---|---|---|---|
| StrOutputParser | 默认解析器。将 LLM 的输出直接解析为字符串。 | str | 简单的文本生成、内容续写、摘要、翻译等。 |
| JsonOutputParser | 极其常用。将 LLM 输出的 JSON 字符串解析为 Python 字典。 | dict | API 调用 :生成符合 API 规范的 JSON 请求体。 数据提取:从非结构化文本(如邮件、报告)中提取实体信息。 |
| PydanticOutputParser | 极其常用。将 LLM 输出解析为预先定义的 Pydantic 对象,提供类型安全和数据验证。 | 自定义的 pydantic.BaseModel 对象 | 高可靠性系统 :从简历中提取信息并存入数据库,确保字段类型正确(如年龄是整数)。 配置生成:根据自然语言生成严格的 JSON 配置文件。 |
| CommaSeparatedListParser | 将 LLM 输出的、用逗号分隔的文本解析为列表。 | list[str] | 标签生成 :为文章、产品生成关键词标签。 头脑风暴:生成一系列相关的想法或选项。 |
| DatetimeOutputParser | 从文本中智能地解析出日期和时间信息。 | datetime.datetime | 任务调度 :从 "明天下午三点提醒我开会" 中提取精确时间。 信息归档:从新闻或日志中提取事件发生的时间。 |
来个实现示例吧,这里我们使用的是JsonOutputParser:
python
# 确保您已安装必要的库,并已在环境中设置好 API 密钥
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser, PydanticOutputParser,StrOutputParser
from pydantic.v1 import BaseModel, Field
from typing import List
from config import Config
conf=Config()
llm = ChatOpenAI(
model=conf.MODEL,
api_key=conf.API_KEY,
base_url=conf.API_URL,
temperature=0.7,
max_tokens=150
)
print("--- 1. JsonOutputParser 示例 ---")
# 1. 创建一个 JsonOutputParser 实例
json_parser = JsonOutputParser()
# 2. 创建一个 Prompt 模板,并注入格式化指令
# .get_format_instructions() 会自动生成告诉 LLM 如何输出 JSON 的指令
prompt = ChatPromptTemplate.from_template(
"""从以下文本中提取用户的姓名和城市。
{format_instructions}
文本: {text}"""
)
# 3. 构建 LCEL 链
chain = prompt | llm | json_parser
# 4. 准备输入数据
unstructured_text = "张伟是一位居住在北京的软件工程师,他今年30岁。"
# 5. 调用链并传入格式化指令和文本
result = chain.invoke({
"format_instructions": json_parser.get_format_instructions(),
"text": unstructured_text
})
# 6. 验证输出
print(f"【输入文本】: {unstructured_text}")
print(f"【输出类型】: {type(result)}")
print(f"【解析结果】: {result}")
# 预期输出:
# 【输出类型】: <class 'dict'>
# 【解析结果】: {'name': '张伟', 'city': '北京'}
八、文档加载器(document_loaders)
用于从各种数据源加载文档的组件,旨在将不同格式的文件(文本、PDF、Markdown、Word、PowerPoint等)或数据源(网页、数据库)转换为统一的Document对象。每个Document对象包含page_content(文档内容)和 metadata(元数据,如文件路径、页码等)
| 加载器名称 | 支持文件类型 | 功能描述 | 模块路径 | 工业场景应用 |
|---|---|---|---|---|
| TextLoader | .txt | 加载纯文本文件,支持指定编码(如 UTF-8) | langchain_community. document_loaders | 解析日志文件、用户指令、配置文件(如设备日志、控制指令) |
| PyPDFLoader | 加载 PDF 文件,按页面提取文本 | langchain_community. document_loaders | 提取技术手册、合同、报告内容,生成结构化数据(如设备说明书、质量报告) | |
| Unstructured MarkdownLoader | .md | 加载 Markdown 文件,保留标题、列表等结构 | langchain_community. document_loaders | 解析技术文档、API 文档,构建知识库或 RAG 系统(如开发手册、操作指南) |
| Docx2txtLoader | .docx | 加载 Word 文档,提取纯文本 | langchain_community. document_loaders | 处理企业文档、报告、会议记录(如项目计划书、操作规程) |
| UnstructuredPower PointLoader | .ppt, .pptx | 加载 PowerPoint 文件,提取幻灯片文本 | langchain_community. document_loaders | 解析培训材料、演示文档(如技术培训 PPT、产品介绍) |
| WebBaseLoader | 网页 | 加载网页内容,支持 URL 或 HTML | langchain_community. document_loaders | 抓取在线技术文档、新闻、产品说明,补充知识库 |
| CSVLoader | .csv | 加载 CSV 文件,支持按行或列提取数据 | langchain_community. document_loaders | 解析工业数据表格、传感器数据(如生产数据、设备状态记录) |
| JSONLoader | .json | 加载 JSON 文件,提取指定字段或全部内容 | langchain_community. document_loaders | 解析结构化数据、API 响应(如设备状态 JSON、日志文件) |
| DirectoryLoader | 目录 | 批量加载目录中的多种文件类型 | langchain_community. document_loaders | 批量处理企业文档库、混合格式文件(如技术文档、日志文件夹) |
来个实现的代码示例吧,这里示例使用的是textloader:
python
from langchain_community.document_loaders import TextLoader # 使用新模块路径
from config import Config
from langchain_openai import ChatOpenAI
from datetime import datetime
# 初始化配置和模型
conf = Config()
llm = ChatOpenAI(
model=conf.MODEL, # 直接指定模型名称
api_key=conf.API_KEY,
base_url=conf.API_URL,
temperature=0.7,
max_tokens=150
)
# Document Loaders 示例:加载文档并接入大模型总结
loader = TextLoader(r"D:\LLM_Codes\Chapter3_RAG\rag_base_frame\data\林青霞.txt", encoding="utf-8")
documents = loader.load()
doc=documents[0]
print("\n--- 1. 加载后的原始元信息 ---")
print(doc.metadata)
# 1.2 像操作字典一样,为 Document 对象添加自定义元信息
print("\n--- 2. 添加自定义元信息 ---")
doc.metadata['author'] = 'DT.L'
doc.metadata['version'] = '1.1'
doc.metadata['processed_at'] = datetime.now().isoformat()
doc.metadata['tags'] = ['test', 'loader', 'metadata']
print("更新后的元信息:")
print(doc.metadata)
print("\n--- 3. 访问特定的元信息 ---")
print(f"Author: {doc.metadata.get('author', 'Unknown')}")
print(f"Tags: {doc.metadata.get('tags')}")
print("\n--- 4. 删除元信息 ---")
del doc.metadata['version']
print("更新后的元信息:")
print(doc.metadata)
print("\n--- 5. 获取文本信息 ---")
print(doc.page_content)
# --- 6. 清空文本信息 -不能采用del ---
print("\n--- 6. 清空文本信息 ---")
doc.page_content = "" # 正确做法:赋值为空字符串,而不是删除属性
print("更新后的信息:")
print(doc)
九、文本分割器
文本分割器是依据字符、结构、语义等规则,将各类格式的文本拆分为适配 LLM 输入、知识库检索等场景的合适片段,辅助后续文本处理的工具。
| 分割器名称 | 功能描述 | 类型 | 工业场景应用 |
|---|---|---|---|
| RecursiveCharacterTextSplitter | 递归按字符分割,先尝试自然边界(如段落、句子),太大则继续细分。 | 通用字符解析 | 通用文本处理,如日志、报告、PDF 文档分割,便于 RAG 检索。 |
| CharacterTextSplitter | 简单按指定分隔符(如换行、逗号)直接分割。 | 基础字符解析 | 简单字符串或 CSV 数据处理,如传感器数据日志。 |
| TokenTextSplitter | 按 token(词元)分割,支持 LLM token 计数。 | Token 基于解析 | LLM 输入优化,如处理 API 响应或长查询,控制 token 限制。 |
| MarkdownTextSplitter | 按 Markdown 结构(如标题、列表)智能分割。 | 结构化解析 | Markdown 文档分割,保留语义结构,用于知识库构建。 |
| HTMLSplitter | 按 HTML 标签(如 、 )分割网页内容。 | 结构化解析 | 网页数据爬取,如在线技术文档或新闻提取。 |
| SentenceTextSplitter | 按句子边界分割,使用 NLP 识别句子(包括标点)。 | 语义解析 | 自然语言文本,如文章或对话分析,保持句子完整。 |
| PythonCodeTextSplitter | 按 Python 代码结构(如函数、类)分割。 | 代码解析 | 源代码文件分析,如脚本调试或代码库管理。 |
| LatexTextSplitter | 按 LaTeX 结构(如章节、公式)分割。 | 结构化解析 | 学术论文或数学文档处理。 |
| SpacyTextSplitter | 使用 SpaCy NLP 库按句子或实体分割(需安装 SpaCy)。 | 语义解析 | 高级 NLP 场景,如实体提取或生物医学文本。 |
| NLTKTextSplitter | 使用 NLTK 库按句子或词分割(需安装 NLTK)。 | 语义解析 | 文本研究或分析,如时间序列数据描述。 |
来个示例吧,这里使用的是RecursiveCharacterTextSplitter:
python
# ----------------------------------------------------
# 1. 准备环境和依赖库
# ----------------------------------------------------
# 确保你已经安装了 langchain# pip install langchain langchain-core
import logging
from langchain_core.documents import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
# ----------------------------------------------------
# 2. 配置日志
# ----------------------------------------------------
# 配置一个简单的日志记录器,方便在控制台清晰地看到输出
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
log = logging.getLogger(__name__)
# ----------------------------------------------------
# 3. 准备示例文本和Document对象
# ----------------------------------------------------
# 模拟一段工业监控报告,这段文本包含了由空行分隔的段落、
# 由换行符分隔的列表项,以及一个没有内部换行的长句子。
sample_text = """
LangChain框架是构建大语言模型应用的核心工具。它提供了模块化的组件和标准化的接口,极大地简化了从数据处理到模型调用和输出解析的整个流程。
在工业监控领域,LangChain展现了巨大潜力。例如,工程师可以利用它快速构建一个RAG系统,该系统能够:
1. 实时读取设备传感器日志。
2. 将非结构化的日志文本分割、向量化并存入知识库。
3. 当检测到异常指标时,自动从知识库中检索相关维护手册和历史故障案例。
4. 利用LLM分析检索到的信息,并生成一份详细的故障诊断报告和操作建议。
这个过程不仅响应迅速,而且极大地提升了故障排查的准确性和效率。传统的监控系统通常依赖固定的规则和阈值,而基于LangChain的智能系统则能理解日志的深层语义,从而发现更复杂的潜在问题。
"""
# 将原始文本封装成 LangChain 的 Document 对象
# metadata 字段可以用来存储文档的来源、ID等元信息
doc = Document(
page_content=sample_text,
metadata={"source": "industrial_monitor_log.txt", "id": "doc_001"}
)
log.info(f"原始文档创建成功,总长度: {len(doc.page_content)} 字符。")
# print("-" * 80)
# print(f"原始文档内容:\n{doc.page_content}")
# print("-" * 80)
# ----------------------------------------------------
# 4. 初始化并使用 RecursiveCharacterTextSplitter# ----------------------------------------------------
# 初始化递归字符分割器
# chunk_size: 定义每个文本块(chunk)的最大字符数。这是硬性限制。
# chunk_overlap: 定义相邻块之间重叠的字符数,用于保持上下文的连续性。
# separators: 定义了分割文本时尝试使用的分隔符列表,按从左到右的优先级顺序。
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=100,
chunk_overlap=50,
length_function=len,
is_separator_regex=False,
separators=["\n\n", "\n", " ", ""] # 默认分隔符,这里显式写出以便理解
)
# 执行分割操作
# 注意:split_documents 方法接收一个 Document 列表,返回也是一个 Document 列表
chunks = text_splitter.split_documents([doc])
# ----------------------------------------------------
# 5. 打印和验证分割结果
# ----------------------------------------------------
log.info(f"文本分割完成,共生成 {len(chunks)} 个块(chunks)。")
print("\n" + "="*80)
print(" 分割结果展示")
print("="*80 + "\n")
for i, chunk in enumerate(chunks):
print(f">>> --- Chunk {i+1} / {len(chunks)} ---")
print(f" 源数据 (Metadata): {chunk.metadata}")
print(f" 块长度 (Length): {len(chunk.page_content)} characters")
print(f" 块内容 (Content):\n\n'{chunk.page_content}'")
print("\n" + "-"*80 + "\n")
十、工具
工具是赋予大型语言模型(LLM)与外部世界交互能力的接口。
LangChain 工具调用的实质是让大语言模型(LLM)通过标准化接口动态调用外部工具,扩展自身能力。其核心流程包括:1、意图识别与参数提取,2、工具适配与执行,3、结果格式化反馈16。
示例 :
用户问 "12345×67890 是多少",LLM 调用内置的CalculatorTool,传入参数 "12345*67890",工具计算后返回结果,LLM 再整理成自然语言回答16。整个过程无需人工干预,工具调用逻辑完全自动化。
代码落地:
python
# --- 步骤2: 定义我们的工具 ---@tool
def multiply(a: int, b: int) -> int:
"""用于计算两个整数的乘积。"""
print(f"正在执行乘法: {a} * {b}")
return a * b
@tool
def search_weather(city: str) -> str:
"""用于查询指定城市的实时天气。"""
print(f"正在查询天气: {city}")
if "北京" in city:
return "北京今天是晴天,气温25摄氏度。"
elif "上海" in city:
return "上海今天是阴天,有小雨,气温22摄氏度。"
else:
return f"抱歉,我没有'{city}'的天气信息。"
完整代码:
python
# -*- coding: utf-8 -*-
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, AIMessage
# --- DeepSeek API 配置 ---# 请替换为你的 DeepSeek API 密钥
API_KEY = "sk-52****************************"
API_URL = "https://api.deepseek.com/v1"
MODEL = "deepseek-chat"
# --- 步骤1: 初始化 ChatOpenAI ---# 虽然我们用的是DeepSeek,但它兼容OpenAI的API格式,所以可以使用ChatOpenAI类
llm = ChatOpenAI(
model=MODEL,
api_key=API_KEY,
base_url=API_URL,
temperature=0.8,
max_tokens=300
)
# --- 步骤2: 定义我们的工具 ---
@tool
def multiply(a: int, b: int) -> int:
"""用于计算两个整数的乘积。"""
print(f"正在执行乘法: {a} * {b}")
return a * b
'''
下面的工具中,返回的东西是写死的,真实的业务场景中,会去调用其他数据提供商的api接口来调用实时数据
'''
@tool
def search_weather(city: str) -> str:
"""用于查询指定城市的实时天气。"""
print(f"正在查询天气: {city}")
if "北京" in city:
return "北京今天是晴天,气温25摄氏度。"
elif "上海" in city:
return "上海今天是阴天,有小雨,气温22摄氏度。"
else:
return f"抱歉,我没有'{city}'的天气信息。"
# 将工具列表放入一个变量
tools = [multiply, search_weather]
# --- 步骤3: 将工具绑定到LLM ---
# .bind_tools() 方法会将工具的结构信息(名称、描述、参数)传递给模型
# 这样模型在推理时就知道自己有哪些"超能力"了
llm_with_tools = llm.bind_tools(tools)
# --- 步骤4: 发起调用 ---# 第一次调用:让模型决定是否以及如何调用工具
print("--- 第一次调用:模型生成工具调用指令 ---")
query = "北京今天天气怎么样?另外请帮我计算一下 12乘以8 等于多少?"
# invoke 方法会返回一个 AIMessage 对象
# 如果模型决定调用工具,相关信息会储存在 .tool_calls 属性中
ai_msg = llm_with_tools.invoke(query)
print("模型返回的AIMessage:")
print(ai_msg)
print("\n解析出的工具调用请求:")
print(ai_msg.tool_calls)
# --- 步骤5: 执行工具调用 ---print("\n--- 执行工具调用 ---")
tool_results = []
for tool_call in ai_msg.tool_calls:
# 根据工具名称找到对应的工具函数
tool_name = tool_call["name"]
tool_args = tool_call["args"]
# 查找对应的工具
selected_tool = None
for t in tools:
if t.name == tool_name:
selected_tool = t
break
if selected_tool:
print(f"执行工具: {tool_name}, 参数: {tool_args}")
# 执行工具并获取结果
"""
使用 @tool 装饰器定义函数时,LangChain 会自动将这个普通函数包装成一个 Tool 对象。
这个 Tool 类继承了 Runnable 基类,因此具有 invoke() 方法。
""" result = selected_tool.invoke(tool_args)
tool_results.append({
"tool_call_id": tool_call["id"],
"name": tool_name,
"result": result
})
print(f"工具执行结果: {result}")
else:
print(f"未找到工具: {tool_name}")
十一、智能代理(Agent)
Agent内部包含一个LLM和一套工具,他能像一个"思考者"一样,自主规划、决定是否需要调用工具、调用哪个工具、以及如何组织调用顺序,直到最终完成任务。
适用于处理复杂、多步骤的任务,当我们武大确定完成任务需要调用多少次,调用哪些工具时,Agent就是最佳选择~
直接来个代码示例吧:
python
# -*- coding: utf-8 -*-
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder # 修正:需要导入 MessagesPlaceholder
# --- 步骤1: DeepSeek API 配置和初始化 ---# 1.1 配置API参数
API_KEY = "sk-52e226ac3cac46838cb282b45b1a648e" # 替换为你的实际API密钥
API_URL = "https://api.deepseek.com/v1" # DeepSeek API端点
MODEL = "deepseek-chat" # 使用的模型名称
# 1.2 初始化ChatOpenAI客户端
# 虽然使用DeepSeek,但兼容OpenAI API格式
llm = ChatOpenAI(
model=MODEL,
api_key=API_KEY,
base_url=API_URL,
temperature=0, # 温度参数设为0,使输出更确定性
max_tokens=1000 # 增加最大token数以处理更长的响应
)
# --- 步骤2: 定义工具函数 ---# 2.1 定义乘法计算工具
@tool
def multiply(a: int, b: int) -> int:
"""
用于计算两个整数的乘积。
参数:
a: 第一个整数
b: 第二个整数
返回:
两个整数的乘积
""" print(f"正在执行乘法: {a} * {b}")
return a * b
# 2.2 定义天气查询工具
@tool
def search_weather(city: str) -> str:
"""
用于查询指定城市的实时天气。
参数:
city: 城市名称
返回:
该城市的天气信息
""" print(f" 正在查询天气: {city}")
if "北京" in city:
return "北京今天是晴天,气温25摄氏度。"
elif "上海" in city:
return "上海今天是阴天,有小雨,气温22摄氏度。"
elif "广州" in city:
return "广州今天是多云,气温28摄氏度。"
elif "深圳" in city:
return "深圳今天是晴天,气温30摄氏度。"
else:
return f"抱歉,我没有'{city}'的天气信息。"
# 2.3 创建工具列表
tools = [multiply, search_weather]
# --- 步骤3: 创建Agent提示模板 ---# 3.1 构建提示模板
# ChatPromptTemplate用于定义对话的结构和内容
prompt = ChatPromptTemplate.from_messages([
# 系统消息:定义AI助手的角色和行为准则
("system", "你是一个乐于助人的AI助手。请根据用户需求选择合适的工具来解决问题。"),
# 用户输入:{input}是占位符,运行时会被实际用户输入替换
("human", "{input}"),
# 关键修正:使用MessagesPlaceholder而不是("placeholder", "...")
# agent_scratchpad用于存储Agent思考过程中的中间步骤(工具调用和结果)
# agent_scratchpad 是 LangChain Agent 框架中一个预定义的、固定的变量名,用于存储Agent思考过程中的中间步骤。
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
# --- 步骤4: 创建工具调用Agent ---
# 4.1 使用create_tool_calling_agent创建Agent
# 这个函数将LLM、工具和提示模板组合成一个完整的Agent
agent = create_tool_calling_agent(
llm=llm, # 语言模型
tools=tools, # 可用工具列表
prompt=prompt # 提示模板
)
# --- 步骤5: 创建Agent执行器 ---# 5.1 创建AgentExecutor
# AgentExecutor负责管理Agent的执行循环:思考->调用工具->整合结果->继续思考
agent_executor = AgentExecutor(
agent=agent, # 要执行的Agent
tools=tools, # 可用工具列表
verbose=True, # 开启详细日志,显示Agent的思考过程
handle_parsing_errors=True, # 自动处理解析错误
max_iterations=5 # 最大迭代次数,防止无限循环
)
# --- 步骤6: 运行Agent处理任务 ---print("=" * 60)
print("=" * 60)
# 6.1 处理单步任务
print("\n--- 运行Agent处理单步任务 ---")
print("用户问题: 上海今天天气怎么样?")
response1 = agent_executor.invoke({"input": "上海今天天气怎么样?"})
print("\n 最终回答:")
print(response1["output"])
# 6.2 处理多步任务
print("\n" + "=" * 60)
print("--- 运行Agent处理多步任务 ---")
print("用户问题: 先帮我查一下北京的天气,然后计算 35 乘以 3 的结果是多少?")
response2 = agent_executor.invoke({
"input": "先帮我查一下北京的天气,然后计算 35 乘以 3 的结果是多少?"
})
print("\n最终回答:")
print(response2["output"])
# 6.3 处理更复杂的任务
print("\n" + "=" * 60)
print("--- 运行Agent处理复杂任务 ---")
print("用户问题: 请告诉我广州的天气,然后计算23乘以4,最后告诉我深圳的天气")
response3 = agent_executor.invoke({
"input": "请告诉我广州的天气,然后计算23乘以4,最后告诉我深圳的天气"
})
print("\n最终回答:")
print(response3["output"])
print("\n" + "=" * 60)
十二、记忆模块
默认情况下,大模型是无状态的,也就是它不会记住你之前的任何交互。所以每一次提问都是一次全新的、独立的对话。
记忆模块 的作用就是赋予LLM上下文感知能力。通过存储、管理和传递历史对话信息,让LLM能够"记住"之前的交流内容,从而实现连贯的、有逻辑的多轮对话。
| 对比维度 | ConversationBufferMemory | ConversationBufferWindowMemory |
|---|---|---|
| 核心机制 | 完整保留全部对话历史(用户提问 + AI 回复),无自动清理逻辑 | 仅保留最近 N 轮对话(N 为窗口大小,可配置),自动丢弃早期历史 |
| 优点 | 1. 上下文完整性最高,可回溯任意早期对话信息; 2. 配置简单(无需设置窗口大小); 3. 无信息丢失风险,适合依赖长历史的任务 | 1. 严格控制 Token 消耗,降低 API 成本; 2. 减少冗余信息,提升模型响应速度; 3. 灵活适配不同对话长度需求(调整 N 值); 4. 避免历史信息过载导致的模型逻辑偏移 |
| 缺点 | 1. 对话越长,Token 消耗越大,成本越高; 2. 长历史可能包含冗余信息,拖慢响应速度; 3. 极端长对话可能触发模型 Token 上限(如 GPT-3.5 的 4k/8k 上下文); 4. 历史信息过载可能导致模型聚焦性下降 | 1. 无法回溯窗口外的早期对话,可能丢失关键历史信息; 2. 需手动调试窗口大小(N 过小可能缺失必要上下文,N 过大则失去窗口意义); 3. 短窗口场景下,模型无法感知远期对话逻辑 |
| 适用场景 | 1. 短对话交互(如单轮 / 3-5 轮问答、简单咨询); 2. 需完整回溯历史的任务(如复杂问题拆解、多步骤协作、上下文依赖强的推理); 3. 对 Token 成本不敏感、追求上下文完整性的场景 | 1. 长对话场景(如客服对话、多轮聊天、持续任务协作); 2. 对 Token 成本和响应速度敏感的场景(如批量交互、高频调用); 3. 仅需基于近期上下文决策的任务(如日常聊天、即时咨询、短期任务跟进); 4. 模型上下文窗口有限的场景(如使用 4k 上下文的模型处理长对话) |
| Token 消耗 | 随对话长度线性增长(无上限,除非手动清理) | Token 消耗稳定在窗口大小对应的范围(可控) |
| 配置难度 | 低(仅需初始化,无需额外参数) | 中(需合理设置 k 参数,如 k=5 表示保留最近 5 轮) |
至此我们才算是大概的过了一遍LangChain都有什么组件,这些组件的作用和如何使用,还有其他的组件比如embedding Models、Retrievers等等我们不再详细介绍,自己补充一下吧~~