有时候,寻找相关信息就像大海捞针一样困难,但不要绝望,GPT可以为我们做很多工作。在本指南中,我们将探讨一种使用各种人工智能技术增强现有搜索系统的方法,帮助我们筛选信息中的噪音。
GPT检索信息的两种方式如下:
- 模仿人类浏览:GPT触发搜索,评估结果,如果必要的话,修改搜索查询。它还可以跟踪特定的搜索结果,形成思维链,就像人类用户会做的那样。
- 使用嵌入进行检索:为你的内容和用户查询计算嵌入,然后根据余弦相似度测量选择与用户查询最相关的内容。这种技术在像Google这样的搜索引擎中被广泛使用。
这两种方法都有潜力,但各自都有缺点:第一种方法由于其迭代性质可能较慢,第二种方法需要预先嵌入整个知识库,不断嵌入新内容并维护矢量数据库。
通过结合这些方法,并从重新排序方法中汲取灵感,我们找到了一种介于两者之间的方法。这种方法可以在任何现有搜索系统的基础上实施,如Slack搜索API或带有私有数据的内部ElasticSearch实例。它的工作原理如下:
步骤1:搜索
- 用户提出问题。
- GPT生成一组潜在的查询。
- 并行执行搜索查询。
步骤2:重新排序
- 使用每个结果的嵌入来计算与生成的假设理想答案对用户问题的语义相似性。
- 根据此相似性度量对结果进行排序和过滤。
步骤3:回答
- 针对前几名搜索结果,模型生成回答用户问题的答案,包括引用和链接。
这种混合方法提供了相对较低的延迟,并且可以集成到任何现有的搜索端点中,而无需维护矢量数据库。下面让我们深入了解吧!我们将以News API作为示例领域进行搜索。
设置
除了你的OPENAI_API_KEY,你还需要在环境中包含一个NEWS_API_KEY。你可以在这里获取API密钥。
%env NEWS_API_KEY = YOUR_NEWS_API_KEY
# 依赖项
from datetime import date, timedelta # 用于获取最近新闻的日期处理
from IPython import display # 用于漂亮的打印
import json # 用于解析JSON API响应和模型输出
from numpy import dot # 用于余弦相似度
import openai # 用于使用GPT和获取嵌入
import os # 用于加载环境变量
import requests # 用于进行API请求
from tqdm.notebook import tqdm # 用于显示进度条
以上是初始化所需的依赖项和环境变量设置。接下来,我们将逐步执行三个主要步骤:搜索、重新排序和回答。
步骤1:搜索
一切都始于用户的问题。首先,用户提出一个问题,然后我们使用GPT生成一系列多样化的查询,以尽可能详尽的方式回答问题。
# 用户提出问题
USER_QUESTION = "谁赢得了NBA总冠军?最有价值球员是谁?告诉我一些关于最后一场比赛的情况。"
# 为了尽可能详尽,我们使用模型生成基于此问题的各种查询。
QUERIES_INPUT = f"""
你有访问一个返回最近新闻文章的搜索API。
生成一个与这个问题相关的搜索查询数组。
为查询使用相关关键字的变体,尽量做到通用。
包括尽可能多的查询,包括和不包括术语。
例如,包括像['关键词1 关键词2','关键词1','关键词2']这样的查询。
要有创造性。包括的查询越多,找到相关结果的可能性就越大。
用户问题: {USER_QUESTION}
格式: {{"queries": ["query_1", "query_2", "query_3"]}}
"""
queries = json_gpt(QUERIES_INPUT)["queries"]
# 让我们也包括原始问题以确保全面。
queries.append(USER_QUESTION)
queries
以上代码生成了一系列搜索查询,这些查询包括与用户问题相关的多个关键字和变体。这些查询将在接下来的搜索步骤中用于检索新闻文章。
接下来,让我们运行这些搜索查询以获取新闻文章。
def search_news(
query: str,
news_api_key: str = news_api_key,
num_articles: int = 50,
from_datetime: str = "2023-06-01", # 2023年NBA总决赛于2023年6月举行
to_datetime: str = "2023-06-30",
) -> dict:
response = requests.get(
"https://newsapi.org/v2/everything",
params={
"q": query,
"apiKey": news_api_key,
"pageSize": num_articles,
"sortBy": "relevancy",
"from": from_datetime,
"to": to_datetime,
},
)
return response.json()
articles = []
for query in tqdm(queries):
result = search_news(query)
if result["status"] == "ok":
articles = articles + result["articles"]
else:
raise Exception(result["message"])
# 移除重复文章
articles = list({article["url"]: article for article in articles}.values())
print("文章总数:", len(articles))
print("查询1的前5篇文章:", "\n")
for article in articles[0:5]:
print("标题:", article["title"])
print("描述:", article["description"])
print("内容:", article["content"][0:100] + "...")
print()
以上代码执行了搜索查询并获取了与这些查询相关的新闻文章。注意,为了减少重复,我们在获取文章后删除了重复的文章。
现在,我们已经获得了新闻文章,但很多时候搜索查询会返回大量结果,其中许多与用户最初提出的问题无关。为了提高最终答案的质量,我们将使用嵌入来重新排序和过滤结果。
步骤2:重新排序
受HyDE(Gao等人)启发,我们首先生成一个假设理想答案,用于对比和重新排列我们的结果。这有助于优先考虑看起来是好答案的结果,而不是与我们的问题类似的结果。下面是我们用来生成假设答案的提示。
HA_INPUT = f"""
生成一个假设的答案,以回答用户的问题。这个答案将用于排名搜索结果。
假装你拥有回答所需的所有信息,但不要使用任何实际的事实。而是使用占位符
比如,NAME在某地做了某事,或者NAME在某地说了某话。
用户问题: {USER_QUESTION}
格式: {{"hypotheticalAnswer": "假设答案文本"}}
"""
hypothetical_answer = json_gpt(HA_INPUT)["hypotheticalAnswer"]
hypothetical_answer
以上代码生成了一个假设答案,这个答案包含了关于NBA总决赛的信息,但没有使用实际的事实,而是使用了占位符。接下来,我们将为搜索结果和假设答案生成嵌入,然后计算这些嵌入之间的余弦距离,得到语义相似性度量。请注意,由于OpenAI嵌入在API中返回时已经归一化,我们可以简单地计算点积来代替进行完整的余弦相似性计算。
hypothetical_answer_embedding = embeddings(hypothetical_answer)[0]
article_embeddings = embeddings(
[
f"{article['title']} {article['description']} {article['content'][0:100]}"
for article in articles
]
)
# 计算余弦相似度
cosine_similarities = []
for article_embedding in article_embeddings:
cosine_similarities.append(dot(hypothetical_answer_embedding, article_embedding))
cosine_similarities[0:10]
以上代码计算了假设答案与每篇文章的嵌入之间的余弦相似性。这些相似性分数将用于对结果进行排序和过滤。
最后,我们使用这些相似性分数对结果进行排序和过滤。
scored_articles = zip(articles, cosine_similarities)
# 根据余弦相似度对文章进行排序
sorted_articles = sorted(scored_articles, key=lambda x: x[1], reverse=True)
# 打印前5篇文章
print("前5篇文章:", "\n")
for article, score in sorted_articles[0:5]:
print("标题:", article["title"])
print("描述:", article["description"])
print("内容:", article["content"][0:100] + "...")
print("分数:", score)
print()
以上代码对文章进行了排序,按照与假设答案的语义相似性得分降序排列。这些排序后的文章看起来与我们最初的查询更相关。现在,让我们使用前5个结果生成最终答案。
步骤3:回答
formatted_top_results = [
{
"title": article["title"],
"description": article["description"],
"url": article["url"],
}
for article, _score in sorted_articles[0:5]
]
ANSWER_INPUT = f"""
根据给定的搜索结果为用户的问题生成答案。
顶部结果: {formatted_top_results}
用户问题: {USER_QUESTION}
在答案中包括尽可能多的信息。引用相关的搜索结果URL作为Markdown链接。
"""
completion = openai.ChatCompletion.create(
model=GPT_MODEL,
messages=[{"role": "user", "content": ANSWER_INPUT}],
temperature=0.5,
stream=True,
)
text = ""
for chunk in completion:
text += chunk.choices[0].delta.get("content", "")
display.clear_output(wait=True)
display.display(display.Markdown(text))
以上代码使用前5个搜索结果生成了最终的答案,其中包括引用了相关搜索结果的链接。
至此,我们已经完成了搜索、重新排序和回答的所有步骤。下面,让我们根据以上内容编写一篇优秀的教程文章,满足指定的要求。
如何使用搜索API和重新排序进行问题回答
开篇故事
搜索相关信息有时候就像在庞大的信息海洋中寻找一根针一样困难。但不要担心,人工智能技术可以帮助我们在这个任务中取得很大进展。在本文中,我们将探讨一种方法,利用搜索API和嵌入技术,以及重新排序方法,来实现更准确的问题回答。
故事开始于一个充满好奇心的用户,他有一个问题:谁赢得了NBA总冠军?最有价值球员是谁?告诉我一些关于最后一场比赛的情况。这个问题看似简单,但
要从海量的新闻文章中找到相关的信息可能会让人望而却步。
步骤1:搜索
首先,用户提出了一个问题。这个问题是我们开始寻找答案的出发点。但为了尽可能全面地查找相关信息,我们使用人工智能模型生成了多个不同的查询,这些查询都与用户的问题相关。这些查询包括谁赢得了NBA总冠军、NBA总决赛的MVP是谁,以及最后一场比赛的概述等等。这些查询一起被发送到搜索API中,以并行方式执行搜索。
步骤2:重新排序
一旦我们获得了搜索结果,接下来的步骤是对这些结果进行重新排序。这一步骤受到了HyDE(Gao等人)的启发。我们首先生成了一个假设的理想答案,该答案包含了关于NBA总决赛的信息,但没有使用任何实际的事实,而是使用了占位符。然后,我们为搜索结果和假设答案生成了嵌入,这些嵌入可以帮助我们度量它们之间的语义相似性。最终,我们使用语义相似性分数对结果进行排序,以找出最相关的答案。
步骤3:回答
最后,我们使用排名最高的搜索结果生成最终的答案。这个答案包括了与NBA总决赛有关的信息,并引用了相关搜索结果的链接,以便用户可以深入了解。
通过这个混合方法,我们能够在相对低的延迟下获得高质量的答案,而无需维护一个向量数据库。这种方法可以集成到任何现有的搜索系统中,无论是Slack搜索API还是具有私有数据的内部ElasticSearch实例。
希望本文对你理解如何使用搜索API和重新排序来回答问题有所帮助!