LangChain 实战指南:回调系统、自定义组件与有状态对话全落地

文章目录

    • [一、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 核心能力落地场景

  1. 回调系统:生产日志监控、流式对话输出、异常告警、性能埋点,是LLM应用可观测性的核心
  2. 自定义检索器:实现关键词+向量混合检索、业务规则过滤、数据库联动检索,提升RAG问答精准度
  3. 自定义加载器:适配私有文件格式、内存数据、批量文件解析,突破原生组件格式限制
  4. 有状态对话:多轮对话机器人、客服系统、私人知识库问答,实现上下文连贯交互

7.2 生产开发规范

  • 文档加载优先使用 lazy_load 惰性加载,避免大文件内存溢出
  • 高并发场景必须实现检索器、加载器的异步方法,提升吞吐能力
  • 全局监控用构造函数回调,单次调试用运行时回调,各司其职
  • 多用户对话必须通过 session_id 做会话隔离,避免上下文串扰

结语

LangChain 原生组件仅能满足基础Demo开发,真正的企业级LLM应用,完全依赖自定义组件开发、全链路回调监控、有状态会话管理三大高阶能力。本文覆盖的回调系统、自定义检索器、自定义加载器、多轮记忆RAG,构成了LangChain高阶开发的核心知识体系,所有代码均可直接落地复用,助力开发者快速搭建稳定、可扩展、可观测的生产级大模型应用。

相关推荐
mCell1 小时前
别给烂代码写注释——重写它
agent·代码规范·源码阅读
发光发热吧1 小时前
模型负责聪明,工程负责让聪明可靠
agent
CoovallyAIHub2 小时前
Claude Code 被曝安全后门:Coco 给出了 AI 协作的另一种答案
agent
新知图书3 小时前
记忆能力的定义与内涵
人工智能·agent·ai agent·智能体
昵称好难啊3 小时前
1. Loop Engineering详解
llm·agent
饼干哥哥4 小时前
一夜之间,全世界的 Agent 能力提高了一个档次
设计模式·agent·代码规范
武子康4 小时前
🔥 vLLM / SGLang / TGI / TensorRT-LLM / Triton 真实分工:分层定位 + 5 步决策路径
人工智能·llm·gpu
武子康5 小时前
🔥 多 GPU 推理决策顺序全解:单卡 → DP → TP → PP → EP 怎么选(vLLM/TensorRT-LLM/SGLang)
人工智能·llm·openai
不一样的少年_5 小时前
不用框架,手搓 AI Agent:(一) 先让它跑起来
前端·后端·agent