LangChain实战进阶:数据转换与会话记忆全解析(JsonOutputParser+RunnableLambda+Memory)

3 阅读11分钟

LangChain实战进阶:数据转换与会话记忆全解析(JsonOutputParser+RunnableLambda+Memory)

LangChain的核心优势在于组件的灵活组合与协同,而实现组件高效协同的关键,离不开数据转换的规范性和会话记忆的完整性。在实际开发中,无论是多模型链路的构建、自定义逻辑的嵌入,还是多轮对话的上下文关联,都需要掌握三大核心工具:JsonOutputParser、RunnableLambda与Memory组件。

本文将以全新的实战场景为切入点,从零讲解三大组件的核心原理与实操方法,通过原创代码示例,帮你吃透组件间的数据流转逻辑、自定义函数入链技巧,以及临时与长期会话记忆的实现方式,助力你构建更稳定、更灵活的LangChain应用。

一、JsonOutputParser:规范多模型链的数据流转

核心定位:解决多模型协同的“数据格式壁垒”

在多模型链路开发中,模型输出与下一级组件输入的格式不匹配,是导致链路报错的最常见原因。LangChain中,Chat模型(如ChatTongyi)的输出默认是AIMessage类对象,而提示词模板(PromptTemplate)的输入要求是字典类型,直接传递会触发类型错误。

JsonOutputParser作为LangChain内置的输出解析器,核心作用就是打通这一“格式壁垒”——将模型输出的AIMessage对象,规范解析为Python字典,使其能够直接注入下一个提示词模板,实现多模型间的无缝协同。

关键特性与类型约束

JsonOutputParser的使用有明确的类型约束,这是保证链路稳定执行的前提:

  • 输入约束:仅接收AIMessage类对象(Chat模型的标准输出),若传入其他类型(如字符串、字典),会直接报错;
  • 输出约束:固定输出Python字典(dict),字典的key需与下一级提示词模板的占位符严格对应,否则会出现参数缺失问题。

结合LangChain核心组件的类型流转规律,多模型链的标准数据流向为: 初始字典输入 → PromptTemplate(输出PromptValue) → Chat模型(输出AIMessage) → JsonOutputParser(输出dict) → 下一级PromptTemplate → 最终解析输出

实操:用户需求提取与多模型响应

以“提取用户咨询需求 → 生成针对性回复”为实战场景,构建多模型链,使用JsonOutputParser实现数据格式转换

from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_community.chat_models.tongyi import ChatTongyi

# 初始化核心组件
model = ChatTongyi(model="qwen3-max")
json_parser = JsonOutputParser()  # 负责AIMessage → dict转换
str_parser = StrOutputParser()    # 负责最终结果格式化

# 第一个提示词模板:提取用户咨询的核心需求(要求JSON格式输出)
需求提取模板 = PromptTemplate.from_template(
    "用户咨询内容:{user_input},请提取咨询的核心需求,包含需求类型、具体要求两个维度,"
    "严格按照JSON格式返回,key分别为demand_type、specific_requirements,不添加任何额外说明"
)

# 第二个提示词模板:根据提取的需求,生成专业回复
回复生成模板 = PromptTemplate.from_template(
    "用户需求类型:{demand_type},具体要求:{specific_requirements},"
    "请生成简洁、专业的回复,贴合用户需求,不超过50字"
)

# 构建多模型链:需求提取→模型→JSON解析→回复生成→模型→最终解析
demand_chain = 需求提取模板 | model | json_parser | 回复生成模板 | model | str_parser

# 执行链路,模拟用户咨询场景
user_input = "我想学习LangChain,零基础,希望推荐一套30天的学习计划,侧重实操"
result = demand_chain.invoke({"user_input": user_input})

print("用户咨询:", user_input)
print("模型回复:", result)
print("结果类型:", type(result))

实操要点总结

上述链路中,JsonOutputParser的核心作用是将模型输出的“需求提取结果”(AIMessage)转换为字典,确保能够顺利注入回复生成模板。需要注意两点:一是提示词模板中需明确要求模型输出JSON格式,避免解析失败;二是JSON的key需与下一级模板的占位符完全一致,否则会出现参数不匹配报错。

二、RunnableLambda:自定义函数入链的灵活方案

核心价值:打破固定组件的功能限制

JsonOutputParser仅能实现“AIMessage→dict”的固定转换,而实际开发中,我们常常需要更灵活的自定义数据处理逻辑——比如过滤模型输出中的无效信息、对数据进行格式化拼接、新增自定义参数等。RunnableLambda的出现,正是为了实现自定义函数与LangChain链路的无缝融合。

RunnableLambda是LangChain内置的Runnable子类,其核心功能是将普通Python函数(或匿名函数)转换为符合Runnable接口规范的实例,从而让自定义函数能够像内置组件一样,通过|符号加入链路中。

两种入链方式详解

基于Runnable接口对__or__方法的重写,自定义函数入链支持两种方式,可根据项目复杂度灵活选择:

  1. 显式封装:将函数传入RunnableLambda,生成Runnable实例后入链,代码规范,便于调试和维护,适合复杂函数;
  2. 隐式转换:直接将函数入链,底层会自动将其转换为RunnableLambda对象,代码简洁,适合简单函数或匿名函数。

实操:自定义数据过滤与链路优化

延续“用户需求咨询”场景,新增自定义数据过滤逻辑:过滤模型输出中无关的冗余信息,提取核心内容并添加优先级标签,分别用两种方式实现函数入链,代码原创且脱离原有逻辑。

方式1:显式封装RunnableLambda入链
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda
from langchain_core.prompts import PromptTemplate
from langchain_community.chat_models.tongyi import ChatTongyi

# 初始化组件
model = ChatTongyi(model="qwen3-max")
str_parser = StrOutputParser()

# 自定义数据过滤函数:提取核心需求,过滤冗余信息,添加优先级标签
def filter_demand(ai_msg):
    # ai_msg为模型输出的AIMessage对象,content为需求提取结果(纯文本)
    demand_content = ai_msg.content.strip()
    # 过滤冗余信息(假设模型输出包含"需求提取结果:"前缀)
    if "需求提取结果:" in demand_content:
        demand_content = demand_content.replace("需求提取结果:", "")
    # 自定义处理:添加优先级标签
    processed_data = {
        "core_demand": demand_content,
        "priority": "高" if "零基础" in demand_content else "中"
    }
    return processed_data

# 显式封装为RunnableLambda实例
filter_func = RunnableLambda(filter_demand)

# 构建提示词模板
需求提取模板 = PromptTemplate.from_template(
    "用户咨询:{user_input},提取核心学习需求,仅输出需求内容,不要额外说明"
)
回复生成模板 = PromptTemplate.from_template(
    "用户核心需求:{core_demand},需求优先级:{priority},"
    "生成贴合零基础用户的学习建议,简洁实用"
)

# 构建链路:需求提取→模型→自定义过滤→回复生成→模型→解析
chain = 需求提取模板 | model | filter_func | 回复生成模板 | model | str_parser

# 执行链路
user_input = "零基础想学LangChain,求推荐入门资料,最好有实操案例"
result = chain.invoke({"user_input": user_input})
print("模型回复:", result)
方式2:函数直接入链(隐式转换)

无需显式封装,直接将自定义函数加入链路,底层自动转换为RunnableLambda,代码更简洁高效:

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_community.chat_models.tongyi import ChatTongyi

# 初始化组件(与方式1一致)
model = ChatTongyi(model="qwen3-max")
str_parser = StrOutputParser()

# 自定义数据过滤函数(与方式1一致)
def filter_demand(ai_msg):
    demand_content = ai_msg.content.strip()
    if "需求提取结果:" in demand_content:
        demand_content = demand_content.replace("需求提取结果:", "")
    processed_data = {
        "core_demand": demand_content,
        "priority": "高" if "零基础" in demand_content else "中"
    }
    return processed_data

# 构建提示词模板(与方式1一致)
需求提取模板 = PromptTemplate.from_template(
    "用户咨询:{user_input},提取核心学习需求,仅输出需求内容,不要额外说明"
)
回复生成模板 = PromptTemplate.from_template(
    "用户核心需求:{core_demand},需求优先级:{priority},"
    "生成贴合零基础用户的学习建议,简洁实用"
)

# 函数直接入链(底层自动转换为RunnableLambda)
chain = 需求提取模板 | model | filter_demand | 回复生成模板 | model | str_parser

# 执行链路
user_input = "零基础想学LangChain,求推荐入门资料,最好有实操案例"
result = chain.invoke({"user_input": user_input})
print("模型回复:", result)

三、Memory组件:实现会话记忆的临时与长期存储

LangChain的默认链路是无状态的,每次invoke调用都是独立的,无法记住上一轮的对话内容,导致多轮对话出现“失忆”问题。Memory组件的核心作用,就是为链路添加会话记忆能力,实现多轮对话的上下文关联,分为临时会话记忆和长期会话记忆两种场景。

1. 临时会话记忆:InMemoryChatMessageHistory

临时会话记忆适用于调试、临时测试等场景,核心组合是RunnableWithMessageHistory + InMemoryChatMessageHistory:

  • RunnableWithMessageHistory:用于给原有链路包装历史记忆功能,自动加载、注入、保存会话历史;
  • InMemoryChatMessageHistory:基于内存存储会话历史,程序退出后历史记录丢失,实现简单、无需额外配置。
实操:构建临时记忆链(学习咨询多轮对话)
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables.history import RunnableWithMessageHistory

# 初始化组件
model = ChatTongyi(model="qwen3-max")
str_parser = StrOutputParser()

# 构建基础链路(包含会话历史和用户输入占位符)
base_prompt = PromptTemplate.from_template(
    "结合对话历史,回复用户当前问题,语气亲切、专业。\n对话历史:{chat_history}\n用户当前输入:{input}"
)
base_chain = base_prompt | model | str_parser

# 定义会话历史存储与获取函数(支持多会话隔离)
session_store = {}
def get_temp_history(session_id: str) -> InMemoryChatMessageHistory:
    if session_id not in session_store:
        session_store[session_id] = InMemoryChatMessageHistory()
    return session_store[session_id]

# 包装临时记忆链
temp_memory_chain = RunnableWithMessageHistory(
    runnable=base_chain,
    get_session_history=get_temp_history,
    input_messages_key="input",
    history_messages_key="chat_history"
)

# 测试多轮对话
if __name__ == "__main__":
    session_config = {"configurable": {"session_id": "learner_001"}}
    
    # 第一轮:咨询学习方向
    print("第一轮对话:")
    res1 = temp_memory_chain.invoke({"input": "LangChain零基础该从哪里学起?"}, session_config)
    print("模型回复:", res1)
    
    # 第二轮:追问具体资料(依赖上一轮历史)
    print("\n第二轮对话:")
    res2 = temp_memory_chain.invoke({"input": "有具体的实操资料推荐吗?"}, session_config)
    print("模型回复:", res2)

2. 长期会话记忆:自实现FileChatMessageHistory

临时记忆无法满足生产环境需求,因为程序重启后历史记录会丢失。要实现会话记忆的持久化,可自实现基于JSON文件的FileChatMessageHistory类,继承BaseChatMessageHistory,实现消息的长期存储。

自定义FileChatMessageHistory需实现3个核心方法:add_messages(添加消息)、messages(获取消息)、clear(清除消息),核心思路是以会话ID为文件名,将消息序列化后存储到本地JSON文件中。

实操:自实现长期记忆链(学习计划多轮对话)
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.chat_history import BaseChatMessageHistory, BaseMessage
from langchain_core.messages import messages_from_dict, message_to_dict
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables.history import RunnableWithMessageHistory
import json
import os

# 自实现FileChatMessageHistory(长期存储会话历史)
class FileChatMessageHistory(BaseChatMessageHistory):
    def __init__(self, session_id: str, storage_dir: str = "./chat_memory"):
        self.session_id = session_id
        self.storage_dir = storage_dir
        # 确保存储目录存在
        if not os.path.exists(storage_dir):
            os.makedirs(storage_dir)
        # 会话文件路径(JSON格式)
        self.file_path = os.path.join(storage_dir, f"{session_id}.json")
    
    @property
    def messages(self) -> list[BaseMessage]:
        # 读取文件中的历史消息,转换为BaseMessage列表
        if not os.path.exists(self.file_path):
            return []
        with open(self.file_path, "r", encoding="utf-8") as f:
            msg_dict = json.load(f)
        return messages_from_dict(msg_dict)
    
    def add_messages(self, messages: list[BaseMessage]) -> None:
        # 追加消息并写入文件
        current_msgs = self.messages
        current_msgs.extend(messages)
        with open(self.file_path, "w", encoding="utf-8") as f:
            json.dump(message_to_dict(current_msgs), f, ensure_ascii=False, indent=2)
    
    def clear(self) -> None:
        # 清除会话历史(删除对应文件)
        if os.path.exists(self.file_path):
            os.remove(self.file_path)

# 初始化核心组件与基础链路
model = ChatTongyi(model="qwen3-max")
str_parser = StrOutputParser()
base_prompt = PromptTemplate.from_template(
    "结合对话历史,为用户提供学习计划建议。\n对话历史:{chat_history}\n用户当前输入:{input}"
)
base_chain = base_prompt | model | str_parser

# 定义长期会话历史获取函数
def get_long_history(session_id: str) -> BaseChatMessageHistory:
    return FileChatMessageHistory(session_id=session_id, storage_dir="./langchain_chat_memory")

# 包装长期记忆链
long_memory_chain = RunnableWithMessageHistory(
    runnable=base_chain,
    get_session_history=get_long_history,
    input_messages_key="input",
    history_messages_key="chat_history"
)

# 测试长期记忆(程序重启后仍可读取历史)
if __name__ == "__main__":
    session_config = {"configurable": {"session_id": "learner_002"}}
    
    # 第一轮:咨询30天学习计划
    print("第一轮对话:")
    res1 = long_memory_chain.invoke({"input": "制定一份30天LangChain学习计划,侧重实操"}, session_config)
    print("模型回复:", res1)
    
    # 第二轮:调整计划(依赖上一轮历史)
    print("\n第二轮对话:")
    res2 = long_memory_chain.invoke({"input": "把前10天的内容调整为基础组件学习"}, session_config)
    print("模型回复:", res2)

四、实战总结

本文通过实战场景,完整讲解了LangChain中数据转换与会话记忆的核心实现:

  1. JsonOutputParser:解决多模型链的格式不兼容问题,实现AIMessage到dict的规范转换,是多模型协同的基础;
  2. RunnableLambda:实现自定义函数的灵活入链,支持显式封装与隐式转换两种方式,打破固定组件的功能限制;
  3. Memory组件:临时记忆(InMemoryChatMessageHistory)适用于调试场景,长期记忆(自实现FileChatMessageHistory)适用于生产环境,实现多轮对话的上下文关联。

掌握这三大组件的用法,能够帮你避开LangChain开发中的常见坑点,提升链路开发的规范性与灵活性,为构建复杂的LangChain应用(如智能问答、多步骤数据处理)打下坚实基础。