Uso de Ollama con la API de inferencia
Aprende cómo integrar Ollama con Elasticsearch usando la API de Inference.
En este artículo, aprenderemos cómo conectar modelos locales al modelo de inferencia de Elasticsearch usando Ollama y luego hacer preguntas a tus documentos usando Playground.
Elasticsearch permite a los usuarios conectarse a LLM empleando la API de Open Inference, compatible con proveedores como Amazon Bedrock, Cohere, Google AI, Azure AI Studio, HuggingFace, como servicio, entre otros.
Ollama es una herramienta que le permite descargar y ejecutar modelos LLM empleando su propia infraestructura (su máquina/servidor local). Aquí puedes encontrar una lista de los modelos disponibles que son compatibles con Ollama.
Ollama es una gran opción si quieres alojar y probar diferentes modelos de código abierto sin tener que preocuparte por las diferentes formas en que se podría tener que configurar cada uno de los modelos, o por cómo crear una API para acceder a las funciones del modelo, ya que Ollama se encarga de todo.
Dado que la API de Ollama es compatible con la API de OpenAI, podemos integrar fácilmente el modelo de inferencia y crear una aplicación RAG empleando Playground.
Prerrequisitos
Elasticsearch 8.17
Kibana 8.17
Python
Pasos
Configuración del servidor Ollama LLM
Vamos a configurar un servidor LLM para conectarlo a nuestra instancia de Playground usando Ollama. Necesitaremos:
Descargue y ejecute Ollama.
Use ngrok para acceder al servidor sitio web local que hospeda Ollama a través de Internet
Descarga y ejecuta Ollama
Para usar Ollama, primero debemos descargarlo. Ollama ofrece soporte para Linux, Windows y macOS, así que simplemente descargue la versión de Ollama compatible con su sistema operativo aquí. Una vez instalado Ollama, podemos elegir un modelo de esta lista de LLM compatibles. En este ejemplo, usaremos el modelo llama3.2, un modelo general multilingüe. En el proceso de configuración, habilitará la herramienta de línea de comandos para Ollama. Una vez descargado, puede ejecutar la siguiente línea:
ollama pull llama3.2Lo que dará como resultado:
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
successUna vez instalado, puede probarlo con este comando:
ollama run llama3.2Hagamos una pregunta:

Con el modelo en ejecución, Ollama habilita una API que se ejecutaría de forma predeterminada en el puerto "11434". Hagamos una solicitud a esa API, siguiendo la documentación oficial:
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2",
"prompt": "What is the capital of France?"
}'Esta es la respuesta que obtuvimos:
{"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}Tenga en cuenta que la respuesta específica para este punto de conexión es una transmisión.
Exponer el punto de conexión a Internet mediante ngrok
Dado que nuestro endpoint funciona en un entorno local, no se puede acceder a él desde otro punto, como nuestra instancia de Elastic Cloud, a través de Internet. ngrok nos permite exponer un puerto que ofrece una IP pública. Cree una cuenta en ngrok y siga la guía de configuración oficial.
Una vez instalado y configurado el agente ngrok, podemos exponer el puerto que usa Ollama:
ngrok http 11434 --host-header="localhost:11434"Nota: El encabezado --host-header="localhost:11434" garantiza que el encabezado "Host" de las solicitudes coincida con "localhost:11434"
La ejecución de este comando devolverá un vínculo público que funcionará siempre que ngrok y el servidor de Ollama se ejecuten 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 ```En "Reenvío" podemos ver que ngrok generó una dirección URL. Almacénalo para más tarde.
Intentemos realizar una solicitud HTTP al punto de conexión de nuevo, ahora con la dirección URL generada por ngrok:
curl https://your-ngrok-endpoint.ngrok-free.app/api/generate -d '{
"model": "llama3.2",
"prompt": "What is the capital of France?"
}'La respuesta debe ser similar a la anterior.
Creación de asignaciones
Punto final ELSER
Para este ejemplo, crearemos un punto final de inferencia mediante la API de inferencia de Elasticsearch. Además, usaremos ELSER para generar las incrustaciones.
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 ejemplo, imaginemos que tiene una farmacia que vende dos tipos de medicamentos:
Medicamentos que requieren receta médica.
Medicamentos que NO requieren receta médica.
Esta información se incluiría en el campo de descripción de cada medicamento.
El LLM debe interpretar este campo, por lo que estas son las asignaciones de datos 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"
}
}
}
}El campo text_description almacenará el texto sin formato de las descripciones, mientras que semantic_field, que es un tipo de campo semantic_text , almacenará las incrustaciones generadas por ELSER.
La propiedad copy_to copiará el contenido del nombre y la text_description de los campos en el campo semántico para que se generen las incrustaciones de esos campos.
Indexación de datos
Ahora, indexemos los datos usando la API de _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."}Respuesta:
{
"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
}
}
]
}Hacer preguntas con Playground
Playground es una herramienta de Kibana que te permite crear rápidamente un sistema RAG empleando índices de Elasticsearch y un proveedor de LLM. Puedes leer este artículo para saber más al respecto.
Conectando el LLM local con Playground
Primero debemos crear un conector que use la dirección URL pública que acabamos de crear. En Kibana, ve a Buscar>Playground y luego haz clic en "Conectar a un LLM".

Esta acción revelará un menú en el lado izquierdo de la interfaz de Kibana. Allí, haga clic en "OpenAI".

Ahora podemos comenzar a configurar el conector OpenAI.
Vaya a "Configuración del conector" y para el proveedor de OpenAI, seleccione "Otro (servicio compatible con OpenAI)":

Ahora, configuremos los otros campos. Para este ejemplo, llamaremos a nuestro modelo "medicines-llm". En el campo URL, usa la que genera ngrok (/v1/chat/completions). En el campo "Modelo predeterminado", selecciona "llama3.2". No usaremos una clave API, así que simplemente pon cualquier texto aleatorio para continuar:

Haga clic en "Almacenar" y agregue los medicamentos índice haciendo clic en "Agregar fuentes de datos":


¡Bien! Ahora tenemos acceso a Playground usando el LLM que estamos ejecutando localmente como motor RAG.

Antes de probarlo, agreguemos instrucciones más específicas al agente y aumentemos el número de documentos enviados al modelo a 10, para que la respuesta tenga la mayor cantidad de documentos posibles disponibles. El campo de contexto será semantic_field, que incluye el nombre y la descripción de los medicamentos, gracias a la propiedad copy_to.

Ahora hagamos la pregunta: ¿Puedo comprar clonazepam sin receta? y vea qué sucede:

Como era de esperar, obtuvimos la respuesta correcta.
Pasos siguientes
¡El siguiente paso es crear su propia aplicación! Playground proporciona un script de código en Python que puede ejecutar en su máquina y personalizarlo para satisfacer sus necesidades. Por ejemplo, colocándolo detrás de un servidor FastAPI para crear un chatbot de medicamentos de control de calidad consumido por su interfaz de usuario.
Puedes encontrar este código haciendo clic en el botón Ver código en la sección superior derecha de Playground:

Y usa los puntos finales y las claves API para generar la variable de entorno ES_API_KEY requerida en el código.
Para este ejemplo en individuo, el código es el siguiente:
## 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 con Ollama, debe cambiar el cliente OpenAI para conectarse al servidor Ollama en lugar del servidor OpenAI. Puede encontrar la lista completa de ejemplos de OpenAI y puntos finales compatibles aquí.
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',
)Y también cambie el modelo a llama3.2 al llamar al método de finalización:
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.contentAgreguemos nuestra pregunta: ¿Puedo comprar Clonazepam sin receta? A la consulta de 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"]Y también a la llamada de finalización con un par de impresiones, para que podamos confirmar que estamos enviando los resultados de Elasticsearch como parte del contexto de la pregunta:
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 ==========")Ahora ejecutemos el comando
pip install -qU elasticsearch openai
python main.py
Deberías ver algo como esto:
========== 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 ==========Conclusión
En este artículo, podemos ver el poder y la versatilidad de herramientas como Ollama cuando las usamos junto con la API de inferencia de Elasticsearch y Playground.
Luego de algunos pasos simples, teníamos una aplicación RAG en funcionamiento con un chat que usaba un LLM que se ejecutaba en nuestra propia infraestructura sin costo alguno. Esto también nos permite tener más control sobre los recursos y la información sensible, además de darnos acceso a una variedad de modelos para diferentes tareas.
Cómo usar Ollama con la API de Inferencia Elástica
Usar Ollama con la API Elastic Inference para crear una aplicación RAG con Playground
1.
Configurar el servidor LLM de Ollama
2.
Crear mapeos
3.
Datos de índice
4.
Haz preguntas usando Playground
Preguntas frecuentes
¿Qué es Ollama?
Ollama es una herramienta que te permite descargar y ejecutar modelos LLM usando tu propia infraestructura (tu máquina/servidor local).
