Blog

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

  1. Elasticsearch 8.17

  2. Kibana 8.17

  3. Python

Etapas

  1. Configurando o servidor Ollama LLM

  2. Criando mapeamentos

  3. Indexação de dados

  4. Fazendo perguntas usando o Playground

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.2

O 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
success

Uma vez instalado, você pode testá-lo com este comando:

ollama run llama3.2

Vamos fazer uma pergunta:

Execute Ollama e faça 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".

Conectando o LLM local ao Playground para Ollama

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

Selecione um conector: Open AI Ollama

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)":

Definir a configuração do conector para usar o Ollama com a API de Inferência

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:

Adicionar configurações

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

Adicione fontes de dados para fazer perguntas aos seus documentos usando o Playground
Adicionar dados de consulta

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

Selecione as configurações do modelo no Playground

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.

Configurações Moel no Elastic Playground

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

Video Thumbnail

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:

Botão Ver código

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.content

Vamos 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).

Conteúdo relacionado

Descreva, não desenhe: dashboards nativos de IA do Kibana via MCP e ES|QL

Stratoula Kalafateli

Pronto para criar buscas de última geração?

Uma pesquisa suficientemente avançada não se consegue apenas com o esforço de uma só pessoa. O Elasticsearch é impulsionado por cientistas de dados, especialistas em operações de aprendizado de máquina, engenheiros e muitos outros que são tão apaixonados por buscas quanto você. Vamos nos conectar e trabalhar juntos para construir a experiência de busca mágica que lhe trará os resultados desejados.

Experimente você mesmo(a)