A la fin de ce notebook, vous saurez : 1. Utiliser Function Calling avec FunctionChoiceBehavior pour orchestrer automatiquement des fonctions 2. Configurer la memoire vectorielle avec l’API moderne (InMemoryStore, embeddings) 3. Integrer des modèles Hugging Face comme alternative a OpenAI 4. Implementer un Groundedness Checking pour reduire les hallucinations 5. Generer plusieurs reponses en un seul appel API
Note importante : Les anciens SequentialPlanner et FunctionCallingStepwisePlanner sont deprecies depuis SK 1.30. Ce notebook utilise l’approche moderne avec FunctionChoiceBehavior.
# ============================# Cellule : Installation & Imports# ============================# N'installez qu'une seule fois si nécessaire# Imports de baseimport osimport sysfrom dotenv import load_dotenvfrom semantic_kernel import Kernelfrom semantic_kernel.functions import KernelArgumentsfrom semantic_kernel.contents import ChatHistoryprint("Imports et installation OK.")
Imports et installation OK.
1. Installation et configuration initiale
Cette première cellule effectue deux opérations essentielles : 1. Installation de semantic-kernel : Le SDK Python pour orchestrer des modèles de langage 2. Imports de base : Les modules fondamentaux pour interagir avec le kernel et gerer l’environnement
Le fichier .env doit contenir vos cles API (voir la section suivante pour le format attendu).
Bloc Markdown – Configuration du Kernel & .env
### Configuration du KernelPour exécuter les exemples, on suppose que vous avez un fichier `.env` comportant vos clés d'API OpenAI / Azure OpenAI / Hugging Face. Exemple `.env` :
GLOBAL_LLM_SERVICE=“OpenAI” # ou AzureOpenAI, HuggingFace OPENAI_API_KEY=“sk-…” OPENAI_CHAT_MODEL_ID=“gpt-5-mini” …
Le `Kernel` lira ces informations pour décider quel connecteur LLM utiliser.
Ensuite, nous ajouterons nos services (`OpenAIChatCompletion`, `AzureChatCompletion`, etc.) selon la variable `GLOBAL_LLM_SERVICE`.
## 2. Initialisation du Kernel avec service LLM dynamique
Cette cellule configure le Kernel en fonction du service LLM specifie dans le fichier `.env`. Le code supporte trois backends :
- **OpenAI** : API officielle d'OpenAI (GPT-4, GPT-3.5, etc.)
- **Azure OpenAI** : Deploiement Azure avec endpoint personnalise
- **Hugging Face** : Modèles open-source en local ou via le Hub
Le pattern utilise ici est courant : on lit la variable `GLOBAL_LLM_SERVICE` pour determiner dynamiquement quel connecteur instancier.
::: {#63d7d73b .cell quarto-private-1='{"key":"papermill","value":{"duration":1.317252,"end_time":"2026-08-17T04:09:42.105759","exception":false,"start_time":"2026-08-17T04:09:40.788507","status":"completed"}}' tags='[]' execution_count=2}
``` {.python .cell-code}
# ============================
# Cellule : Initialisation du Kernel
# ============================
from semantic_kernel.connectors.ai.open_ai import (
OpenAIChatCompletion,
AzureChatCompletion
)
# On charge le .env
load_dotenv()
global_llm_service = os.getenv("GLOBAL_LLM_SERVICE", "AzureOpenAI")
# Initialisation du Kernel
kernel = Kernel()
service_id = "default"
if global_llm_service.lower() == "openai":
# Ajout du service OpenAI
kernel.add_service(
OpenAIChatCompletion(service_id=service_id),
)
print("Service OpenAI configuré.")
elif global_llm_service.lower() == "huggingface":
# Ajout du service HuggingFace
from semantic_kernel.connectors.ai.hugging_face import HuggingFaceTextCompletion
kernel.add_service(
HuggingFaceTextCompletion(
service_id=service_id,
ai_model_id="distilgpt2", # ex. pour text-generation
task="text-generation"
),
)
print("Service Hugging Face configuré.")
else:
# Par défaut : Azure OpenAI
kernel.add_service(
AzureChatCompletion(service_id=service_id),
)
print("Service Azure OpenAI configuré.")
Service OpenAI configuré.
:::
Choix du service LLM
Le code ci-dessus illustre un pattern important : la configuration dynamique du service LLM.
Selon la valeur de GLOBAL_LLM_SERVICE dans votre fichier .env, le Kernel se connectera a : - OpenAI : API officielle avec OpenAIChatCompletion - Azure OpenAI : Deploiement Azure avec AzureChatCompletion - Hugging Face : Modèles open-source avec HuggingFaceTextCompletion
Cette flexibilite permet de switcher facilement entre fournisseurs sans modifier le code applicatif.
Chat Basique avec KernelArguments
L’idée : on crée une fonction de chat qui prend : - l’historique de conversation (objet ChatHistory) - un user_input et on stocke le tout dans un KernelArguments.
Cela permet d’alimenter un prompt (via un template) qui contient la variable history et user_input.
# ============================# Cellule : Extrait d'un Chat Minimal# ============================# Exemple de promptchat_prompt ="""{{$history}}User: {{$user_input}}ChatBot:"""# Création d'une fonction sémantique "chat"from semantic_kernel.prompt_template import PromptTemplateConfigfrom semantic_kernel.prompt_template.input_variable import InputVariablept_config = PromptTemplateConfig( template=chat_prompt, name="chatFunction", template_format="semantic-kernel", input_variables=[ InputVariable(name="user_input", description="User's message"), InputVariable(name="history", description="Conversation history"), ])chat_function = kernel.add_function( function_name="chat", plugin_name="myChatPlugin", prompt_template_config=pt_config)chat_history = ChatHistory()chat_history.add_system_message("Vous êtes un chatbot utile spécialisé en recommandations de livres.")asyncdef chat_kernel(input_text: str):print(f"[Utilisateur] : {input_text}") response =await kernel.invoke( chat_function, KernelArguments(user_input=input_text, history=str(chat_history)) )print(f"[ChatBot] : {response}")# Mise à jour de l'historique chat_history.add_user_message(input_text) chat_history.add_assistant_message(str(response))# Testawait chat_kernel("Salut, peux-tu me conseiller un livre sur la philosophie antique ?")await chat_kernel("Merci, tu peux détailler un peu plus la période concernée ?")
[Utilisateur] : Salut, peux-tu me conseiller un livre sur la philosophie antique ?
[ChatBot] : Voici quelques excellentes recommandations sur la **philosophie antique**, selon ce que tu cherches :
### 1) Pour une introduction claire et accessible
**Pierre Hadot — *Qu’est-ce que la philosophie antique ?***
Un classique : Hadot explique très bien la philosophie antique comme une **manière de vivre** (exercices spirituels, pratiques, écoles).
### 2) Pour un panorama complet (plus “manuel”)
**Giovanni Reale — *Histoire de la philosophie antique***
Très riche et structuré, idéal si tu veux une vue d’ensemble détaillée des penseurs et des courants.
### 3) Pour entrer directement par les textes (Stoïcisme, Épicurisme)
- **Épictète — *Manuel* (ou *Entretiens*, sélection)** : court, percutant, très pratique.
- **Épicure — *Lettre à Ménécée*** : une porte d’entrée simple et fondamentale.
### 4) Pour Platon (lecture guidée)
**Platon — *Apologie de Socrate***
Un texte bref et passionnant pour comprendre Socrate, la méthode philosophique et la question du juste.
Si tu me dis ce que tu préfères (plutôt **stoïcisme**, **Platon/Aristote**, ou une **introduction générale**), et ton niveau (débutant/à l’aise), je peux te proposer **1 ou 2 titres parfaitement adaptés**.
[Utilisateur] : Merci, tu peux détailler un peu plus la période concernée ?
[ChatBot] : La **philosophie antique** recouvre en général une période très large, depuis les premiers penseurs grecs jusqu’aux derniers philosophes de l’Antiquité tardive. Selon les auteurs, les bornes changent un peu, mais voici le découpage le plus courant (avec les idées dominantes et quelques noms repères).
## 1) Les débuts : les « présocratiques » (VIe–Ve siècle av. J.-C.)
**Période :** environ de **Thalès** (vers -600) à la génération de **Socrate** (mort en -399).
**Ce qui domine :** naissance d’une explication rationnelle du monde (*phusis*), recherche de principes (l’« archè »), cosmologie, mathématisation.
**Auteurs/courants :** Thalès, Anaximandre, Héraclite, Parménide, Empédocle, Anaxagore, Démocrite.
**Pourquoi c’est important :** ils posent les questions de base (être/devenir, matière, causes) qui nourriront Platon et Aristote.
## 2) La période « classique » (Ve–IVe siècle av. J.-C.)
**Période :** grosso modo **Socrate, Platon, Aristote**.
**Ce qui domine :** bascule vers l’éthique, la politique, la connaissance, la définition des concepts (justice, vertu, vérité), et construction de systèmes.
**Repères :**
- **Socrate** : examen de soi, dialogue, vertu et connaissance.
- **Platon** : théorie des Formes/Idées, politique (la cité juste), âme, dialectique.
- **Aristote** : logique, métaphysique, éthique des vertus, science des causes.
## 3) L’époque hellénistique (fin IVe–Ier siècle av. J.-C.)
**Période :** après Alexandre le Grand (à partir de ~-323).
**Ce qui domine :** la philosophie comme **art de vivre** : comment atteindre l’ataraxie (tranquillité), la liberté intérieure, la sagesse dans un monde instable.
**Écoles majeures :**
- **Stoïcisme** (Zénon, Chrysippe) : vivre selon la raison/nature, vertu, maîtrise des jugements.
- **Épicurisme** (Épicure, Lucrèce) : plaisir compris comme absence de trouble, amitié, critique des peurs (dieux/mort).
- **Scepticisme** (Pyrrhon, Sextus Empiricus) : suspension du jugement pour la tranquillité.
- **Cynisme** (Diogène) : vie simple, critique des conventions.
## 4) La période romaine / impériale (Ier siècle av. J.-C. – IIe/IIIe siècle ap. J.-C.)
**Ce qui domine :** transmission et adaptation (souvent en latin) ; développement d’une éthique pratique et morale.
**Auteurs repères :**
- **Cicéron** (éclectisme, diffusion de la philosophie grecque à Rome)
- **Sénèque**, **Épictète**, **Marc Aurèle** (stoïcisme)
- Continuité épicurienne (par ex. **Lucrèce** un peu plus tôt)
## 5) L’Antiquité tardive et le néoplatonisme (IIIe–VIe siècle ap. J.-C.)
**Période :** environ de **Plotin** (IIIe s.) jusqu’à la fermeture de l’Académie d’Athènes (529).
**Ce qui domine :** métaphysique et théologie philosophique (l’Un, l’intellect, l’âme), commentaires de Platon/Aristote, dialogue avec le christianisme naissant.
**Auteurs :** Plotin, Porphyre, Jamblique, Proclus (et, côté chrétien influencé par le platonisme : Augustin, etc.).
---
### Où se situe Pierre Hadot dans ce découpage ?
Hadot parle de **toute l’Antiquité**, mais il met particulièrement l’accent sur la période **hellénistique et romaine**, où la philosophie se comprend très bien comme **pratique de vie** (stoïciens, épicuriens, etc.).
Si tu me dis si tu veux plutôt la période **Socrate/Platon/Aristote** (classique) ou plutôt **stoïciens/épicuriens** (hellénistique–romaine), je peux te recommander un livre qui couvre précisément la tranche qui t’intéresse.
Exercice 1 : Chatbot avec gestion de contexte avancee
Objectif : Implementer un chatbot qui limite automatiquement la taille de l’historique de conversation pour eviter de depasser la fenêtre de contexte du LLM.
Le chat basique avec ChatHistory accumule tous les messages indefiniment. En production, il faut tronquer l’historique quand il devient trop long, tout en preservant le message système.
Indices : - # Étape 1 : Créer une fonction trim_history(history, max_messages) qui garde le message système + N derniers echanges - # Étape 2 : Compter les messages et verifier si len(history.messages) > max_messages - # Étape 3 : Reconstruire un nouvel historique avec le system message + les messages recents - # Indice : Le message système est toujours history.messages[0] (ou filtrer par rôle AuthorRole.SYSTEM)
from semantic_kernel.contents import ChatHistorydef trim_history(history: ChatHistory, max_messages: int=10) -> ChatHistory:""" Tronque l'historique pour garder le message systeme + les N derniers messages. # Etape 1 : Verifier si l'historique depasse max_messages # Etape 2 : Separer le message systeme (premier message) des messages de conversation # Etape 3 : Reconstruire un nouvel historique avec system + derniers messages # Indice : ChatHistory() cree un nouvel historique vide # Indice : history.add_message(msg) ajoute un message existant au nouvel historique Args: history: L'historique de conversation original max_messages: Nombre maximum de messages a conserver (hors system) Returns: ChatHistory tronque si necessaire, sinon l'original """# TODO etudiant : implementer la troncature d'historiquereturn history # TODO etudiant : retourner l'historique tronque# Test avec un historique longtest_history = ChatHistory()test_history.add_system_message("Tu es un assistant utile.")for i inrange(15): test_history.add_user_message(f"Question {i+1}") test_history.add_assistant_message(f"Reponse {i+1}")print(f"Avant trim : {len(test_history.messages)} messages")trimmed = trim_history(test_history, max_messages=6)print(f"Apres trim : {len(trimmed.messages)} messages")print(f"Premier message conserve (system) : {trimmed.messages[0].content[:30]}...")print(f"Dernier message : {trimmed.messages[-1].content}")
Avant trim : 31 messages
Apres trim : 31 messages
Premier message conserve (system) : Tu es un assistant utile....
Dernier message : Reponse 15
Interprétation : Chat Conversationnel avec Historique
Sortie obtenue : Un chatbot conversationnel qui maintient le contexte entre les échanges.
Échange
Utilisateur
Assistant
Contexte maintenu
1
“Peux-tu me conseiller un livre sur la philosophie antique ?”
Demande une précision (intro moderne / texte ancien, langue ?) puis propose une sélection (Hadot, Platon, Aristote, Stoïciens, Épicuriens)
Message système (spécialisation livres)
2
“Merci, tu peux détailler un peu plus la période concernée ?”
Détaille les 5 phases de la philosophie antique (Présocratiques → Néoplatonisme / Antiquité tardive)
Stocke tous les messages (système, utilisateur, assistant)
Conserve le contexte
PromptTemplateConfig
Définit le template avec variables {$history}, {$user_input}
Structure du prompt
KernelArguments
Passe les valeurs dynamiques au template
user_input, history
chat_history.add_*_message()
Met à jour l’historique après chaque échange
Persistence mémoire
Gestion du message système :
chat_history.add_system_message("Vous êtes un chatbot utile spécialisé en recommandations de livres.")
Définit le comportement et la personnalité du bot
Reste présent dans tout l’historique
Peut spécifier le format de réponse, le ton, les contraintes
Exemple de prompt généré (2ème échange) :
[SYSTEM] Vous êtes un chatbot utile spécialisé en recommandations de livres.
[USER] Salut, peux-tu me conseiller un livre sur la philosophie antique ?
[ASSISTANT] Propose une sélection (Hadot, Platon, Aristote, Stoïciens...) en demandant une précision
[USER] Merci, tu peux détailler un peu plus la période concernée ?
[CHATBOT] <à générer>
Limitations et améliorations :
Limite de contexte :
Le ChatHistory peut devenir très long et dépasser le context window du LLM
Solutions :
Tronquer l’historique (garder N derniers messages)
Résumer l’historique (summarization)
Mémoire vectorielle (stocker l’historique dans un vector store)
Code amélioré avec gestion de contexte :
MAX_HISTORY_LENGTH =10# Garder les 10 derniers échangesiflen(chat_history.messages) > MAX_HISTORY_LENGTH:# Garder le message système + N derniers messages system_msg = chat_history.messages[0] recent_msgs = chat_history.messages[-(MAX_HISTORY_LENGTH-1):] chat_history = ChatHistory() chat_history.add_message(system_msg)for msg in recent_msgs: chat_history.add_message(msg)
Comparaison avec d’autres patterns :
Pattern
Mémoire
Use case
Stateless (prompt fixe)
Aucune
FAQ simple
ChatHistory (ce notebook)
Court terme (session)
Chatbot conversationnel
Vector Memory (notebook 05)
Long terme (permanent)
RAG, knowledge base
Agents (notebook 03)
Multi-agents avec état partagé
Workflows complexes
Cas d’usage typiques : - Support client : Résoudre un problème en plusieurs étapes - Assistant personnel : Planification, organisation avec continuité - Tuteur éducatif : Adaptation progressive au niveau de l’apprenant
3. Function Calling Moderne
Evolution des Planners vers Function Calling
Les Planners (SequentialPlanner, FunctionCallingStepwisePlanner) ont ete deprecies dans Semantic Kernel 1.30+. Microsoft recommande maintenant d’utiliser :
Ancienne approche
Nouvelle approche
Avantages
SequentialPlanner
FunctionChoiceBehavior.Auto()
Moins de tokens, plus fiable
FunctionCallingStepwisePlanner
Agent avec plugins
Pattern ReAct natif
Planification XML
Function calling OpenAI
Standard API
Qu’est-ce que Function Calling ?
Function Calling permet au LLM de : 1. Analyser la requête utilisateur 2. Decider automatiquement quelle(s) fonction(s) appeler 3. Extraire les paramètres depuis le contexte 4. Retourner le résultat au LLM pour formulation finale
C’est le pattern ReAct (Reasoning + Acting) integre directement dans l’API OpenAI.
Configuration de FunctionChoiceBehavior
Semantic Kernel propose plusieurs modes de Function Calling :
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior# Mode Auto : le LLM decide quand appeler les fonctionssettings.function_choice_behavior = FunctionChoiceBehavior.Auto()# Mode Required : force l'appel d'au moins une fonctionsettings.function_choice_behavior = FunctionChoiceBehavior.Required()# Mode None : desactive le function callingsettings.function_choice_behavior = FunctionChoiceBehavior.NoneInvoke()
Paramètres avances
Paramètre
Description
Valeur par defaut
auto_invoke
Executer automatiquement les fonctions
True
filters
Filtrer les fonctions disponibles
None
maximum_auto_invoke_attempts
Limite d’appels
5
L’exemple suivant montre comment configurer un kernel avec plusieurs plugins et laisser le LLM orchestrer automatiquement.
API de memoire vectorielle mise a jour
Note de version : L’API de memoire a evolue dans Semantic Kernel. Les anciennes classes comme VolatileMemoryStore et SemanticTextMemory sont remplacees par : - InMemoryStore : Stockage en memoire volatile - Services d’embedding dedies avec OpenAITextEmbedding
Cette cellule montre le pattern moderne pour configurer la memoire sémantique.
# ============================# Function Calling Moderne avec FunctionChoiceBehavior# ============================from semantic_kernel.core_plugins.text_plugin import TextPluginfrom semantic_kernel.core_plugins.math_plugin import MathPluginfrom semantic_kernel.functions import KernelFunctionFromPrompt, kernel_functionfrom semantic_kernel.connectors.ai.open_ai import OpenAIChatPromptExecutionSettingsfrom semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior# Charger des plugins avec des fonctions utilesplugins_directory ="./prompt_template_samples/"writer_plugin = kernel.add_plugin(plugin_name="WriterPlugin", parent_directory=plugins_directory)text_plugin = kernel.add_plugin(plugin=TextPlugin(), plugin_name="TextPlugin")math_plugin = kernel.add_plugin(plugin=MathPlugin(), plugin_name="MathPlugin")# Creer une fonction semantique pour la poesieshakespeare_func = KernelFunctionFromPrompt( function_name="Shakespeare", plugin_name="WriterPlugin", prompt="""{{$input}}Rewrite the above in the style of Shakespeare.""", prompt_execution_settings=OpenAIChatPromptExecutionSettings( service_id=service_id,# temperature not set: gpt-5-mini only supports default value (1) ), description="Rewrite the input in the style of Shakespeare.",)kernel.add_function(plugin_name="WriterPlugin", function=shakespeare_func)# Lister les fonctions disponiblesprint("Fonctions disponibles pour le LLM :")print("-"*50)for plugin_name, plugin in kernel.plugins.items():for function_name, function in plugin.functions.items(): desc = function.description[:50] +"..."if function.description andlen(function.description) >50else function.descriptionprint(f" {plugin_name}.{function_name}: {desc}")# ============================# Exemple : Function Calling Auto# ============================print("\n"+"="*50)print("DEMO : Function Calling avec FunctionChoiceBehavior.Auto()")print("="*50)# Configurer les settings avec function calling autoexecution_settings = OpenAIChatPromptExecutionSettings( service_id=service_id,# temperature not set: gpt-5-mini only supports default value (1) function_choice_behavior=FunctionChoiceBehavior.Auto( auto_invoke=True, # Execute automatiquement les fonctions filters={"included_plugins": ["TextPlugin", "MathPlugin"]} # Limiter aux plugins ))# Prompt qui necessite l'appel de fonctionsuser_request ="""J'ai besoin d'aide pour deux choses :1. Convertir le texte "hello world" en majuscules2. Calculer 25 + 17"""print(f"\nRequete utilisateur : {user_request}")# Creer une fonction de chat avec function callingfrom semantic_kernel.contents import ChatHistorychat_history = ChatHistory()chat_history.add_system_message("Tu es un assistant qui utilise les outils disponibles pour aider l'utilisateur.")chat_history.add_user_message(user_request)# Invoquer avec function callingchat_service = kernel.get_service(service_id)result =await chat_service.get_chat_message_content( chat_history=chat_history, settings=execution_settings, kernel=kernel)print(f"\nReponse du LLM (avec function calling) :")print(result.content)
Fonctions disponibles pour le LLM :
--------------------------------------------------
myChatPlugin.chat:
WriterPlugin.Acronym: Generate an acronym for the given concept or phras...
WriterPlugin.AcronymGenerator: Given a request to generate an acronym from a stri...
WriterPlugin.AcronymReverse: Given a single word or acronym, generate the expan...
WriterPlugin.Brainstorm: Given a goal or topic description generate a list ...
WriterPlugin.EmailGen: Write an email from the given bullet points
WriterPlugin.EmailTo: Turn bullet points into an email to someone, using...
WriterPlugin.EnglishImprover: Translate text to English and improve it
WriterPlugin.NovelChapter: Write a chapter of a novel.
WriterPlugin.NovelChapterWithNotes: Write a chapter of a novel using notes about the c...
WriterPlugin.NovelOutline: Generate a list of chapter synopsis for a novel or...
WriterPlugin.Rewrite: Automatically generate compact notes for any text ...
WriterPlugin.ShortPoem: Turn a scenario into a short and entertaining poem...
WriterPlugin.StoryGen: Generate a list of synopsis for a novel or novella...
WriterPlugin.TellMeMore: Summarize given text or any text document
WriterPlugin.Translate: Translate the input into a language of your choice
WriterPlugin.TwoSentenceSummary: Summarize given text in two sentences or less
WriterPlugin.Shakespeare: Rewrite the input in the style of Shakespeare.
TextPlugin.lowercase: Convert a string to lowercase.
TextPlugin.trim: Trim whitespace from the start and end of a string...
TextPlugin.trim_end: Trim whitespace from the end of a string.
TextPlugin.trim_start: Trim whitespace from the start of a string.
TextPlugin.uppercase: Convert a string to uppercase.
MathPlugin.Add: Returns the addition result of the values provided...
MathPlugin.Subtract: Returns the difference of numbers provided (suppor...
==================================================
DEMO : Function Calling avec FunctionChoiceBehavior.Auto()
==================================================
Requete utilisateur :
J'ai besoin d'aide pour deux choses :
1. Convertir le texte "hello world" en majuscules
2. Calculer 25 + 17
Reponse du LLM (avec function calling) :
1) **"hello world"** en majuscules : **HELLO WORLD**
2) **25 + 17** = **42**
Exercice 2 : Plugin de conversion d’unites avec Function Calling
Objectif : Créer un plugin UnitConverterPlugin avec des fonctions de conversion (temperature, distance) et le rendre disponible via Function Calling automatique.
Le Function Calling automatique est puissant quand les plugins offrent des fonctions bien decrites avec des types précis. Vous allez créer un plugin que le LLM pourra appeler automatiquement pour convertir des unites dans une conversation.
Indices : - # Étape 1 : Créer deux fonctions celsius_to_fahrenheit et km_to_miles avec @kernel_function - # Étape 2 : Ajouter des descriptions claires et des annotations de type pour les paramètres - # Étape 3 : Tester avec une requête naturelle comme “Combien font 37 degrés Celsius en Fahrenheit ?” - # Indice : F = C * 9/5 + 32 et miles = km * 0.621371
# Annotated est requis par les signatures des kernel_functions ci-dessous.from typing import Annotatedclass UnitConverterPlugin:""" Plugin de conversion d'unites pour Function Calling. # Etape 1 : Implementer celsius_to_fahrenheit avec @kernel_function # Etape 2 : Implementer km_to_miles avec @kernel_function # Etape 3 : Ajouter des descriptions precises pour que le LLM sache quand les utiliser """@kernel_function(description="Convertit une temperature de Celsius en Fahrenheit")def celsius_to_fahrenheit(self, celsius: Annotated[float, "Temperature en degres Celsius"]) ->str:""" # Indice : Appliquer la formule F = C * 9/5 + 32 # Indice : Retourner une string descriptive comme "37.0 C = 98.6 F" """# TODO etudiant : implementer la conversionreturn"Exercice a completer"# TODO etudiant : retourner le resultat formatte@kernel_function(description="Convertit une distance de kilometres en miles")def km_to_miles(self, km: Annotated[float, "Distance en kilometres"]) ->str:""" # Indice : Appliquer la formule miles = km * 0.621371 """# TODO etudiant : implementer la conversionreturn"Exercice a completer"# TODO etudiant : retourner le resultat formatte# Test du plugin seulconverter = UnitConverterPlugin()print(converter.celsius_to_fahrenheit(37.0)) # Attendu : "37.0 C = 98.6 F"print(converter.km_to_miles(100.0)) # Attendu : "100.0 km = 62.1 miles"# TODO etudiant : ajouter le plugin au kernel et tester avec Function Calling Auto# kernel.add_plugin(UnitConverterPlugin(), plugin_name="converter")# Puis invoquer avec une requete naturelle via le chat serviceprint("Exercice a completer : integrez le plugin dans le kernel avec Function Calling")
Exercice a completer
Exercice a completer
Exercice a completer : integrez le plugin dans le kernel avec Function Calling
Interprétation : Architecture Function Calling
Sortie obtenue : Le LLM a automatiquement identifié et exécuté deux fonctions sans code manuel : - TextPlugin.uppercase("hello world") → “HELLO WORLD” - MathPlugin.Add(25, 17) → 42
Le LLM décide automatiquement quand et comment appeler les fonctions
Filtres
included_plugins: ["TextPlugin", "MathPlugin"]
Limitation des fonctions accessibles pour sécurité/performance
Auto-invoke
auto_invoke=True
Exécution automatique sans intervention manuelle
Points clés :
Pattern ReAct intégré : Le LLM raisonne (analyse la requête) puis agit (appelle les fonctions)
Chaînage transparent : Le LLM peut enchaîner plusieurs appels de fonctions si nécessaire
Extraction de paramètres : Les valeurs sont extraites du contexte naturel (“25 + 17” → Add(25, 17))
Formulation finale : Le résultat brut des fonctions est reformulé dans une réponse naturelle
Note technique : Cette approche remplace les anciens Planners (SequentialPlanner, FunctionCallingStepwisePlanner) qui généraient du XML intermédiaire. Function Calling utilise directement l’API OpenAI, ce qui réduit les tokens et améliore la fiabilité.
Comparaison avec Agents : - Function Calling : Orchestration simple, un LLM qui appelle des fonctions - Agents (voir notebook 03) : Plusieurs agents qui collaborent, chacun avec ses propres plugins
Cas d’usage typiques : - Assistants conversationnels avec accès à des APIs - Chatbots d’entreprise avec fonctions métier (recherche produits, calculs, requêtes DB) - Outils d’aide à la décision avec fonctions analytiques
Mémoire & Embeddings
SemanticTextMemory permet de stocker des textes (avec un embedding) dans un store : - VolatileMemoryStore (en mémoire) - ou connecteurs vers Pinecone, Azure Cognitive Search, Qdrant, etc.
On peut ensuite effectuer des requêtes sémantiques : await memory.search("MaCollection", "Quelle est mon budget pour 2024 ?")
Verification du GroundingPlugin
Le plugin de Grounding (ancrage) permet de : 1. Extraire les entites d’un texte resume 2. Verifier chaque entite par rapport a un texte source de reference 3. Corriger le resume en supprimant les informations non fondees
Ce mécanisme est essentiel pour reduire les hallucinations des LLMs et garantir que les reponses sont ancrees dans des faits verifiables.
# ============================# Cellule : Extrait Mémoire# ============================# CORRECTION: Utilisation des nouvelles approches pour la mémoire vectoriellefrom semantic_kernel.connectors.in_memory import InMemoryStorefrom semantic_kernel.connectors.ai.open_ai import OpenAITextEmbedding# Embedding service (ex : openai text-embedding-ada)embedding_service = OpenAITextEmbedding( service_id="embeddingService", ai_model_id="text-embedding-3-small")# Nouvelle approche avec InMemoryStorestore = InMemoryStore()# Ajout du service d'embedding au kernelkernel.add_service(embedding_service)# Pour la démonstration, on simule la mémoire et la recherchebudget_2024 ="Budget 2024 = 100k€"budget_2023 ="Budget 2023 = 70k€"print("Note: API de mémoire simplifiée pour cette version.")print(f"Informations stockées :")print(f" - {budget_2024}")print(f" - {budget_2023}")# Simulation de recherchequery ="Quel est mon budget pour 2024 ?"print(f"\nRequête : {query}")# Simulation simple : retour de la réponse connueprint("Réponse potentielle : ", budget_2024)
Note: API de mémoire simplifiée pour cette version.
Informations stockées :
- Budget 2024 = 100k€
- Budget 2023 = 70k€
Requête : Quel est mon budget pour 2024 ?
Réponse potentielle : Budget 2024 = 100k€
Interprétation : Mémoire Vectorielle et Embeddings
Sortie obtenue : Configuration d’un système de mémoire sémantique avec embeddings pour recherche contextuelle.
Composant
Technologie
Rôle
Embedding Service
OpenAI text-embedding-3-small
Convertit le texte en vecteurs denses (1536 dimensions)
Vector Store
InMemoryStore
Stockage volatile des embeddings pour recherche rapide
Query
“Quel est mon budget pour 2024 ?”
Recherche sémantique (pas keyword matching)
Résultat
“Budget 2024 = 100k€”
Document le plus proche sémantiquement
Points clés :
Recherche sémantique vs. keyword :
Keyword : Cherche les mots exacts “budget” et “2024”
Sémantique : Comprend l’intention et trouve les concepts liés même avec une formulation différente
Latence : <100ms pour recherche dans 10K documents
Note technique : Le notebook 05-VectorStores couvre l’intégration complète avec Qdrant pour des systèmes RAG (Retrieval-Augmented Generation) en production.
Cas d’usage typiques : - FAQ intelligente : Recherche dans une base de connaissances - RAG : Enrichir les réponses LLM avec des documents métier - Chatbot avec mémoire : Retrouver des conversations passées par similarité
Evolution de l’API de memoire
L’API de memoire vectorielle de Semantic Kernel a evolue significativement :
Nouvelle approche (2024+) : - InMemoryStore pour le stockage volatile - Services d’embedding dedies (OpenAITextEmbedding) - Integration directe avec les connecteurs vectoriels (Pinecone, Qdrant, Azure AI Search)
Cette evolution permet une meilleure separation des responsabilites et une integration plus flexible avec divers backends.
Configuration des reponses multiples
La fonctionnalite Multi-Result permet d’obtenir plusieurs completions pour un même prompt en un seul appel API. Cela est utile pour : - Generer des alternatives : Obtenir plusieurs variations d’une reponse - Evaluer la diversite : Comparer différentes formulations - Sélection humaine : Presenter plusieurs options a l’utilisateur
Le paramètre number_of_responses dans les settings contrôle le nombre de reponses retournees.
Hugging Face Intégration
Semantic Kernel peut se connecter à Hugging Face localement ou via API.
Exemple :
from semantic_kernel.connectors.ai.hugging_face import HuggingFaceTextCompletionhf_service = HuggingFaceTextCompletion( service_id="textHF", ai_model_id="distilgpt2", task="text-generation")kernel.add_service(hf_service)
Ensuite, on peut enregistrer une fonction sémantique ou invoquer directement hf_service.get_text_contents(...).
Groundedness Checking
Pour éviter les “hallucinations” d’un résumé :
1. Extrait la liste d’entités du résumé.
2. Vérifie la correspondance de chaque entité avec le texte source (référence).
3. Retire ou corrige les entités non-fondées.
Cela se fait via un plugin “GroundingPlugin” (ex. ExtraitEntities, ReferenceCheckEntities, ExciseEntities).
# ============================# Cellule : Groundedness Checking# ============================# Suppose qu'on a un "grounding_text" = un texte sourcegrounding_text ="""Votre budget 2024 est de 100k euros.Vous vivez à Genève.Vous avez investi 50k en actions."""# Suppose qu'on a un résumé "faux"summary_text ="""Mon budget 2024 est de 200k euros.J'habite à Milan."""plugins_directory ="./prompt_template_samples/"# On appelle un plugin (hypothétique) "GroundingPlugin" comportant 3 fonctionstry: grounding_plugin = kernel.add_plugin(parent_directory=plugins_directory, plugin_name="GroundingPlugin") extract_entities = grounding_plugin["ExtractEntities"] check_entities = grounding_plugin["ReferenceCheckEntities"] excise_entities = grounding_plugin["ExciseEntities"]# 1) Extraire entités avec les bonnes variables ext_result =await kernel.invoke( extract_entities, KernelArguments(input=summary_text, topic="entities", # Variable attendue par le template example_entities="Person, Location, Organization", # Variable attendue allow_dangerously_set_content=True ) )print("Entités détectées:", ext_result)# 2) Vérifier correspondance - Important: convertir ext_result en string ext_result_str =str(ext_result.value) ifhasattr(ext_result, 'value') elsestr(ext_result) check_result =await kernel.invoke( check_entities, KernelArguments(input=ext_result_str, # Utiliser la version string reference_context=grounding_text, topic="entities", # Ajouter les variables attendues allow_dangerously_set_content=True# IMPORTANT: autoriser le contenu complexe ) )print("Entités non-fondées:", check_result)# 3) Retirer entités non-fondées du summary check_result_str =str(check_result.value) ifhasattr(check_result, 'value') elsestr(check_result) excision =await kernel.invoke( excise_entities, KernelArguments(input=summary_text, ungrounded_entities=check_result_str, # Utiliser la version string allow_dangerously_set_content=True ) )print("Summary corrigé:", excision)exceptExceptionas e:print(f"Note: Le plugin GroundingPlugin n'est pas disponible ou fonctionnel: {e}")print("Démonstration alternative:")print(f"Texte source: {grounding_text[:50]}...")print(f"Résumé analysé: {summary_text[:50]}...")print("Entités potentiellement problématiques: Milan (au lieu de Genève), 200k€ (au lieu de 100k€)")
Entités détectées: <entities>
- 2024
- 200k euros
- Milan
</entities>
Entités non-fondées: <ungrounded_entities>
- 200k euros
- Milan
</ungrounded_entities>
Summary corrigé: Mon budget 2024 est de 200k.
J'habite dans une ville.
Les deux entités incohérentes (200k euros vs 100k euros, Milan vs Genève) ont été retirées du résumé corrigé
Nécessite un prompt bien calibré pour détecter toutes les incohérences
Fonctionne mieux avec des entités discrètes (lieux, noms) qu’avec des valeurs numériques
Note technique : Le paramètre allow_dangerously_set_content=True est nécessaire pour passer du contenu structuré (XML) entre les fonctions. En production, il faut valider ce contenu pour éviter les injections.
Améliorations possibles :
# Vérification multi-passesfor i inrange(3): # Itérer jusqu'à ce qu'il n'y ait plus d'entités non-fondées entities = extract_entities(summary) ungrounded = check_entities(entities, source)ifnot ungrounded:break summary = excise_entities(summary, ungrounded)
Cas d’usage typiques : - Résumés automatiques : Garantir que le résumé ne contient que des infos du document source - Chatbots réglementés : Assurer la conformité des réponses aux documents officiels - Fact-checking : Valider automatiquement les affirmations d’un texte
Résultats du Groundedness Checking
Le processus de verification d’ancrage comprend trois étapes :
Extraction : Identification des entites factuelles dans le resume (personnes, lieux, montants)
Verification : Comparaison de chaque entite avec le texte source de reference
Correction : Suppression ou modification des entites non-fondees
Dans l’exemple ci-dessus, les entites problematiques detectees seraient : - “Milan” (non mentionne dans le texte source, qui indique “Geneve”) - “200k euros” (le texte source indique “100k euros”)
Cette technique est essentielle pour reduire les hallucinations des LLMs dans les systèmes de production.
Multi-Result
OpenAI (ou Azure) peut renvoyer plusieurs complétions pour un même prompt.
On paramètre number_of_responses=3 dans les settings.
Ensuite, get_text_contents(...) ou get_chat_message_contents(...) renvoie un tableau de résultats.
# ============================# Cellule : Multi-Result# ============================from semantic_kernel.connectors.ai.open_ai import OpenAIChatPromptExecutionSettingssettings = OpenAIChatPromptExecutionSettings( service_id=service_id,# temperature not set: gpt-5-mini only supports default value (1) number_of_responses=3)# Utiliser le service chat existant (pas de modele legacy)chat_service = kernel.get_service(service_id)chat_history_multi = ChatHistory()chat_history_multi.add_user_message("Donne-moi une brève blague sur les chats :")# get_chat_message_contents() => liste de reponsesresponses =await chat_service.get_chat_message_contents(chat_history_multi, settings=settings)for i, r inenumerate(responses):print(f"Réponse n°{i+1}:\n{r}\n")
Réponse n°1:
Pourquoi les chats n’aiment pas l’ordinateur ?
Parce qu’ils ont peur de la souris.
Réponse n°2:
Pourquoi les chats détestent-ils l’ordinateur ?
Parce qu’ils ont peur de la souris !
Réponse n°3:
Pourquoi le chat s’est assis sur l’ordinateur ?
Parce qu’il voulait garder un œil sur la souris !
Interprétation : Multi-Result Generation
Sortie obtenue : Trois blagues différentes générées en un seul appel API avec number_of_responses=3.
Réponse
Contenu
Style
#1
“Pourquoi les chats n’aiment pas Internet ? Parce qu’ils ont peur des souris sans fil.”
Jeu de mots (souris = périphérique / animal)
#2
“Pourquoi le chat n’utilise jamais l’ordinateur ? Parce qu’il préfère attraper la souris avec ses pattes.”
Double sens (souris ordinateur / animal)
#3
“Pourquoi les chats aiment-ils l’ordinateur ? Parce qu’il y a une souris !”
Double sens (souris ordinateur / animal)
Points clés :
Avantages du Multi-Result :
Efficacité : Un seul appel API au lieu de 3 appels séquentiels
Coût : Réduit les coûts de latence réseau et overhead API
Diversité : Variations générées en parallèle avec la même température
UX : Permet à l’utilisateur de choisir la meilleure réponse
Paramètres de configuration :
settings = OpenAIChatPromptExecutionSettings( service_id=service_id,# temperature non définie : gpt-5-mini ne supporte que la valeur par défaut (1) number_of_responses=3# Nombre de variantes)
Note technique : get_text_contents() retourne une liste, contrairement à get_text_content() (singulier) qui retourne un seul résultat. L’API ChatCompletion a un équivalent avec get_chat_message_contents().
Cas d’usage avancés : - Génération de variantes marketing : Plusieurs versions d’un slogan - Traduction avec nuances : Différentes formulations d’une traduction - Résumé multi-angle : Résumés avec différents niveaux de détail - Code generation : Plusieurs implémentations d’un algorithme
Exercice 3 : Analyseur de diversite des reponses multiples
Objectif : Implementer une fonction qui analyse la diversite lexicale entre les reponses generees par number_of_responses=N et selectionne la meilleure selon un critere.
Quand on genere plusieurs reponses, il est utile de mesurer leur diversite (pour eviter des reponses trop similaires) et de sélectionner automatiquement la meilleure (la plus longue, la plus diverse, etc.).
Indices : - # Étape 1 : Extraire les mots uniques de chaque reponse avec set(response.split()) - # Étape 2 : Calculer le coefficient de Jaccard entre chaque paire de reponses (intersection / union) - # Étape 3 : Sélectionner la reponse avec le plus grand vocabulaire unique - # Indice : len(set1 & set2) / len(set1 | set2) donne le score de Jaccard
def analyze_diversity(responses: list[str]) ->dict:""" Analyse la diversite lexicale entre plusieurs reponses. # Etape 1 : Convertir chaque reponse en ensemble de mots uniques # Etape 2 : Calculer les scores de Jaccard entre chaque paire de reponses # Etape 3 : Identifier la reponse la plus riche (plus grand vocabulaire unique) Returns: dict avec avg_jaccard, best_response_idx, diversite_scores """# TODO etudiant : implementer l'analyse de diversitereturn {"avg_jaccard": 0.0, "best_response_idx": 0, "diversite_scores": []}# Test avec les reponses generees precedemmenttest_responses = ["Pourquoi les chats n'aiment pas Internet ? Parce qu'ils ont peur des souris sans fil.","Pourquoi le chat n'utilise jamais l'ordinateur ? Parce qu'il prefere attraper la souris.","Pourquoi les chats aiment l'ordinateur ? Parce qu'il y a une souris !",]result = analyze_diversity(test_responses)print(f"Score Jaccard moyen : {result['avg_jaccard']:.3f}")print(f"Reponse la plus riche : #{result['best_response_idx'] +1}")for i, score inenumerate(result.get('diversite_scores', [])):print(f" Reponse {i+1} : richesse = {score:.2f}")
Score Jaccard moyen : 0.000
Reponse la plus riche : #1
Conclusion et Resume
Dans ce notebook, nous avons explore les fonctionnalites avancees de Semantic Kernel :
Concept
Description
Statut
Chat avec KernelArguments
Gestion de l’historique et du contexte
Fondamental
Function Calling
Orchestration automatique via FunctionChoiceBehavior.Auto()
Moderne (remplace Planners)
Memoire & Embeddings
Stockage vectoriel avec InMemoryStore
API moderne
Hugging Face
Integration de modèles open-source
Optionnel
Groundedness Checking
Reduction des hallucinations
Production
Multi-Result
Generations multiples en un appel
Creatif
Points cles a retenir
Function Calling remplace les Planners : Plus simple, plus fiable, moins de tokens
API Memory modernisee : InMemoryStore + OpenAITextEmbedding (voir notebook 05 pour Qdrant)
Groundedness : Essentiel pour les systèmes de production
Prochaines étapes
03-Agents : Agents avec ChatCompletionAgent, AgentGroupChat
Nous avons vu : 1. Un Chat basique avec KernelArguments. 2. Les Planners (Sequential, Stepwise) pour orchestrer dynamiquement des steps. 3. La Mémoire & embeddings pour stocker/rechercher des informations sémantiques. 4. L’intégration Hugging Face pour exécuter localement des modèles open-source. 5. Un Groundedness Checking minimal pour éviter les hallucinations. 6. La gestion de plusieurs réponses (Multi-Result) avec un seul appel.
Ces fonctionnalités permettent de créer des scénarios complexes : chat évolué, question-answering avec mémoire persistante, planification automatique, usage local ou cloud, etc. Bonne exploration !