Blog

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

  1. Elasticsearch 8.17

  2. Kibana 8.17

  3. Python

Pasos

  1. Configuración del servidor Ollama LLM

  2. Creación de asignaciones

  3. Indexación de datos

  4. Hacer preguntas con Playground

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

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

Una vez instalado, puede probarlo con este comando:

ollama run llama3.2

Hagamos una pregunta:

Ejecuta Ollama y hazle 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".

Conectando el LLM local con Playground for Ollama

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

Seleccione un conector: Open AI Ollama

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

Establecer la configuración del conector para usar Ollama con la API de inferencia

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:

Agregar configuración

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

Agrega fuentes de datos para hacer preguntas a tus documentos con Playground
Agregar datos de consulta

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

Seleccionar la configuración del modelo en Playground

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.

Configuración de Moel en Elastic Playground

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

Video Thumbnail

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:

Ver botón de código

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

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

Contenido relacionado

Descríbelo, no lo dibujes: dashboard de Kibana con IA integrada a través de MCP y ES|QL

Stratoula Kalafateli

¿Estás listo para crear experiencias de búsqueda de última generación?

No se logra una búsqueda suficientemente avanzada con los esfuerzos de uno. Elasticsearch está impulsado por científicos de datos, operaciones de ML, ingenieros y muchos más que son tan apasionados por la búsqueda como tú. Conectemos y trabajemos juntos para crear la experiencia mágica de búsqueda que te dará los resultados que deseas.

Pruébalo tú mismo