LangChain大模型开发

现在市面上的模型多如牛毛,各种各样的模型不断出现,LangChain模型组件提供了与各种模型的集成,并为所有模型提供一个精简的统一接口。

一、LangChain调用模型

LangChain目前支持三种类型的模型:LLMS(大语言模型)、Chat Models(聊天模型)、Embeddings Models(嵌入模型)

  • LLMS:是技术范畴的统称,指基于大参数量、海量文本训练的 Transformer 架构模型,核心能力是理解和生成自然语言,主要服务于文本生成场景
  • 聊天模型:是应用范畴的细分,是专为对话场景优化的 LLMS,核心能力是模拟人类对话的轮次交互,主要服务于聊天场景
  • 文本嵌入模型:文本嵌入模型接收文本作为输入,得到文本的向量.
    LangChain支持的三类模型,它们的使用场景不同,输入和输出不同,开发者需要根据项目需要选择相应。

LLMS使用常用大模型的下载库:
https://huggingface.co/models
https://modelscope.cn/models

LangChain调用三种模型的API如下:
image

1、LangChain调用大语言模型LLM

同时LangChain支持对许多模型的调用,以通义干问为例:

from langchain_community.llms.tongyi import Tongyi  
  
llm = Tongyi(model= 'qwen-max')  
res = llm.invoke("帮我讲个笑话吧")   #一次性返回完整结果
print(res)

res = llm.stream("帮我讲个笑话吧")   #流式输出
for chunk in res:  
    print(chunk, end='', flush=True)

LangChain也支持Ollama本地模型的访问:

from langchain_ollama import OllamaLLM  
model = OllamaLLM(model='gemma4:e4b')   #一次性返回完整结果
res = model.invoke("你是谁?能做什么")  
print(res)

res = model.stream("你是谁?简单回答我")   #流式输出  
for chunk in res:  
    print(chunk, end='', flush=True)

2、LangChain调用聊天模型ChatModel

聊天模型需要传入下面几种类型:

  • SystemMessage:Prompt,可以用于指定模型具体所处的环境和背景,如角色扮演等。你可以在这里给出具体的指示,比如“作为一个代码专家",或者"返回json格式".
  • HumanMessage:人类消息就是用户信息,由人给出的信息发送给LLMS的提示信息,比如“实现一个快速排序方法”.
  • AIMessage:就是 AI 输出的消息,可以是针对问题的回答.(OpenAI库中的assistant角色)

LangChain调用通义千问聊天模型:

from langchain_community.chat_models.tongyi import ChatTongyi  
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage  
  
chatModel = ChatTongyi(model='qwen3-max')  
humanMessage = [  
    SystemMessage(content='你是一名文笔细腻的现代诗人'),  
    HumanMessage(content= '给我写一首简单的现代诗'),  
    AIMessage(content='你,一会看我,一会看云。我觉得,你看我时很远,你看云时很近。'),  
    HumanMessage(content='给予上一首类似的情感,再来一首'),  
]  
  
for chunk in chatModel.stream(input=humanMessage):  
    print(chunk.content, end='', flush=True)

LangChain调用Ollama本地聊天模型:

from langchain_ollama import ChatOllama  
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage  
  
chatModel = ChatOllama(model='gemma4:e4b')  
humanMessage = [  
    SystemMessage(content='你是一名文笔细腻的现代诗人'),  
    HumanMessage(content= '给我写一首简单的现代诗'),  
    AIMessage(content='你,一会看我,一会看云。我觉得,你看我时很远,你看云时很近。'),  
    HumanMessage(content='给予上一首类似的情感,再来一首'),  
]  
  
for chunk in chatModel.stream(input=humanMessage):  
    print(chunk.content, end='', flush=True)

3、LangChain聊天消息的简写形式

SystemMessage、HumanMessage、AIMessage可以用system、human、ai来替换。

  • SystemMessage、HumanMessage、AIMessage是静态的。代码中直接得到了Message类的类对象
  • system、human、ai是动态的,代码运行时由LangChain内部机制转换为Message类对象。

动态运行的好处为:

  • 它可以避免导包,写起来更简单
  • 由于是动态的,代码运行时实时转换,所以简写形式支持内部填充{变量}占位,可在运行时填充具体值,可以在提示词模版中使用。
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
HumanMessage = [  
    SystemMessage(content='你是一名文笔细腻的现代诗人'),  
    HumanMessage(content= '给我写一首简单的现代诗'),  
    AIMessage(content='你,一会看我,一会看云。我觉得,你看我时很远,你看云时很近。'),  
    HumanMessage(content='给予上一首类似的情感,再来一首'),  
]  
  
messages = [  
    ('system', '你是一名:{文笔细腻的现代诗人}'),  
    ('human', '给我:{写一首简单的现代诗}'),  
    ('ai', '{你,一会看我,一会看云。我觉得,你看我时很远,你看云时很近。}'),  
    ('human', '给予上一首类似的情感,再来一首'),  
]
print(chatModel.invoke(input=messages))

4、LangChain调用文本嵌入模型

Embeddings Models嵌入模型的作用:将字符串作为输入,返回一个浮点数的列表(向量)、
在NLP中,Embedding的作用就是将数据进行文本向量化。

阿里千问文本嵌入模型的使用:

#本文嵌入模型  
from langchain_community.embeddings import DashScopeEmbeddings  
  
embedder = DashScopeEmbeddings()  
  
print(embedder.embed_query('你好'))    #单次转换  
print(embedder.embed_documents(['你好', '喝水了吗', '一起打游戏']))     #批量转换

Ollama文本嵌入模型的使用:

#Ollama本地  
from langchain_ollama import OllamaEmbeddings  
embedder = OllamaEmbeddings(model='qwen3-embedding')  
  
print(embedder.embed_query('你好'))    #单次转换  
print(embedder.embed_documents(['你好', '喝水了吗', '一起打游戏']))     #批量转换

二、LangChain提示词模型

提示词优化在模型应用中非常重要,LangChain提供了PromptTempleta类,用于协助优化提示词。

0. 模版类的format和invoke方法

PromptTemplate、FewShotPromptTemplate、ChatPromptTemplate都拥有format和invoke这2类方法。通过占位符的方式设置提示词
image

format和invoke的区别:

区别 format invke
功能 解析占位符,用纯字符串替换生成提示词。静态注入 Runnable接口的标准方法,解析占位符生成提示词。动态注入
返回值 字符串 PromptValue类对象
传参 .format(k=v, k=v, ...) .invoke({"k":v, "k":v, ...})
解析 解析{}占位符 解析{}占位符和MessagePlaceholder结构化占位符(ChatPromptTemplate使用)
template = PromptTemplate.from_template("我是:{lastname}")  
 
res = template.format(lastname="李逗逗") 
res2 = template.invoke({"lastname": "李逗逗"}) 

1. 通用提示词模版 PromptTemplate

PromptTemplate表示提示词模版,可以构建一个自定义的基础提示词模版,支持变量的注入,最终生成所需的提示词。它相比手动拼接字符串的好处:

  • 使用Template模板构建提示词,在大型工程中更容易做标准化模板
  • Template模板类,支持LangChian框架的链式调用(Runnable接口)
    • PromptTempLate
    • FewShotPromptTemplate
    • ChatPromptTemplat
      标准写法:通过from_template设置提示词
from langchain_core.prompts import PromptTemplate  
from langchain_community.llms.tongyi import Tongyi  
  
prompt_template = PromptTemplate.from_template(  
    "我姓{lastname}, 是{gender}, 帮忙起一个名字,请简略回答"  
)  
  
prompt_text = prompt_template.format(lastname='桑', gender='女性')  
  
model = Tongyi(model='qwen-max')  
response = model.invoke(prompt_text)  
print(response)

基于Chain链的写法:

prompt_template = PromptTemplate.from_template(  
    "我姓{lastname}, 是{gender}, 帮忙起一个名字,请简略回答"  
)  

model = Tongyi(model='qwen-max')    
chain = prompt_template | model  
response = chain.invoke(input={"lastname": "桑", "gender": "女性"})  
print(response)
  • zero-shot(零样本学习),也就是单纯prompt,可以基于PromptTemplate直接完成
  • few-shot(少样本学习),需要提供实例的话,需要转换为FewShotPromptTemplate完成。

2. 少样本学习提示词模版 FewShotPromptTemplate

FewShotPromptTemplate通过5个参数设置提示词模板

  • example_prompt:示例数据的模板
  • examples:示例数据,list,嵌套字典
  • prefix:前提示词,示例数据前内容
  • suffix:后提示词,示例数据的后内容
  • input_variables:list,动态输入的变量列表,用于前缀或后缀中需要注入的变量名
from langchain_core.prompts import FewShotPromptTemplate, PromptTemplate  
from langchain_community.llms.tongyi import Tongyi  
  
example_prompt = PromptTemplate.from_template("单词:{word}, 反义词:{antonym}")  
example_data = [  
    {"word": "大", "antonym": "小"},  
    {"word": "上", "antonym": "下"},  
]  
  
few_shot_prompt = FewShotPromptTemplate(  
    prefix="给予给定词的反义词,有如下示例:",  
    example_prompt=example_prompt,  
    examples=example_data,  
    suffix="基于示例告诉我:{input_word} 的反义词",  
    input_variables=['input_word'],  
)  
  
#获得最终的提示词  
prompt = few_shot_prompt.invoke({"input_word": "左"}).to_string()  
# prompt_text = few_shot_prompt.format(input_word = "左")
  
model = Tongyi(model="qwen-max")  
response = model.invoke(input = prompt)  
print(response)

3. 聊天提示词模板 ChatPromptTemplate

  • PromptTemplate: 通用提示词模板,支持动态注入信息。只能传入一条消息。
  • FewShotPromptTemplate: 支持基于模板注入任意数量的示例信息
  • ChatPromptTemplate: 支持注入任意数量的历史会话信息

ChatPromptTemplate通过from_messages方法,从列表中获取多轮次会话作为聊天的基础模板
MessagePlaceholder作为占位,提供了占位符,必须基于invoke动态注入历史对话记录。历史对话信息并不是静态的,随着对话不断增加,所以历史会话信息需要支持动态注入。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder  
  
chat_prompt_template = ChatPromptTemplate.from_messages(  
    [  
        ("system", "..."),  
        ("ai", "..."),
        MessagesPlaceholder("history"),
        ("human", "..."),  
    ]  
)  
  
history_data = [  
    ("human", "..."),  
    ("ai", "..."),  
    ("human", "..."),  
    ("ai", "...")  
]  
  
prompt = chat_prompt_template.invoke({"history": history_data})

model = Tongyi(model="qwen-max")  
print(model.invoke(input=prompt))

三、Chain的基础使用

1. chain简介

Chain的作用:将组件串联,上一个组件的输出作为下一个组件的输入。 这是LangChain链,尤其是|管道链的核心工作原理,也是链式调用的核心:实现数据的自动化流程转与组件的协同工作

Chain的语法:chain = prompt_template | model | ...

Chain的组件能串联的核心前提是:Runnable的子类对象才能入链(以及Callable、Maping接口的子类对象也能入链)。
image

chain变量是RunnableSequence(RunnableSerializable子类)类型, 而得到这个类型的原因就是Runnable基类内部对__or__魔术方法的改写。对于传入的数据始终返回RunnableSequence,这就是链的基础架构。

def __or__(  
    self,  
    other: Union[  
        Runnable[Any, Other],  
        Callable[[Iterator[Any]], Iterator[Other]],  
        Callable[[AsyncIterator[Any]], AsyncIterator[Other]],  
        Callable[[Any], Other],  
        Mapping[str, Union[Runnable[Any, Other], Callable[[Any], Other], Any]],  
    ],  
) -> RunnableSerializable[Input, Other]:  
    
    
    return RunnableSequence(self, coerce_to_runnable(other))

2. chain链式调用

  • 通过|链接提示词模板和模型对象
  • 返回值chain对象是RunnableSerializable类型。RunnableSerializable是Runnable的直接子类,也是绝大多数组件的父类。
  • 通过chain通过invoke和stream触发整个链条的执行,进行阻塞执行或流式执行
1. 非链式调用
prompt = chat_prompt_template.invoke({"history": history_data})
model = Tongyi(model="qwen-max")  
model.invoke(input=prompt)


2. 链式调用
model = ChatTongyi(model="qwen-max")  
  
chain: RunnableSerializable = chat_prompt_template | model  
res = chain.invoke({"history": history_data})     #阻塞执行
print(res.content)  
  

res2 = chain.stream({"history": history_data})  
for chunk in res2:  
    print(chunk.content, end='', flush=True)      #流式执行

chain的执行是上一个组件的输出作为下一个组件的输入,所以chain = chat_prompt_template | model 的数据传递流程:
image

四、LangChain解析器

1. StrOutputParser字符串解析器

如果我们想第一次模型的输出结果,作为第二次询问模型:

chain = prompt | model | model

这样的链构建没有问题,但会运行报错:
ValueError: Invalid input type <class 'Langchain_core.messages.ai.AIMessage'>. Must be a PromptValue,str,or List of BaseMessages.

错误原因是原因是prompt是PromptValue类型,而model的输出是AIMessage类型。不是模型的规定输入。模型规定的输入类型是

  • PromptValue
  • 字符串
  • 序列Sequence[MessageLikeRepresentation]
StrOutputParser简介

StrOutputParser是LangChain的内置的字符串解析器

  • 可以将AIMessage解析为简单的字符串,符合了模型invoke方法的输入要求
  • 是Runnable接口的子类(可以加入链)
parser = StrOutputParser()
chain = prompt | model | parser | model
from langchain_core.output_parsers import StrOutputParser  
from langchain_core.prompts import PromptTemplate  
from langchain_community.chat_models.tongyi import ChatTongyi  
  
parser = StrOutputParser()  
model = ChatTongyi(model="qwen3-max")  
prompt = PromptTemplate.from_template(  
    "我姓:{lastname}, 是{gender},请给我起名,仅告知名字"  
)  
  
chain = prompt | model | parser | model | parser  
res  = chain.invoke({"lastname": "桑", "gender": "女性"})  
print(res)

2、JsonOutputParser解析器

strOutputParser字符串解析器模型输出结果进行简单的字符串解析,便输入模型中,这样的做法并不标准,因为上一个模型的输出没有被处理就输入到下一个模型中。

chain = prompt | model | parser | model | parser

正常模型的处理逻辑为:
invoke -> stream初始输入 -> 提示词模板 -> 模型 -> **数据处理 -> 提示词模板** -> 模型 ->解析器 ->结果
image

所以,对于第一个模型的输出,需要完成AIMessage转换为dict字典,注入到第二个提示词模板中,形成新的提示词PromptValue输出

  • 模型输出的结果为AIMessage
  • 提示词模板要求输入的类型为dict字典
  • 提示词模板输出类型是:PromptValue
JsonOutputParser简介

JsonOutputParser的作用是将AIMessage转换为字典类型(AIMessage -> Dict(JSON)),AIMessage的输出必须是json格式

from langchain_core.output_parsers import StrOutputParser, JsonOutputParser  
from langchain_core.prompts import PromptTemplate  
from langchain_community.chat_models.tongyi import ChatTongyi  
  
str_parser = StrOutputParser()  
json_parser = JsonOutputParser()  
  
first_prompt = PromptTemplate.from_template(  
    "我姓:{lastname}, 性别是: {gender},请帮我起名,并封装为JSON格式返回给我,"  
    "要求key是name,value就是起的名字。请严格遵守格式要求"  
)  
  
second_prompt = PromptTemplate.from_template(  
    "姓名{name}, 请帮我解析含义"  
)  
  
model = ChatTongyi(model="qwen3-max")  
# chain = first_prompt | model | str_parser  #第一个模型输出内容为{"name":"桑xx"}
chain = first_prompt | model | json_parser | second_prompt | model | str_parser  
res = chain.invoke({"lastname": "桑", "gender": "女性"})  
print(res)

3. 自定义函数加入链

除了JsonOutputParser这类固定功能的解析器之外。我们也可以自己编写Lambda匿名函数来完成自定义逻辑的数据转换,想怎么转换就怎么转换,更自由。

想要实现自定义链函数,可以基于RunnableLambda类实现。RunnableLambda类是LangChain内置的,将普通函数等转换为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()  
#AIMessage.content是str类型  
#my_func返回的内容是{'name': 'xxx'}, 类型是 <class 'dict'>
my_func = RunnableLambda(lambda ai_message: {"name": ai_message.content})  
  
first_prompt = PromptTemplate.from_template(  
    "我姓:{lastname}, 性别是: {gender},请帮我起名,仅告诉我名字"  
)  
  
second_prompt = PromptTemplate.from_template(  
    "姓名{name}, 请帮我简单解析含义"  
)  
  
model = ChatTongyi(model="qwen3-max")  
chain = first_prompt | model | my_func | second_prompt | model |str_parser  
res = chain.invoke({"lastname": "桑", "gender": "女性"})  
print(res, type(res))

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

  • 而函数就是Callable接口的实例。
    image

chain = first_prompt | model | (lambda ai_message: {"name": ai_message.content}) | second_prompt | model |str_parser
如上代码示例,|符号(底层是重写了_or_)组链,是支持函数加入的。其本质是将函数自动转换为RunnableLambda

五、Memory会话记忆

如果想要封装消息,可以借用提示词模板自行封装消息,也可以借助LangChain内置的历史记录history功能。帮助模型在有历史记录的情况下回答。

  • RunnableWithMessageHistory。在原有链的基础上创建带有历史记录功能的新Chain(新Runnable实例),这个chain可以利用LangChain的会话记忆类,自动存取会话历史记录
from langchain_core.runnables.history import RunnableWithMessageHistory  
  
new_conversation_chain = RunnableWithMessageHistory(  
    some_chain,                      #附加历史消息的Runnable,通常是chain  
    None,                            #根据会话ID,返回历史会话记录的函数, 
    input_messages_key="input",      #声明哪个是用户输入消息,占位符  
    history_messages_key="history"   #声明哪个是历史消息,占位符  
)

1. Memory临时会话记忆

  • InMemoryChatMessageHistory方法可以实现内存中存储历史对话。它可以创建BaseChatMessageHistory的子类,BaseChatMessageHistory专门用于存放会话历史记录
#存储多个会话ID锁对应的历史会话记录  
chat_history_store = {}  
#根据会话ID,获取历史会话记录。
#传入为会话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]

代码实现:

from langchain_core.chat_history import InMemoryChatMessageHistory  
from langchain_core.runnables.history import RunnableWithMessageHistory  
from langchain_core.output_parsers import StrOutputParser  
from langchain_core.prompts import PromptTemplate  
from langchain_community.chat_models.tongyi import ChatTongyi  
  
  
def print_prompt(full_prompt):  
    print("=" * 20, full_prompt.to_string(), "=" * 20)  
    return full_prompt  
  
# prompt = PromptTemplate.from_template(  
#     "你需要根据历史对话回应用户的问题,对话历史:{history}。当前用户输入:{input}, 请给出回应"  
# )  
prompt = ChatPromptTemplate.from_messages(  
    [  
        ("system", "你需要根据历史对话回应用户的问题"),  
        MessagesPlaceholder("history"),  
        ("human", "请给出回应:{input}"),  
    ]  
)
  
str_parse = StrOutputParser()  
model = ChatTongyi(model="qwen3-max")   
base_chain = prompt | print_prompt | model | str_parse  

#获取历史对话记录
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]  
  
#创建带有历史记录功能的新Chain
new_conversation_chain = RunnableWithMessageHistory(  
    base_chain,   
    get_history,  
    input_messages_key="input",  
    history_messages_key="history"    
)  
  
if __name__ == "__main__":  
    # 固定格式,添加LangChain的会话配置,表明当前程序所属的会话ID  
    session_config = {"configurable": {"session_id": "user_001"}}  
  
    print(new_conversation_chain.invoke({"input": "我有一只猫"}, session_config)) 
    print(new_conversation_chain.invoke({"input": "我有两只狗"}, session_config)) 
    print(new_conversation_chain.invoke({"input": "我共有几只宠物?"}, session_config))
    
    
    
第一次提问,会话历史是空的

第二次提问,会话历史是:
对话历史:[HumanMessage(content='我有一只猫', additional_kwargs={}, response_metadata={}), AIMessage(content='哇,真棒!你有一只猫呀~', additional_kwargs={}, response_metadata={})]

第三次提问,会话历史是:
对话历史:[HumanMessage(content='我有一只猫', additional_kwargs={}, response_metadata={}), AIMessage(content='哇,真棒!你有一只猫呀~', additional_kwargs={}, response_metadata={}), 
HumanMessage(content='我有两只狗', additional_kwargs={}, response_metadata={}), AIMessage(content='哎呀,原来你不仅有一只猫,还有两只狗呀!', additional_kwargs={}, response_metadata={})]

2、FileChatMessageHistory长期会话记忆

基于FileChatMessageHistory,可以实现一个基于Json格式和本地文件的会话记录保存

  • 文件存储的会话记录,以session_id为文件名,不同session_id有不同的文件存储消息

FileChatMessageHistory继承了BaseChatMessageHistory类,需要实现3个方法:

  • add_message: 添加消息到文件,同步模式
  • messages:从文件中获取消息,同步模式
  • clear:清除消息,同步模式

其中消息的存储包含2个自带的方法:

  • AIMessage、HumanMessage和SystemMessage都是BaseMessage的子类
  • message_to_dict: 单个消息消息对象转为字典(BaseMessage -> dict字典)
[  
    {  
        "type": "human",  
        "data": {  
            "content": "我又一只猫",  
            "additional_kwargs": {  
  
            },  
            "response_metadata": {  
  
            },  
            "type": "human",  
            "name": null,  
            "id": null,  
            "example": false  
        }  
    }  
]
  • message_from_dict: 将字典消息解析为BaseMessage消息。[字典、字典...] -> [消息、消息...]

官方FileChatMessageHistory示例

import json, os  
from langchain_core.messages import messages_from_dict, message_to_dict  
  
  
class 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)  # Existing messages  
        all_messages.extend(messages)  # Add new 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)

代码实现:

import json, os  
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  
  
  
# AIMessage、HumanMessage和SystemMessage都是BaseMessage的子类  
# message_to_dict: 单个消息对象(BaseMessage类示例 -> dict字典)  
# message_from_dict: [字典、字典...] -> [消息、消息...]  
  
  
class FileChatMessageHistory(BaseChatMessageHistory):  
    def __init__(self, session_id, storage_path):  
        self.session_id = session_id  # 会话ID  
        self.storage_path = storage_path  # 会话记录存储的路径  
        # 完整的文件路径  
        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)  
  
        # 将数据同步存储到本地文件中  
        # 类对象写入文件中,会变成一堆二进制  
        # 为例方便查看,message_to_dict可以将BaseMessage消息转为字典,在借助json将字典以字符串写入文件n  
        # new_messages = []        # for message in messages:        #     m = message_to_dict(message)        #     new_messages.append(m)        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) -> Sequence[BaseMessage]:  
        # 文件存储的是:list[消息字典]  
        try:  
            with open(self.file_path, "r", encoding="utf-8") as f:  
                messages_data = json.load(f)  
                return messages_from_dict(messages_data)  
        except FileNotFoundError:  
            return []  
        #文件存储的是:list[消息字典]  
  
    def clear(self) -> None:  
        with open(self.file_path, "w", encoding="utf-8") as f:  
            json.dump([], f)  
  
def print_prompt(full_prompt):  
    print("=" * 20, full_prompt.to_string(), "=" * 20)  
    return full_prompt  
  
prompt = ChatPromptTemplate.from_messages(  
    [  
        ("system", "你需要根据历史对话回应用户的问题"),  
        MessagesPlaceholder("history"),  
        ("human", "请给出回应:{input}"),  
    ]  
)  
str_parse = StrOutputParser()  
model = ChatTongyi(model="qwen3-max")  
base_chain = prompt | print_prompt | model | str_parse  
  
#获取历史对话记录  
def get_history(session_id):  
    return FileChatMessageHistory(session_id, storage_path="./chat_history")  
  
#创建带有历史记录功能的新Chain  
new_conversation_chain = RunnableWithMessageHistory(  
    base_chain,  # 附加历史消息的Runnable,通常是chain  
    get_history,  # 获取历史会话的函数  
    input_messages_key="input",  # 声明模版prompt中,哪个是用户输入消息,占位符  
    history_messages_key="history"  # 声明模版prompt中,哪个是历史消息,占位符  
)  
  
if __name__ == "__main__":  
    # 固定格式,添加LangChain的会话配置,表明当前程序所属的会话ID  
    session_config = {"configurable": {"session_id": "user_001"}}  
  
    # print(new_conversation_chain.invoke({"input": "我有一只猫"}, session_config))  
    # print(new_conversation_chain.invoke({"input": "我有两只狗"}, session_config))  
    print(new_conversation_chain.invoke({"input": "我共有几只宠物?"}, session_config))
    

六、LangChain文档加载器

文档加载器提供了一套标准接口,用于将不同来源(如CSV、PDF 或 JSON等)的数据读取为
LangChain的Class Document文档格式。这确保了无论数据来源如何,都能对其进行一致性处理。

文档加载器可以内置或自行实现,均实现了BaseLoader接口。返回Document对象的list

Class Document,是LangChain内文档的统一载体,所有文档加载器最终返回此类的实例。Document类主要记录了:

  • page_content: 文档内容
  • metadata: 文档元数据(字典)
一个基础的Document类实例,基于如下代码创建:
from langchain_core.documents import Document
document = Document(
page_content="Hello, world!", metadata={"source":"https://example.com"}
)

LangChain内置了许多文档加载器,不同的文档加载器有不同的参数,但因为都继承与BaseLoder类,返回都实现了统一的接口

  • load(): 一次性加载全部文档,返回list格式,包含多个docement
  • lazy_load(): 批量单个加载Document,对大型数据集很有用,避免内存溢出
    image

1. CSVLoader

CSVLoader语法:

loader = CSVLoader(  
    file_path="./data/stu.csv",  
    csv_args={  
        "delimiter": ",", #指定分隔符  
        "quotechar": "'", #指定分隔符文本的包裹的符号,如果文本被这个符号包裹表示一列 
        "fieldnames": ['name', 'age', 'gender', '爱好'], # 如果数据原本有表头,就不要下面的代码,如果没有可以使用
        “source_column”: "source"    #指定本条数据的来源是哪一列 
    },  
    encoding="utf-8",  
)  

示例:

from langchain_community.document_loaders import CSVLoader  
  
loader = CSVLoader(  
    file_path="./data/stu.csv",  
    csv_args={  
        "delimiter": ",",               #指定分隔符  
        "quotechar": "'"               #指定分隔符文本的包裹的符号  
        # ,"fieldnames": ['name', 'age', 'gender', '爱好']       ## 如果数据原本有表头,就不要下面的代码,如果没有可以使用  
    },  
    encoding="utf-8",  
)  
  
  
# # 1. 批量加载  
# documents = loader.load()  
# for document in documents:  
#     print(document, type(document))  
  
# 2.懒加载,使用迭代器加载  
for document in loader.lazy_load():  
    print(document, type(document))

2. JSONLoader

JSONLoader用于将Json数据加载为Document对象。LangChain底层对Json的解析基于jq工具实现,封装为Document,抽取的内容使用jq_schema语法。

安装jq库:`pip install jq

JSONLoader语法:

from langchain_community.document_loaders import JSONLoader  
  
loader = JSONLoader(  
    file_path="",           # 文件路径  
    jq_schema=".",          # 提取的内容,jq_schema语法  
    text_content=False,     # 提取的内容是否是字符串,默认为True  
    json_content=True,      # 是否是JsonLines文件,即每一行都是JSON的文件  
)

示例1:

{  
    "name": "周杰轮",  
    "age": 11,  
    "hobby": [  
        "唱",  
        "跳",  
        "RAP"  
    ],  
    "other": {  
        "addr": "深圳",  
        "tel": "12332112321"  
    }  
}

jq_schema语法:

  • .表示整个JSON对象
  • []表示数据
  • .name表示抽取name的值,也就是周杰伦
  • .hobby[1]表示抽取唱
  • .other.addr表示抽取“深圳”
from langchain_community.document_loaders import JSONLoader
loader = JSONLoader(  
    file_path="./data/stu.json",  # 文件路径  
    jq_schema=".name",  # 提取的内容,jq_schema语法  
)  
  
document = loader.load()  
print(document)

示例2:

[{"name":"周杰轮","age":11,"gender":"男"},
{"name":"蔡依临","age":12,"gender":"女"},
{"name":"王力鸿","age":11,"gender":"男"}]

jq_schema语法:

  • .[]. 抽取了这3个字典
  • .[].name 表示抽取全部的name的值
loader2 = JSONLoader(  
    file_path="./data/stus.json",  # 文件路径  
    jq_schema=".[].name",  # 提取的内容,jq_schema语法  
)  
  
document2 = loader2.load()  
print(document2)

示例3:

{"name": "周杰轮", "age": 11, "gender": "男"}  
{"name": "蔡依临", "age": 12, "gender": "女"}  
{"name": "王力鸿", "age": 11, "gender": "男"}

JsonLines文件,每一行都是一个Json

loader3 = JSONLoader(  
    file_path="./data/stu_json_lines.json",  
    jq_schema=".name",  
    text_content=False,  
    json_lines=True  
)  
document3 = loader3.load()  
print(document3)

3. TextLoader和文档分割器

TextLoader的作用是:读取文本文件(.txt), 将全部内容放入一个Document对象中,即返回的Document数量为1

from langchain_community.document_loaders import TextLoader  
  
loader = TextLoader(  
    "./data/Python基础语法.txt",    #文件路径  
    encoding="utf-8",  
)  
  
document = loader.load()  
print(document, len(document))
#结果为1

如果文档很大,则文本全都加载到一个Document中不合适
RecursivecharacterTextplitter递归字符文本分割器,主要用于按自然段落分割大文档
它返回了Document对象的list。[Document, Document,...]
是LangChain官方推荐的默认字符分割器。它在保持上下文完整性控制片段大小之间实现了良好平衡,开箱即用效果佳。

from langchain_community.document_loaders import TextLoader  
from langchain_text_splitters import RecursiveCharacterTextSplitter  
  
loader = TextLoader(  
    "./data/Python基础语法.txt",  # 文件路径  
    encoding="utf-8",  
)  
  
document = loader.load()  
  
splitter = RecursiveCharacterTextSplitter(  
    chunk_size=500,  # 分段的最大字符数量  
    chunk_overlap=50,  # 分段的之间允许重叠的字符数  
    separators=["\n\n", "\n", "。", "!", "?", ".", "!", "?", " ", ""],  
    length_function=len,  # 字符统计依据(函数)  
)  
  
split_docs = splitter.split_documents(document)  
print(len(split_docs))  
for split_doc in split_docs:  
    print("=" * 20)  
    print(split_doc)

4. PyPDFLoader加载器

PyPDFLoader可以加载PDF中文字内容。返回了Document对象的list

安装pypdf库:pip install pypdf

使用语法:

from langchain_community.document_loaders import PyPDFLoader  
  
loader = PyPDFLoader(  
    file_path="./data/pdf1.pdf",    #文件路径必填  
    mode="page",                    #读取模式:Page按页面读取,single按单个文档读取 
    password="password",        #文件密码  
)

七、向量存储 Vector stores

LangChain支持向量存储,存储嵌入数据,并执行相似性搜索

image

典型的RAG流程包含了向量存储和应用。主要涉及:

  • 向量嵌入算法,将文本转为向量
  • 创建向量数据存储,基于向量存储,实现:
    • add_documents:存入向量
    • delete:删除向量
    • similarity_search: 向量相似度检索

1. InMemoryVectorStore向量存储

安装chroma包:pip install langchain-chroma chromadb

from langchain_core.vectorstores import InMemoryVectorStore  
from langchain_community.embeddings import DashScopeEmbeddings  
  
 #初始化
vector_store = InMemoryVectorStore(embedding=DashScopeEmbeddings()) 
 
# 添加文档到向量存储,并指定 id# 注意:这里的需要是 Document 对象的list  
vector_store.add_documents(documents=[doc1, doc2], ids=["id1", "id2"]) 

#添加字符到向量库,传入类型是list[str], 自动转换为向量存储到内存中,id如果不传向量库也会自动赋值
vector_store.add_texts(["str", "str2",... ])

# 删除文档 (通过指定的 id 删除)  
vector_store.delete(ids=["id1"])  

# 相似性搜索,返回的是Document对象的list
similar_docs = vector_store.similarity_search("your query here", 4,filter = {"key":"value"})

示例:

from langchain_core.vectorstores import InMemoryVectorStore  
from langchain_community.embeddings import DashScopeEmbeddings  
from langchain_community.document_loaders import CSVLoader  
  
loader = CSVLoader(  
    file_path="./data/info.csv",  
    encoding="utf-8",  
    source_column="source",  # 指定本条数据的来源是哪里  
)  
documents = loader.load()  
  
# 向量存储的增加、删除和检索  
vector_store = InMemoryVectorStore(  
    embedding=DashScopeEmbeddings()  
)  
  
vector_store.add_documents(  
    documents=documents,  
    ids=["id" + str(i) for i in range(1, len(documents) + 1)]  
)  
  
vector_store.delete(["id1", "id2"])  
  
result = vector_store.similarity_search(  
    "python是不是简单易学",  
    3           #检索几个结果  
)  
print(result)

2. 外部(Chroma)向量持久化存储:

外部存储数据库,长期记忆

from langchain_community.embeddings import DashScopeEmbeddings  
from langchain_chroma import Chroma  
  
# 初始化 Chroma 向量存储,类似于文件会话记忆 
vector_store = Chroma(  
    collection_name="example_collection",       #数据库的表
    embedding_function=DashScopeEmbeddings(),   #Embedding 模型
    persist_directory="./chroma_langchain_db",  #存储路径,外部数据存储的地方
)

示例:

from langchain_chroma  import Chroma  
from langchain_community.embeddings import DashScopeEmbeddings  
from langchain_community.document_loaders import CSVLoader  
  
loader = CSVLoader(  
    file_path="./data/info.csv",  
    encoding="utf-8",  
    source_column="source",  # 指定本条数据的来源列是那个
)  
documents = loader.load()  
  
# 向量存储的增加、删除和检索  
vector_store = Chroma(  
    collection_name="test",             #当前向量存储的名字,类似于数据库的表名  
    embedding_function=DashScopeEmbeddings(),  
    persist_directory="./chroma_db"     #指定数据存放的文件夹  
)  
  
vector_store.add_documents(  
    documents=documents,  
    ids=["id" + str(i) for i in range(1, len(documents) + 1)]  
)  
  
vector_store.delete(["id1", "id2"])  
  
result = vector_store.similarity_search(  
    "python是不是简单易学",  
    3           #检索几个结果  
)  
print(result)

本地存储,一次存储,随时使用:

from langchain_chroma  import Chroma  
from langchain_community.embeddings import DashScopeEmbeddings  
  
  
# 向量存储的增加、删除和检索  
vector_store = Chroma(  
    collection_name="test",             #当前向量存储的名字,类似于数据库的表名  
    embedding_function=DashScopeEmbeddings(),  
    persist_directory="./chroma_db"     #指定数据存放的文件夹  
)  
  
result = vector_store.similarity_search(  
    "努力努力,改变自己",  
    3,  # 检索几个结果  
	filter={"source": "黑马程序员"}      #检索过滤
)  
print(result)

八、检索向量并构建提示词

简单实现在向量库中检索到的参考资料+用户提问,组合成提示词,对大模型进行提问

"""  
提示词:用户的提问 + 向量库中检索到的参考资料  
"""  
from langchain_community.chat_models.tongyi import ChatTongyi  
from langchain_community.embeddings import DashScopeEmbeddings  
from langchain_core.vectorstores import InMemoryVectorStore  
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  
  
  
# 建立向量库  
vector_store = InMemoryVectorStore(embedding=DashScopeEmbeddings(model="text-embedding-v4"))  
vector_store.add_texts(  
    ["减肥就是要少吃多练", "在减脂期间吃东西很重要,清淡少油控制卡路里摄入并运动起来", "跑步是很好的运动哦"]  
)  
input_text = "怎么减肥?"  
  
# 检索向量库  
result = vector_store.similarity_search(input_text, 2)  
  
# 根据用户输入进行向量库检索,将检索的结果构建提示词  
reference_text = "["  
for doc in result:  
    reference_text += doc.page_content  
reference_text += "]"  
  
#提问模型  
model = ChatTongyi(model="qwen3-max")  
prompt = ChatPromptTemplate(  
    [  
        ("system", "以我提供的已知参考资料为主,简洁和专业的回答用户问题。参考资料:{context}。"),  
        ("human", "用户提问:{input}"),  
    ]  
)  
chain = prompt | print_prompt | model | StrOutputParser()  
res = chain.invoke({"input": input_text, "context": reference_text})  
print(res)

九、RunnablePassthrough的使用

RunnablePassthrough和as_retriever方法用于将向量检索加入chain中。

向量存储对象不是Runnable接口的子类,LangChain中向量存储的对象,有一个as_retriever方法,可以返回一个Runnable接口的子类实例对象 Retriever

retriever = vector_store.as_retriever(search_kwargs={"k":2})

Retriever用于根据用户的提问,在向量库中检索结果

  • retriever:
    • 输入:用户的提问,类型是str
    • 输出:向量库的检索结果,类型是list[Document]
  • prompt:
    • 输入:用户的提问+向量库的检索结果, 类型dict
      • 输出:完整的提示词,类型是PromptValue

利用retriever进行用户检索,再结合用户提问形成提示词的复合执行chain为:

  • {}字典可以入链,因为chain接受的类型为Runnable、函数和Mapping类型(字典的父类)
  • RunnablePassthrough相当于占位符,把invoke整个链条的输入截流一份到这个为止
  • retriever相当于第一个链中的执行组件,所以invoke原本的输入就传给retriever
  • 而第一个{}的执行结果,整体作为输入传递个prompt
  • retriever执行结果是list[Document],format_func将此类型转换为str
chain = (
	{"input":RunnablePassthrough(), "context":retriever | format_func} | prompt | model | StrOutputParser()
)

chain.invoke(...)

示例代码

"""  
提示词:用户的提问 + 向量库中检索到的参考资料  
"""  
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  
  
  
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  
  
  
# 建立向量库  
vector_store = InMemoryVectorStore(embedding=DashScopeEmbeddings(model="text-embedding-v4"))  
vector_store.add_texts(  
    ["减肥就是要少吃多练", "在减脂期间吃东西很重要,清淡少油控制卡路里摄入并运动起来", "跑步是很好的运动哦"]  
)  
input_text = "怎么减肥?"  
  
# 检索向量库chain  
retriever = vector_store.as_retriever(search_kwargs={"k": 2})  
  
# 提问模型  
model = ChatTongyi(model="qwen3-max")  
prompt = ChatPromptTemplate(  
    [  
        ("system", "以我提供的已知参考资料为主,简洁和专业的回答用户问题。参考资料:{context}。"),  
        ("human", "用户提问:{input}"),  
    ]  
)  
  
chain = (  
        {"input": RunnablePassthrough(), "context": retriever | format_func} | prompt | print_prompt | model | StrOutputParser()  
)  
res = chain.invoke(input_text)  
print(res)
posted @ 2026-04-29 13:06  言思宁  阅读(48)  评论(0)    收藏  举报