介绍如何通过事实增强和提示设计让 LLM 基于可靠信息回答,而非生成虚假内容,对实际应用很有指导价值。
大语言模型是先进的 AI 系统,能够回答各种各样的问题。尽管它们可以针对熟悉的主题提供信息丰富的回答,但面对不熟悉的主题时,其回答并不总是准确。这种现象被称为幻觉。
在查看大语言模型产生幻觉的示例之前,我们先来了解一下 Wikipedia.com 对“幻觉”一词的定义:
“幻觉是在缺乏外部刺激的情况下产生的一种知觉,但它具有真实知觉的特征。”
“幻觉生动、具体,并且会被感知为存在于外部客观空间之中。”
换句话说,幻觉是对某个真实或具体事物的错误知觉(或虚假知觉)。例如,有人询问 ChatGPT(OpenAI 推出的一款著名大语言模型)什么是大语言模型幻觉,它给出的回答是:
因此,问题就变成了:我们该如何改进(或修正)这个结果?简洁的答案是,在问题中加入事实,例如在提问之前或之后向大语言模型提供相关定义。
大语言模型(LLM)是一种对人类说话和写作方式进行建模的人工神经网络。请告诉我,什么是大语言模型幻觉?
ChatGPT 对这个问题给出的公开回答是:
第一句话“很抱歉,我之前的回答造成了困惑”之所以会出现,是因为在给出第二个提示词“An LLM...”之前,我们先向 ChatGPT 提出了第一个问题:什么是大语言模型幻觉?
这些补充信息提升了回答的质量。至少它不再认为大语言模型幻觉是“老年偏头痛伴随症状(Late-Life Migraine Accompaniment)”了!😆
此时必须特别强调:大语言模型并非绝不会出错,也不是所有知识的最终权威。大语言模型使用海量数据进行训练并学习语言中的模式,但它们不一定总能获取最新信息,也未必能够全面理解复杂主题。
那么接下来该怎么办?如何提高减少大语言模型幻觉的概率?
解决这个问题的方法是在查询(或提示词)中加入辅助文档,引导大语言模型给出更加准确、信息更加充分的回答。和人类一样,它需要从这些文档中学习,才能准确、正确地回答你的问题。
有用的文档可以来自很多来源,包括 Google 或 Bing 等搜索引擎以及 Arxiv 等数字图书馆,它们能够提供搜索相关段落的接口。使用数据库也是不错的选择,它可以提供更加灵活且私密的查询接口。
从各种来源检索到的知识必须与问题或提示词相关。检索相关文档的方法有很多,包括:
基于关键词:在纯文本中搜索关键词,适合对术语进行精确匹配。
基于向量搜索:搜索与嵌入向量距离更近的记录,有助于查找合适的同义改写或一般性文档。
如今,向量搜索非常流行,因为它既能解决同义改写问题,也能计算段落的含义。不过,向量搜索并不是一种万能的解决方案;为了保持性能,应当将它与特定的过滤条件结合使用,尤其是在搜索海量记录时。例如,如果你只想检索有关物理学这一学科的知识,就必须过滤掉所有其他学科的信息。这样,大语言模型就不会被其他学科的知识干扰。
大语言模型还应该学会在回答问题之前查询其数据源中的数据,从而实现整个流程的自动化。事实上,大语言模型已经能够编写 SQL 查询并遵循指令。
SQL 功能强大,可用于构建复杂的搜索查询。它支持多种不同的数据类型和函数。我们还可以使用 ORDER BY 和 LIMIT 在 SQL 中编写向量搜索,将嵌入向量之间的相似度分数视为一列距离。非常直观,不是吗?
有关如何构建向量 SQL 查询的更多信息,请参阅下一节“向量 SQL 是什么样的”。
使用向量 SQL 构建复杂搜索查询具有显著优势,包括:
提高对各种数据类型和函数的支持灵活性
提高效率,因为 SQL 经过了高度优化,并且在数据库内部执行
作为标准 SQL 的扩展,它易于阅读和学习
互联网上有大量 SQL 示例和教程。大语言模型既熟悉标准 SQL,也熟悉它的一些方言。
除了 MyScale,ClickHouse 和 PostgreSQL 等许多 SQL 数据库解决方案也在现有功能中加入向量搜索,让用户能够使用向量 SQL 和大语言模型回答复杂主题的问题。同样,越来越多的应用开发者开始将向量搜索与 SQL 集成到自己的应用程序中。
向量结构化查询语言(Vector Structured Query Language,Vector SQL)旨在教大语言模型如何查询向量 SQL 数据库,其中包含以下额外函数:
DISTANCE(column, query_vector):此函数以精确或近似方式比较向量列与查询向量之间的距离。
NeuralArray(entity):此函数将一个实体(例如一张图片或一段文本)转换为嵌入向量。
借助这两个函数,我们可以扩展标准 SQL,使其支持向量搜索。例如,如果你想搜索与单词 flower 相关的 10 条记录,可以使用以下 SQL 语句:
SELECT * FROM table
ORDER BY DISTANCE(vector, NeuralArray(flower))
LIMIT 10
DISTANCE 函数的执行过程包括:
内部函数 NeuralArray(flower) 将单词 flower 转换为嵌入向量。
随后,该嵌入向量会被序列化并注入 DISTANCE 函数。
向量 SQL 是 SQL 的扩展版本,需要根据所使用的向量数据库进行进一步转换。例如,许多实现对 DISTANCE 函数采用了不同的名称。它在 MyScale 中称为 distance,在 ClickHouse 中则称为 L2Distance 或 CosineDistance。此外,这个函数名称会根据数据库的不同而被转换成不同形式。
现在我们已经了解了向量 SQL 的基本原理及其特有函数,接下来让我们使用大语言模型来帮助编写向量 SQL 查询。
首先,我们需要教大语言模型什么是标准向量 SQL。我们的目标是确保大语言模型在编写向量 SQL 查询时,能够主动完成以下三件事:
从问题或提示词中提取关键词。关键词可以是对象、概念或主题。
决定使用哪一列执行相似度搜索。执行相似度搜索时,它始终应该选择向量列。
将问题中的其余约束转换成有效的 SQL。
在准确确定大语言模型构建向量 SQL 查询所需的信息后,我们可以按如下方式设计提示词:
# 下面是一个向量 SQL 提示词示例
_prompt = f"""You are a MyScale expert. Given an input question, first create a syntactically correct MyScale query to run, then look at the results of the query and return the answer to the input question.
MyScale queries has a vector distance function called `DISTANCE(column, array)` to compute relevance to the user's question and sort the feature array column by the relevance.
When the query is asking for {top_k} closest row, you have to use this distance function to calculate distance to entity's array on vector column and order by the distance to retrieve relevant rows.
*NOTICE*: `DISTANCE(column, array)` only accept an array column as its first argument and a `NeuralArray(entity)` as its second argument. You also need a user defined function called `NeuralArray(entity)` to retrieve the entity's array.
Unless the user specifies in the question a specific number of examples to obtain, query for at most {top_k} results using the LIMIT clause as per MyScale. You should only order according to the distance function.
Never query for all columns from a table. You must query only the columns that are needed to answer the question. Wrap each column name in double quotes (") to denote them as delimited identifiers.
Pay attention to use only the column names you can see in the tables below. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.
Pay attention to use today() function to get the current date, if the question involves "today". `ORDER BY` clause should always be after `WHERE` clause. DO NOT add semicolon to the end of SQL. Pay attention to the comment in table schema.
Use the following format:
======== table info ========
<some table infos>
Question: "提问"
SQLQuery: "要运行的SQL查询"
让我们开始:
======== 表信息 ========
{table_info}
Question: {input}
SQLQuery:
这个提示应该能完成工作。但你添加的示例越多越好,例如使用以下向量SQL-文本对作为提示:
SQL表创建语句:
------ 表结构 ------
CREATE TABLE "ChatPaper" (
abstract String,
id String,
vector Array(Float32),
categories Array(String),
pubdate DateTime,
title String,
authors Array(String),
primary_category String
) ENGINE = ReplicatedReplacingMergeTree()
ORDER BY id
PRIMARY KEY id
问题和答案:
Question: 什么是PaperRank?这些作品的贡献是什么?使用包含2个以上类别的论文。
SQLQuery: SELECT ChatPaper.title, ChatPaper.id, ChatPaper.authors FROM ChatPaper WHERE length(categories) > 2 ORDER BY DISTANCE(vector, NeuralArray(PaperRank contribution)) LIMIT {top_k}
你添加到提示中的相关示例越多,LLM构建正确向量SQL查询的过程就会改进得越多。
最后,这里有几个额外的提示可以帮助你设计提示:
覆盖所有可能出现在任何提问中的函数。
避免单调的问题。
修改表结构,例如添加/删除/修改名称和数据类型。
对齐提示的格式。
现在让我们构建一个真实案例,按以下步骤进行:
我们为你准备了一个包含超过200万篇论文的游乐场可供查询。你可以通过将以下Python代码添加到你的应用中来访问此数据。
from sqlalchemy import create_engine
MYSCALE_HOST = "msc-950b9f1f.us-east-1.aws.myscale.com"
MYSCALE_PORT = 443
MYSCALE_USER = "chatdata"
MYSCALE_PASSWORD = "myscale_rocks"
engine = create_engine(f'clickhouse://{MYSCALE_USER}:{MYSCALE_PASSWORD}@{MYSCALE_HOST}:{MYSCALE_PORT}/default?protocol=https')
如果你愿意,可以跳过接下来的步骤(我们在MyScale控制台中创建表和插入数据),直接跳到我们使用向量SQL并创建SQLDatabaseChain来查询数据库的部分。
创建数据库表:
CREATE TABLE default.ChatArXiv (
`abstract` String,
`id` String,
`vector` Array(Float32),
`metadata` Object('JSON'),
`pubdate` DateTime,
`title` String,
`categories` Array(String),
`authors` Array(String),
`comment` String,
`primary_category` String,
CONSTRAINT vec_len CHECK length(vector) = 768)
ENGINE = ReplacingMergeTree ORDER BY id SETTINGS index_granularity = 8192
INSERT INTO ChatArXiv
SELECT
abstract, id, vector, metadata,
parseDateTimeBestEffort(JSONExtractString(toJSONString(metadata), 'pubdate')) AS pubdate,
JSONExtractString(toJSONString(metadata), 'title') AS title,
arrayMap(x->trim(BOTH '"' FROM x), JSONExtractArrayRaw(toJSONString(metadata), 'categories')) AS categories,
arrayMap(x->trim(BOTH '"' FROM x), JSONExtractArrayRaw(toJSONString(metadata), 'authors')) AS authors,
JSONExtractString(toJSONString(metadata), 'comment') AS comment,
JSONExtractString(toJSONString(metadata), 'primary_category') AS primary_category
FROM
s3(
'https://myscale-demo.s3.ap-southeast-1.amazonaws.com/chat_arxiv/data.part*.zst',
'JSONEachRow',
'abstract String, id String, vector Array(Float32), metadata Object(''JSON'')',
'zstd'
);
ALTER TABLE ChatArXiv ADD VECTOR INDEX vec_idx vector TYPE MSTG('metric_type=Cosine');
你需要LangChain实验包来使用VectorSQLDatabaseChain。可以通过执行以下安装脚本来安装它:
python3 -m venv .venv
source .venv/bin/activate
pip3 install langchain langchain-experimental --upgrade
安装完此功能后,下一步是使用它来查询数据库,如以下Python代码所示:
from sqlalchemy import create_engine
MYSCALE_HOST = "msc-950b9f1f.us-east-1.aws.myscale.com"
MYSCALE_PORT = 443
MYSCALE_USER = "chatdata"
MYSCALE_PASSWORD = "myscale_rocks"
# 创建数据库连接
engine = create_engine(f'clickhouse://{MYSCALE_USER}:{MYSCALE_PASSWORD}@{MYSCALE_HOST}:{MYSCALE_PORT}/default?protocol=https')
from langchain.embeddings import HuggingFaceInstructEmbeddings
from langchain.callbacks import StdOutCallbackHandler
from langchain.llms import OpenAI
from langchain.utilities.sql_database import SQLDatabase
from langchain_experimental.sql.prompt import MYSCALE_PROMPT
from langchain_experimental.sql.vector_sql import VectorSQLDatabaseChain
from langchain_experimental.sql.vector_sql import VectorSQLRetrieveAllOutputParser
# 此解析器将 `NeuralArray()` 转换为嵌入
output_parser = VectorSQLRetrieveAllOutputParser(
model=HuggingFaceInstructEmbeddings(model_name='hkunlp/instructor-xl')
)
# 使用上面的提示
PROMPT = PromptTemplate(
input_variables=["input", "table_info", "top_k"],
template=_prompt,
)
# 将元数据绑定到SqlAlchemy引擎
metadata = MetaData(bind=engine)
# 创建SQLDatabaseChain
query_chain = VectorSQLDatabaseChain.from_llm(
# GPT-3.5生成有效的SQL更好
llm=OpenAI(openai_api_key=OPENAI_API_KEY, temperature=0),
# 使用预定义的提示,将其更改为你自己的提示
prompt=PROMPT,
# 返回前10个相关文档
top_k=10,
# 直接使用来自DB的结果
return_direct=True,
# 使用我们的数据库进行检索
db=SQLDatabase(engine, None, metadata),
# 将 `NeuralArray()` 转换为嵌入
sql_cmd_parser=output_parser)
# 启动链!并在标准输出中追踪所有链调用
query_chain.run("介绍一些在2019年左右发表的使用生成对抗网络的论文。",
callbacks=[StdOutCallbackHandler()])
你也可以将此VectorSQLDatabaseChain用作检索器。你可以将其插入到某些检索QA链中,就像LangChain中的其他检索器一样。
from langchain_experimental.retrievers.vector_sql_database \
import VectorSQLDatabaseChainRetriever
from langchain.chains.qa_with_sources.map_reduce_prompt import combine_prompt_template
OPENAI_API_KEY = "sk-***"
# 定义如何序列化来自数据库的结构化数据
document_with_metadata_prompt = PromptTemplate(
input_variables=["page_content", "id", "title", "authors", "pubdate", "categories"],
template="Content:\n\tTitle: {title}\n\tAbstract: {page_content}\n\t" +
"Authors: {authors}\n\tDate of Publication: {pubdate}\n\tCategories: {categories}\nSOURCE: {id}"
)
# 定义你用来提问LLM的提示
COMBINE_PROMPT = PromptTemplate(
template=combine_prompt_template, input_variables=["summaries", "question"])
# 用SQLDatabaseChain定义一个检索器
retriever = VectorSQLDatabaseChainRetriever(
sql_db_chain=query_chain, page_content_key="abstract")
# 最后,用于组织所有这些的提问链
ask_chain = RetrievalQAWithSourcesChain.from_chain_type(
ChatOpenAI(model_name='gpt-3.5-turbo-16k',
openai_api_key=OPENAI_API_KEY, temperature=0.6),
retriever=retriever,
chain_type='stuff',
chain_type_kwargs={
'prompt': COMBINE_PROMPT,
'document_prompt': document_with_metadata_prompt,
}, return_source_documents=True)
# 运行链!并从LLM获取结果
ask_chain("介绍一些在2019年左右发表的使用生成对抗网络的论文。",
callbacks=[StdOutCallbackHandler()])
我们还在huggingface上提供了一个实时演示,代码也可在GitHub上获得!我们使用了一个定制的检索QA链来最大化我们的搜索和LangChain提问管道的性能!
实际上,大多数LLM会产生幻觉。减少其出现的最实用方法是向你的问题添加额外的事实(外部知识)。外部知识对于改进LLM系统的性能至关重要,可以实现高效准确的答案检索。每一个词都很重要,你不想浪费金钱在通过不准确查询检索的未使用信息上。
引入向量SQL,它允许你执行精细粒度的向量搜索以定位和检索所需信息。
向量SQL功能强大且易于被人类和机器学习。你可以使用许多数据类型和函数来创建复杂查询。LLM也喜欢向量SQL,因为其训练数据集包含许多参考资料。
最后,可以使用不同的嵌入模型将向量SQL转换为许多向量数据库。我们相信这就是向量数据库的未来。
对我们正在做的事情感兴趣?立即加入我们的discord!