文章目录
-
- [一、LangChain 回调系统核心概念](#一、LangChain 回调系统核心概念)
-
- [1.1 回调系统作用](#1.1 回调系统作用)
- [1.2 全量回调事件清单](#1.2 全量回调事件清单)
- [1.3 回调处理程序核心原理](#1.3 回调处理程序核心原理)
- 二、两种回调传递方式
-
- [2.1 构造函数回调(全局实例生效)](#2.1 构造函数回调(全局实例生效))
- [2.2 运行时请求回调(单次请求生效)](#2.2 运行时请求回调(单次请求生效))
- [2.3 运行时回调完整实战示例](#2.3 运行时回调完整实战示例)
- 三、自定义回调实战:流式Token输出
- 四、高阶自定义:自定义Retriever检索器
-
- [4.1 自定义检索器接口规范](#4.1 自定义检索器接口规范)
- [4.2 完整自定义检索器实战](#4.2 完整自定义检索器实战)
- 五、高阶自定义:自定义DocumentLoader文档加载器
-
- [5.1 核心抽象组件](#5.1 核心抽象组件)
- [5.2 标准自定义加载器(同步+异步)](#5.2 标准自定义加载器(同步+异步))
- [5.3 Blob 二进制解析模式](#5.3 Blob 二进制解析模式)
- [5.4 通用文件加载器 GenericLoader](#5.4 通用文件加载器 GenericLoader)
- 六、企业级能力:有状态对话历史管理
-
- [6.1 核心原理](#6.1 核心原理)
- [6.2 多轮记忆RAG对话链](#6.2 多轮记忆RAG对话链)
- 七、生产落地总结与最佳实践
-
- [7.1 核心能力落地场景](#7.1 核心能力落地场景)
- [7.2 生产开发规范](#7.2 生产开发规范)
- 结语
在LangChain实际项目开发中,原生内置的文档加载器、检索器、日志能力仅能满足通用场景。面对私有文件格式、定制化检索逻辑、全链路日志监控、多轮会话持久化等企业级需求时,必须掌握LangChain自定义开发能力。
本文将从零详解四大核心高阶能力:回调事件系统、自定义Callback、自定义Retriever检索器、自定义DocumentLoader文档加载器、有状态对话历史管理,搭配完整可运行代码、原理剖析与实战案例,帮助开发者彻底摆脱原生组件限制,适配各类复杂LLM应用落地场景。
一、LangChain 回调系统核心概念
1.1 回调系统作用
LangChain 回调系统是贯穿LLM应用全链路的事件监听机制,可深度接入模型调用、链式执行、工具调用、检索查询等所有执行阶段。核心用途包括:全链路日志记录、实时流式输出、执行异常监控、性能耗时统计、业务埋点追踪,是生产级LLM应用可观测性的核心基础。
开发者可通过 callbacks 参数订阅系统内置事件,通过自定义处理程序实现个性化业务逻辑。
1.2 全量回调事件清单
LangChain 内置完整生命周期事件,覆盖模型、链式、工具、检索、异常、重试等全场景,所有事件与绑定方法一一对应:
| 事件类型 | 触发时机 | 绑定处理方法 |
|---|---|---|
| Chat model start | 聊天模型开始执行 | on_chat_model_start |
| LLM start | 通用大模型开始执行 | on_llm_start |
| LLM new token | 模型每生成一个新Token(流式场景核心) | on_llm_new_token |
| LLM ends | 模型执行正常结束 | on_llm_end |
| LLM errors | 模型执行异常报错 | on_llm_error |
| Chain start | LCEL链式任务开始执行 | on_chain_start |
| Chain end | 链式任务执行结束 | on_chain_end |
| Chain error | 链式任务执行异常 | on_chain_error |
| Tool start/end/error | 工具执行开始/结束/异常 | on_tool_start/end/error |
| Retriever start/end/error | 检索器执行开始/结束/异常 | on_retriever_start/end/error |
| Agent action/finish | 智能代理执行动作/执行完毕 | on_agent_action/finish |
| Retry | 任务重试触发 | on_retry |
1.3 回调处理程序核心原理
CallbackHandlers 是回调系统的核心载体,所有自定义回调类必须继承 BaseCallbackHandler 基类。
该基类为每一个内置事件预留了空方法,开发者只需重写对应事件方法,即可实现自定义业务逻辑。
执行机制:当LLM、Chain、Retriever等组件触发对应事件时,CallbackManager 会自动调用处理程序中已实现的对应方法,完成事件响应。
python
# 基类核心方法极简示例
from typing import Dict, Any, List
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.messages import BaseMessage
from langchain_core.outputs import LLMResult
class BaseCallbackHandler:
"""LangChain 通用回调处理基类"""
def on_llm_start(self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any) -> Any:
"""LLM 开始运行触发"""
pass
def on_chat_model_start(self, serialized: Dict[str, Any], messages: List[List[BaseMessage]], **kwargs: Any) -> Any:
"""聊天模型开始运行触发"""
pass
# 其余事件方法省略
二、两种回调传递方式
LangChain 提供两种回调注入方式,适配不同业务场景,核心区别在于作用域范围,可按需选择:
2.1 构造函数回调(全局实例生效)
在模型、Chain、检索器等组件初始化时传入回调,作用域为当前组件所有调用,生命周期跟随组件实例,适合需要全局监控组件所有请求的场景。
2.2 运行时请求回调(单次请求生效)
在 invoke 执行请求时,通过 config 参数传入回调,仅对当前单次请求及所有嵌套子任务生效,不影响组件其他调用,适合临时监控、单次调试场景。
2.3 运行时回调完整实战示例
自定义日志回调类,监控Chain与模型全生命周期事件,全局嵌套任务自动生效,无需逐个组件绑定。
python
from typing import Any, Dict, List
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.messages import BaseMessage
from langchain_core.outputs import LLMResult
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 自定义日志回调处理类
class LoggingHandler(BaseCallbackHandler):
def on_chat_model_start(self, serialized: Dict[str, Any], messages: List[List[BaseMessage]], **kwargs) -> None:
print("聊天模型开始执行")
def on_llm_end(self, response: LLMResult, **kwargs) -> None:
print(f"模型执行完成,返回结果:{response.generations[0][0].text}")
def on_chain_start(self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs) -> None:
print(f"链式任务启动:{serialized.get('name')}")
def on_chain_end(self, outputs: Dict[str, Any], **kwargs) -> None:
print(f"链式任务执行结束,输出:{outputs}")
# 初始化组件
callbacks = [LoggingHandler()]
llm = ChatOpenAI(model="gpt-4", temperature=0)
prompt = ChatPromptTemplate.from_template("What is 1 + {number}?")
chain = prompt | llm
# 运行时注入回调,仅本次请求生效
chain.invoke({"number": "2"}, config={"callbacks": callbacks})
执行输出效果:自动监听链式启动、模型运行、任务结束全流程事件,打印完整执行日志,无需手动埋点。
三、自定义回调实战:流式Token输出
原生流式输出灵活性有限,通过自定义回调的 on_llm_new_token 方法,可实现自定义流式解析、实时打印、前端流式推送等个性化需求,是对话机器人流式交互的核心实现方案。
python
from langchain_openai import ChatOpenAI
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.prompts import ChatPromptTemplate
# 自定义流式回调处理类
class MyCustomHandler(BaseCallbackHandler):
def on_llm_new_token(self, token: str, **kwargs) -> None:
# 实时捕获每一个生成的Token,自定义处理逻辑
print(f"实时Token:{token}", end="")
# 构建流式对话链
prompt = ChatPromptTemplate.from_messages(["给我讲个关于{animal}的笑话,限制20个字"])
# 开启流式输出,绑定自定义回调
model = ChatOpenAI(model="gpt-4", streaming=True, callbacks=[MyCustomHandler()])
chain = prompt | model
# 执行调用,触发流式回调
response = chain.invoke({"animal": "猫"})
print("\n\n最终完整回答:", response.content)
核心优势:彻底解耦模型生成逻辑与前端展示逻辑,可自由扩展Token过滤、实时推送、敏感词拦截等业务功能。
四、高阶自定义:自定义Retriever检索器
LangChain 原生检索器仅支持向量相似度检索,无法适配自定义关键词检索、混合检索、业务规则过滤、外部数据库检索 等场景。通过继承 BaseRetriever 可实现完全自定义的检索逻辑。
4.1 自定义检索器接口规范
自定义检索器必须遵循固定接口,兼容LangChain所有原生生态(Chain、Agent、回调系统):
- 必需方法 :
_get_relevant_documents(同步检索核心逻辑) - 可选方法 :
_aget_relevant_documents(异步检索,适配高并发场景)
继承 BaseRetriever 后,自动拥有 LangChain Runnable 全部特性,支持批量调用、流式事件、回调监听等能力。
4.2 完整自定义检索器实战
python
from typing import List
from langchain_core.callbacks import CallbackManagerForRetrieverRun, AsyncCallbackManagerForRetrieverRun
from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever
import asyncio
class AnimalRetriever(BaseRetriever):
"""自定义动物知识库检索器:基于关键词匹配检索文档"""
documents: List[Document] # 待检索文档列表
k: int # 返回Top-K结果数量
def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun) -> List[Document]:
"""同步检索核心逻辑:关键词模糊匹配"""
matching_docs = []
for doc in self.documents:
if len(matching_docs) >= self.k:
break
# 忽略大小写模糊匹配
if query.lower() in doc.page_content.lower():
matching_docs.append(doc)
return matching_docs
async def _aget_relevant_documents(self, query: str, *, run_manager: AsyncCallbackManagerForRetrieverRun) -> List[Document]:
"""异步检索实现,适配高并发场景"""
matching_docs = []
for doc in self.documents:
if len(matching_docs) >= self.k:
break
if query.lower() in doc.page_content.lower():
matching_docs.append(doc)
return matching_docs
# 测试数据集
documents = [
Document(page_content="狗是很好的伴侣,以其忠诚和友好著称。", metadata={"type": "狗", "trait": "忠诚"}),
Document(page_content="猫是独立的宠物,通常喜欢自己的空间。", metadata={"type": "猫", "trait": "独立"}),
Document(page_content="金鱼是初学者的热门宠物,护理相对简单。", metadata={"type": "鱼", "trait": "低维护"}),
Document(page_content="鹦鹉是聪明的鸟类,能够模仿人类的语言。", metadata={"type": "鸟", "trait": "聪明"}),
Document(page_content="兔子是社交动物,需要足够的空间跳跃。", metadata={"type": "兔子", "trait": "社交"}),
]
# 初始化自定义检索器
retriever = AnimalRetriever(documents=documents, k=2)
# 同步调用测试
print("【同步检索结果】")
print(retriever.invoke("宠物"))
# 异步调用测试
print("\n【异步检索结果】")
async def async_test():
res = await retriever.ainvoke("狗")
print(res)
# 批量检索测试
print("\n【批量检索结果】")
print(retriever.batch(["猫", "兔子"]))
五、高阶自定义:自定义DocumentLoader文档加载器
原生文档加载器仅支持主流格式,面对自定义私有格式、特殊编码文件、内存二进制数据等场景无法适配。LangChain 提供标准化加载器开发规范,支持自定义同步/异步加载逻辑。
5.1 核心抽象组件
- Document :基础文档对象,包含
page_content文本内容 +metadata元数据 - BaseLoader:文档加载器基类,定义标准化加载接口
- Blob:二进制数据抽象,支持文件/内存二进制数据统一封装
- BaseBlobParser:二进制数据解析器,实现Blob到Document的转换
5.2 标准自定义加载器(同步+异步)
实现逐行读取文件的自定义加载器,适配生产环境惰性加载(节省内存)、异步高并发场景。
python
from typing import AsyncIterator, Iterator
from langchain_core.document_loaders import BaseLoader
from langchain_core.documents import Document
import aiofiles
class CustomDocumentLoader(BaseLoader):
"""自定义逐行文档加载器:支持同步惰性加载+异步加载"""
def __init__(self, file_path: str) -> None:
self.file_path = file_path
def lazy_load(self) -> Iterator[Document]:
"""惰性同步加载:逐行生成文档,低内存占用(生产推荐)"""
with open(self.file_path, encoding="utf-8") as f:
line_num = 0
for line in f:
yield Document(
page_content=line.strip(),
metadata={"line_number": line_num, "source": self.file_path}
)
line_num += 1
async def alazy_load(self) -> AsyncIterator[Document]:
"""异步惰性加载:适配高并发IO场景"""
async with aiofiles.open(self.file_path, encoding="utf-8") as f:
line_num = 0
async for line in f:
yield Document(
page_content=line.strip(),
metadata={"line_number": line_num, "source": self.file_path}
)
line_num += 1
# 测试自定义加载器
if __name__ == "__main__":
# 生成测试文件
with open("./test.txt", "w", encoding="utf-8") as f:
f.write("LangChain 自定义加载器实战\n")
f.write("回调系统高阶开发\n")
f.write("RAG 组件自定义扩展")
loader = CustomDocumentLoader("./test.txt")
# 惰性加载测试
print("【惰性加载结果】")
for doc in loader.lazy_load():
print(doc)
5.3 Blob 二进制解析模式
通过 BaseBlobParser 实现加载逻辑与解析逻辑解耦,可复用解析逻辑适配不同存储源(本地文件、内存数据、云端文件)。
python
from langchain_core.document_loaders import BaseBlobParser, Blob
from langchain_core.documents import Document
from typing import Iterator
class MyBlobParser(BaseBlobParser):
"""自定义二进制解析器:逐行解析Blob数据"""
def lazy_parse(self, blob: Blob) -> Iterator[Document]:
line_num = 0
with blob.as_bytes_io() as f:
for line in f:
line_num += 1
yield Document(
page_content=line.decode("utf-8").strip(),
metadata={"line_number": line_num, "source": blob.source}
)
# 1. 解析本地文件Blob
blob = Blob.from_path("./test.txt")
parser = MyBlobParser()
print("【本地文件解析结果】")
print(list(parser.lazy_parse(blob)))
# 2. 解析内存二进制数据(无需本地文件)
memory_blob = Blob(data=b"内存自定义数据\nLangChain 高阶开发")
print("\n【内存数据解析结果】")
print(list(parser.lazy_parse(memory_blob)))
5.4 通用文件加载器 GenericLoader
LangChain 提供 GenericLoader 统一封装 Blob 加载与解析逻辑,支持批量加载指定格式文件,适配项目批量文档处理场景。
python
from langchain_community.document_loaders.generic import GenericLoader
from langchain_community.document_loaders.blob_loaders import FileSystemBlobLoader
# 批量加载当前目录所有txt文件,绑定自定义解析器
loader = GenericLoader.from_filesystem(
path=".",
glob="*.txt",
show_progress=True,
parser=MyBlobParser()
)
# 批量遍历文档
for idx, doc in enumerate(loader.lazy_load()):
if idx < 5:
print(doc)
六、企业级能力:有状态对话历史管理
普通RAG链无法记忆多轮对话上下文,RunnableWithMessageHistory 是LangChain官方推荐的会话状态管理方案,支持多会话隔离、历史自动注入、持久化管理,完美适配多轮对话机器人。

6.1 核心原理
- BaseChatMessageHistory:统一对话历史存储接口
- RunnableWithMessageHistory:链包装器,自动完成历史消息注入、更新、会话隔离
- session_id:会话唯一标识,实现多用户、多对话独立隔离
6.2 多轮记忆RAG对话链
python
import bs4
from langchain_chroma import Chroma
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
# 1. 加载网页文档并构建向量知识库
loader = WebBaseLoader(
web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),
bs_kwargs=dict(parse_only=bs4.SoupStrainer(class_=("post-content", "post-title", "post-header")))
)
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
retriever = vectorstore.as_retriever()
# 2. 构建上下文感知检索提示词
contextualize_q_system_prompt = (
"根据聊天历史和用户最新问题,重构出无上下文依赖的独立问题,仅返回重构后的问题,不回答内容"
)
contextualize_q_prompt = ChatPromptTemplate.from_messages([
("system", contextualize_q_system_prompt),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
])
# 3. 构建问答提示词
qa_system_prompt = "基于检索上下文回答用户问题,不知道则如实告知,回答简洁。\n\n{context}"
qa_prompt = ChatPromptTemplate.from_messages([
("system", qa_system_prompt),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
])
# 4. 初始化模型与链式结构
llm = ChatOpenAI(temperature=0)
history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt)
qa_chain = create_stuff_documents_chain(llm, qa_prompt)
rag_chain = create_retrieval_chain(history_aware_retriever, qa_chain)
# 5. 会话存储管理
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
"""根据会话ID获取/创建对话历史"""
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 6. 包装为带记忆的对话链
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history",
output_messages_key="answer",
)
# 测试多轮对话 & 会话隔离
if __name__ == "__main__":
# 会话1:持续对话记忆
res1 = conversational_rag_chain.invoke(
{"input": "什么是任务分解?"},
config={"configurable": {"session_id": "session_001"}}
)["answer"]
print("回答1:", res1)
res2 = conversational_rag_chain.invoke(
{"input": "我刚刚问了什么?"},
config={"configurable": {"session_id": "session_001"}}
)["answer"]
print("回答2:", res2)
# 会话2:全新独立会话,无历史记忆
res3 = conversational_rag_chain.invoke(
{"input": "我刚刚问了什么?"},
config={"configurable": {"session_id": "session_002"}}
)["answer"]
print("回答3:", res3)
七、生产落地总结与最佳实践
7.1 核心能力落地场景
- 回调系统:生产日志监控、流式对话输出、异常告警、性能埋点,是LLM应用可观测性的核心
- 自定义检索器:实现关键词+向量混合检索、业务规则过滤、数据库联动检索,提升RAG问答精准度
- 自定义加载器:适配私有文件格式、内存数据、批量文件解析,突破原生组件格式限制
- 有状态对话:多轮对话机器人、客服系统、私人知识库问答,实现上下文连贯交互
7.2 生产开发规范
- 文档加载优先使用 lazy_load 惰性加载,避免大文件内存溢出
- 高并发场景必须实现检索器、加载器的异步方法,提升吞吐能力
- 全局监控用构造函数回调,单次调试用运行时回调,各司其职
- 多用户对话必须通过 session_id 做会话隔离,避免上下文串扰
结语
LangChain 原生组件仅能满足基础Demo开发,真正的企业级LLM应用,完全依赖自定义组件开发、全链路回调监控、有状态会话管理三大高阶能力。本文覆盖的回调系统、自定义检索器、自定义加载器、多轮记忆RAG,构成了LangChain高阶开发的核心知识体系,所有代码均可直接落地复用,助力开发者快速搭建稳定、可扩展、可观测的生产级大模型应用。