LangChain 与 Dify 入门与实践

LangChain 和 Dify 都是构建智能体(Agents)的工具,用于通过集成语言模型(如 OpenAI GPT、LLMs)来开发智能应用。这两者在功能、设计理念和用法上有一定的区别,下面将从设计思想、功能特点、和使用方法等方面进行对比和详细介绍。

Langchain 开发实战

LangChain是一个用于开发由语言模型支持的应用程序的框架。

LangChain 安装

开发环境:python 3.11

# 终端输入:
# pip install langchain

# 使用清华源Pypi安装:
pip install langchain -i https://pypi.tuna.tsinghua.edu.cn/simple
# 安装 langchain-community 依赖
pip install --upgrade langchain langchain-community

Jupyter

# 终端输入:
# pip install jupyterlab

# 使用清华源Pypi安装:
pip install jupyterlab -i https://pypi.tuna.tsinghua.edu.cn/simple

# 终端输入:
jupyter notebook//会弹出网页

LCEL langchain表达式语言

chain 链的使用

提示词(prompt)+模型(model)+输出解释器(output parser)示例
单独使用LLM适用于简单的应用程序,但更复杂的应用程序需要将LLM链接起来,要么彼此链接,要么与其他组件链接。

from langchain_community.llms import Ollama #导入ollama包
from langchain_core.prompts import ChatPromptTemplate #提示词
from langchain_core.output_parsers import StrOutputParser #输出解析器
# from langchain_community.embeddings import GPT4AllEmbeddings #文本嵌入模型

if __name__ == '__main__':
    try:
        # 配置提示词
        prompt = ChatPromptTemplate.from_template("《三国演义》{topic} 介绍")

        model = Ollama(model="llama3.1")  # 这里选gpu、cpu负担小的0.5b模型
        output_parser = StrOutputParser()
        chain = prompt | model | output_parser  # 组成一条链
        res = chain.invoke({"topic": "曹操"})
        print(res)
    except Exception as e:
        print(f"发生了错误:{e}")

代码执行:
file

我们来分析下这段代码的流程:

首先,我们将topic定为"曹操",{"topic": "曹操"}。
prompt组件获取用户输入,然后在使用topic构造提示,用于构造 PromptValue。
model组件采用生成的提示,并传递到llama2模型进行评估。模型生成的输出是一个ChatMessage对象。
最后,该output_parser组件接收 aChatMessage并将其转换为 Python 字符串,该字符串从 invoke 方法返回。

file

RAG(Retrieval Augmented Generation)检索增强生成

什么是RAG?

尽管我们已经获得了想要的回答,但是还存在着下面几个问题:

知识的局限性

模型自身的知识完全源于它的训练数据,而现有的主流大模型(ChatGPT、文心一言、通义千问…)都是构建于网络公开的数据,对于一些实时性的、非公开的或离线的数据是无法获取到的,这部分知识也就无从具备。

"幻觉"问题

AI modle的底层原理都是基于数学概率,其模型输出实质上是一系列数值运算,所以它有时候会一本正经地胡说八道。尤其是在大模型自身不具备某一方面的知识或不擅长的场景。在区分这些问题时,还需要使用者的知识背景。

数据安全性

对于企业来说,数据安全至关重要,没有企业愿意承担数据泄露的风险,上传第三方平台进行训练。这也导致完全依赖通用大模型自身能力的应用方案不得不在数据安全和效果方面进行取舍。

我们现在所说的RAG就是在解决这些问题。

RAG框架

RAG的架构如图中所示,简单来讲,RAG就是通过检索获取相关的知识并将其融入Prompt,让大模型能够参考相应的知识从而给出合理回答

file
RAG框架图

因此,可以将RAG的核心理解为“检索+生成”,前者主要是利用向量数据库的高效存储和检索能力,召回目标知识后者则是利用大模型和Prompt工程,将召回的知识合理利用,生成目标答案

完整的RAG应用流程主要包含两个阶段:

数据准备阶段:数据提取——>文本分割——>文本向量化(embedding)——>数据入库
应用阶段:用户提问——>数据检索——>生成Prompt——>LLM生成答案

RAG示例

LLM

# 导入ollama包
from langchain_community.llms import Ollama

# 通过ollama远程获取千问7b模型
model = Ollama(model="qwen:7b",base_url="http://192.168.3.122:11435")

不管RAG有多厉害,还是基于LLM的,脱离了LLM的RAG会缺乏“人”的感觉。

目前主流的LLM有:

国外有:Openai-ChatGPT、Google-LaMDA、Llama等。
国内有:百度-Ernie 3.0 Titan、智谱AI-GLM、阿里-M6、通义千问等。

文档加载器

# txt文本加载器
from langchain.document_loaders import TextLoader
loader = TextLoader('Data/宇宁无锡考勤.txt',encoding="utf-8")
doc = loader.load()
doc

输出结果:

[Document(page_content='无锡宇宁智能科技有限公司(无锡)考勤管理制度\n从2024年2月1日起执行\n第一章总则\n第一条为规范员工管理,提高工作效率,保证各项工作的正常运行,根据有关法律法规,结合本公司实际情况,特制定本制度。\n第二条本制度适用人员范围为宇宁智能(无锡)全体人员。\n第二章细则\n第一节考勤打卡规范\n第三条实行打卡考勤的人员范围\n本公司员工除下列人员外,均应按规定打卡。\n1、\t总经理、其他总经理特批者;\n2、\t因公出差者:\n3、\t其他驻外机构人员。\n第四条考勤打卡时间及说明\n1、\t上下班时间:09:00-12:10,13:40-18:00其中工厂:08:00-11:45,12:40-17:00\n2、\t员工上下班必须打卡

······

这里以txt文本为例,langchain还支持word、txt、xlsx、json、PDF、Web浏览器等格式的文本加载。

文档分割器(chucking)

# 文档分割
from langchain.text_splitter import CharacterTextSplitter
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 创建拆分器 128个字符分割为一组,重叠暂时不设置
text_splitter = RecursiveCharacterTextSplitter(chunk_size=128, 
                                               chunk_overlap=0,
                                               separators=["\n"])

# 拆分文档
documents = text_splitter.split_documents(doc)
documents

输出结果:

[Document(page_content='无锡宇宁智能科技有限公司(无锡)考勤管理制度\n从2024年2月1日起执行\n第一章总则\n第一条为规范员工管理,提高工作效率,保证各项工作的正常运行,根据有关法律法规,结合本公司实际情况,特制定本制度。\n第二条本制度适用人员范围为宇宁智能(无锡)全体人员。', metadata={'source': 'Data/宇宁无锡考勤.txt'}),
 Document(page_content='第二章细则\n第一节考勤打卡规范\n第三条实行打卡考勤的人员范围\n本公司员工除下列人员外,均应按规定打卡。\n1、\t总经理、其他总经理特批者;\n2、\t因公出差者:\n3、\t其他驻外机构人员。\n第四条考勤打卡时间及说明', metadata={'source': 'Data/宇宁无锡考勤.txt'}),
 Document(page_content='1、\t上下班时间:09:00-12:10 

 ······

将文本分割为128个字符为一组,为了实现内容检索的准确性,这里可以细分为段落、甚至逐句逐词分割。

文本向量嵌入 (embedding)

# 接下来对分割后的数据进行embedding,并写入数据库。

# 选用OpenAIEmbeddings作为embedding模型
from langchain.embeddings.openai import OpenAIEmbeddings

# 向量数据库选用FAISS。
from langchain.vectorstores import FAISS

#这里填写的本人的openai key
API_SECRET_KEY = "***";
embedding = OpenAIEmbeddings(openai_api_key=API_SECRET_KEY)
docsearch = FAISS.from_documents(documents, embedding)
docsearch.similarity_search("宇宁")

输出结果:

# 有关“宇宁”相关的数据库检索
[Document(page_content='无锡宇宁智能科技有限公司(无锡)考勤管理制度\n从2024年2月1日起执行\n第一章总则\n第一条为规范员工管理,提高工作效率,保证各项工作的正常运行,根据有关法律法规,结合本公司实际情况,特制定本制度。\n第二条本制度适用人员范围为宇宁智能(无锡)全体人员。', metadata={'source': 'Data/宇宁无锡考勤.txt'}),
 Document(page_content='第二十条调休', metadata={'source': 'Data/宇宁无锡考勤.txt'})

 ······

文本嵌入模型和向量数据库的选择,对检索库的量级和准确性至关重要。这里使用openai embedding的api接口和Faiss (cpu)数据库运行。

目前主流的开源Embedding有:

BGE、me3、通义千问embedding、text-embedding-ada-002等。

目前主流的向量数据库有:

Milvus、Weaviate、qdrant等等。

注意:这里文本向量嵌入的时候,需要配置大模型,通过大模型来进行高维向量的映射,而不是简单的讲文本转为词向量而已;

本地实战代码 用 llama3.1 做文本向量嵌入,跑不动,本地电脑配置太小;

from langchain_community.llms import Ollama #导入ollama包
from langchain_community.llms import OpenAI # 导入openai 模型
from langchain_core.prompts import ChatPromptTemplate #提示词
from langchain_core.output_parsers import StrOutputParser #输出解析器

# 文件加载
from langchain_community.document_loaders import TextLoader

# 文档分割
from langchain.text_splitter import CharacterTextSplitter
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 选用OpenAIEmbeddings作为embedding模型
# from langchain.embeddings.openai import OpenAIEmbeddings
# from langchain.embeddings.ollama import OllamaEmbeddings
# from langchain_community.embeddings import OllamaEmbeddings
# from langchain_community.embeddings.openai import OpenAIEmbeddings

# 选择新的OpenAIEmbeddings作为embedding模型
from langchain_openai import OpenAIEmbeddings

if __name__ == '__main__':
    try:

        # -------- 示例 1 RAG(检索增强生成)------
        # 1、加载文本
        loader = TextLoader('./doc.txt', encoding='utf-8')
        doc = loader.load()
        print(doc)

        # 2、分割文档
        # 创建拆分器 128个字符分割为一组,重叠暂时不设置
        text_spliter = RecursiveCharacterTextSplitter(chunk_size=128,
                                       chunk_overlap=0,
                                       separators=["\n"])

        # 拆分文档
        documents = text_spliter.split_documents(doc)
        print(documents)

        # ------------ 文本向量嵌入 (embedding) -----------
        # 接下来对分割后的数据进行embedding,并写入数据库。
        from langchain_community.vectorstores import FAISS

        # 这里填写的本人的openai key
        API_SECRET_KEY = "sk-27Rb7QfL8HrEhmjnE64bF96940Bd4aC889785e9501774244"
        # embedding = OllamaEmbeddings(model="llama3.1")
        # 使用openai 的代理
        API_BASE_URL = "https://open.xiaojingai.com/v1"

        embedding = OpenAIEmbeddings(openai_api_key=API_SECRET_KEY, openai_api_base=API_BASE_URL)
        docsearch = FAISS.from_documents(documents, embedding)
        res = docsearch.similarity_search("风险管理", k=3)

        print(res)

        # 配置提示词
        #prompt = ChatPromptTemplate.from_template("《三国演义》{topic} 介绍")

        # 通过ollama远程获取千问7b模型
        # model = Ollama(model="qwen:7b", base_url="http://192.168.3.122:11435")

        # 加载本地LLM
        #model = Ollama(model="llama3.1")  # 这里选gpu、cpu负担小的0.5b模型
        #output_parser = StrOutputParser()
        #chain = prompt | model | output_parser  # 组成一条链
        #res = chain.invoke({"topic": "曹操"})

        # --------  2 提示词 prompt 设计 ------
        from langchain.prompts import ChatPromptTemplate
        template = """你是问答任务助手。使用以下检索到的上下文片段来回答问题。如果你不知道答案,就说你不知道。最多使用三个句子,保持答案简洁。
                       Question: {question} 
                       Context: {context} 
                       Answer:
                    """

        prompt = ChatPromptTemplate.from_template(template)
        print("------ 打印 prompt -------------")
        print(prompt)

        # -------- 示例 3 RAG(检索增强生成)------
        # 为聊天记录添加buffer缓存
        from langchain.memory import ConversationBufferMemory
        from langchain.chains import ConversationalRetrievalChain

        retriever = docsearch.as_retriever()

        memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

        # 对话式检索问答
        # 通过ollama远程获取千问7b模型
        # model = Ollama(model="qwen:7b", base_url="http://192.168.3.122:11435")
        print("-------- init model --------")
        model = OpenAI(api_key=API_SECRET_KEY, base_url=API_BASE_URL, model_name="gpt-3.5-turbo-instruct")
        qa = ConversationalRetrievalChain.from_llm(model, retriever, memory=memory)
        res = qa({"question": "什么是风险模型管理?为什么要做模型风险管理,请说明"})

        print(qa)
        print("=================")
        print(res)
    except Exception as e:
        print(f"发生了错误:{e}")

执行打印:


/Users/kaiyi/Work/develop/Code/llm-study/venv/bin/python /Users/kaiyi/Work/develop/Code/llm-study/lang_test.py
[Document(metadata={'source': './doc.txt'}, page_content='政策解读\n\n一、商业银行互联网贷款管理暂行办法\n中国银行保险监督管理委员会规章\nhttp://www.cbirc.gov.cn/cn/view/pages/ItemDetail.html?docId=916525&itemId=926\n\n\n模型风险管理的目的是为了防范模型和算法可能存在的缺陷,确保模型能够持续适应风险管理要求,保护消费者权益,减少算法歧视的风险。'}
[Document(metadata={'source': './doc.txt'}, page_content='第三章 风险数据和风险模型管理'), Document(metadata={'source': './doc.txt'}, page_content='六、风险防范'), Document(metadata={'source': './doc.txt'}, page_content='(三)业务风险分析和监管指标表现分析;\n\n(四)识别、计量、监测、控制风险的主要方法及改进情况,信息科技风险防控措施的有效性;\n\n(五)风险模型的监测与验证情况;\n\n(六)合规管理和内控管理情况;\n\n(七)投诉及处理情况;\n\n(八)下一年度业务发展规划;')]
------ 打印 prompt -------------
input_variables=['context', 'question'] messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['context', 'question'], template='你是问答任务助手。使用以下检索到的上下文片段来回答问题。如果你不知道答案,就说你不知道。最多使用三个句子,保持答案简洁。\n                       Question: {question} \n                       Context: {context} \n                       Answer:\n                    '))]
-------- init model --------
/Users/kaiyi/Work/develop/Code/llm-study/lang_test.py:98: LangChainDeprecationWarning: The class `OpenAI` was deprecated in LangChain 0.0.10 and will be removed in 1.0. An updated version of the class exists in the langchain-openai package and should be used instead. To use it run `pip install -U langchain-openai` and import as `from langchain_openai import OpenAI`.
  model = OpenAI(api_key=API_SECRET_KEY, base_url=API_BASE_URL, model_name="gpt-3.5-turbo-instruct")
/Users/kaiyi/Work/develop/Code/llm-study/lang_test.py:100: LangChainDeprecationWarning: The method `Chain.__call__` was deprecated in langchain 0.1.0 and will be removed in 1.0. Use invoke instead.
  res = qa({"question": "什么是风险模型管理?为什么要做模型风险管理,请说明"})
memory=ConversationBufferMemory(chat_memory=InMemoryChatMessageHistory(messages=[HumanMessage(content='什么是风险模型管理?为什么要做模型风险管理,请说明'), AIMessage(content=' 风险模型管理是指建立对模型和算法风险的全面管理框架,制定管理制度,对模型数据的准确性和充足性进行交叉验证和定期评估,审慎设置模型参数,定期评估模型预测能力及局限性,加强消费者权益保护等措施,以保证风险模型的有效性和稳定性。模型风险管理的目的是为了防范模型和算法可能存在的缺陷,确保模型能够持续适应风险管理要求,保护消费者权益,减少算法歧视的风险。')]), return_messages=True, memory_key='chat_history') 
=================
{'question': '什么是风险模型管理?为什么要做模型风险管理,请说明', 'chat_history': [HumanMessage(content='什么是风险模型管理?为什么要做模型风险管理,请说明'), AIMessage(content=' 风险模型管理是指建立对模型和算法风险的全面管理框架,制定管理制度,对模型数据的准确性和充足性进行交叉验证和定期评估,审慎设置模型参数,定期评估模型预测能力及局限性,加强消费者权益保护等措施,以保证风险模型的有效性和稳定性。模型风险管理的目的是为了防范模型和算法可能存在的缺陷,确保模型能够持续适应风险管理要求,保护消费者权益,减少算法歧视的风险。')], 'answer': ' 风险模型管理是指建立对模型和算法风险的全面管理框架,制定管理制度,对模型数据的准确性和充足性进行交叉验证和定期评估,审慎设置模型参数,定期评估模型预测能力及局限性,加强消费者权益保护等措施,以保证风险模型的有效性和稳定性。模型风险管理的目的是为了防范模型和算法可能存在的缺陷,确保模型能够持续适应风险管理要求,保护消费者权益,减少算法歧视的风险。'}

Process finished with exit code 0

查看命中

要查看使用 RAG(Retrieval Augmented Generation) 进行增强后的命中文档,可以通过在 LangChain 框架中获取检索到的相关文本。具体可以通过以下方式获取:

  1. 修改 ConversationalRetrievalChain 以输出命中文档
    在默认情况下,LangChain 的 ConversationalRetrievalChain 会从文档中检索到的结果返回给模型生成响应。为了查看检索到的文本,你可以通过修改返回值来包含检索到的文档内容。

  2. 在调用qa时,记录或打印检索结果
    可以在执行问答时,直接访问检索器(retriever)的结果。

修改代码示例:

from langchain.chains import ConversationalRetrievalChain
from langchain.prompts import PromptTemplate

# 配置 embedding 和 retriever
embedding = OpenAIEmbeddings(openai_api_key=API_SECRET_KEY, openai_api_base=API_BASE_URL)
docsearch = FAISS.from_documents(documents, embedding)  
retriever = docsearch.as_retriever()

memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

# 初始化模型
model = OpenAI(api_key=API_SECRET_KEY, base_url=API_BASE_URL, model_name="gpt-3.5-turbo-instruct")
qa = ConversationalRetrievalChain.from_llm(model, retriever, memory=memory)

# 执行查询
query = "模型风险管理的目的是什么?"
result = qa({"question": query})

# 查看返回的结果
print("生成的答案:", result['answer'])  # 输出生成的答案

# 如果想获取被检索到的文档
retrieved_docs = retriever.get_relevant_documents(query)
for i, doc in enumerate(retrieved_docs):
    print(f"命中的文档 {i+1}:", doc.page_content)  # 打印每个检索到的文档内容

关键步骤说明:

  1. retriever.get_relevant_documents(query):
    使用这个方法,你可以直接获取检索到的与查询相关的文档。retriever 实际上是一个负责在向量数据库中进行相似性搜索的组件,get_relevant_documents 方法会返回包含文本内容的文档对象。

  2. 检索的文档:
    每个文档对象(doc)的 page_content 属性包含了实际的文本内容,你可以通过遍历它们来查看具体命中的内容。

  3. 调试与查看
    通过 print() 或其他日志工具,可以输出检索到的文本,从而了解模型生成的答案所基于的上下文信息。

扩展:

如果你想要将命中的文档与生成的答案一起返回给前端或用于进一步分析,可以将命中的文档内容打包在返回结果中。


相关文章:
langchain开发实战及结合RAG检索增强生成
《LangChain编程从入门到实践》
动手实现企业级问答知识库

为者常成,行者常至