在我之前的文章 “将 Cohere 与 Elasticsearch 结合使用” 里,我详述了如何使用 Cohere 及 inference API 来创建 RAG 应用。鉴于 semantic_text 已经推出,我们将使用 semantic_text 及 semantic 搜索来完成之前的练习。
Elasticsearch 拥有开发人员使用生成式 AI 构建下一代搜索体验所需的所有工具,并且它通过其推理 API(inference API)支持与 Cohere 的本机集成。
如果你想要使用以下工具构建,请使用 Elastic:
- 向量数据库
- 部署多个 ML 模型
- 执行文本、向量和混合搜索
- 使用过滤器、方面、聚合进行搜索
- 应用文档和字段级安全性
- 在本地、云或 serverless 运行(预览)
本指南使用维基百科文章数据集来设置语义搜索管道。它将涵盖:
- 使用 Cohere 嵌入创建 Elastic 推理处理器
- 使用嵌入创建 Elasticsearch 索引
- 对 Elasticsearch 索引执行混合搜索并重新排名结果
- 执行基本 RAG
要查看完整的代码示例,请参阅此笔记本。你还可以在此处找到集成指南。
注:在上面所示的笔记本中,它没有使用 semantic_text 字段。在下面的展示中,我将使用最新的 semantic_text 字段来代替之前的 dense_vector 字段。
要求
- 一个 Cohere 帐户。你可以在地址申请一个 API key。你可以在地址 Login | Cohere 进行申请。
- 一个本地安装的集群。安装指令如下
- Python 3.7 或更高版本
安装
Elasticsearch 及 Kibana
如果你还没有安装好自己的 Elasticsearch 及 Kibana,请参考如下的链接来进行安装:
- 如何在 Linux,MacOS 及 Windows 上进行安装 Elasticsearch
- Kibana:如何在 Linux,MacOS 及 Windows上安装 Elastic 栈中的 Kibana
在安装的时候,我们选择 Elastic Stack 8.x 来进行安装。特别值得指出的是:ES|QL 只在 Elastic Stack 8.11 及以后得版本中才有。你需要下载 Elastic Stack 8.11 及以后得版本来进行安装。
在首次启动 Elasticsearch 的时候,我们可以看到如下的输出:
在上面,我们可以看到 elastic 超级用户的密码。我们记下它,并将在下面的代码中进行使用。
我们还可以在安装 Elasticsearch 目录中找到 Elasticsearch 的访问证书:
1. $ pwd
2. /Users/liuxg/elastic/elasticsearch-8.16.0/config/certs
3. $ ls
4. http.p12 http_ca.crt transport.p12
在上面,http_ca.crt 是我们需要用来访问 Elasticsearch 的证书。
我们首先克隆已经写好的代码:
git clone https://github.com/liu-xiao-guo/elasticsearch-labs
我们然后进入到该项目的根目录下:
1. $ pwd
2. /Users/liuxg/python/elasticsearch-labs/notebooks/cohere
3. $ ls
4. cohere-elasticsearch.ipynb inference-cohere.ipynb
5. inference-cohere-semantic-text.ipynb
如上所示,inference-cohere-semantic-text.ipynb 就是我们今天想要工作的 notebook。
我们通过如下的命令来拷贝所需要的证书:
1. $ cp ~/elastic/elasticsearch-8.16.0/config/certs/http_ca.crt .
2. $ ls
3. cohere-elasticsearch.ipynb inference-cohere-semantic-text.ipynb
4. http_ca.crt inference-cohere.ipynb
安装所需要的 python 依赖包
pip3 install elasticsearch==8.16.0 python-dotenv cohere
我们通过如下的命令来查看 Elasticsearch 客户端的版本:
1. $ pip3 list | grep cohere
2. cohere 5.5.8
3. $ pip3 list | grep elasticsearch
4. elasticsearch 8.16.0
启动白金试用
在下面,我们需要使用 ELSER。这是一个白金试用的功能。我们按照如下的步骤来启动白金试用:
这样我们就完成了白金试用功能。
创建环境变量
为了能够使得下面的应用顺利执行,在项目当前的目录下创建如下的一个叫做 .env 的文件:
.env
1. export ES_ENDPOINT="localhost"
2. export ES_USER="elastic"
3. export ES_PASSWORD="uK+7WbkeXMzwk9YvP-H3"
4. export COHERE_API_KEY="YourCohereAPIkey"
你需要根据自己的 Elasticsearch 配置进行相应的修改。你需要在地址获得你的 COHER_API_KEY。
然后,我们在运行上面命令的 terminal 中打入如下的命令:
1. $ pwd
2. /Users/liuxg/python/elasticsearch-labs/notebooks/cohere
3. $ ls
4. cohere-elasticsearch.ipynb inference-cohere-semantic-text.ipynb
5. http_ca.crt inference-cohere.ipynb
6. $ jupter notebook inference-cohere-semantic-text.ipynb
准备数据
我们通过如下的命令来活动数据集:
wget https://raw.githubusercontent.com/cohere-ai/notebooks/main/notebooks/data/embed_jobs_sample_data.jsonl
1. $ wget https://raw.githubusercontent.com/cohere-ai/notebooks/main/notebooks/data/embed_jobs_sample_data.jsonl
2. --2024-11-21 18:41:18-- https://raw.githubusercontent.com/cohere-ai/notebooks/main/notebooks/data/embed_jobs_sample_data.jsonl
3. Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133
4. Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.
5. HTTP request sent, awaiting response... 200 OK
6. Length: 1545639 (1.5M) [text/plain]
7. Saving to: ‘embed_jobs_sample_data.jsonl’
9. embed_jobs_sample_data.jso 100%[=====================================>] 1.47M 1.23MB/s in 1.2s
11. 2024-11-21 18:41:21 (1.23 MB/s) - ‘embed_jobs_sample_data.jsonl’ saved [1545639/1545639]
13. $ ls
14. cohere-elasticsearch.ipynb inference-cohere-semantic-text.ipynb
15. embed_jobs_sample_data.jsonl inference-cohere.ipynb
16. http_ca.crt
上面的 embed_jobs_sample_data.jsonl 具有如下的格式:
展示
读入变量并连接到 Elasticsearch
现在我们可以实例化 Python Elasticsearch 客户端。
1. from elasticsearch import Elasticsearch, helpers
2. import cohere
3. import json
4. import requests
5. from dotenv import load_dotenv
6. import os
1. load_dotenv()
3. ES_USER = os.getenv("ES_USER")
4. ES_PASSWORD = os.getenv("ES_PASSWORD")
5. ES_ENDPOINT = os.getenv("ES_ENDPOINT")
6. COHERE_API_KEY = os.getenv("COHERE_API_KEY")
8. url = f"https://{ES_USER}:{ES_PASSWORD}@{ES_ENDPOINT}:9200"
9. print(url)
11. client = Elasticsearch(url, ca_certs = "./http_ca.crt", verify_certs = True)
12. print(client.info())
创建推理端点
构建向量搜索索引的最大痛点之一是计算大量数据的嵌入。幸运的是,Elastic 提供了推理端点,可用于摄取管道,以便在执行批量索引操作时自动计算嵌入。
要设置用于摄取的推理管道,我们首先必须创建一个使用 Cohere 嵌入的推理端点。你需要一个 Cohere API 密钥,你可以在 Cohere 帐户的 API 密钥部分下找到它。在上面我们已经把它从 .env 文件中读出来了。
我们将创建一个使用 embed-english-v3.0 和 int8 或字节压缩来节省存储空间的推理端点。
1. from elasticsearch import BadRequestError
3. try:
4. client.inference.delete_model(inference_id="cohere_embeddings")
5. except:
6. ;
8. try:
9. client.inference.put(
10. task_type="text_embedding",
11. inference_id="cohere_embeddings",
12. body={
13. "service": "cohere",
14. "service_settings": {
15. "api_key": COHERE_API_KEY,
16. "model_id": "embed-english-v3.0",
17. "embedding_type": "int8",
18. "similarity": "cosine"
19. },
20. },
21. )
22. except BadRequestError as e:
23. print(e)
我们可以在 Kibana 中进行查看:
GET _inference/_all
或者:
GET _inference/cohere_embeddings
创建索引
必须创建目标索引的映射(包含模型将根据你的输入文本生成的嵌入的索引)。目标索引必须具有具有 semantic_text 字段类型的字段,以便索引 Cohere 模型的输出。
让我们创建一个名为 cohere-wiki-embeddings 的索引,其中包含我们需要的映射:
1. index_
3. try:
4. client.indices.delete(index=index_name, ignore_unavailable=True)
5. except:
6. ;
8. if not client.indices.exists(index=index_name):
9. client.indices.create(
10. index=index_name,
11. settings={"index": {"default_pipeline": "cohere_embeddings"}},
12. mappings={
13. "properties": {
14. "text_semantic": {
15. "type": "semantic_text",
16. "inference_id": "cohere_embeddings"
17. },
18. "text": {"type": "text", "copy_to": "text_semantic"},
19. "wiki_id": {"type": "integer"},
20. "url": {"type": "text"},
21. "views": {"type": "float"},
22. "langs": {"type": "integer"},
23. "title": {"type": "text"},
24. "paragraph_id": {"type": "integer"},
25. "id": {"type": "integer"}
26. }
27. },
28. )
我们可以到 Kibana 里查看已经创建的索引:
GET cohere-wiki-embeddings
让我们注意一下该 API 调用中的几个重要参数:
- semantic_text:字段类型使用推理端点自动生成文本内容的嵌入。
- inference_id:指定要使用的推理端点的 ID。在此示例中,模型 ID 设置为 cohere_embeddings。
- copy_to:指定包含推理结果的输出字段
创建摄入管道
现在,你已拥有一个推理端点和一个可用于存储嵌入的索引。下一步是创建一个摄取管道,该管道使用推理端点创建嵌入并将其存储在索引中。
1. client.ingest.put_pipeline(
2. id="cohere_embeddings",
3. description="Ingest pipeline for Cohere inference.",
4. processors=[
5. {
6. "inference": {
7. "model_id": "cohere_embeddings",
8. "input_output": {
9. "input_field": "text",
10. "output_field": "text_embedding",
11. },
12. }
13. }
14. ],
15. )
插入文档
让我们插入示例 wiki 数据集。你需要一个生产 Cohere 帐户来完成此步骤,否则文档摄取将因 API 请求速率限制而超时。
注:在本例中,我们采用一个试用的账号来进行展示。我们只摄取少量的数据,以避免限流。
1. # url = "https://raw.githubusercontent.com/cohere-ai/notebooks/main/notebooks/data/embed_jobs_sample_data.jsonl"
2. # response = requests.get(url)
4. # Load the response data into a JSON object
5. #jsonl_data = response.content.decode('utf-8').splitlines()
7. import json
8. from elasticsearch.helpers import BulkIndexError
10. with open('./embed_jobs_sample_data.jsonl', 'r') as file:
11. content = file.read()
13. # Split the content by new lines and parse each line as JSON
14. data = [json.loads(line) for line in content.strip().split("\n") if line]
16. # We just take the very first 10 documents
17. data = data[:10]
18. print(f"Successfully loaded {len(data)} documents")
20. # Prepare the documents to be indexed
21. documents = []
22. for line in data:
23. data_dict = line
24. documents.append({
25. "_index": index_name,
26. "_source": data_dict,
27. }
28. )
30. # Use the bulk endpoint to index
31. try:
32. helpers.bulk(client, documents)
33. except BulkIndexError as exc:
34. print(f"Failed to index {len(exc.errors)} documents:")
35. for error in exc.errors:
36. print(error)
38. print("Done indexing documents into `cohere-wiki-embeddings` index!")
我们可以通过 Kibana 进行查看:
GET cohere-wiki-embeddings/_search
语义搜索
在使用嵌入丰富数据集后,你可以使用 Elasticsearch 提供的语义查询来查询数据。Elasticsearch 中的 semantic_text大大简化了语义搜索。详细了解 Elasticsearch 中的语义文本如何让你专注于模型和结果而不是技术细节。
1. query = "What are the Video categories on YouTube?"
3. response = client.search(
4. index="cohere-wiki-embeddings",
5. size=100,
6. query = {
7. "semantic": {
8. "query": query,
9. "field": "text_semantic"
10. }
11. }
12. )
14. raw_documents = response["hits"]["hits"]
16. # Display the first 10 results
17. for document in raw_documents[0:10]:
18. print(f'Title: {document["_source"]["title"]}\nText: {document["_source"]["text"]}\n')
20. # Format the documents for ranking
21. documents = []
22. for hit in response["hits"]["hits"]:
23. documents.append(hit["_source"]["text"])
混合搜索
使用嵌入丰富数据集后,你可以使用混合搜索查询数据。传递语义查询,并提供查询文本和用于创建嵌入的模型。
1. query = "What are the Video categories on YouTube?"
3. response = client.search(
4. index="cohere-wiki-embeddings",
5. size=100,
6. query={
7. "bool": {
8. "must": {
9. "multi_match": {
10. "query": query,
11. "fields": ["text", "title"]
12. }
13. },
14. "should": {
15. "semantic": {
16. "query": query,
17. "field": "text_semantic"
18. }
19. },
20. }
21. }
23. )
25. raw_documents = response["hits"]["hits"]
27. # Display the first 10 results
28. for document in raw_documents[0:10]:
29. print(f'Title: {document["_source"]["title"]}\nText: {document["_source"]["text"]}\n')
31. # Format the documents for ranking
32. documents = []
33. for hit in response["hits"]["hits"]:
34. documents.append(hit["_source"]["text"])
排名
为了有效地结合向量和 BM25 检索的结果,我们可以通过推理 API 使用 Cohere 的 Rerank 3 模型来对我们的结果进行最终、更精确的语义重新排名。
首先,使用你的 Cohere API 密钥创建一个推理端点。确保为你的端点指定一个名称,以及其中一个重新排名模型的 model_id。在此示例中,我们将使用 Rerank 3。
1. # Delete the inference model if it already exists
2. client.options(ignore_status=[404]).inference.delete(inference_id="cohere_rerank")
4. client.inference.put(
5. task_type="rerank",
6. inference_id="cohere_rerank",
7. body={
8. "service": "cohere",
9. "service_settings":{
10. "api_key": COHERE_API_KEY,
11. "model_id": "rerank-english-v3.0"
12. },
13. "task_settings": {
14. "top_n": 10,
15. },
16. }
17. )
现在,你可以使用该推理端点对结果进行重新排序。在这里,我们将传入用于检索的查询,以及我们刚刚使用混合搜索检索到的文档。
推理服务将以相关性降序排列的文档列表作为响应。每个文档都有一个对应的索引(反映文档发送到推理端点时的顺序),如果 “return_documents” 任务设置为 True,则文档文本也将包含在内。
在这种情况下,我们将响应设置为 False,并根据响应中返回的索引重建输入文档。
1. response = client.inference.inference(
2. inference_id="cohere_rerank",
3. body={
4. "query": query,
5. "input": documents,
6. "task_settings": {
7. "return_documents": False
8. }
9. }
10. )
12. # Reconstruct the input documents based on the index provided in the rereank response
13. ranked_documents = []
14. for document in response.body["rerank"]:
15. ranked_documents.append({
16. "title": raw_documents[int(document["index"])]["_source"]["title"],
17. "text": raw_documents[int(document["index"])]["_source"]["text"]
18. })
20. # Print the top 10 results
21. for document in ranked_documents[0:10]:
22. print(f"Title: {document['title']}\nText: {document['text']}\n")
检索增强生成 - RAG
现在我们已经对结果进行了排序,我们可以使用 Cohere 的 Chat API 轻松地将其转变为 RAG 系统。传入检索到的文档以及查询,并使用 Cohere 最新的生成模型 Command R+ 查看基础响应。
首先,我们将创建 Cohere 客户端。
co = cohere.Client(COHERE_API_KEY)
接下来,我们可以轻松地从 Cohere Chat API 获得带有引文的接地生成。我们只需将用户查询和从 Elastic 检索到的文档传递给 API,然后打印出我们的接地响应。
1. response = co.chat(
2. message=query,
3. documents=ranked_documents,
4. model='command-r-plus-08-2024'
5. )
7. source_documents = []
8. for citation in response.citations:
9. for document_id in citation.document_ids:
10. if document_id not in source_documents:
11. source_documents.append(document_id)
13. print(f"Query: {query}")
14. print(f"Response: {response.text}")
15. print("Sources:")
16. for document in response.documents:
17. if document['id'] in source_documents:
18. print(f"{document['title']}: {document['text']}")
就这样!使用 Cohere 和 Elastic 快速轻松地实现混合搜索和 RAG。
整个 notebook 的源码可以在地址 github.com/liu-xiao-gu… 下载。