Aller au contenu
Développement high

Ollama avancé : SDK Python, Structured Output et embeddings

28 min de lecture

logo ollama

Vous savez déjà utiliser Ollama en ligne de commande et avec LiteLLM. Mais saviez-vous qu’Ollama dispose d’un SDK Python natif avec des fonctionnalités exclusives ? Ce guide vous montre comment exploiter les Structured Outputs (sorties JSON typées), les embeddings pour la recherche sémantique, et comment construire un assistant DevOps complet en local.

  • SDK natif vs LiteLLM : quand utiliser chaque approche
  • Streaming : afficher les réponses en temps réel
  • Structured Output : obtenir du JSON typé avec Pydantic
  • Embeddings : générer des vecteurs pour la recherche sémantique
  • Conversation multi-turn : maintenir le contexte entre les échanges
  • TP fil rouge : construire DevOps Buddy, un assistant DevOps local

Avant de commencer, assurez-vous d’avoir :

  1. Ollama installé (guide d’installation)
  2. Un modèle téléchargé :
Fenêtre de terminal
ollama pull llama3.2:3b
  1. Le SDK Python :
Fenêtre de terminal
pip install ollama pydantic

Vérifiez les versions :

Fenêtre de terminal
ollama --version # 0.16.x ou supérieur
pip show ollama # 0.6.x ou supérieur
AspectSDK ollamaLiteLLM
Installationpip install ollamapip install litellm
ScopeOllama uniquement100+ providers (OpenAI, Anthropic…)
Structured Output✅ Natif avec format=Via function calling
Embeddingsembed() intégréembedding()
Pensée (Thinking)think=True
Cas d’usageScripts dédiés OllamaCode portable multi-providers

Recommandation : Utilisez le SDK natif si vous ciblez uniquement Ollama et voulez exploiter ses fonctionnalités spécifiques. Utilisez LiteLLM si vous voulez pouvoir basculer vers d’autres providers.

Tout au long de ce guide, nous construisons DevOps Buddy, un assistant DevOps local qui :

  • Répond aux questions DevOps en streaming
  • Utilise une base de connaissances avec embeddings (mini-RAG)
  • Retourne des commandes structurées avec Pydantic

Créez le répertoire de travail :

Fenêtre de terminal
mkdir -p ~/lab-ollama && cd ~/lab-ollama
python3 -m venv .venv && source .venv/bin/activate
pip install ollama pydantic

La fonction chat() du SDK est l’équivalent de litellm.completion(), mais avec une API légèrement différente.

01_chat_basique.py
#!/usr/bin/env python3
"""
TP 01 : Appel basique avec le SDK Ollama natif
Objectif : Comprendre la différence avec litellm
"""
from ollama import chat
# Appel simple avec le SDK natif
response = chat(
model='llama3.2:3b',
messages=[
{'role': 'user', 'content': 'Explique Docker en une phrase pour un débutant.'}
]
)
print("=== Réponse du modèle ===")
print(response.message.content)
print("\n=== Métadonnées ===")
print(f"Modèle utilisé : {response.model}")
print(f"Tokens prompt : {response.prompt_eval_count}")
print(f"Tokens réponse : {response.eval_count}")
print(f"Durée totale : {response.total_duration / 1e9:.2f}s")

Exécutez le script :

Fenêtre de terminal
python 01_chat_basique.py

Sortie attendue :

=== Réponse du modèle ===
Docker est une technologie qui permet de créer des conteneurs virtuels, qui sont
des environnements isolés et sécurisés où les applications peuvent être exécutées
sans nécessiter d'installations supplémentaires sur l'hôte.
=== Métadonnées ===
Modèle utilisé : llama3.2:3b
Tokens prompt : 36
Tokens réponse : 54
Durée totale : 5.88s

Le streaming affiche la réponse mot par mot au fur et à mesure de la génération. C’est essentiel pour une bonne expérience utilisateur.

02_streaming.py
#!/usr/bin/env python3
"""
TP 02 : Streaming avec le SDK Ollama
Objectif : Afficher la réponse en temps réel, mot par mot
"""
from ollama import chat
# Streaming : la réponse arrive progressivement
print("=== Réponse en streaming ===")
stream = chat(
model='llama3.2:3b',
messages=[
{'role': 'user', 'content': 'Donne 5 bonnes pratiques Dockerfile, très bref.'}
],
stream=True # Active le streaming
)
# Afficher chaque chunk au fur et à mesure
for chunk in stream:
print(chunk.message.content, end='', flush=True)
print("\n\n=== Fin du streaming ===")

Le paramètre flush=True force l’affichage immédiat de chaque caractère.

C’est la fonctionnalité la plus puissante du SDK Ollama. Au lieu d’obtenir du texte libre, vous obtenez du JSON typé qui correspond exactement à un schéma Pydantic.

  • Parsing fiable : plus besoin de regex ou de parsing manuel
  • Validation automatique : Pydantic vérifie que la réponse est conforme
  • Typage : votre IDE connaît les types des champs
  • Intégration : les données sont directement utilisables dans votre code
03_structured_output.py
#!/usr/bin/env python3
"""
TP 03 : Structured Output avec Pydantic
Objectif : Obtenir des réponses JSON typées et validées automatiquement
Feature : Disponible depuis Ollama 0.5.0+
"""
from ollama import chat
from pydantic import BaseModel
# Définir le schéma de sortie avec Pydantic
class DockerCommand(BaseModel):
"""Une commande Docker expliquée"""
command: str # La commande complète
description: str # Explication en une phrase
example: str # Un exemple concret
level: str # "debutant", "intermediaire", "avance"
# Appeler le modèle avec le schéma
response = chat(
model='llama3.2:3b',
messages=[
{
'role': 'user',
'content': 'Explique la commande docker build'
}
],
format=DockerCommand.model_json_schema() # Forcer le format JSON
)
# Valider et parser la réponse avec Pydantic
result = DockerCommand.model_validate_json(response.message.content)
# Afficher le résultat structuré
print("=== Sortie structurée (Pydantic) ===")
print(f"Commande : {result.command}")
print(f"Description : {result.description}")
print(f"Exemple : {result.example}")
print(f"Niveau : {result.level}")
print("\n=== JSON brut ===")
print(response.message.content)

Sortie attendue :

=== Sortie structurée (Pydantic) ===
Commande : docker build
Description : Crée une image Docker à partir d'un fichier Dockerfile.
Exemple : docker build -t mon-image .
Niveau : debutant
=== JSON brut ===
{"command": "docker build", "description": "Crée une image Docker...", ...}

Pour des structures plus complexes avec des listes d’objets :

04_structured_avance.py
#!/usr/bin/env python3
"""
TP 04 : Structured Output avancé (listes et objets imbriqués)
Objectif : Extraire plusieurs éléments structurés en une seule requête
"""
from ollama import chat
from pydantic import BaseModel
class Step(BaseModel):
"""Une étape d'un processus"""
number: int
action: str
command: str
class Tutorial(BaseModel):
"""Un tutoriel complet"""
title: str
steps: list[Step]
total_time: str
print("🔧 Définition du schéma Pydantic...")
print("\n📤 Envoi de la requête à Ollama (llama3.2:3b)...")
# Demander un tutoriel structuré
response = chat(
model='llama3.2:3b',
messages=[
{
'role': 'user',
'content': 'Donne un tutoriel Docker en 2 étapes avec titre, steps (number, action, command) et total_time'
}
],
format=Tutorial.model_json_schema(),
options={'temperature': 0, 'num_predict': 500}
)
print("✅ Réponse reçue!")
print(f" Durée: {response.total_duration / 1e9:.2f}s")
# Parser la réponse
tutorial = Tutorial.model_validate_json(response.message.content)
# Afficher le tutoriel formaté
print(f"\n📚 {tutorial.title}")
print(f"⏱️ {tutorial.total_time}")
for step in tutorial.steps:
print(f" {step.number}. {step.action}{step.command}")

Sortie attendue :

📚 Tutoriel Docker en 2 étapes
⏱️ > 30 minutes
1. Créer un nouveau projet Docker → docker run -it ubuntu /bin/bash
2. Créer une image personnalisée → docker build -t mon-image .

Pour maintenir le contexte entre plusieurs échanges, vous devez gérer l’historique des messages.

05_conversation.py
#!/usr/bin/env python3
"""
TP 05 : Conversation multi-turn avec historique
Objectif : Maintenir le contexte entre plusieurs échanges
"""
from ollama import chat
# Historique de la conversation
messages = []
# Contexte système
system_message = {
'role': 'system',
'content': '''Tu es DevOps Buddy, un assistant spécialisé DevOps.
Tu réponds de manière concise et technique.
Tu donnes des exemples de commandes quand c'est pertinent.'''
}
messages.append(system_message)
print("🤖 DevOps Buddy initialisé")
print("=" * 50)
def ask(question: str) -> str:
"""Pose une question et met à jour l'historique"""
print(f"\n👤 {question}")
# Ajouter la question à l'historique
messages.append({'role': 'user', 'content': question})
# Appeler le modèle avec tout l'historique
response = chat(
model='llama3.2:3b',
messages=messages,
options={'temperature': 0.3}
)
# Ajouter la réponse à l'historique
assistant_message = response.message.content
messages.append({'role': 'assistant', 'content': assistant_message})
print(f"🤖 {assistant_message}")
return assistant_message
# Simulation d'une conversation
ask("C'est quoi un Pod Kubernetes en une phrase ?")
ask("Et un Deployment, c'est quoi par rapport à un Pod ?")
ask("Donne-moi la commande pour créer un Deployment avec 3 réplicas")
ask("Comment je scale à 5 réplicas maintenant ?")
print("\n" + "=" * 50)
print(f"📊 Total messages dans l'historique : {len(messages)}")

Le modèle se souvient du contexte : la 4e question “Comment je scale à 5 réplicas” fait référence au Deployment mentionné précédemment.

Les embeddings transforment du texte en vecteurs numériques. Deux textes similaires auront des vecteurs proches. C’est la base du RAG.

06_embeddings.py
#!/usr/bin/env python3
"""
TP 06 : Embeddings avec Ollama
Objectif : Générer des vecteurs pour la recherche sémantique
Feature : Disponible depuis Ollama 0.3+
"""
from ollama import embed
import math
def cosine_similarity(vec1: list[float], vec2: list[float]) -> float:
"""Calcule la similarité cosinus entre deux vecteurs"""
dot_product = sum(a * b for a, b in zip(vec1, vec2))
norm1 = math.sqrt(sum(a ** 2 for a in vec1))
norm2 = math.sqrt(sum(b ** 2 for b in vec2))
return dot_product / (norm1 * norm2)
# Base de connaissances DevOps
documents = [
"Docker est une plateforme de conteneurisation qui permet de packager des applications",
"Kubernetes orchestre les conteneurs à grande échelle dans un cluster",
"Ansible automatise la configuration des serveurs et le déploiement",
"Terraform gère l'infrastructure as code pour le cloud",
"GitLab CI/CD permet l'intégration et le déploiement continus"
]
print("📦 Génération des embeddings pour 5 documents...")
# Générer les embeddings pour chaque document
doc_embeddings = []
for i, doc in enumerate(documents):
response = embed(model='llama3.2:3b', input=doc)
doc_embeddings.append(response.embeddings[0])
print(f" ✅ Doc {i+1} : {len(response.embeddings[0])} dimensions")
print(f"\n📊 Dimension des vecteurs : {len(doc_embeddings[0])}")
# Question de test
query = "Comment déployer des conteneurs en production ?"
print(f"\n🔍 Requête : {query}")
# Générer l'embedding de la requête
query_response = embed(model='llama3.2:3b', input=query)
query_embedding = query_response.embeddings[0]
# Calculer la similarité avec chaque document
print("\n📈 Scores de similarité :")
scores = []
for i, doc_emb in enumerate(doc_embeddings):
score = cosine_similarity(query_embedding, doc_emb)
scores.append((score, documents[i]))
print(f" {score:.4f}{documents[i][:50]}...")
# Trier par score décroissant
scores.sort(reverse=True)
print(f"\n🏆 Document le plus pertinent :")
print(f" Score: {scores[0][0]:.4f}")
print(f" {scores[0][1]}")

Sortie attendue :

📦 Génération des embeddings pour 5 documents...
✅ Doc 1 : 3072 dimensions
✅ Doc 2 : 3072 dimensions
...
📈 Scores de similarité :
0.3004 → Docker est une plateforme de conteneurisation...
0.3473 → Kubernetes orchestre les conteneurs à grande...
...
🏆 Document le plus pertinent :
Score: 0.4581
Ansible automatise la configuration des serveurs et le déploiement

Combinons toutes les fonctionnalités dans un assistant DevOps interactif.

07_devops_buddy.py
#!/usr/bin/env python3
"""
TP 07 : DevOps Buddy - Assistant local complet
Objectif : Combiner toutes les fonctionnalités Ollama dans un assistant interactif
Features utilisées : chat, streaming, structured output, embeddings
"""
from ollama import chat, embed
from pydantic import BaseModel
import math
# === Configuration ===
MODEL = 'llama3.2:3b'
SYSTEM_PROMPT = '''Tu es DevOps Buddy, un assistant expert DevOps/DevSecOps.
Tu réponds en français de manière concise et technique.
Tu donnes des exemples de commandes quand c'est pertinent.
Tu structures tes réponses avec des listes à puces.'''
# === Base de connaissances embarquée ===
KNOWLEDGE_BASE = [
{"topic": "docker", "content": "Docker utilise des conteneurs pour isoler les applications. Commandes: docker build, docker run, docker compose"},
{"topic": "kubernetes", "content": "Kubernetes orchestre les conteneurs. Concepts: Pod, Deployment, Service, Ingress. Commandes: kubectl get, kubectl apply"},
{"topic": "ansible", "content": "Ansible automatise la configuration. Concepts: playbook, inventory, role, task. Commande: ansible-playbook"},
{"topic": "terraform", "content": "Terraform gère l'infrastructure as code. Concepts: provider, resource, module, state. Commandes: terraform init, plan, apply"},
{"topic": "gitlab", "content": "GitLab CI/CD pipeline avec .gitlab-ci.yml. Concepts: stages, jobs, artifacts, cache, runners"},
{"topic": "prometheus", "content": "Prometheus collecte les métriques. Concepts: scraping, PromQL, alertmanager, exporters"},
]
# Pré-calculer les embeddings de la base de connaissances
print("🔄 Chargement de la base de connaissances...")
knowledge_embeddings = []
for item in KNOWLEDGE_BASE:
response = embed(model=MODEL, input=item["content"])
knowledge_embeddings.append(response.embeddings[0])
print(f"✅ {len(KNOWLEDGE_BASE)} documents indexés\n")
# === Fonctions utilitaires ===
def cosine_similarity(vec1: list[float], vec2: list[float]) -> float:
dot = sum(a * b for a, b in zip(vec1, vec2))
norm1 = math.sqrt(sum(a ** 2 for a in vec1))
norm2 = math.sqrt(sum(b ** 2 for b in vec2))
return dot / (norm1 * norm2) if norm1 and norm2 else 0
def find_relevant_context(query: str, top_k: int = 2) -> str:
"""Recherche les documents pertinents pour enrichir le contexte"""
query_emb = embed(model=MODEL, input=query).embeddings[0]
scores = []
for i, doc_emb in enumerate(knowledge_embeddings):
score = cosine_similarity(query_emb, doc_emb)
scores.append((score, KNOWLEDGE_BASE[i]))
scores.sort(reverse=True)
top_docs = scores[:top_k]
context = "\n".join([f"- {doc['content']}" for _, doc in top_docs])
return context
# === Schéma pour les commandes structurées ===
class CommandSuggestion(BaseModel):
command: str
description: str
warning: str | None = None
def get_structured_command(query: str) -> CommandSuggestion | None:
"""Obtenir une suggestion de commande structurée"""
try:
response = chat(
model=MODEL,
messages=[
{'role': 'system', 'content': 'Tu suggères des commandes DevOps. Réponds uniquement en JSON.'},
{'role': 'user', 'content': f'Suggère une commande pour: {query}'}
],
format=CommandSuggestion.model_json_schema(),
options={'temperature': 0, 'num_predict': 200}
)
return CommandSuggestion.model_validate_json(response.message.content)
except Exception as e:
print(f"⚠️ Erreur structured output: {e}")
return None
# === Chat avec RAG ===
def chat_with_rag(question: str, history: list) -> str:
"""Répond à une question avec contexte enrichi"""
# Trouver le contexte pertinent
context = find_relevant_context(question)
# Construire le prompt enrichi
augmented_question = f"""Contexte (base de connaissances):
{context}
Question de l'utilisateur: {question}"""
# Ajouter au historique
history.append({'role': 'user', 'content': augmented_question})
# Appeler le modèle avec streaming
response_text = ""
stream = chat(
model=MODEL,
messages=history,
stream=True,
options={'temperature': 0.3}
)
for chunk in stream:
content = chunk.message.content
print(content, end='', flush=True)
response_text += content
print() # Nouvelle ligne après le streaming
# Ajouter la réponse à l'historique
history.append({'role': 'assistant', 'content': response_text})
return response_text
# === Main ===
def main():
print("=" * 60)
print("🤖 DevOps Buddy - Assistant DevOps Local (Ollama)")
print("=" * 60)
print(f"Modèle: {MODEL}")
print("Commandes spéciales:")
print(" /cmd <action> : Obtenir une commande structurée")
print(" /quit : Quitter")
print("-" * 60)
history = [{'role': 'system', 'content': SYSTEM_PROMPT}]
while True:
try:
user_input = input("\n👤 Vous: ").strip()
if not user_input:
continue
if user_input.lower() == '/quit':
print("👋 À bientôt!")
break
if user_input.startswith('/cmd '):
# Mode commande structurée
query = user_input[5:]
print("\n🔧 Recherche de commande...")
result = get_structured_command(query)
if result:
print(f"\n📋 Commande: {result.command}")
print(f"📝 Description: {result.description}")
if result.warning:
print(f"⚠️ Attention: {result.warning}")
else:
print("❌ Impossible de générer la commande")
else:
# Mode conversation avec RAG
print("\n🤖 DevOps Buddy: ", end='')
chat_with_rag(user_input, history)
except EOFError:
print("\n👋 À bientôt!")
break
except KeyboardInterrupt:
print("\n👋 Interruption - À bientôt!")
break
if __name__ == "__main__":
main()
Fenêtre de terminal
python 07_devops_buddy.py

Exemple de session :

🔄 Chargement de la base de connaissances...
✅ 6 documents indexés
============================================================
🤖 DevOps Buddy - Assistant DevOps Local (Ollama)
============================================================
Modèle: llama3.2:3b
Commandes spéciales:
/cmd <action> : Obtenir une commande structurée
/quit : Quitter
------------------------------------------------------------
👤 Vous: C'est quoi un Pod ?
🤖 DevOps Buddy: Un Pod est l'unité de base dans Kubernetes...
👤 Vous: /cmd lister les pods kubernetes
🔧 Recherche de commande...
📋 Commande: kubectl get pods
📝 Description: Liste tous les pods dans le namespace courant
⚠️ Attention: Ajoutez -A pour lister tous les namespaces
  1. SDK natif vs LiteLLM : Le SDK ollama offre des fonctionnalités exclusives (structured output, thinking) mais ne supporte que Ollama
  2. format= : Passez un schéma Pydantic pour obtenir du JSON typé
  3. stream=True : Affiche la réponse en temps réel
  4. embed() : Génère des embeddings pour la recherche sémantique
  5. Historique : Passez tous les messages pour maintenir le contexte
  6. options : Contrôlez la température, le nombre de tokens, etc.
  7. RAG local : Combinez embeddings + contexte pour enrichir les réponses
  • SDK ollama installé (pip show ollama)
  • Pydantic installé (pip show pydantic)
  • Modèle téléchargé (ollama list)
  • 01_chat_basique.py : réponse + métadonnées
  • 02_streaming.py : affichage temps réel
  • 03_structured_output.py : JSON validé par Pydantic
  • 04_structured_avance.py : listes d’objets
  • 05_conversation.py : historique multi-turn
  • 06_embeddings.py : similarité cosinus
  • 07_devops_buddy.py : assistant complet

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.