Articles de blog

Utiliser Ollama avec l'API d'inférence

Apprenez à intégrer Ollama avec Elasticsearch en utilisant l'API Inference.

Dans cet article, nous allons apprendre à connecter des modèles locaux au modèle d'inférence d'Elasticsearch à l'aide d'Ollama, puis à poser des questions à vos documents à l'aide de Playground.

Elasticsearch permet aux utilisateurs de se connecter aux LLM à l'aide de l'API Open Inference, qui prend en charge des fournisseurs tels qu'Amazon Bedrock, Cohere, Google AI, Azure AI Studio, HuggingFace - en tant que service, entre autres.

Ollama est un outil qui vous permet de télécharger et d'exécuter des modèles LLM en utilisant votre propre infrastructure (votre machine/serveur local). Vous trouverez ici une liste des modèles disponibles qui sont compatibles avec Ollama.

Ollama est une excellente option si vous souhaitez héberger et tester différents modèles open source sans avoir à vous soucier des différentes façons dont chacun des modèles pourrait être configuré, ou de la façon de créer une API pour accéder aux fonctions du modèle, car Ollama s'occupe de tout.

L'API d'Ollama étant compatible avec l'API d'OpenAI, nous pouvons facilement intégrer le modèle d'inférence et créer une application RAG à l'aide de Playground.

Produits requis

  1. Elasticsearch 8.17

  2. Kibana 8.17

  3. Python

Étapes

  1. Mise en place du serveur Ollama LLM

  2. Création de mappings

  3. Indexation des données

  4. Poser des questions à l'aide de l'aire de jeu

Mise en place du serveur Ollama LLM

Nous allons mettre en place un serveur LLM pour le connecter à notre instance Playground en utilisant Ollama. Nous en aurons besoin :

  • Téléchargez et exécutez Ollama.

  • Utilisez ngrok pour accéder à votre serveur web local qui héberge Ollama sur Internet.

Télécharger et lancer Ollama

Pour utiliser Ollama, il faut d'abord le télécharger. Ollama est compatible avec Linux, Windows et macOS. Il vous suffit donc de télécharger la version d'Ollama compatible avec votre système d'exploitation ici. Une fois Ollama installé, nous pouvons choisir un modèle dans cette liste de LLMs supportés. Dans cet exemple, nous utiliserons le modèle llama3.2, un modèle général multilingue. Dans le processus d'installation, vous activerez l'outil de ligne de commande pour Ollama. Une fois qu'il est téléchargé, vous pouvez exécuter la ligne suivante :

ollama pull llama3.2

Ce qui produira un résultat :

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

Une fois installé, vous pouvez le tester avec cette commande :

ollama run llama3.2

Posons une question :

Lancez Ollama et posez-lui une question

Une fois le modèle en cours d'exécution, Ollama active une API qui s'exécute par défaut sur le port "11434". Faisons une demande à cette API, en suivant la documentation officielle :

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

Voici la réponse que nous avons reçue :

{"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}

Notez que la réponse spécifique pour ce point d'accès est un flux.

Exposer un point de terminaison à l'internet en utilisant ngrok

Comme notre point d'extrémité fonctionne dans un environnement local, il n'est pas possible d'y accéder à partir d'un autre point, comme notre instance Elastic Cloud, via l'internet. ngrok nous permet d'exposer un port en offrant une IP publique. Créez un compte dans ngrok et suivez le guide d'installation officiel.

Une fois l'agent ngrok installé et configuré, nous pouvons exposer le port utilisé par Ollama :

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

Remarque : l'en-tête --host-header="localhost:11434" garantit que l'en-tête "Host" dans les demandes correspond à "localhost:11434."

L'exécution de cette commande renverra un lien public qui fonctionnera tant que le ngrok et le serveur Ollama fonctionneront localement.

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                                                ```

Dans "Forwarding", nous pouvons voir que ngrok a généré une URL. Gardez-le pour plus tard.

Essayons à nouveau de faire une requête HTTP vers le point d'accès, en utilisant maintenant l'URL générée par le moteur de recherche :

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

La réponse devrait être similaire à la précédente.

Création de mappings

Point final ELSER

Pour cet exemple, nous allons créer un point de terminaison d'inférence à l'aide de l'API d'inférence d'Elasticsearch. En outre, nous utiliserons ELSER pour générer les enchâssements.

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

Pour cet exemple, imaginons que vous ayez une pharmacie qui vend deux types de médicaments :

  • Médicaments nécessitant une ordonnance.

  • Médicaments qui ne nécessitent pas d'ordonnance.

Cette information serait incluse dans le champ de description de chaque médicament.

Le LLM doit interpréter ce champ, c'est donc le mappage des données que nous utiliserons :

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"
      }
    }
  }
}

Le champ text_description stockera le texte brut des descriptions tandis que semantic_field, qui est un champ de type semantic_text, stockera les enchâssements générés par ELSER.

La propriété copy_to copiera le contenu des champs name et text_description dans le champ sémantique afin de générer les embeddings pour ces champs.

Indexation des données

Maintenant, indexons les données à l'aide de l'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."}

Réponse :

{
   "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
   	}
 	}
   ]
 }

Poser des questions à l'aide de l'aire de jeu

Playground est un outil Kibana qui vous permet de créer rapidement un système RAG en utilisant des index Elasticsearch et un fournisseur LLM. Vous pouvez lire cet article pour en savoir plus.

Connecter le programme local d'éducation et de formation tout au long de la vie à l'aire de jeux

Nous devons d'abord créer un connecteur qui utilise l'URL publique que nous venons de créer. Dans Kibana, allez sur Search>Playground et cliquez sur "Connect to an LLM".

Connecter le mécanisme local d'apprentissage tout au long de la vie au terrain de jeu d'Ollama

Cette action fait apparaître un menu sur le côté gauche de l'interface Kibana. Cliquez ensuite sur "OpenAI".

Sélectionnez un connecteur : Open AI Ollama

Nous pouvons maintenant commencer à configurer le connecteur OpenAI.

Allez sur "Connector settings" et pour le fournisseur OpenAI, sélectionnez "Other (OpenAI Compatible Service)":

Définir les paramètres du connecteur pour l'utilisation d'Ollama avec l'API Inference

Configurons maintenant les autres champs. Pour cet exemple, nous nommerons notre modèle "medicines-llm". Dans le champ URL, utilisez celle générée par ngrok (/v1/chat/completions). Dans le champ "Modèle par défaut", sélectionnez "llama3.2". Nous n'utiliserons pas de clé API, vous pouvez donc saisir n'importe quel texte au hasard :

Ajouter des paramètres

Cliquez sur "Sauvegarder" et ajoutez les médicaments de l'index en cliquant sur "Ajouter des sources de données":

Ajouter des sources de données pour poser des questions à vos documents à l'aide de Playground
Ajouter des données d'interrogation

Excellent ! Nous avons maintenant accès à Playground en utilisant le LLM que nous exécutons localement en tant que moteur RAG.

Sélectionner les paramètres du modèle dans l'aire de jeu

Avant de le tester, ajoutons des instructions plus spécifiques à l'agent et augmentons le nombre de documents envoyés au modèle à 10, afin que la réponse dispose du plus grand nombre possible de documents. Le champ contextuel sera semantic_field, qui comprend le nom et la description des médicaments, grâce à la propriété copy_to.

Paramètres de Moel dans Elastic Playground

Posons maintenant la question : Puis-je acheter du Clonazepam sans ordonnance ? et voir ce qui se passe :

Video Thumbnail

Comme prévu, nous avons obtenu la bonne réponse.

Étapes suivantes

L'étape suivante consiste à créer votre propre application ! Playground fournit un code script en Python que vous pouvez exécuter sur votre machine et adapter à vos besoins. Par exemple, en le plaçant derrière un serveur FastAPI pour créer un chatbot d'assurance qualité consommé par votre interface utilisateur.

Vous pouvez trouver ce code en cliquant sur le bouton Voir le code dans la partie supérieure droite de l'aire de jeux :

Bouton de visualisation du code

Et vous utilisez les clés de l'API Endpoints & pour générer la variable d'environnement ES_API_KEY requise dans le code.

Pour cet exemple particulier, le code est le suivant :

## 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)

Pour que cela fonctionne avec Ollama, vous devez modifier le client OpenAI pour qu'il se connecte au serveur Ollama au lieu du serveur OpenAI. Vous pouvez trouver la liste complète des exemples OpenAI et des points de terminaison compatibles ici.

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',
)

Il faut également changer le modèle en llama3.2 lors de l'appel de la méthode d'achèvement :

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

Ajoutons notre question : Puis-je acheter du Clonazepam sans ordonnance ? Pour la requête 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"]

Et aussi à l'appel d'achèvement avec quelques impressions, pour que nous puissions confirmer que nous envoyons les résultats d'Elasticsearch dans le cadre du contexte de la question :

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

Exécutons maintenant la commande

pip install -qU elasticsearch openai

python main.py

Vous devriez voir quelque chose comme ceci :

========== 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 ==========

Conclusion

Dans cet article, nous pouvons voir la puissance et la polyvalence d'outils comme Ollama lorsque nous les utilisons avec l'API d'inférence Elasticsearch et Playground.

Après quelques étapes simples, nous avions une application RAG fonctionnelle avec un chat qui utilisait un LLM fonctionnant dans notre propre infrastructure à un coût nul. Cela nous permet également de mieux contrôler les ressources et les informations sensibles, tout en nous donnant accès à une variété de modèles pour différentes tâches.

Comment utiliser Ollama avec l'API Elastic Inference

Utiliser Ollama avec l'API Elastic Inference pour créer une application RAG avec Playground

1.

Mise en place du serveur Ollama LLM

2.

Créer des correspondances

3.

Indexer des données

4.

Posez des questions sur l'utilisation de Playground

Questions fréquentes

Qu'est-ce que l'Ollama ?

Ollama est un outil qui vous permet de télécharger et d'exécuter des modèles LLM en utilisant votre propre infrastructure (votre machine/serveur local).

Pour aller plus loin

Décrivez, ne dessinez pas : tableaux de bord Kibana IA natifs via MCP et ES|QL

Prêt à créer des expériences de recherche d'exception ?

Une recherche suffisamment avancée ne se fait pas avec les efforts d'une seule personne. Elasticsearch est alimenté par des data scientists, des ML ops, des ingénieurs et bien d'autres qui sont tout aussi passionnés par la recherche que vous. Mettons-nous en relation et travaillons ensemble pour construire l'expérience de recherche magique qui vous permettra d'obtenir les résultats que vous souhaitez.

Jugez-en par vous-même