一、链是什么
「将组件串联,上一个组件的输出作为下一个组件的输入」是 LangChain 链(尤其是 | 管道链)的核心工作原理,也是链式调用的核心价值:实现数据的自动化流转与组件的协同工作。
chain = prompt_template | model
1.1 谁能入链
核心前提: 即 Runnable 子类对象才能入链(以及 Callable、Mapping 接口子类对象也可加入)。目前学到的组件均是 Runnable 接口的子类。

1.2 链执行起来什么样
通过 | 链接提示词模板对象和模型对象:
-
返回值 chain 是 RunnableSerializable 对象
-
它是 Runnable 接口的直接子类,也是绝大多数组件的父类
-
通过
invoke或stream进行阻塞执行或流式执行 -
组成的链:上一个组件的输出作为下一个组件的输入
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.runnables.base import RunnableSerializablechat_prompt_template = ChatPromptTemplate.from_messages(
[
("system", "你是一个边塞诗人,可以作诗。"),
MessagesPlaceholder("history"),
("human", "请再来一首唐诗,无需额外输出"),
]
)history_data = [
("human", "你来写一个唐诗"),
("ai", "床前明月光,疑是地上霜,举头望明月,低头思故乡"),
("human", "好诗再来一个"),
("ai", "锄禾日当午,汗滴禾下锄,谁知盘中餐,粒粒皆辛苦"),
]model = ChatTongyi(model="qwen3-max")
chain: RunnableSerializable = chat_prompt_template | model
print(type(chain))Runnable接口,invoke执行
res = chain.invoke({"history": history_data})
print(res.content)Runnable接口,stream执行
for chunk in chain.stream({"history": history_data}):
print(chunk.content, end="", flush=True)

1.3 链小结
-
链是将各个组件串联在一起,按顺序执行,前一个组件的输出作为下一个组件的输入
-
通过
|符号让各个组件形成链 -
成链的各个组件,需是 Runnable 接口的子类
-
形成的链是 RunnableSerializable 对象
-
可通过链调用
invoke或stream触发整个链条的执行
二、| 运算符为什么能组链
2.1 本质是 __or__
chain = chat_prompt_template | model 在语法上使用了 | 运算符的重写。
在 Python 中,运算符行为由类的魔法方法决定:
-
a + b本质调用a.__add__(b) -
a | b本质调用a.__or__(b)
自行实现 __or__,即可重写 |。
2.2 课件示例:a | b | c
class Test(object):
def __init__(self, name):
self.name = name
def __str__(self):
return f"Test({self.name})"
def __or__(self, other):
return MySequence(self, other)
class MySequence(object):
def __init__(self, *args):
self.sequence = []
for arg in args:
self.sequence.append(arg)
def __or__(self, other):
self.sequence.append(other)
return self
def run(self):
for arg in self.sequence:
print(arg)
if __name__ == "__main__":
a = Test("a")
b = Test("b")
c = Test("c")
d = a | b | c
d.run()
print(type(d))

2.3 落到 LangChain 上
chain = prompt | model 得到的是 RunnableSequence (RunnableSerializable 子类),原因就是 Runnable 基类内部对 __or__ 的改写。后面继续用 | 加组件,依旧得到 RunnableSequence------这就是链的基础架构。

三、StrOutputParser:为什么 prompt | model | model 会报错
3.1 复现
需求:第一次模型的输出,再拿去第二次询问模型。
from langchain_core.prompts import PromptTemplate
from langchain_community.chat_models.tongyi import ChatTongyi
model = ChatTongyi(model="qwen3-max")
prompt = PromptTemplate.from_template(
"我邻居姓:{lastname}, 刚生了{gender},请起名,仅告知名字无需其它内容"
)
chain = prompt | model | model
res = chain.invoke({"lastname": "张", "gender": "女儿"})
print(res.content)
运行报错:
ValueError: Invalid input type <class 'langchain_core.messages.ai.AIMessage'>.
Must be a PromptValue, str, or list of BaseMessages.
3.2 原因
-
prompt 的结果是
PromptValue,输入给了 model ------ 这一段是合法的 -
model 的输出是 AIMessage
-
模型
invoke的 input 类型是LanguageModelInput = PromptValue | str | Sequence[MessageLikeRepresentation],不接收 AIMessage
3.3 用 StrOutputParser 做类型转换
LangChain 内置 StrOutputParser 字符串输出解析器:把 AIMessage 解析为简单字符串,且它是 Runnable 子类,可以加入链。
parser = StrOutputParser()
chain = prompt | model | parser | model

小结: StrOutputParser 是内置的简单字符串解析器,可以将 AIMessage 转换为基础字符串,可以加入 chain。
四、JsonOutputParser 与标准多模型链
4.1 更标准的处理逻辑
prompt | model | parser | model 并不标准:上一个模型的输出没有被处理成「下一个提示词模板」所需的输入。
正常逻辑:
invoke / stream 初始输入 → 提示词模板 → 模型 → 数据处理 → 提示词模板 → 模型 → 解析器 → 结果
即:上一个模型的输出,应作为提示词模板的输入,构建下一个提示词,用来二次调用模型。
-
模型输出:
AIMessage -
提示词模板
invoke要求输入:dict,输出:PromptValue -
StrOutputParser:AIMessage → str(不够) -
JsonOutputParser:AIMessage → Dict(JSON)
4.2 完整代码
from langchain_core.output_parsers import StrOutputParser
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_community.chat_models.tongyi import ChatTongyi
str_parser = StrOutputParser()
json_parser = JsonOutputParser()
model = ChatTongyi(model="qwen3-max")
first_prompt = PromptTemplate.from_template(
"我邻居姓:{lastname},刚生了{gender},请起名,并封装到JSON格式返回给我,"
"要求key是name,value就是起的名字。请严格遵守格式要求"
)
second_prompt = PromptTemplate.from_template(
"姓名{name},请帮我解析含义。"
)
chain = first_prompt | model | json_parser | second_prompt | model | str_parser
res: str = chain.invoke({"lastname": "张", "gender": "女儿"})
print(res)
print(type(res))
4.3 输入输出必须对齐
| 组件 | 输入 | 输出 |
|---|---|---|
| 模型 | PromptValue 或字符串或序列(BaseMessage、list、tuple、str、dict) | AIMessage |
| 提示词模板 | 字典 | PromptValue |
| StrOutputParser | AIMessage | str |
| JsonOutputParser | AIMessage | dict |
标准链类型流:
字典 → first_prompt → PromptValue → model → AIMessage
→ json_parser → 字典 → second_prompt → PromptValue
→ model → AIMessage → str_parser → 字符串
五、RunnableLambda:自定义函数入链
5.1 语法
除了固定功能的解析器,也可以自己编写 Lambda 完成自定义逻辑。RunnableLambda 把普通函数转换为 Runnable 实例,方便自定义函数加入 chain。
语法:RunnableLambda(函数对象或 lambda 匿名函数)
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
str_parser = StrOutputParser()
my_func = RunnableLambda(lambda ai_msg: {"name": ai_msg.content})
model = ChatTongyi(model="qwen3-max")
first_prompt = PromptTemplate.from_template(
"我邻居姓:{lastname},刚生了{gender},请起名,仅告知我名字,不要额外信息"
)
second_prompt = PromptTemplate.from_template(
"姓名{name},请帮我解析含义。"
)
chain = first_prompt | model | my_func | second_prompt | model | str_parser
res: str = chain.invoke({"lastname": "张", "gender": "女儿"})
print(res)
print(type(res))
5.2 函数也可以直接入链
chain = first_prompt | model | (lambda ai_msg: {"name": ai_msg.content}) | second_prompt | model | str_parser
因为 Runnable 在实现 __or__ 时支持 Callable;函数就是 Callable 实例,本质是将函数自动转换为 RunnableLambda。
小结:
-
将函数封装入
RunnableLambda,它是 Runnable 接口实例,可以直接入链 -
直接将函数入链,函数会自动转换为 RunnableLambda 对象
六、临时记忆:InMemoryChatMessageHistory
如果想要封装历史记录,除了自行维护历史消息外,也可以借助 LangChain 内置的历史记录功能,帮助模型在有历史记忆的情况下回答。
6.1 两个关键类
-
基于 RunnableWithMessageHistory 在原有链的基础上创建带有历史记录功能的新链(新 Runnable 实例)
-
基于 InMemoryChatMessageHistory 为历史记录提供内存存储(临时用)
from langchain_core.runnables.history import RunnableWithMessageHistory
conversation_chain = RunnableWithMessageHistory(
some_chain, # 被附加历史消息的 Runnable,通常是 chain
None, # 获取指定会话 ID 的历史会话的函数
input_messages_key="input", # 用户输入在模板中的占位符
history_messages_key="chat_history" # 历史消息在模板中的占位符
)chat_history_store = {} # 存放多个会话 ID 所对应的历史会话记录
def get_history(session_id):
if session_id not in chat_history_store:
chat_history_store[session_id] = InMemoryChatMessageHistory()
return chat_history_store[session_id]
6.2 完整代码
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
def print_prompt(full_prompt):
print("=" * 20, full_prompt.to_string(), "=" * 20)
return full_prompt
model = ChatTongyi(model="qwen3-max")
prompt = PromptTemplate.from_template(
"你需要根据对话历史回应用户问题。对话历史:{chat_history}。用户当前输入:{input}, 请给出回应"
)
base_chain = prompt | print_prompt | model | StrOutputParser()
chat_history_store = {}
def get_history(session_id):
if session_id not in chat_history_store:
chat_history_store[session_id] = InMemoryChatMessageHistory()
return chat_history_store[session_id]
conversation_chain = RunnableWithMessageHistory(
base_chain,
get_history,
input_messages_key="input",
history_messages_key="chat_history"
)
if __name__ == '__main__':
session_config = {"configurable": {"session_id": "user_001"}}
print(conversation_chain.invoke({"input": "小明有一只猫"}, session_config))
print(conversation_chain.invoke({"input": "小刚有两只狗"}, session_config))
print(conversation_chain.invoke({"input": "共有几只宠物?"}, session_config))
注意:若想在执行链的同时把提示词 print 出来,可在链中加入自定义函数;函数的输入应原封不动返回出去,避免破坏原有业务,仅在 return 之前 print 所需信息即可。
6.3 小结
-
RunnableWithMessageHistory用于创建一个带有历史记忆功能的 Runnable 实例(链) -
创建时需要提供
BaseChatMessageHistory的具体实现 -
InMemoryChatMessageHistory实现在内存中存储历史
七、长期记忆:自实现 FileChatMessageHistory
7.1 为什么内存不够
InMemoryChatMessageHistory 仅在内存中临时存储,程序退出则记忆丢失。它继承自 BaseChatMessageHistory。官方注释给出了实现指南,并给出基于文件的历史消息存储示例。可以自行实现基于 JSON 和本地文件的会话数据保存。
7.2 核心思路
-
基于文件存储会话记录,以
session_id为文件名,不同 session 不同文件 -
继承
BaseChatMessageHistory,实现 3 个方法:-
add_messages:同步添加消息 -
messages:同步获取消息 -
clear:同步清除消息import json, os
from langchain_core.messages import messages_from_dict, message_to_dictclass FileChatMessageHistory(BaseChatMessageHistory):
storage_path: str
session_id: str@property def messages(self) -> list[BaseMessage]: try: with open( os.path.join(self.storage_path, self.session_id), "r", encoding="utf-8", ) as f: messages_data = json.load(f) return messages_from_dict(messages_data) except FileNotFoundError: return [] def add_messages(self, messages: Sequence[BaseMessage]) -> None: all_messages = list(self.messages) all_messages.extend(messages) serialized = [message_to_dict(message) for message in all_messages] file_path = os.path.join(self.storage_path, self.session_id) os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, "w", encoding="utf-8") as f: json.dump(serialized, f) def clear(self) -> None: file_path = os.path.join(self.storage_path, self.session_id) os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, "w", encoding="utf-8") as f: json.dump([], f)
-
7.3 业务链部分
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory, BaseMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.messages import messages_from_dict, message_to_dict
from langchain_community.chat_models.tongyi import ChatTongyi
from typing import Sequence, List
import json
llm = ChatTongyi(model="qwen3-max")
prompt = PromptTemplate.from_template("""你是一个贴心的助手,需要根据对话历史回应用户的问题。
对话历史:{chat_history}
用户当前输入:{input}
你的回应:""")
base_chain = prompt | llm | StrOutputParser()
def get_message_history(session_id: str) -> BaseChatMessageHistory:
"""根据会话 ID 获取对应的对话历史存储实例"""
return FileChatMessageHistory(session_id=session_id, storage_path="./chat_history")
conversation_chain = RunnableWithMessageHistory(
runnable=base_chain,
get_session_history=get_message_history,
input_messages_key="input",
history_messages_key="chat_history",
)
if __name__ == "__main__":
session_config = {"configurable": {"session_id": "user_001"}}
response1 = conversation_chain.invoke({"input": "小明有1只猫"}, config=session_config)
print("第一轮:", response1)
response2 = conversation_chain.invoke({"input": "小刚有2只狗"}, config=session_config)
print("\n第二轮:", response2)
response3 = conversation_chain.invoke(
{"input": "小明和小刚一共有几只宠物?"},
config=session_config
)
print("\n第三轮:", response3)
# 测试程序重启后读取历史(注释上面的代码,单独运行下面的代码仍能获取历史)
# response4 = conversation_chain.invoke(
# {"input": "分别是什么宠物?"},
# config=session_config
# )
# print("\n重启后第四轮:", response4)