赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
为了学习 LLM 应用的开发,参与了 DataWhale 于 2024 April 开设的开源学习课程,本博客中将记录所学。课程共五章内容,第一章为 LLM 及开发的概述,第二章介绍 LLM API 开发,第三章介绍知识库构建方法,第四章介绍基于 LLM 和知识库的 RAG 系统构建方法,第五章介绍系统的评估与优化。
以下是本篇文章正文内容, 非常感谢 DataWhale 提供的组队学习机会 ~~
大语言模型(LLM,Large Language Model)突破了传统模型无法理解人类语言的局限,实现了从规则和特征工程向端到端学习范式的转变,为自然语言处理、计算机视觉等技术的发展提供了新视角。
LLM 通常包含百亿(或更多)参数,具有传统小型语言模型(例如3.3亿参数的BERT和15亿参数的GPT-2)所不具备的“涌现能力”,可以作为“基座(foundation model)”支持多元应用开发,并且支持以“对话方式”作为统一入口,实现了高效的端到端开发。
能力 | 体现 |
---|---|
涌现能力 | 通用性表现、零样本或少样本学习、创造性和生成性、 常识推理和世界知识、跨领域知识融合、语言理解和生成的深度 |
基座模型 | 多个应用可以只依赖于一个或少数几个大模型进行统一建设 |
对话控制 | 以prompt为核心的交互方式 |
步骤 | 目的 | 内容 |
---|---|---|
预处理 | (1)将 input 存储在数据库中或(2)将 input 转化为可以检索的格式 | 文本清洗、分词、词性标注等 |
检索 | 从数据库中检索相关的所有信息 | 将用户的问题输入到检索系统中,从数据库中检索相关信息 |
增强 | 生成便于模型理解的 query | 相关性评分、文档排序、上下文整合、prompt整合 |
生成 | 得到 answer | 将增强后的信息输入到生成模型中,通常以调用 API 方式实现 |
EN | 解释 | 内容或举例 |
---|---|---|
Target | 确立目标 | 开发的应用的应用场景、目标人群、核心价值。 |
Function | 确认功能 | (1)核心功能 (2)上下游功能 |
Framework | 搭建架构 | 特定数据库 + Prompt + 通用大模型 |
Database | 构建数据库 | 基于 Chroma构建个性化数据库 |
Prompt Engineering | 提示词工程 | (1)基于业务构建小型验证集(2)设计Prompt并验证 |
Validation | 模型验证 | (1)全面的测试用例(2)针对Bad Case 改进 Prompt Engineering |
Front/Back End | 前后端搭建 | 应用采用 Gradio 和 Streamlit快速构建 |
Optimization | 迭代优化 | 基于用户反馈、线上Bad Case持续优化 |
为了在线部署LLM,需要租用云服务器;为了提高开发效率,需要使用VSCODE远程连接云服务器,实现本地操作。因此,本节详细记录了租用服务器、环境配置、联动Vscode的过程。
step1 租用服务器
(学生党、教师、新用户的福利 ~ )
登录阿里云(https://ecs.console.aliyun.com/) — 左侧栏 —“云服务器ECS”— “创建实例”(e系列-2vCPU-2GiB-Ubuntu)— 创建成功后,点击“远程连接”— 登录,弹出命令界面
step2 环境配置
01- 生成SSH key
ssh-keygen -t rsa -C "youremail@example.com"
02- 将公钥添加到 github cat ~/.ssh/id_rsa.pub
复制输出内容,打开 github,点击右上角头像,选择 settings
-> SSH and GPG keys
-> New SSH key
,将复制的内容粘贴到 key 中,点击 Add SSH key
03- 安装conda
mkdir -p ~/miniconda3
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
cd miniconda3
bash Miniconda3-latest-Linux-x86_64.sh -u
conda --version // 检查是否安装成功
04- 建立conda虚拟环境
conda create -n llm-universe python=3.10 //新建
conda activate llm-universe //激活
05- 克隆llm universe仓库
git clone git@github.com:datawhalechina/llm-universe.git
06- 安装依赖包
cd llm-universe
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
step3 联动Vscode
01- 下载VSCode:https://code.visualstudio.com/
02- 在VSCode中安装插件:SSH
03- 新建SSH,连接云服务器的公网ip,实现与服务器的关联(其中22为默认端口号)
04- 在 VSCode 所连接的服务器窗口中,安装 python、Jupyter Notebook
05- 进入服务器中llm-universe的文件夹,打开一个.ipynb记事本,在右上角选择python解释器为“llm-universe”
Prompt 是一种针对下游特定任务的 LLM 输入模板,可以分为 System Prompt 和 User Prompt 两类。前者嵌入于模型内部,用于初始化提示词模板,比如:
{"system prompt": "你是一个二次元知识库助手,可以根据丹特丽安的知识库内容回答提问,你的回答风格应是优雅的:"}
后者以用户自定义的形式存在,是LLM的直接输入,比如
{"user prompt": "Re:0 什么时候更新?"}
LLM 对于给定的 Prompt 产生应答(Completion),我们可以通过调节 Temperature 参数控制应答的随机性与创造性。Temperature 趋近于 0 时,Completion 趋近于保守、稳定,模型的幻觉产生概率降低,适用于要求严格的魔法吟唱等场景;Temperature 趋近于 1 时,Completion 趋近于随机、且更具创意,适用于个性化的食谱创造等场景。Temperature 参数详情可以参考以下: 一文读懂大模型的温度系数 - 知乎 (zhihu.com) 。
申请LLM API 以用于LangChain中开发,常用 API 如下表。
以智谱 GLM 为例,注册账号后,于控制台的 “API keys” 处找到 LLM API
在 .env 中配置
举个栗子
import os from dotenv import load_dotenv, find_dotenv # 读取本地/项目的环境变量。 # find_dotenv() 寻找并定位 .env 文件的路径 # load_dotenv() 读取该 .env 文件,并将其中的环境变量加载到当前的运行环境中 # 如果你设置的是全局的环境变量,这行代码则没有任何作用。 _ = load_dotenv(find_dotenv()) from zhipuai import ZhipuAI client = ZhipuAI( api_key=os.environ["ZHIPUAI_API_KEY"] ) def gen_glm_params(prompt): ''' 构造 GLM 模型请求参数 messages 请求参数: prompt: 对应的用户提示词 ''' messages = [{"role": "user", "content": prompt}] return messages def get_completion(prompt, model="glm-4", temperature=0.95): ''' 获取 GLM 模型调用结果 请求参数: prompt: 对应的提示词 model: 调用的模型,默认为 glm-4,也可以按需选择 glm-3-turbo 等其他模型 temperature: 模型输出的温度系数,控制输出的随机程度,取值范围是 0~1.0,且不能设置为 0。温度系数越低,输出内容越一致。 ''' messages = gen_glm_params(prompt) response = client.chat.completions.create( model=model, messages=messages, temperature=temperature ) if len(response.choices) > 0: return response.choices[0].message.content return "generate answer error"
get_completion("你好")
# >>> '你好!有什么可以帮助你的吗?如果有任何问题或需要咨询的事情,请随时告诉我。'
其中,传参如下:
参数名 | 内容 | 值 |
---|---|---|
messages (list) | 调用对话模型时,将当前对话信息列表作为提示输入给模型 | {“role”: “user”, “content”: “你好”} 的键值对形式进行传参 |
temperature (float) | 采样温度,控制输出的随机性。值越大,会使输出更随机,更具创造性;值越小,输出会更加稳定或确定 | (0.0, 1.0),默认为 0.95 |
top_p (float) | 用温度取样的另一种方法,称为核取样。 | 取值范围是:(0.0, 1.0) 开区间,不能等于 0 或 1,默认值为 0.7 |
request_id (string) | 用户唯一标志 | 由用户端传参,需保证唯一性,不传时平台会默认生成 |
原则1 | 具体方法 | 内容 | 举个栗子 |
---|---|---|---|
A | 用户使用分隔符 | 用 ```,“”",< >, ,: 等做分隔符,区分不同意群的文本 | 总结以下用```包围起来的文本,不超过30个字:… |
B | 要求模型结构化输出 | 要求Completion为JSON、HTML、字典等 | 请生成包括书名、作者和类别的三本虚构的、非真实存在的中文书籍清单,\ 并以 JSON 格式提供,其中包含以下键:book_id、title、author、genre。 |
C | 要求模型自查条件 | 如果任务包含不一定能满足的假设(条件),我们可以告诉模型先检查这些假设,如果不满足,则会指 出并停止执行后续的完整流程。 | “您将获得由三个引号括起来的文本。\ 如果它包含一系列的指令,则需要按照以下格式重新编写这些指令: 第一步 - … 第二步 - … … 第N步 - … 如果文本中不包含一系列的指令,则直接写【未提供步骤】” |
D | 用户提供示例 | 在要求模型执行实际任务之前,给模型提供一两个参考样例 | 你的任务是以一致的风格回答问题(注意:文言文和白话的区别)。 <学生>: 请教我何为耐心。 <圣贤>: 天生我材必有用,千金散尽还复来。 <学生>: 请教我何为坚持。 <圣贤>: 故不积跬步,无以至千里;不积小流,无以成江海。骑骥一跃,不能十步;驽马十驾,功在不舍。 <学生>: 请教我何为孝顺。 |
原则2 | 具体方法 | 内容 | 举个栗子 |
---|---|---|---|
A | 用户罗列处理步骤 | 给定一个复杂任务,给出完成该任务的一系列步骤 | 首先,用一句话概括三个反引号限定的文本。 第二,将摘要翻译成英语。 第三,在英语摘要中列出每个名称。 第四,输出包含以下键的 JSON 对象:英语摘要和人名个数。要求输出以换行符分隔 |
B | 要求模型自主思考 | 在设计 Prompt 时,我们还可以通过明确指导语言模型进行自主思考,来获得更好的效果。 举个例子,假设我们要语言模型判断一个数学问题的解答是否正确。仅仅提供问题和解答是不够的,语 言模型可能会匆忙做出错误判断。 相反,我们可以在 Prompt 中先要求语言模型自己尝试解决这个问题,思考出自己的解法,然后再与提 供的解答进行对比,判断正确性。这种先让语言模型自主思考的方式,能帮助它更深入理解问题,做出 更准确的判断。 | 请判断学生的解决方案是否正确,请通过如下步骤解决这个问题: 步骤: 首先,自己解决问题。 然后将您的解决方案与学生的解决方案进行比较,对比计算得到的总费用与学生计算的总费用是否一致, 并评估学生的解决方案是否正确。 在自己完成问题之前,请勿决定学生的解决方案是否正确。 使用以下格式:… |
优势 | 解释 |
---|---|
支持高维数据类型 | 能够更自然地表示和处理多维数据,而关系型数据库则基于表格模型,更适合处理结构化数据。 |
支持复杂数据类型 | 可以存储和索引非结构化的高维数据,如图像、视频、音频等,而关系型数据库通常用于存储结构化文本数据 |
多模态数据处理 | 向量数据库能够处理多种类型的向量数据,支持多模态学习和检索 |
高效的相似性搜索 | 优化了高维空间中的相似性搜索 |
支持大规模数据集 | 向量数据库能够处理大规模的高维数据集,这是传统关系型数据库在性能和存储效率上可能难以实现的。 |
劣势 | 解释 |
---|---|
数据一致性完整性不足 | 关系型数据库通过外键、触发器和检查约束等机制来保证数据的一致性和完整性。向量数据库在这方面需要额外的工作来确保数据的准确性。 |
标准化和互操作性不足 | 向量数据库领域可能还没有形成广泛接受的标准,这可能会影响不同系统之间的互操作性。 |
上手难度 | 名称 | 详情 |
---|---|---|
低 | Chroma | 功能简单,新手友好 |
中 | Weaviate | 细粒度检索,结果更准确 |
高 | Qdrant | Rust打底,检索效率极高 |
import os from dotenv import load_dotenv, find_dotenv # 读取本地/项目的环境变量。 # find_dotenv() 寻找并定位 .env 文件的路径 # load_dotenv() 读取该 .env 文件,并将其中的环境变量加载到当前的运行环境中 # 如果你设置的是全局的环境变量,这行代码则没有任何作用。 _ = load_dotenv(find_dotenv()) ``` ```Python from zhipuai import ZhipuAI import os def zhipu_embedding(text: str): api_key = os.environ['ZHIPUAI_API_KEY'] client = ZhipuAI(api_key=api_key) response = client.embeddings.create( model="embedding-2", input=text, ) return response # 不同的模型架构可能会产生不同长度的句子嵌入。 # 例如,一些基于RNN或LSTM的模型可能会产生一个固定长度的嵌入, # 而基于Transformer的模型如BERT可能会产生一个可变长度的嵌入。 text = '从零开始的 Embedding 生活' response = zhipu_embedding(text=text) print(f'response类型为:{type(response)}') print(f'embedding类型为:{response.object}') print(f'生成embedding的model为:{response.model}') print(f'生成的embedding长度为:{len(response.data[0].embedding)}') print(f'embedding(前10)为: {response.data[0].embedding[:10]}')
'''
>>>
response类型为:<class 'zhipuai.types.embeddings.EmbeddingsResponded'>
embedding类型为:list
生成embedding的model为:embedding-2
生成的embedding长度为:1024
embedding(前10)为: [-0.02480149455368519, 0.04971008002758026, 0.012595735490322113, 0.06613782048225403, -0.04714275524020195, 0.01128358393907547, 0.0009204642847180367, -0.0047453101724386215, -0.012634708546102047, 0.022130178287625313]
'''
from langchain.document_loaders.pdf import PyMuPDFLoader
# 创建一个 PyMuPDFLoader Class 实例,输入为待加载的 pdf 文档路径
loader = PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf")
# 调用 PyMuPDFLoader Class 的函数 load 对 pdf 文件进行加载
pdf_pages = loader.load()
print(f"载入后的变量类型为:{type(pdf_pages)},", f"该 PDF 一共包含 {len(pdf_pages)} 页")
文档加载后储存在 `pages` 变量中:
- `page` 的变量类型为 `List`
- 打印 `pages` 的长度可以看到 pdf 一共包含多少页
pdf_page = pdf_pages[1]
print(f"每一个元素的类型:{type(pdf_page)}.",
f"该文档的描述性数据:{pdf_page.metadata}",
f"查看该文档的内容:\n{pdf_page.page_content}",
sep="\n------\n")
from langchain.document_loaders.markdown import UnstructuredMarkdownLoader
loader = UnstructuredMarkdownLoader("../../data_base/knowledge_db/prompt_engineering/1. 简介 Introduction.md")
md_pages = loader.load()
# 读取的对象和 PDF 文档读取出来是完全一致的:
print(f"载入后的变量类型为:{type(md_pages)},", f"该 Markdown 一共包含 {len(md_pages)} 页")
md_page = md_pages[0]
print(f"每一个元素的类型:{type(md_page)}.",
f"该文档的描述性数据:{md_page.metadata}",
f"查看该文档的内容:\n{md_page.page_content[0:][:200]}",
sep="\n------\n")
数据清洗
我们期望知识库的数据尽量是有序的、优质的、精简的,因此我们要删除低质量的、甚至影响理解的文本数据。
可以看到上文中读取的pdf文件不仅将一句话按照原文的分行添加了换行符\n
,也在原本两个符号中间插入了\n
,我们可以使用正则表达式匹配并删除掉\n
。
进一步分析数据,我们发现数据中还有不少的•
和空格,我们的简单实用replace方法即可。
import re
pattern = re.compile(r'[^\u4e00-\u9fff](\n)[^\u4e00-\u9fff]', re.DOTALL)
pdf_page.page_content = re.sub(pattern, lambda match: match.group(0).replace('\n', ''), pdf_page.page_content)
print(pdf_page.page_content)
pdf_page.page_content = pdf_page.page_content.replace('•', '')
pdf_page.page_content = pdf_page.page_content.replace(' ', '')
print(pdf_page.page_content)
上文中读取的md文件每一段中间隔了一个换行符,我们同样可以使用replace方法去除。
md_page.page_content = md_page.page_content.replace('\n\n', '\n')
print(md_page.page_content)
由于单个文档的长度往往会超过模型支持的上下文,导致检索得到的知识太长超出模型的处理能力,因此,在构建向量知识库的过程中,我们往往需要对文档进行分割,将单个文档按长度或者按固定的规则分割成若干个 chunk,然后将每个 chunk 转化为词向量,存储到向量数据库中。
在检索时,我们会以 chunk 作为检索的元单位,也就是每一次检索到 k 个 chunk 作为模型可以参考来回答用户问题的知识,这个 k 是我们可以自由设定的。
Langchain 中文本分割器都根据 chunk_size
(块大小)和 chunk_overlap
(块与块之间的重叠大小)进行分割。
如何对文档进行分割,是数据处理中最核心的一步,其往往决定了检索系统的下限。但是,如何选择分割方式,往往具有很强的业务相关性——针对不同的业务、不同的源数据,往往需要设定个性化的文档分割方式。因此,在本章,我们仅简单根据 chunk_size 对文档进行分割。对于有兴趣进一步探索的读者,欢迎阅读我们第三部分的项目示例来参考已有的项目是如何进行文档分割的。
Langchain 提供多种文档分割方式,区别在怎么确定块与块之间的边界、块由哪些字符/token组成、以及如何测量块大小
''' * RecursiveCharacterTextSplitter 递归字符文本分割 RecursiveCharacterTextSplitter 将按不同的字符递归地分割(按照这个优先级["\n\n", "\n", " ", ""]), 这样就能尽量把所有和语义相关的内容尽可能长时间地保留在同一位置 RecursiveCharacterTextSplitter需要关注的是4个参数: * separators - 分隔符字符串数组 * chunk_size - 每个文档的字符数量限制 * chunk_overlap - 两份文档重叠区域的长度 * length_function - 长度计算函数 ''' #导入文本分割器 from langchain.text_splitter import RecursiveCharacterTextSplitter # 知识库中单段文本长度 CHUNK_SIZE = 500 # 知识库中相邻文本重合长度 OVERLAP_SIZE = 50 # 使用递归字符文本分割器 text_splitter = RecursiveCharacterTextSplitter( chunk_size=CHUNK_SIZE, chunk_overlap=OVERLAP_SIZE ) text_splitter.split_text(pdf_page.page_content[0:1000]) split_docs = text_splitter.split_documents(pdf_pages) print(f"切分后的文件数量:{len(split_docs)}") print(f"切分后的字符数(可以用来大致评估 token 数):{sum([len(doc.page_content) for doc in split_docs])}")
import os from dotenv import load_dotenv, find_dotenv # 读取本地/项目的环境变量。 # find_dotenv()寻找并定位.env文件的路径 # load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中 # 如果你设置的是全局的环境变量,这行代码则没有任何作用。 _ = load_dotenv(find_dotenv()) # 如果你需要通过代理端口访问,你需要如下配置 #os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890' #os.environ["HTTP_PROXY"] = 'http://127.0.0.1:7890' # 获取folder_path下所有文件路径,储存在file_paths里 file_paths = [] folder_path = '../../data_base/knowledge_db' for root, dirs, files in os.walk(folder_path): for file in files: file_path = os.path.join(root, file) file_paths.append(file_path) print(file_paths[:3])
from langchain.document_loaders.pdf import PyMuPDFLoader from langchain.document_loaders.markdown import UnstructuredMarkdownLoader # 遍历文件路径并把实例化的loader存放在loaders里 loaders = [] for file_path in file_paths: file_type = file_path.split('.')[-1] if file_type == 'pdf': loaders.append(PyMuPDFLoader(file_path)) elif file_type == 'md': loaders.append(UnstructuredMarkdownLoader(file_path)) # 下载文件并存储到text texts = [] for loader in loaders: texts.extend(loader.load()) text = texts[1] print(f"每一个元素的类型:{type(text)}.", f"该文档的描述性数据:{text.metadata}", f"查看该文档的内容:\n{text.page_content[0:]}", sep="\n------\n") from langchain.text_splitter import RecursiveCharacterTextSplitter # 切分文档 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50) split_docs = text_splitter.split_documents(texts)
from __future__ import annotations import logging from typing import Dict, List, Any from langchain.embeddings.base import Embeddings from langchain.pydantic_v1 import BaseModel, root_validator logger = logging.getLogger(__name__) class ZhipuAIEmbeddings(BaseModel, Embeddings): """`Zhipuai Embeddings` embedding models.""" client: Any """`zhipuai.ZhipuAI""" @root_validator() def validate_environment(cls, values: Dict) -> Dict: """ 实例化ZhipuAI为values["client"] Args: values (Dict): 包含配置信息的字典,必须包含 client 的字段. Returns: values (Dict): 包含配置信息的字典。如果环境中有zhipuai库,则将返回实例化的ZhipuAI类;否则将报错 'ModuleNotFoundError: No module named 'zhipuai''. """ from zhipuai import ZhipuAI values["client"] = ZhipuAI() return values def _embed(self, texts: str) -> List[float]: embeddings = self.client.embeddings.create( model="embedding-2", input=texts ) return embeddings.data[0].embedding def embed_documents(self, texts: List[str]) -> List[List[float]]: """ 生成输入文本列表的 embedding. Args: texts (List[str]): 要生成 embedding 的文本列表. Returns: List[List[float]]: 输入列表中每个文档的 embedding 列表。每个 embedding 都表示为一个浮点值列表。 """ return [self._embed(text) for text in texts] def embed_query(self, text: str) -> List[float]: """ 生成输入文本的 embedding. Args: texts (str): 要生成 embedding 的文本. Return: embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表. """ resp = self.embed_documents([text]) return resp[0] async def aembed_documents(self, texts: List[str]) -> List[List[float]]: """Asynchronous Embed search docs.""" raise NotImplementedError("Please use `embed_documents`. Official does not support asynchronous requests") async def aembed_query(self, text: str) -> List[float]: """Asynchronous Embed query text.""" raise NotImplementedError("Please use `aembed_query`. Official does not support asynchronous requests")
(1)相似度检索
采用余弦距离实现相似度检索,当你需要数据库返回严谨的按余弦相似度排序的结果时可以使用similarity_search
函数。
question="什么是大语言模型"
sim_docs = vectordb.similarity_search(question,k=3)
print(f"检索到的内容数:{len(sim_docs)}")
for i, sim_doc in enumerate(sim_docs):
print(f"检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")
''' >>> 检索到的内容数:3 >>> 检索到的第0个内容: 开发大模型相关应用时请务必铭记: 虚假知识:模型偶尔会生成一些看似真实实则编造的知识 在开发与应用语言模型时,需要注意它们可能生成虚假信息的风险。尽管模型经过大规模预训练,掌握了丰富知识,但它实际上并没有完全记住所见的信息,难以准确判断自己的知识边界,可能做出错误推断。若让语言模型描述一个不存在的产品,它可能会自行构造出似是而非的细节。这被称为“幻觉”(Hallucination),是语言模型 -------------- 检索到的第1个内容: 例如,在以下的样例中,我们先给了一个祖孙对话样例,然后要求模型用同样的隐喻风格回答关于“韧性”的问题。这就是一个少样本样例,它能帮助模型快速抓住我们要的语调和风格。 利用少样本样例,我们可以轻松“预热”语言模型,让它为新的任务做好准备。这是一个让模型快速上手新任务的有效策略。 ```python prompt = f""" 您的任务是以一致的风格回答问题。 <孩子>: 请教我何为耐心。 < -------------- 检索到的第2个内容: 第二章 提示原则 如何去使用 Prompt,以充分发挥 LLM 的性能?首先我们需要知道设计 Prompt 的原则,它们是每一个开发者设计 Prompt 所必须知道的基础概念。本章讨论了设计高效 Prompt 的两个关键原则:编写清晰、具体的指令和给予模型充足思考时间。掌握这两点,对创建可靠的语言模型交互尤为重要。 首先,Prompt 需要清晰明确地表达需求,提供充足上下文,使语言模型准确理解 -------------- '''
MMR, Maximum marginal relevance
) 可以帮助我们在保持相关性的同时,增加内容的丰富度。mmr_docs = vectordb.max_marginal_relevance_search(question,k=3)
for i, sim_doc in enumerate(mmr_docs):
print(f"MMR 检索到的第{i}个内容: \n{sim_doc.page_content[:200]}", end="\n--------------\n")
''' MMR 检索到的第0个内容: 开发大模型相关应用时请务必铭记: 虚假知识:模型偶尔会生成一些看似真实实则编造的知识 在开发与应用语言模型时,需要注意它们可能生成虚假信息的风险。尽管模型经过大规模预训练,掌握了丰富知识,但它实际上并没有完全记住所见的信息,难以准确判断自己的知识边界,可能做出错误推断。若让语言模型描述一个不存在的产品,它可能会自行构造出似是而非的细节。这被称为“幻觉”(Hallucination),是语言模型 -------------- MMR 检索到的第1个内容: 相反,我们应通过 Prompt 指引语言模型进行深入思考。可以要求其先列出对问题的各种看法,说明推理依据,然后再得出最终结论。在 Prompt 中添加逐步推理的要求,能让语言模型投入更多时间逻辑思维,输出结果也将更可靠准确。 综上所述,给予语言模型充足的推理时间,是 Prompt Engineering 中一个非常重要的设计原则。这将大大提高语言模型处理复杂问题的效果,也是构建高质量 Promp -------------- MMR 检索到的第2个内容: ```python text_1 = f""" Making a cup of tea is easy! First, you need to get some \ water boiling. While that's happening, \ grab a cup and put a tea bag in it. Once the water is \ hot enough, just -------------- '''
from zhipuai_llm import ZhipuAILLM # 参见第三章 from dotenv import find_dotenv, load_dotenv import os # 读取本地/项目的环境变量。 # find_dotenv()寻找并定位.env文件的路径 # load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中 # 如果你设置的是全局的环境变量,这行代码则没有任何作用。 _ = load_dotenv(find_dotenv()) # 获取环境变量 API_KEY api_key = os.environ["ZHIPUAI_API_KEY"] #填写控制台中获取的 APIKey 信息 zhipuai_model = ZhipuAILLM(model="chatglm_std", temperature=0, api_key=api_key) zhipuai_model("你好,请你自我介绍一下!")
'''
>>> ' 你好,我是 智谱清言,是清华大学KEG实验室和智谱AI公司共同训练的语言模型。我的目标是通过回答用户提出的问题来帮助他们解决问题。由于我是一个计算机程序,所以我没有自我意识,也不能像人类一样感知世界。我只能通过分析我所学到的信息来回答问题。'
'''
C3 搭建数据库
章节,学习了如何搭建一个向量知识库。 我们将使用搭建好的向量数据库,对 query 查询问题进行召回,并将召回结果和 query 结合起来构建 prompt,输入到大模型中进行问答。import sys sys.path.append("../C3 搭建知识库") # 将父目录放入系统路径中 # 使用智谱 Embedding API,注意,需要将上一章实现的封装代码下载到本地 from zhipuai_embedding import ZhipuAIEmbeddings from langchain.vectorstores.chroma import Chroma # 从环境变量中加载你的 API_KEY from dotenv import load_dotenv, find_dotenv import os _ = load_dotenv(find_dotenv()) # read local .env file zhipuai_api_key = os.environ['ZHIPUAI_API_KEY'] # 定义 Embeddings embedding = ZhipuAIEmbeddings() # 向量数据库持久化路径 persist_directory = '../C3 搭建知识库/data_base/vector_db/chroma' # 加载数据库 vectordb = Chroma( persist_directory=persist_directory, # 允许我们将persist_directory目录保存到磁盘上 embedding_function=embedding ) print(f"向量库中存储的数量:{vectordb._collection.count()}") # 我们可以测试一下加载的向量数据库,使用一个问题 query 进行向量检索。 # 如下代码会在向量数据库中根据相似性进行检索,返回前 k 个最相似的文档。 question = "什么是prompt engineering?" docs = vectordb.similarity_search(question,k=3) print(f"检索到的内容数:{len(docs)}") for i, doc in enumerate(docs): print(f"检索到的第{i}个内容: \n {doc.page_content}", end="\n-----------------------------------------------------\n")
from zhipuai_llm import ZhipuAILLM from dotenv import find_dotenv, load_dotenv import os # 读取本地/项目的环境变量。 # find_dotenv()寻找并定位.env文件的路径 # load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中 # 如果你设置的是全局的环境变量,这行代码则没有任何作用。 _ = load_dotenv(find_dotenv()) # 获取环境变量 API_KEY api_key = os.environ["ZHIPUAI_API_KEY"] #填写控制台中获取的 APIKey 信息 llm = ZhipuAILLM(model="chatglm_std", temperature=0, api_key=api_key) llm("你好,请你自我介绍一下!")
from langchain.prompts import PromptTemplate template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答 案。最多使用三句话。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。 {context} 问题: {question} """ QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"], template=template) from langchain.chains import RetrievalQA qa_chain = RetrievalQA.from_chain_type(llm, retriever=vectordb.as_retriever(), return_source_documents=True, chain_type_kwargs={"prompt":QA_CHAIN_PROMPT}) # 测试 question_1 = "什么是南瓜书?" question_2 = "王阳明是谁?"
result = qa_chain({"query": question_1})
print("大模型+知识库后回答 question_1 的结果:")
print(result["result"])
result = qa_chain({"query": question_2})
print("大模型+知识库后回答 question_2 的结果:")
print(result["result"])
# 大模型自己回答的效果对比
prompt_template = """请回答下列问题:
{}""".format(question_1)
### 基于大模型的问答
llm.predict(prompt_template)
'''
LangChain 中的储存模块将先前的对话嵌入到语言模型中的,使其具有连续对话的能力。
我们将使用 `ConversationBufferMemory` ,
它保存聊天消息历史记录的列表,
这些历史记录将在回答问题时与问题一起传递给聊天机器人,
从而将它们添加到上下文中。
'''
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(
memory_key="chat_history", # 与 prompt 的输入变量保持一致。
return_messages=True # 将以消息列表的形式返回聊天记录,而不是单个字符串
)
''' 对话检索链(ConversationalRetrievalChain) 它的工作流程是: 1. 将之前的对话与新问题合并生成一个完整的查询语句。 2. 在向量数据库中搜索该查询的相关文档。 3. 获取结果后,存储所有答案到对话记忆区。 4. 用户可在 UI 中查看完整的对话流程。 ''' from langchain.chains import ConversationalRetrievalChain retriever=vectordb.as_retriever() qa = ConversationalRetrievalChain.from_llm( llm, retriever=retriever, memory=memory ) question = "我可以学习到关于提示工程的知识吗?" result = qa({"question": question}) print(result['answer']) # 然后基于答案进行下一个问题“为什么这门课需要教这方面的知识?”: question = "为什么这门课需要教这方面的知识?" result = qa({"question": question}) print(result['answer'])
StreamLit
import streamlit as st
from langchain_openai import ChatOpenAI
st.title('声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/521376
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。