Usando Ollama com a API de Inferência
Aprenda como integrar o Ollama com o Elasticsearch usando a API de Inferência.
Neste artigo, aprenderemos como conectar modelos locais ao modelo de inferência do Elasticsearch usando o Ollama e, em seguida, fazer perguntas aos seus documentos usando o Playground.
O Elasticsearch permite que os usuários se conectem a LLMs usando a Open Inference API, oferecendo suporte a provedores como Amazon Bedrock, Cohere, Google AI, Azure AI Studio, HuggingFace - como serviço, entre outros.
Ollama é uma ferramenta que permite baixar e executar modelos LLM usando sua própria infraestrutura (sua máquina/servidor local). Aqui você encontra uma lista dos modelos disponíveis que são compatíveis com o Ollama.
O Ollama é uma ótima opção se você deseja hospedar e testar diferentes modelos de código aberto sem ter que se preocupar com as diferentes maneiras como cada um dos modelos pode ter que ser configurado ou sobre como criar uma API para acessar as funções do modelo, pois o Ollama cuida de tudo.
Como a API Ollama é compatível com a API OpenAI, podemos integrar facilmente o modelo de inferência e criar um aplicativo RAG usando o Playground.
Pré-requisitos
Elasticsearch 8.17
Kibana 8.17
Python
Etapas
Configurando o servidor Ollama LLM
Vamos configurar um servidor LLM para conectá-lo à nossa instância do Playground usando o Ollama. Precisaremos:
Baixe e execute o Ollama.
Use o ngrok para acessar seu servidor web local que hospeda o Ollama pela internet
Baixe e execute o Ollama
Para usar o Ollama, primeiro precisamos baixá-lo. O Ollama oferece suporte para Linux, Windows e macOS, então basta baixar a versão do Ollama compatível com seu sistema operacional aqui. Depois que o Ollama estiver instalado, podemos escolher um modelo desta lista de LLMs suportados. Neste exemplo, usaremos o modelo llama3.2, um modelo multilíngue geral. No processo de configuração, você habilitará a ferramenta de linha de comando do Ollama. Depois que o download for concluído, você pode executar a seguinte linha:
ollama pull llama3.2O que produzirá:
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
successUma vez instalado, você pode testá-lo com este comando:
ollama run llama3.2Vamos fazer uma pergunta:

Com o modelo em execução, o Ollama habilita uma API que seria executada por padrão na porta "11434". Vamos fazer uma requisição para essa API, seguindo a documentação oficial:
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2",
"prompt": "What is the capital of France?"
}'Esta é a resposta que obtivemos:
{"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}Observe que a resposta específica para esse ponto de extremidade é um streaming.
Exponha o endpoint à internet usando o ngrok
Como nosso endpoint funciona em um ambiente local, ele não pode ser acessado de outro ponto, como nossa instância do Elastic Cloud, pela Internet. O ngrok nos permite expor uma porta que oferece um IP público. Crie uma conta no ngrok e siga o guia oficial de configuração.
Depois que o agente ngrok for instalado e configurado, podemos expor a porta que o Ollama está usando:
ngrok http 11434 --host-header="localhost:11434"Observação: o cabeçalho --host-header="localhost:11434" garante que o cabeçalho "Host" nas solicitações corresponda a "localhost:11434"
Executar este comando retornará um link público que funcionará enquanto o ngrok e o servidor Ollama forem executados localmente.
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 ```Em "Encaminhamento" podemos ver que o ngrok gerou uma URL. Guarde para mais tarde.
Vamos tentar fazer uma solicitação HTTP para o endpoint novamente, agora usando a URL gerada pelo ngrok:
curl https://your-ngrok-endpoint.ngrok-free.app/api/generate -d '{
"model": "llama3.2",
"prompt": "What is the capital of France?"
}'A resposta deve ser semelhante à anterior.
Criando mapeamentos
Ponto final ELSER
Neste exemplo, criaremos um ponto de extremidade de inferência usando a API de inferência do Elasticsearch. Além disso, usaremos o ELSER para gerar os embeddings.
PUT _inference/sparse_embedding/medicines-inference
{
"service": "elasticsearch",
"service_settings": {
"num_allocations": 1,
"num_threads": 1,
"model_id": ".elser_model_2_linux-x86_64"
}
}Para este exemplo, vamos imaginar que você tem uma farmácia que vende dois tipos de medicamentos:
Medicamentos que exigem receita médica.
Medicamentos que NÃO exigem receita médica.
Essas informações seriam incluídas no campo de descrição de cada medicamento.
O LLM deve interpretar esse campo, então estes são os mapeamentos de dados que usaremos:
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"
}
}
}
}O campo text_description armazenará o texto simples das descrições, enquanto semantic_field, que é um tipo de campo semantic_text , armazenará os embeddings gerados pelo ELSER.
A propriedade copy_to copiará o conteúdo dos campos name e text_description para o campo semântico para que os embeddings para esses campos sejam gerados.
Indexação de dados
Agora, vamos indexar os dados usando a API _bulk.
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."}Resposta.
{
"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
}
}
]
}Fazendo perguntas usando o Playground
Playground é uma ferramenta do Kibana que permite criar rapidamente um sistema RAG usando índices do Elasticsearch e um provedor LLM. Você pode ler este artigo para saber mais sobre isso.
Conectando o LLM local ao Playground
Primeiro, precisamos criar um conector que use a URL pública que acabamos de criar. No Kibana, vá em Pesquisar>Playground e depois clique em "Conectar a um LLM".

Esta ação revelará um menu no lado esquerdo da interface do Kibana. Lá, clique em "OpenAI".

Agora podemos começar a configurar o conector OpenAI.
Vá para "Configurações do conector" e, para o provedor OpenAI, selecione "Outro (Serviço compatível com OpenAI)":

Agora, vamos configurar os outros campos. Para este exemplo, vamos nomear nosso modelo como "medicines-llm". No campo URL, use o gerado pelo ngrok (/v1/chat/completions). No campo "Modelo padrão", selecione "llama3.2". Não usaremos uma chave de API, então digite qualquer texto aleatório para prosseguir:

Clique em "Salvar" e adicione os medicamentos de índice clicando em "Adicionar fontes de dados":


Ótimo! Agora temos acesso ao Playground usando o LLM que estamos executando localmente como mecanismo RAG.

Antes de testar, vamos adicionar instruções mais específicas ao agente e aumentar o número de documentos enviados ao modelo para 10, para que a resposta tenha o maior número possível de documentos disponíveis. O campo de contexto será semantic_field, que inclui o nome e a descrição dos medicamentos, graças à propriedade copy_to.

Agora vamos fazer a pergunta: Posso comprar Clonazepam sem receita? e veja o que acontece:

Como esperado, obtivemos a resposta correta.
Próximas etapas
O próximo passo é criar seu próprio aplicativo! O Playground fornece um script de código em Python que você pode executar em sua máquina e personalizá-lo para atender às suas necessidades. Por exemplo, colocando-o atrás de um servidor FastAPI para criar um chatbot de medicamentos de controle de qualidade consumido pela sua interface de usuário.
Você pode encontrar esse código clicando no botão Exibir código na seção superior direita do Playground:

E você usa os Endpoints e as chaves de API para gerar a variável de ambiente ES_API_KEY necessária no código.
Para este exemplo específico, o código é o seguinte:
## 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)Para que funcione com o Ollama, você precisa alterar o cliente OpenAI para se conectar ao servidor Ollama em vez do servidor OpenAI. Você pode encontrar a lista completa de exemplos do OpenAI e endpoints compatíveis aqui.
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',
)E também altere o modelo para llama3.2 ao chamar o método de conclusão:
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.contentVamos adicionar nossa pergunta: Posso comprar Clonazepam sem receita? Para a consulta do 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"]E também para a chamada de conclusão com algumas impressões, para que possamos confirmar que estamos enviando os resultados do Elasticsearch como parte do contexto da pergunta:
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 ==========")Agora vamos executar o comando
pip install -qU elasticsearch openai
python main.py
Você deverá ver algo assim:
========== 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 ==========Conclusão
Neste artigo, podemos ver o poder e a versatilidade de ferramentas como o Ollama quando as usamos em conjunto com a API de inferência do Elasticsearch e o Playground.
Após alguns passos simples, tínhamos um aplicativo RAG funcional com um chat que usava um LLM em execução em nossa própria infraestrutura a custo zero. Isso também nos permite ter mais controle sobre recursos e informações confidenciais, além de nos dar acesso a uma variedade de modelos para diferentes tarefas.
Como usar o Ollama com a API de Inferência Elástica
Usando o Ollama com a API Elastic Inference para criar um aplicativo RAG com o Playground.
1.
Configure o servidor Ollama LLM
2.
Criar mapeamentos
3.
Dados de indexação
4.
Faça perguntas usando o Playground
Perguntas frequentes
O que é Ollama?
O Ollama é uma ferramenta que permite baixar e executar modelos LLM usando sua própria infraestrutura (sua máquina/servidor local).
