博客

将 Ollama 与推理应用程序接口结合使用

了解如何使用 Inference API 将 Ollama 与 Elasticsearch 集成。

在本文中,我们将学习如何使用 Ollama 将本地模型连接到 Elasticsearch 推理模型,然后使用 Playground 提出文档问题。

Elasticsearch 允许用户使用开放推理 API 连接到 LLM,并支持 Amazon Bedrock、Cohere、Google AI、Azure AI Studio、HuggingFace - as a service 等提供商。

Ollama是一款允许您使用自己的基础设施(本地机器/服务器)下载和执行 LLM 模型的工具。在这里,您可以找到与 Ollama 兼容的可用型号列表。

如果你想托管和测试不同的开源模型,Ollama 是一个不错的选择,因为 Ollama 会处理好一切,让你不必担心每个模型的不同设置方式,也不必担心如何创建 API 来访问模型功能。

由于 Ollama API 与 OpenAI API 兼容,我们可以轻松集成推理模型,并使用 Playground 创建 RAG 应用程序。

准备工作

  1. Elasticsearch 8.17

  2. Kibana 8.17

  3. Python

步长

  1. 设置 Ollama LLM 服务器

  2. 创建映射

  3. 索引数据

  4. 使用 Playground 提问

设置 Ollama LLM 服务器

我们将设置一个 LLM 服务器,使用 Ollama 将其连接到 Playground 实例。我们需要

  • 下载并运行 Ollama。

  • 使用 ngrok 通过互联网访问托管 Ollama 的本地网络服务器

下载并运行 Ollama

要使用 Ollama,我们首先需要下载它。Ollama 支持 Linux、Windows 和 macOS,因此只需 在这里 下载与你的操作系统兼容的 Ollama 版本即可 。安装好 Ollama 后,我们可以从支持的 LLM列表中选择一个模型。在本例中,我们将使用llama3.2 模型,这是一个通用的多语言模型。在设置过程中,您将启用 Ollama 的命令行工具。下载完成后,您就可以运行下面一行:

ollama pull llama3.2

将输出

pulling manifest
pulling dde5aa3fc5ff... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏ 2.0 GB
pulling 966de95ca8a6... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏ 1.4 KB
pulling fcc5a6bec9da... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏ 7.7 KB
pulling a70ff7e570d9... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏ 6.0 KB
pulling 56bb8bd477a5... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏   96 B
pulling 34bb5ab01051... 100% ▕█████████████████████████████████████████████████████████████████████████████████████████▏  561 B
verifying sha256 digest
writing manifest
success

安装完成后,可以使用此命令进行测试:

ollama run llama3.2

我们来提个问题:

运行 Ollama 并向它提问

模型运行后,Ollama 会启用一个默认在"11434" 端口运行的 API。让我们按照官方文档,向该应用程序接口提出请求:

curl http://localhost:11434/api/generate -d '{                                          
  "model": "llama3.2",               
  "prompt": "What is the capital of France?"
}'

这就是我们得到的答复:

{"model":"llama3.2","created_at":"2024-11-28T21:48:42.152817532Z","response":"The","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.251884485Z","response":" capital","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.347365913Z","response":" of","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.446837322Z","response":" France","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.542367394Z","response":" is","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.644580384Z","response":" Paris","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.739865362Z","response":".","done":false}
{"model":"llama3.2","created_at":"2024-11-28T21:48:42.834347518Z","response":"","done":true,"done_reason":"stop","context":[128006,9125,128007,271,38766,1303,33025,2696,25,6790,220,2366,18,271,128009,128006,882,128007,271,3923,374,279,6864,315,9822,30,128009,128006,78191,128007,271,791,6864,315,9822,374,12366,13],"total_duration":6948567145,"load_duration":4386106503,"prompt_eval_count":32,"prompt_eval_duration":1872000000,"eval_count":8,"eval_duration":684000000}

请注意,该端点的特定响应是流式响应。

使用 ngrok 将终端接入互联网

由于我们的端点在本地环境中运行,因此无法通过互联网从另一个点(如我们的弹性云实例)进行访问。ngrok允许我们公开提供公共 IP 的端口。在 ngrok 中创建账户,并按照官方设置指南进行操作。

安装并配置好 ngrok 代理后,我们就可以公开 Ollama 正在使用的端口:

ngrok http 11434 --host-header="localhost:11434"

注意: --host-header="localhost:11434" 保证请求中的"Host" 头与"localhost:11434 匹配。"

执行该命令将返回一个公共链接,只要 ngrok 和 Ollama 服务器在本地运行,该链接就能正常工作。

Session Status                online                                                                                                                                                                              
Account                       xxxx@yourEmailProvider.com (Plan: Free)                                                                                                                                             
Version                       3.18.4                                                                                                                                                                              
Region                        United States (us)                                                                                                                                                                  
Latency                       561ms                                                                                                                                                                               
Web Interface                 http://127.0.0.1:4040                                                                                                                                                               
Forwarding                    https://your-ngrok-url.ngrok-free.app -> http://localhost:11434                                                                                                                   


Connections                   ttl     opn     rt1     rt5     p50     p90                                                                                                                                         
                              0       0       0.00    0.00    0.00    0.00                                                ```

在"Forwarding" 中,我们可以看到 ngrok 生成了一个 URL。留着以后用吧。

让我们再次尝试使用 ngrok 生成的 URL 向端点发出 HTTP 请求:

curl https://your-ngrok-endpoint.ngrok-free.app/api/generate -d '{                                          
  "model": "llama3.2",               
  "prompt": "What is the capital of France?"
}'

答复应与前一个答复类似。

创建映射

ELSER 端点

在本示例中,我们将使用 Elasticsearch 推理 API 创建一个推理端点。此外,我们还将使用ELSER生成嵌入。

PUT _inference/sparse_embedding/medicines-inference
{
  "service": "elasticsearch",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1,
    "model_id": ".elser_model_2_linux-x86_64"
  }
}

在这个例子中,我们假设有一家药店出售两种药物:

  • 需要处方的药物。

  • 无需处方的药物。

这些信息将包含在每种药物的描述字段中。

LLM 必须对该字段进行解释,因此这就是我们要使用的数据映射:

PUT medicines
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "copy_to": "semantic_field"
      },
      "semantic_field": {
        "type": "semantic_text",
        "inference_id": "medicines-inference"
      },
      "text_description": {
        "type": "text",
        "copy_to": "semantic_field"
      }
    }
  }
}

字段text_description 将存储描述的纯文本,而作为语义_文本字段类型的semantic_field 将存储由 ELSER 生成的嵌入。

属性copy_to将把字段名和text_description 中的内容复制到语义字段中,以便为这些字段生成嵌入内容。

索引数据

现在,让我们使用_bulk API 为数据建立索引。

POST _bulk
{"index":{"_index":"medicines"}}
{"id":1,"name":"Paracetamol","text_description":"An analgesic and antipyretic that does NOT require a prescription."}
{"index":{"_index":"medicines"}}
{"id":2,"name":"Ibuprofen","text_description":"A nonsteroidal anti-inflammatory drug (NSAID) available WITHOUT a prescription."}
{"index":{"_index":"medicines"}}
{"id":3,"name":"Amoxicillin","text_description":"An antibiotic that requires a prescription."}
{"index":{"_index":"medicines"}}
{"id":4,"name":"Lorazepam","text_description":"An anxiolytic medication that strictly requires a prescription."}
{"index":{"_index":"medicines"}}
{"id":5,"name":"Omeprazole","text_description":"A medication for stomach acidity that does NOT require a prescription."}
{"index":{"_index":"medicines"}}
{"id":6,"name":"Insulin","text_description":"A hormone used in diabetes treatment that requires a prescription."}
{"index":{"_index":"medicines"}}
{"id":7,"name":"Cold Medicine","text_description":"A compound formula to relieve flu symptoms available WITHOUT a prescription."}
{"index":{"_index":"medicines"}}
{"id":8,"name":"Clonazepam","text_description":"An antiepileptic medication that requires a prescription."}
{"index":{"_index":"medicines"}}
{"id":9,"name":"Vitamin C","text_description":"A dietary supplement that does NOT require a prescription."}
{"index":{"_index":"medicines"}}
{"id":10,"name":"Metformin","text_description":"A medication used for type 2 diabetes that requires a prescription."}

响应:

{
   "errors": false,
   "took": 34732020848,
   "items": [
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "mYoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 0,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "mooeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 1,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "m4oeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 2,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "nIoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 3,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "nYoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 4,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "nooeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 5,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "n4oeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 6,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "oIoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 7,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "oYoeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 8,
     	"_primary_term": 1,
     	"status": 201
   	}
 	},
 	{
   	"index": {
     	"_index": "medicines",
     	"_id": "oooeMpQBF7lnCNFTfdn2",
     	"_version": 1,
     	"result": "created",
     	"_shards": {
       	"total": 2,
       	"successful": 2,
       	"failed": 0
     	},
     	"_seq_no": 9,
     	"_primary_term": 1,
     	"status": 201
   	}
 	}
   ]
 }

使用 Playground 提问

Playground是一款 Kibana 工具,可让您使用 Elasticsearch 索引和 LLM 提供商快速创建 RAG 系统。您可以阅读本文了解更多信息。

将当地的法律硕士与游乐场连接起来

我们首先需要创建一个连接器,使用我们刚刚创建的公共 URL。在 Kibana 中,转到搜索>Playground,然后点击"连接到 LLM" 。

将当地的法律硕士与奥拉玛游乐场联系起来

此操作将显示 Kibana 界面左侧的菜单。在那里,点击"OpenAI" 。

选择一个连接器:打开 AI Ollama

现在我们可以开始配置 OpenAI 连接器了。

访问"Connector settings" ,并为 OpenAI 提供商选择"Other (OpenAI Compatible Service)" :

为使用推理应用程序接口的 Ollama 设置连接器设置

现在,让我们配置其他字段。在本例中,我们将模型命名为"medicines-llm" 。在 URL 字段中,使用 ngrok 生成的 URL (/v1/chat/completions)。在"Default model" 字段中,选择"llama3.2" 。我们不会使用 API 密钥,因此只需输入任意文本即可:

添加设置

点击"保存" 并点击"添加数据源" 添加索引药物:

使用 Playground 添加数据源,以便向文档提问
添加查询数据

好极了现在,我们可以使用本地运行的 LLM 作为 RAG 引擎访问 Playground。

在 Playground 中选择模型设置

在测试之前,让我们给代理添加更具体的指令,并将发送给模型的文件数量增加到 10 份,以便答案有尽可能多的可用文件。由于使用了 copy_to 属性,上下文字段将是semantic_field ,其中包括药品的名称和描述。

Elastic Playground 中的 Moel 设置

现在我们来问一个问题:没有处方可以购买氯硝西泮吗?看看会发生什么:

Video Thumbnail

不出所料,我们得到了正确答案。

后续步骤

下一步是创建自己的应用程序!Playground 提供了一个 Python 代码脚本,你可以在自己的机器上运行,并根据自己的需要进行定制。例如,将其置于FastAPI服务器之后,创建一个由用户界面使用的 QA 药品聊天机器人。

点击 Playground 右上方的 "查看代码"按钮即可找到该代码:

查看代码按钮

然后使用Endpoints& API 密钥生成代码中所需的ES_API_KEY 环境变量。

本例的代码如下:

## Install the required packages
## pip install -qU elasticsearch openai
import os
from elasticsearch import Elasticsearch
from openai import OpenAI
es_client = Elasticsearch(
    "https://your-deployment.us-central1.gcp.cloud.es.io:443",
    api_key=os.environ["ES_API_KEY"]
)
openai_client = OpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
)
index_source_fields = {
    "medicines": [
        "semantic_field"
    ]
}
def get_elasticsearch_results():
    es_query = {
        "retriever": {
            "standard": {
                "query": {
                    "nested": {
                        "path": "semantic_field.inference.chunks",
                        "query": {
                            "sparse_vector": {
                                "inference_id": "medicines-inference",
                                "field": "semantic_field.inference.chunks.embeddings",
                                "query": query
                            }
                        },
                        "inner_hits": {
                            "size": 2,
                            "name": "medicines.semantic_field",
                            "_source": [
                                "semantic_field.inference.chunks.text"
                            ]
                        }
                    }
                }
            }
        },
        "size": 3
    }
    result = es_client.search(index="medicines", body=es_query)
    return result["hits"]["hits"]
def create_openai_prompt(results):
    context = ""
    for hit in results:
        inner_hit_path = f"{hit['_index']}.{index_source_fields.get(hit['_index'])[0]}"
        ## For semantic_text matches, we need to extract the text from the inner_hits
        if 'inner_hits' in hit and inner_hit_path in hit['inner_hits']:
            context += '\n --- \n'.join(inner_hit['_source']['text'] for inner_hit in hit['inner_hits'][inner_hit_path]['hits']['hits'])
        else:
            source_field = index_source_fields.get(hit["_index"])[0]
            hit_context = hit["_source"][source_field]
            context += f"{hit_context}\n"
    prompt = f"""
  Instructions:
  - You are an assistant specializing in answering questions about the sale of medicines.
  - Answer questions truthfully and factually using only the context presented.
  - If you don't know the answer, just say that you don't know, don't make up an answer.
  - You must always cite the document where the answer was extracted using inline academic citation style [], using the position.
  - Use markdown format for code examples.
  - You are correct, factual, precise, and reliable.
  Context:
  {context}
  """
    return prompt
def generate_openai_completion(user_prompt, question):
    response = openai_client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": user_prompt},
            {"role": "user", "content": question},
        ]
    )
    return response.choices[0].message.content
if __name__ == "__main__":
    question = "my question"
    elasticsearch_results = get_elasticsearch_results()
    context_prompt = create_openai_prompt(elasticsearch_results)
    openai_completion = generate_openai_completion(context_prompt, question)
    print(openai_completion)

要使其与 Ollama 兼容,必须更改 OpenAI 客户端,使其连接到 Ollama 服务器,而不是 OpenAI 服务器。您可以在这里找到 OpenAI 示例和兼容端点的完整列表。

openai_client = OpenAI(
    # you can use http://localhost:11434/v1/ if running this code locally.
    base_url='https://your-ngrok-url.ngrok-free.app/v1/',
    # required but ignored
    api_key='ollama',
)

在调用完成方法时,将模型更改为 llama3.2:

def generate_openai_completion(user_prompt, question):
    response = openai_client.chat.completions.create(
        model="llama3.2",
        messages=[
            {"role": "system", "content": user_prompt},
            {"role": "user", "content": question},
        ]
    )
    return response.choices[0].message.content

让我们补充一个问题:我可以在没有处方的情况下购买氯硝西泮吗? 至 Elasticsearch 查询:

def get_elasticsearch_results():
    es_query = {
        "retriever": {
            "standard": {
                "query": {
                    "nested": {
                        "path": "semantic_field.inference.chunks",
                        "query": {
                            "sparse_vector": {
                                "inference_id": "medicines-inference",
                                "field": "semantic_field.inference.chunks.embeddings",
                                "query": "Can I buy Clonazepam without a prescription?"
                            }
                        },
                        "inner_hits": {
                            "size": 2,
                            "name": "medicines.semantic_field",
                            "_source": [
                                "semantic_field.inference.chunks.text"
                            ]
                        }
                    }
                }
            }
        },
        "size": 3
    }
    result = es_client.search(index="medicines", body=es_query)
    return result["hits"]["hits"]

此外,我们还在完成调用中打印了一些内容,以便确认我们将 Elasticsearch 结果作为问题上下文的一部分发送:

if __name__ == "__main__":
    question = "Can I buy Clonazepam without a prescription?"
    elasticsearch_results = get_elasticsearch_results()
    context_prompt = create_openai_prompt(elasticsearch_results)
    print("========== Context Prompt START ==========")
    print(context_prompt)
    print("========== Context Prompt END ==========")
    print("========== Ollama Completion START ==========")
    openai_completion = generate_openai_completion(context_prompt, question)
    print(openai_completion)
    print("========== Ollama Completion END ==========")

现在运行命令

pip install -qU elasticsearch openai

python main.py

你应该看到这样的内容:

========== Context Prompt START ==========
  Instructions:
  - You are an assistant specializing in answering questions about the sale of medicines.
  - Answer questions truthfully and factually using only the context presented.
  - If you don't know the answer, just say that you don't know, don't make up an answer.
  - You must always cite the document where the answer was extracted using inline academic citation style [], using the position.
  - Use markdown format for code examples.
  - You are correct, factual, precise, and reliable.
  Context:
  Clonazepam
 ---
An antiepileptic medication that requires a prescription.A nonsteroidal anti-inflammatory drug (NSAID) available WITHOUT a prescription.
 ---
IbuprofenAn anxiolytic medication that strictly requires a prescription.
 ---
Lorazepam


========== Context Prompt END ==========
========== Ollama Completion START ==========
No, you cannot buy Clonazepam over-the-counter (OTC) without a prescription [1]. It is classified as a controlled substance in the United States due to its potential for dependence and abuse. Therefore, it can only be obtained from a licensed healthcare provider who will issue a prescription for this medication.
========== Ollama Completion END ==========

结论

在本文中,当我们将 Ollama 等工具与 Elasticsearch 推论 API 和 Playground 结合使用时,我们可以看到它们的强大功能和多功能性。

经过几个简单的步骤后,我们就拥有了一个可运行的 RAG 应用程序,它可以聊天,使用 LLM 在我们自己的基础设施中运行,成本为零。这也使我们能够对资源和敏感信息有更多的控制权,此外,我们还可以使用各种模型来完成不同的任务。

如何将 Ollama 与弹性推理 API 结合使用

使用 Ollama 和 Elastic Inference API 通过 Playground 创建 RAG 应用程序

1.

设置 Ollama LLM 服务器

2.

创建映射

3.

索引数据

4.

使用 Playground 提问

常见问题

奥拉玛是什么?

Ollama 是一款允许您使用自己的基础设施(本地机器/服务器)下载和执行 LLM 模型的工具。

相关内容

用描述代替手动绘制:通过 MCP 和 ES|QL 构建 AI 原生 Kibana 仪表板。

Stratoula Kalafateli

准备好打造最先进的搜索体验了吗?

足够先进的搜索不是一个人的努力就能实现的。Elasticsearch 由数据科学家、ML 操作员、工程师以及更多和您一样对搜索充满热情的人提供支持。让我们联系起来,共同打造神奇的搜索体验,让您获得想要的结果。

亲自试用