张高兴的大模型开发实战:(二)使用 LangChain 构建本地知识库应用
检索增强生成(Retrieval-Augmented Generation,RAG)是一种优化大型语言模型输出的方法,允许模型在生成回答前,从外部知识库中检索相关信息,而非仅依赖模型内部训练的知识。通过引用外部知识库的信息来生成更准确、实时且可靠的内容,并解决知识过时和幻觉的问题。下面将介绍使用 LangChain 和 Ollama 实现一个本地知识库应用。
基础概念
什么是 LangChain
LangChain 是一个大语言模型(LLM)编程框架,其目的是简化基于大语言模型的应用开发,统一不同大模型的调用方式,开发者无需关心底层 API 差异。
什么是 Ollama
Ollama 是一个开源的本地大语言模型运行框架,其目的是简化在本地设备上部署和运行大型语言模型的流程。它通过容器化管理和标准化接口,让用户能够快速、便捷地使用如 Qwen、Deepseek 等主流模型,无需复杂配置。
环境搭建与配置
安装 Ollama
访问 https://ollama.com/download 下载对应系统的安装文件。Windows 系统执行 .exe 文件安装,Linux 系统执行以下命令安装:
curl -fsSL https://ollama.com/install.sh | sh
访问 https://ollama.com/search 可以查看 Ollama 支持的模型。使用命令行可以下载并运行模型,例如运行 Qwen2.5 7B 模型:
ollama run qwen2.5
安装 LangChain
创建 Python 虚拟环境后,执行命令以下命令:
pip install langchain langchain-ollama langchain-chroma langchain-community chromadb unstructured jq
文档加载
知识库中的文档可以使用 JSON、TXT、PDF、Markdown、网页等格式的数据,只需要选择 LangChain 中合适的解析器即可。例如常见的有:
DirectoryLoader:批量加载文件夹中的文档。JSONLoader:加载 JSON 格式的文档。PDFPlumberLoader:处理 PDF 中的内容。WebBaseLoader:爬取网页的内容。
下面使用上一篇博客爬取的新闻数据作为示例,展示如何将文本数据加载到程序中。
# 数据示例
{"date": "2024年2月27日", "author": "党政办", "title": "各部门固定电话一览表", "content": "...", "url": "http://www.xzcit.cn/2018/0710/c2031a53936/page.htm"}
{"date": "2025年3月6日", "author": "保卫处", "title": "我校召开2025年安全工作大会", "content": "...", "url": "https://www.xzcit.cn/2025/0305/c275a56681/page.htm"}
{"date": "2025年3月3日", "author": "党委宣传部", "title": "我校举行2024-2025第二学期开学升国旗仪式暨“开学第一课”", "content": "...", "url": "https://www.xzcit.cn/2025/0303/c275a56667/page.htm"}
加载 JSON 数据
加载 JSON 数据可以使用 JSONLoader 加载,json_lines=True 表示 JSON 文件的格式为 .jsonl。
from langchain_ollama.embeddings import OllamaEmbeddings
from langchain.document_loaders import JSONLoader
documents = JSONLoader(file_path='./documents/articles.jsonl', jq_schema='.', text_content=False, json_lines=True).load()
打印变量 documents 查看结果,page_content 为加载文档的内容。可以观察到,JSON 文件中的所有数据、所有字段都被加载到了 documents 变量中。
>>> documents[0]
Document(metadata={'source': 'D:\\GitHub\\langchain-ollama\\documents\\articles.jsonl', 'seq_num': 1}, page_content='{"date": "2024\\u5e742\\u670827\\u65e5", "author": "\\u515a\\u653f\\u529e", "title": "\\u5404\\u90e8\\u95e8\\u56fa\\u5b9a\\u7535\\u8bdd\\u4e00\\u89c8\\u8868", "content": "...", "url": "http://www.xzcit.cn/2018/0710/c2031a53936/page.htm"}')
有些时候并不是所有字段都是我们需要的,可以通过 jq_schema 参数指定需要的字段,下面给出了几种常见的示例:
JSON -> [{"text": ...}, {"text": ...}, {"text": ...}]
jq_schema -> ".[].text"
JSON -> {"key": [{"text": ...}, {"text": ...}, {"text": ...}]}
jq_schema -> ".key[].text"
JSON -> ["...", "...", "..."]
jq_schema -> ".[]"
JSON -> {"...", "...", "..."}
jq_schema -> "."
刚刚打印的变量 documents 中,字段 metadata 用于存储文档的元数据。在一些高级检索场景中,除了使用文本内容的向量表示进行相似性搜索,还可以将元数据作为辅助信息进行加权或筛选。下面的例子将 JSON 中的 date、author、url 字段添加至 metadata 中。
def metadata_func(record: dict, metadata: dict) -> dict:
"""添加元数据"""
metadata["title"] = record.get("title")
metadata["author"] = record.get("author")
metadata["date"] = record.get("date")
metadata["source"] = record.get("url")
return metadata
documents = JSONLoader(file_path='documents/articles.jsonl', jq_schema='.', content_key='content', metadata_func=metadata_func, json_lines=True).load()
再次打印变量 documents 查看结果,可以看到 metadata 中新增了 title、author、date、source 字段,page_content 中的内容变为了 JSON 中的 content 字段。
>>> documents[0]
Document(metadata={'source': 'http://www.xzcit.cn/2018/0710/c2031a53936/page.htm', 'seq_num': 1, 'title': '各部门固定电话一览表', 'author': '党政办', 'date': '2024年2月27日'}, page_content='...')
加载文件夹中的文档
更常见的情况,数据不会只在一个文档中,如果文档是分散在文件夹中的,可以使用 DirectoryLoader 加载,它会递归加载文件夹中的所有文件。
from langchain.document_loaders import DirectoryLoader, TextLoader
text_loader_kwargs={'encoding': 'utf-8'} # 设置文本加载器的参数
documents = DirectoryLoader(path='./documents', glob='**/*.txt', show_progress=True, use_multithreading=True, loader_cls=TextLoader, loader_kwargs=text_loader_kwargs).load()
如果加载的单个文档过长,还需要将长文本分割为合理长度的块(Chunk),避免超出 LLM 上下文窗口。RecursiveCharacterTextSplitter 按优先级顺序使用一系列分隔符来分割文本,分割有助于保持文本的逻辑结构,比如保留完整的句子或段落。这里设置了一系列分割符 separators,例如换行、空格、英文和中文标点符号。使用 chunk_size 指定了分割后的文本块的长度为 1000。并设置 chunk_overlap 相邻文本块之间的重叠长度为 20,设置重叠可以使信息不会因为在边界处被切断而丢失。
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
separators=[
"\n\n",
"\n",
" ",
".",
",",
"\u200B", # Zero-width space
"\uff0c", # Fullwidth comma
"\u3001", # Ideographic comma
"\uff0e", # Fullwidth full stop
"\u3002", # Ideographic full stop
"",
], chunk_size=1000, chunk_overlap=20, add_start_index=True)
split_docs = text_splitter.split_documents(documents)
文本向量化
大模型无法直接理解知识库当中的文本,我们需要将人类可读的文本数据转换成机器能够理解和处理的数值形式,这个过程叫做文本向量化。如果将大模型通过词条查找内容的过程比作查字典,那么文本向量化就是在编写字典。这里使用向量化的一种方式—— Embedding(嵌入),能够使向量捕捉词语之间的语义关系、语法相似性等信息。OllamaEmbeddings 是一个大语言模型的 Embedding 工具,将文本数据转换为向量表示,以便于后续的检索和生成。在使用之前还需要下载 Embedding 模型,例如在命令行中执行 ollama pull nomic-embed-text。向量化后还需要使用向量数据库进行存储,例如 FAISS、Chroma 等。
from langchain_ollama.embeddings import OllamaEmbeddings
from langchain_chroma.vectorstores import Chroma
embeddings = OllamaEmbeddings(model="nomic-embed-text")
db = Chroma.from_documents(documents, embeddings, persist_directory="./embeddings")
等待这一过程完成后,试着检索文档,返回与查询语句最相似的文档。
results = db.similarity_search("招聘公告")
for result in results:
print("Title:", result.metadata["title"])
print("Author:", result.metadata["author"])
print("Date:", result.metadata["date"])
print("Content:", result.page_content)
print("------\n")
也可以使用向量进行检索,k 指定了返回结果的个数,filter 设置了元数据字段的搜索条件。
query_vector = embeddings.embed_query("招聘公告")
results = db.similarity_search_by_vector(query_vector, k=1, filter={"author": "人事处"})
实现问答应用
首先引入相关的包并加载文档与模型。
from langchain_ollama.llms import OllamaLLM
from langchain_ollama.embeddings import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
model = OllamaLLM(model="qwen2.5:7b")
embeddings = OllamaEmbeddings(model="nomic-embed-text")
db = Chroma(embedding_function=embeddings, persist_directory='./embeddings')
retriever=db.as_retriever(search_kwargs={"k": 3})
接着使用 ChatPromptTemplate 定义与用户交互时使用的提示模板。这个模板包含了两个占位符 {context} 和 {question},分别代表从知识库中检索到的相关内容和用户的提问。
template = """你是徐州工业职业技术学院的专业助手,能够根据知识库中的新闻稿回答问题:
{context}
问题:{question}
回答应简洁且准确,避免编造信息。
"""
prompt = ChatPromptTemplate.from_template(template)
然后使用 LangChain 中的一个关键概念——链式调用,构建了一个数据处理管道:首先通过检索器获取相关上下文,然后将其与问题一起格式化成提示,接着发送给语言模型以生成回答,最后通过 StrOutputParser 将输出解析为字符串。
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
最后使用 invoke() 方法执行管道,传入问题作为参数,即可得到回答。
response = chain.invoke("学校最近一次招聘是在什么时候")
print(f"AI: {response}")
完整的程序如下:
from langchain_ollama.llms import OllamaLLM
from langchain_ollama.embeddings import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
model = OllamaLLM(model="qwen2.5:0.5b")
embeddings = OllamaEmbeddings(model="nomic-embed-text")
db = Chroma(embedding_function=embeddings, persist_directory='./embeddings')
retriever=db.as_retriever(search_kwargs={"k": 3})
template = """你是徐州工业职业技术学院的专业助手,能够根据知识库中的新闻稿回答问题:
{context}
问题:{question}
回答应简洁且准确,避免编造信息。
"""
prompt = ChatPromptTemplate.from_template(template)
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
def chat_with_system(question):
response = chain.invoke(question)
print(f"AI: {response}")
return response
# 示例对话
if __name__ == "__main__":
while True:
user_input = input("You: ")
if user_input.lower() in ["exit", "quit"]:
break
response = chat_with_system(user_input)
张高兴的大模型开发实战:(二)使用 LangChain 构建本地知识库应用的更多相关文章
- 大数据开发实战:Stream SQL实时开发二
1.介绍 本节主要利用Stream SQL进行实时开发实战,回顾Beam的API和Hadoop MapReduce的API,会发现Google将实际业务对数据的各种操作进行了抽象,多变的数据需求抽象为 ...
- 大数据开发实战:HDFS和MapReduce优缺点分析
一. HDFS和MapReduce优缺点 1.HDFS的优势 HDFS的英文全称是 Hadoop Distributed File System,即Hadoop分布式文件系统,它是Hadoop的核心子 ...
- 大数据开发实战:Spark Streaming流计算开发
1.背景介绍 Storm以及离线数据平台的MapReduce和Hive构成了Hadoop生态对实时和离线数据处理的一套完整处理解决方案.除了此套解决方案之外,还有一种非常流行的而且完整的离线和 实时数 ...
- 大数据开发实战:Stream SQL实时开发三
4.聚合操作 4.1.group by 操作 group by操作是实际业务场景(如实时报表.实时大屏等)中使用最为频繁的操作.通常实时聚合的主要源头数据流不会包含丰富的上下文信息,而是经常需要实时关 ...
- 大数据开发实战:Stream SQL实时开发一
1.流计算SQL原理和架构 流计算SQL通常是一个类SQL的声明式语言,主要用于对流式数据(Streams)的持续性查询,目的是在常见流计算平台和框架(如Storm.Spark Streaming.F ...
- 大数据开发实战:离线大数据处理的主要技术--Hive,概念,SQL,Hive数据库
1.Hive出现背景 Hive是Facebook开发并贡献给Hadoop开源社区的.它是建立在Hadoop体系架构上的一层SQL抽象,使得数据相关人员使用他们最为熟悉的SQL语言就可以进行海量数据的处 ...
- 大数据开发实战:Storm流计算开发
Storm是一个分布式.高容错.高可靠性的实时计算系统,它对于实时计算的意义相当于Hadoop对于批处理的意义.Hadoop提供了Map和Reduce原语.同样,Storm也对数据的实时处理提供了简单 ...
- 大数据开发实战:Hadoop数据仓库开发实战
1.Hadoop数据仓库架构设计 如上图. ODS(Operation Data Store)层:ODS层通常也被称为准备区(Staging area),它们是后续数据仓库层(即基于Kimball维度 ...
- 大数据开发实战:Hive优化实战3-大表join大表优化
5.大表join大表优化 如果Hive优化实战2中mapjoin中小表dim_seller很大呢?比如超过了1GB大小?这种就是大表join大表的问题.首先引入一个具体的问题场景,然后基于此介绍各自优 ...
- 大数据开发实战:Hive优化实战2-大表join小表优化
4.大表join小表优化 和join相关的优化主要分为mapjoin可以解决的优化(即大表join小表)和mapjoin无法解决的优化(即大表join大表),前者相对容易解决,后者较难,比较麻烦. 首 ...
随机推荐
- 基于C#实现串口通信Demo
原文链接:基于C#实现串口通信Demo
- KMS for Office 2021
I. 镜像下载 官方镜像下载地址: Office 2021 专业增强版: https://officecdn.microsoft.com/pr/492350f6-3a01-4f97-b9c0-c7c6 ...
- 搭建gitlab私有仓库
1.下载gitlab 首先在 /etc/yum.repos.d/目录下配置gitlab下载镜像源. # 进入目录 /etc/yum.repos.d/ cd /etc/yum.repos.d/ # 创建 ...
- Python_图片对比问题汇总
问题一. raise OSError(f"cannot write mode {im.mode} as JPEG") from eOSError: cannot write mod ...
- 我的c语言笔记
1. 进制转换 二进制.八进制和十六进制向十进制转换都非常容易,就是"按权相加".如:1010.1101 = 1×23 + 0×22 + 1×21 + 0×20 + 1×2-1 + ...
- C# 获取系统声卡音频数据,并绘制波形
//by wgscd //date:2022/11/7 UI: <Path Stroke="Red" Data="{Binding path}" Rend ...
- C#钩子(Hook) 捕获键盘鼠标所有事件 - 5分钟没有操作,自动关闭 Form 窗体
C# 钩子 捕获键盘鼠标所有事件,可用于:判断鼠标键盘无操作时,关闭 Winform 窗体 5分钟没有操作,自动关闭 Form 窗体 钩子(Hook)的作用主要体现在监视和拦截系统或进程中的各种事件消 ...
- springboot环境下的rokectMQ多数据源实现
业务原因,需要在一个项目中与多方MQ进行业务通信: 步骤一,复制一份RocketMQProperties配置文件,避免与原来的冲突 package com.heit.road.web.config; ...
- 原生JS实现一个日期选择器(DatePicker)组件
这是通过原生HTML/CSS/JavaScript完成一个日期选择器(datepicker)组件,一个纯手搓的组件的开发.主要包括datepicker静态结构的编写.日历数据的计划获取.组件的渲染以及 ...
- linux:问题诊断
查看带宽 1.安装 iftop 工具(iftop 工具为 Linux 服务器下的流量监控小工具) yum install iftop -y 说明:若是是 Ubuntu 系统,请执行 apt-get i ...