
1、FewShotPromptTemplate的使用
FewShotPromptTemplate类对象构建需要5个核心参数:
•example_prompt:示例数据的提示词模板
•examples:示例数据,list,内套字典
•prefix:组装提示词,示例数据前内容
•suffix:组装提示词,示例数据后内容
•input_variables:列表,注入的变量列表
python
from langchain_community.llms.tongyi import Tongyi
from langchain_core.prompts import PromptTemplate, FewShotPromptTemplate
from dotenv import load_dotenv
load_dotenv()
#样例的模版
example_template = PromptTemplate.from_template("单词{word},类别{category}")
example_data = [
{"word":"屎","category":"排泄物"},
{"word":"米饭","category":"粮食"},
{"word":"游戏","category":"娱乐"},
]
template = FewShotPromptTemplate(
example_prompt=example_template, #示例数据的模版
examples=example_data, #示例数据
prefix="告知我词语的所属类别,我提供下面的示例:", #示例数据的,前置提示词
suffix="基于前面的示例告知我,{input_word}的类别是?",#示例数据的后置提示词
input_variables=['input_word'], #输入变量
)
template_test = template.invoke(input="狗屎").to_string()
print(template_test)
model = Tongyi(model="qwen-max")
print(model.invoke(template_test))
2、模板类的format和invoke方法
在PromptTemplate(通用提示词模板)和FewShotPromptTemplate(FewShot提示词模板)的使用中,我们使用了如下:

模板对象的format方法

模板对象的invoke方法
PromptTemplate、FewShotPromptTemplate、ChatPromptTemplate(后续学习)都拥有format和invoke这2类方法

format和invoke的区别在于:

3、ChatPromptTemplate的使用
PromptTemplate:通用提示词模板,支持动态注入信息。
FewShotPromptTemplate:支持基于模板注入任意数量的示例信息。
ChatPromptTemplate:支持注入任意数量的历史会话信息。
•通过from_messages方法,从列表中获取多轮次会话作为聊天的基础模板
•PS: 前面PromptTemplate类用的from_template仅能接入一条消息,而from_messages可以接入一个list的消息
历史会话信息并不是静态的(固定的),而是随着对话的进行不停地积攒,即动态的。
所以,历史会话信息需要支持动态注入。
• MessagePlaceholder 作为占位
• 提供 history 作为占位的 key
• 基于 invoke 动态注入历史会话记录必须是invoke,format无法注入

4、Chains 链的基础使用
将组件串联,上一个组件的输出作为下一个组件的输入」是 LangChain 链(尤其是 | 管道链)的核心工作原理,这也是链式调用的核心价值:实现数据的自动化流转与组件的协同工作,如下。
chain***=***prompt_template ***|***model
核心前提:即Runnable子类对象才能入链(以及Callable、Mapping接口子类对象也可加入(后续了解用的不多))。
我们目前所学习到的组件,均是Runnable接口的子类,如下类的继承关系:

• 通过 | 链接提示词模板对象和模型对象
• 返回值 chain 对象是 RunnableSerializable 对象
• 是 Runnable 接口的直接子类
• 也是绝大多数组件的父类
• 通过 invoke 或 stream 进行阻塞执行或流式执行组成的链在执行上有:上一个组件的输出作为下一个组件的输入的特性。
所以有如下执行流程:

python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_models.tongyi import ChatTongyi
from dotenv import load_dotenv
load_dotenv()
chat_prompt_template = ChatPromptTemplate.from_messages(
[
("system", "你是一个边塞诗人,可以作诗。"),
MessagesPlaceholder("history"),
("human", "请再来一首唐诗"),
]
)
history_data = [
("human", "你来写一个唐诗"),
("ai", "床前明月光,疑是地上霜,举头望明月,低头思故乡"),
("human", "好诗再来一个"),
("ai", "锄禾日当午,汗滴禾下锄,谁知盘中餐,粒粒皆辛苦"),
]
model = ChatTongyi(model="qwen3-max")
chian = chat_prompt_template | model
for i in chian.stream({"history": history_data}):
print(i.content, end="", flush=True)
5、| 运算符的重写
前文代码中: ++chain++ ++=++ ++chat_prompt_template++ ++|++ ++model++
在语法上使用了|运算符的重写
在 Python 中,运算符(如 +、|)的行为由类的魔法方法决定。例如:
• a + b 本质调用的是 a.__add __(b)
• a | b 本质调用的是 a.__or __(b)
只需要自行实现类的__or__方法,即可对|符号的功能进行重写。
示例:
• 让 a|b|c 的代码得到一个自定义的类对象 ( 类似列表即 [a, b, c])
• 调用 run 方法依次输出 a 、 b 、 c
• 我们需要重写 | 即 or 方法

6、StrOutputParser解析器
有如下代码,想要以第一次模型的输出结果,第二次去询问模型:

• 链的构建完全符合要求(参与的组件)
• 但是运行报错( ValueError: Invalid input type <class 'langchain_core.messages.ai.AIMessage'>. Must be a PromptValue, str, or list of BaseMessages. )

错误的主要原因是:
• prompt 的结果是 PromptValue 类型,输入给了 model
• model 的输出结果是:模型(ChatTongyi)源码中关于invoke方法明确指定了input的类型:
需要做类型转换
可以借助LangChain内置的解析器
StrOutputParser字符串输出解析器
StrOutputParser是LangChain内置的简单字符串解析器
• 可以将 AIMessage 解析为简单的字符串,符合了模型 invoke 方法要求(可传入字符串,不接收 AIMessage 类型)
• 是 Runnable 接口的子类(可以加入链)
7、Runnable接口
LangChain 中的绝大多数核心组件都继承了 Runnable 抽象基类(位于 langchain_core.runnables.base)。
代码:
++chain++ ++=++ ++prompt++ ++|++ ++model++
chain变量是RunnableSequence(RunnableSerializable子类)类型
而得到这个类型的原因就是Runnable基类内部对*or*魔术方法的改写。
同时,在后面继续使用|添加新的组件,依旧会得到RunnableSequence,这就是链的基础架构。

8、JsonOutputParser&多模型执行链
在前面我们完成了这样的需求去构建多模型链,不过这种做法并不标准,因为:
上一个模型的输出,没有被处理就输入下一个模型。
正常情况下我们应该有如下处理逻辑:
• 上一个模型的输出结果,应该作为提示词模版的输入,构建下一个提示词,用来二次调用模型。
根据输出和输入的要求:

• 模型的输出为: AIMessage 类对象
• 提示词模板要求输入如右侧代码:
所以,我们需要完成:将模型输出的AIMessage 转为字典 注入第二个提示词模板中,形成新的提示词(PromptValue对象)
在构建链的时候要注意整体兼容性,注意前后组件的输入和输出要求。
• 模型输入: PromptValue 或字符串或序列( BaseMessage 、 list 、 tuple 、 str 、 dict )。
• 模型输出: AIMessage
• 提示词模板输入:要求是字典
• 提示词模板输出: PromptValue 对象
• StrOutputParser : AIMessage 输入、 str 输出JsonOutputParser:AIMessage输入、dict输出

9、RunnableLambda&函数加入链

前文我们根据JsonOutputParser完成了多模型执行链条的构建。
• 除了 JsonOutputParser 这类固定功能的解析器之外
• 我们也可以自己编写 Lambda 匿名函数来完成自定义逻辑的数据转换,想怎么转换就怎么转换,更自由。想要完成这个功能,可以基于RunnableLambda类实现。
RunnableLambda类是LangChain内置的,将普通函数等转换为Runnable接口实例,方便自定义函数加入chain。
语法:
RunnableLambda(函数对象或lambda匿名函数)


跳过RunnableLambda类,直接让函数加入链也是可以的。
因为Runnable接口类在实现__or__的时候,支持Callable接口的实例。
• 函数就是 Callable 接口的实例

如上代码示例,|符号(底层是调用__or__)组链,是支持函数加入的。
其本质是将函数自动转换为RunnableLambda
10、Memory 临时会话记忆
如果想要封装历史记录,除了自行维护历史消息外,也可以借助LangChain内置的历史记录附加功能。
LangChain提供了History功能,帮助模型在有历史记忆的情况下回答。
• 基于 RunnableWithMessageHistory 在原有链的基础上创建带有历史记录功能的新链(新 Runnable 实例)
• 基于 InMemoryChatMessageHistory 为历史记录提供内存存储(临时)
•


python
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import InMemoryChatMessageHistory
model = ChatTongyi(model="qwen3-max")
# prompt = PromptTemplate.from_template(
# "你需要根据会话历史回应用户问题。对话历史:{chat_history},用户提问:{input},请回答"
# )
prompt = ChatPromptTemplate.from_messages(
[
("system", "你需要根据会话历史回应用户问题。对话历史:"),
MessagesPlaceholder("chat_history"),
("human", "请回答如下问题:{input}")
]
)
str_parser = StrOutputParser()
def print_prompt(full_prompt):
print("="*20, full_prompt.to_string(), "="*20)
return full_prompt
base_chain = prompt | print_prompt | model | str_parser
store = {} # key就是session,value就是InMemoryChatMessageHistory类对象
# 实现通过会话id获取InMemoryChatMessageHistory类对象
def get_history(session_id):
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
# 创建一个新的链,对原有链增强功能:自动附加历史消息
conversation_chain = RunnableWithMessageHistory(
base_chain, # 被增强的原有chain
get_history, # 通过会话id获取InMemoryChatMessageHistory类对象
input_messages_key="input", # 表示用户输入在模板中的占位符
history_messages_key="chat_history" # 表示用户输入在模板中的占位符
)
if __name__ == '__main__':
# 固定格式,添加LangChain的配置,为当前程序配置所属的session_id
session_config = {
"configurable": {
"session_id": "user_001"
}
}
# res = conversation_chain.invoke({"input": "小明有2个猫"}, session_config)
# print("第1次执行:", res)
#
# res = conversation_chain.invoke({"input": "小刚有1只狗"}, session_config)
# print("第2次执行:", res)
res = conversation_chain.invoke({"input": "总共有几个宠物"}, session_config)
print("第3次执行:", res)
RunnableWithMessageHistory是LangChain内Runnable接口的实现,主要用于:
• 创建一个带有历史记忆功能的 Runnable 实例(链)它在创建的时候需要提供一个BaseChatMessageHistory的具体实现(用来存储历史消息)
• InMemoryChatMessageHistory 可以 实现在内存中存储历史额外的,如果想要在invoke或stream执行链的同时,将提示词print出来,可以在链中加入自定义函数实现。
• 注意:函数的输入应原封不动返回出去,避免破坏原有业务,仅在 return 之前, print 所需信息即可。
11、Memory 长期会话记忆
使用InMemoryChatMessageHistory仅可以在内存中临时存储会话记忆,一旦程序退出,则记忆丢失。
InMemoryChatMessageHistory 类继承自 BaseChatMessageHistory

在官方注释中给出了相关实现的指南,并给出了基于文件的历史消息存储示例代码。
我们可以自行实现一个基于Json格式和本地文件的会话数据保存。
FileChatMessageHistory类实现,核心思路:• 基于文件存储会话记录,以 session_id 为文件名,不同 session_id 有不同文件存储消息
继承BaseChatMessageHistory实现如下3个方法:
• add_messages : 同步模式,添加消息
• messages: 同步模式,获取消息
• clear :同步模式,清除消息如右侧代码,官方在BaseChatMessageHistory类的注释中提供了一个基于文件存储的示例代码。

其余核心代码
pythonimport os, json from typing import Sequence from langchain_community.chat_models import ChatTongyi from langchain_core.messages import message_to_dict, messages_from_dict, BaseMessage from langchain_core.chat_history import BaseChatMessageHistory from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.runnables import RunnableWithMessageHistory # message_to_dict:单个消息对象(BaseMessage类实例) -> 字典 # messages_from_dict:[字典、字典...] -> [消息、消息...] # AIMessage、HumanMessage、SystemMessage 都是BaseMessage的子类 class FileChatMessageHistory(BaseChatMessageHistory): def __init__(self, session_id, storage_path): self.session_id = session_id # 会话id self.storage_path = storage_path # 不同会话id的存储文件,所在的文件夹路径 # 完整的文件路径 self.file_path = os.path.join(self.storage_path, self.session_id) # 确保文件夹是存在的 os.makedirs(os.path.dirname(self.file_path), exist_ok=True) def add_messages(self, messages: Sequence[BaseMessage]) -> None: # Sequence序列 类似list、tuple all_messages = list(self.messages) # 已有的消息列表 all_messages.extend(messages) # 新的和已有的融合成一个list # 将数据同步写入到本地文件中 # 类对象写入文件 -> 一堆二进制 # 为了方便,可以将BaseMessage消息转为字典(借助json模块以json字符串写入文件) # 官方message_to_dict:单个消息对象(BaseMessage类实例) -> 字典 # new_messages = [] # for message in all_messages: # d = message_to_dict(message) # new_messages.append(d) new_messages = [message_to_dict(message) for message in all_messages] # 将数据写入文件 with open(self.file_path, "w", encoding="utf-8") as f: json.dump(new_messages, f) @property # @property装饰器将messages方法变成成员属性用 def messages(self) -> list[BaseMessage]: # 当前文件内: list[字典] try: with open(self.file_path, "r", encoding="utf-8") as f: messages_data = json.load(f) # 返回值就是:list[字典] return messages_from_dict(messages_data) except FileNotFoundError: return [] def clear(self) -> None: with open(self.file_path, "w", encoding="utf-8") as f: json.dump([], f) model = ChatTongyi(model="qwen3-max") # prompt = PromptTemplate.from_template( # "你需要根据会话历史回应用户问题。对话历史:{chat_history},用户提问:{input},请回答" # ) prompt = ChatPromptTemplate.from_messages( [ ("system", "你需要根据会话历史回应用户问题。对话历史:"), MessagesPlaceholder("chat_history"), ("human", "请回答如下问题:{input}") ] ) str_parser = StrOutputParser() def print_prompt(full_prompt): print("="*20, full_prompt.to_string(), "="*20) return full_prompt base_chain = prompt | print_prompt | model | str_parser def get_history(session_id): return FileChatMessageHistory(session_id, "./chat_history") # 创建一个新的链,对原有链增强功能:自动附加历史消息 conversation_chain = RunnableWithMessageHistory( base_chain, # 被增强的原有chain get_history, # 通过会话id获取InMemoryChatMessageHistory类对象 input_messages_key="input", # 表示用户输入在模板中的占位符 history_messages_key="chat_history" # 表示用户输入在模板中的占位符 ) if __name__ == '__main__': # 固定格式,添加LangChain的配置,为当前程序配置所属的session_id session_config = { "configurable": { "session_id": "user_001" } } # res = conversation_chain.invoke({"input": "小明有2个猫"}, session_config) # print("第1次执行:", res) # # res = conversation_chain.invoke({"input": "小刚有1只狗"}, session_config) # print("第2次执行:", res) res = conversation_chain.invoke({"input": "总共有几个宠物"}, session_config) print("第3次执行:", res)
12、Document loaders: 文档加载器
文档加载器提供了一套标准接口,用于将不同来源(如 CSV、PDF 或 JSON等)的数据读取为 LangChain 的文档格式。这确保了无论数据来源如何,都能对其进行一致性处理。
文档加载器(内置或自行实现)需实现BaseLoader接口。
Class Document,是LangChain内文档的统一载体,所有文档加载器最终返回此类的实例。
一个基础的Document类实例,基于如下代码创建:

可以看到,Document类其核心记录了:
• page_content :文档内容
• metadata :文档元数据(字典)
不同的文档加载器可能定义了不同的参数,但是其都实现了统一的接口(方法)。
•load():一次性加载全部文档
•lazy_load():延迟流式传输文档,对大型数据集很有用,避免内存溢出。一个简单的CSVLoader的使用示例如下:

LangChain内置了许多文档加载器,详细参见官方文档:https://docs.langchain.com/oss/python/integrations/document_loaders
我们简单的学习如下几个常用的文档加载器:
• CSVLoader
• JSONLoader
• PDFLoader

自定义CSV文件的解析和加载

LangChain内置了许多种类的文档加载器
• 文档加载器均继承于 BaseLoader 类
• 返回 Document 类型的对象
• load 方法一次性批量加载(返回 list 内含 Document 对象),如内容过多可能 list 太大,出现内存溢出问题
• lazy_load 方法会得到生成器对象,可用 for 循环依次获取单个 Document 对象,适用于大文档避免内存存不下。CSVLoader用于加载CSV文件,加载成功得到的即Document对象。
13、Document loaders: JSONLoader
JSONLoader用于将JSON数据加载为Document类型对象。
使用JSONLoader需要额外安装: pip install jq
jq是一个跨平台的json解析工具,LangChain底层对JSON的解析就是基于jq工具实现的。
将JSON数据的信息抽取出来,封装为Document对象,抽取的时候依赖jq_schema语法。

• . 表示整个 JSON 对象(根)
• [] 表示数组
• .name 表示抽取周杰轮
• .hobby 表示抽取爱好数组
• .hobby[1] 或 .hobby.[1] 表示抽取跳
• . other.addr 表示抽取地址深圳
• .[]. 得到 3 个字典
• .[].name 表示抽取全部的 name ,即得到 3 个 name 信息


JSONLoader依赖jq库,通过pip install jq安装。
• JSONLoader 使用 jq 的解析语法,常见如:
• . 表示根、 [] 表示数组
• .name 表示从根取 name 的值
• .hobby[1] 表示取 hobby 对应数组的第二个元素
• .[] 表示将数组内的每个字典( JSON 对象)都取到
• .[].name 表示取数组内每个字典( JSON )对象的 name 对应的值JSONLoader初始化有4个主要参数:
• file_path :文件路径,必填
• jq_schema : jq 解析语法,必填
• text_content :抽取到的是否是字符串,默认 True ,非必填
• json_lines :是否是 JsonLines 文件,默认 False ,非必填
• JsonLines 文件:每一行都是一个独立的字典( Json 对象)
14、Document loaders: PyPDFLoader
LangChain内支持许多PDF的加载器,我们选择其中的PyPDFLoader使用。
PyPDFLoader加载器,依赖PyPDF库,所以,需要安装它:
pip install pypdf
PyPDFLoader使用还是比较简单的,如下代码即可快速加载PDF中的文字内容了:
15、TextLoader和文档分割器
除了前文学习的三个Loader以外,还有一个基本的加载器:TextLoader
作用:读取文本文件(如.txt),将全部内容放入一个Document对象中。

RecursiveCharacterTextSplitter,递归字符文本分割器,主要用于按自然段落分割大文档。
是LangChain官方推荐的默认字符分割器。
它在保持上下文完整性和控制片段大小之间实现了良好平衡,开箱即用效果佳

总结
TextLoader是一个简单的加载器,可以加载文本文件内容,返回仅有一个Document对象的list。
RecursiveCharacterTextSplitter递归字符文本分割器,是LangChain官方推荐的默认分割器。
• 基于文本的自然段落分割大文档为小文档
• 可以指定小文档的最大字符数、重叠字符数
• 可以手动指定段落划分的依据(符号)以及字符数量统计函数
16、Vector stores 向量存储
基于LangChain的向量存储,存储嵌入数据,并执行相似性搜索

如图,这是一个典型的向量存储应用,也即是典型的RAG流程。
这部分开发主要涉及到:
• 如何文本转向量(前文已经学习)创建向量存储,基于向量存储完成:

内置向量存储的使用

外部(Chroma)向量存储的使用

LangChain内提供向量存储功能,可以基于:
• InMemoryVectorStore ,完成内存向量存储
• Chroma ,外部数据库向量存储向量存储类均提供3个通用API接口:
• add_document ,添加文档到向量存储
• delete ,从向量存储中删除文档
• similarity_search :相似度搜索
17、检索向量并构建提示词
向量存储的实例,通过add_texts(list[str])方法可以快速添加到向量存储中。
流程:
- 先通过向量存储检索匹配信息
- 将用户提问和匹配信息一同封装到提示词模板中提问模型
18、RunnablePassthrough的使用
让向量检索加入链?
使用RunnablePassthrough类

python
"""
提示词:用户的提问 + 向量库中检索到的参考资料
"""
from langchain_community.chat_models import ChatTongyi
from langchain_core.documents import Document
from langchain_core.runnables import RunnablePassthrough
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_community.embeddings import DashScopeEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
def print_prompt(prompt):
print(prompt.to_string())
print("=" * 20)
return prompt
model = ChatTongyi(model="qwen3-max")
prompt = ChatPromptTemplate.from_messages(
[
("system", "以我提供的已知参考资料为主,简洁和专业的回答用户问题。参考资料:{context}。"),
("user", "用户提问:{input}")
]
)
vector_store = InMemoryVectorStore(embedding=DashScopeEmbeddings(model="text-embedding-v4"))
# 准备一下资料(向量库的数据)
# add_texts 传入一个 list[str]
vector_store.add_texts(
["减肥就是要少吃多练", "在减脂期间吃东西很重要,清淡少油控制卡路里摄入并运动起来", "跑步是很好的运动哦"])
input_text = "怎么减肥?"
# langchain中向量存储对象,有一个方法:as_retriever,可以返回一个Runnable接口的子类实例对象
# 使用用户的提问作为检索向量库的查询内容,search_kwargs指定返回2个最相关的结果
retriever = vector_store.as_retriever(search_kwargs={"k": 2})
def format_func(docs: list[Document]):
if not docs:
return "无相关参考资料"
formatted_str = "["
for doc in docs:
formatted_str += doc.page_content
formatted_str += "]"
return formatted_str
# chain
chain = (
{"input": RunnablePassthrough(), "context": retriever | format_func} | prompt | print_prompt | model | StrOutputParser()
)
res = chain.invoke(input_text)
print(res)
"""
retriever:
- 输入:用户的提问 str
- 输出:向量库的检索结果 list[Document]
prompt:
- 输入:用户的提问 + 向量库的检索结果 dict
- 输出:完整的提示词 PromptValue
"""






