Partager via


Tutoriel : Créer un assistant conversation multitour avec Foundry Local

Dans ce tutoriel, vous créez un assistant de conversation interactif qui s’exécute entièrement sur votre appareil. L’assistant gère le contexte de conversation entre plusieurs échanges, ce qui vous rappelle ce que vous avez abordé précédemment dans la conversation. Vous utilisez le Kit de développement logiciel (SDK) Local Foundry pour sélectionner un modèle, définir une invite système et diffuser en continu le jeton de réponses par jeton.

Dans ce tutoriel, vous allez apprendre à :

  • Configurer un projet et installer le Kit de développement logiciel (SDK) Local Foundry
  • Parcourir le catalogue de modèles et sélectionner un modèle
  • Définir une invite système pour mettre en forme le comportement de l’Assistant
  • Implémenter une conversation à plusieurs tour avec l’historique des messages
  • Diffuser des réponses pour une expérience réactive
  • Nettoyer les ressources lorsque la conversation se termine

Prerequisites

  • Un ordinateur Windows, macOS ou Linux avec au moins 8 Go de RAM.

Référentiel d’exemples

L’exemple de code complet de cet article est disponible dans le dépôt Foundry Local GitHub. Pour cloner le référentiel et accéder à l’exemple d’utilisation :

git clone https://github.com/microsoft/Foundry-Local.git
cd Foundry-Local/samples/cs/tutorial-chat-assistant

Installer des packages

Si vous développez ou expédiez sur Windows, sélectionnez l'onglet Windows. Le package Windows s’intègre au runtime Windows ML . Il fournit la même surface d’surface d’API avec une étendue plus large d’accélération matérielle.

dotnet add package Microsoft.AI.Foundry.Local.WinML
dotnet add package OpenAI

Les exemples C# dans le référentiel GitHub sont des projets préconfigurés. Si vous développez ex nihilo, vous devez lire la référence de Foundry Local SDK pour plus d’informations sur la configuration de votre projet C# avec Foundry Local.

Parcourir le catalogue et sélectionner un modèle

Le SDK Local Foundry fournit un catalogue de modèles qui répertorie tous les modèles disponibles. Dans cette étape, vous initialisez le Kit de développement logiciel (SDK) et sélectionnez un modèle pour votre assistant conversation.

  • Ouvrez et remplacez Program.cs son contenu par le code suivant pour initialiser le Kit de développement logiciel (SDK) et sélectionnez un modèle :

    CancellationToken ct = CancellationToken.None;
    
    var config = new Configuration
    {
        AppName = "foundry_local_samples",
        LogLevel = Microsoft.AI.Foundry.Local.LogLevel.Information
    };
    
    using var loggerFactory = LoggerFactory.Create(builder =>
    {
        builder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information);
    });
    var logger = loggerFactory.CreateLogger<Program>();
    
    // Initialize the singleton instance
    await FoundryLocalManager.CreateAsync(config, logger);
    var mgr = FoundryLocalManager.Instance;
    
    // Download and register all execution providers.
    var currentEp = "";
    await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
    {
        if (epName != currentEp)
        {
            if (currentEp != "") Console.WriteLine();
            currentEp = epName;
        }
        Console.Write($"\r  {epName.PadRight(30)}  {percent,6:F1}%");
    });
    if (currentEp != "") Console.WriteLine();
    
    // Select and load a model from the catalog
    var catalog = await mgr.GetCatalogAsync();
    var model = await catalog.GetModelAsync("qwen2.5-0.5b")
        ?? throw new Exception("Model not found");
    
    await model.DownloadAsync(progress =>
    {
        Console.Write($"\rDownloading model: {progress:F2}%");
        if (progress >= 100f) Console.WriteLine();
    });
    
    await model.LoadAsync();
    Console.WriteLine("Model loaded and ready.");
    
    // Get a chat client
    var chatClient = await model.GetChatClientAsync();
    

    La GetModelAsync méthode accepte un alias de modèle, qui est un nom convivial court qui correspond à un modèle spécifique dans le catalogue. La DownloadAsync méthode extrait les pondérations du modèle dans votre cache local et LoadAsync rend le modèle prêt pour l’inférence.

Définir une invite système

Une invite système définit la personnalité et le comportement de l’assistant. Il s’agit du premier message de l’historique des conversations et du modèle le référence tout au long de la conversation.

Ajoutez une invite système pour mettre en forme la façon dont l’Assistant répond :

// Start the conversation with a system prompt
var messages = new List<ChatMessage>
{
    new ChatMessage
    {
        Role = "system",
        Content = "You are a helpful, friendly assistant. Keep your responses " +
                  "concise and conversational. If you don't know something, say so."
    }
};

Conseil / Astuce

Expérimentez avec différentes invites système pour modifier le comportement de l’assistant. Par exemple, vous pouvez lui demander de répondre en tant que pirate, enseignant ou expert de domaine.

Implémenter une conversation multitour

Un assistant de conversation doit maintenir le contexte entre plusieurs échanges. Pour ce faire, conservez la liste de tous les messages (système, utilisateur et Assistant) et envoyez la liste complète avec chaque requête. Le modèle utilise cet historique pour générer des réponses contextuellement pertinentes.

Ajoutez une boucle de conversation qui :

  • Lit l’entrée utilisateur à partir de la console.
  • Ajoute le message utilisateur à l’historique.
  • Envoie l’historique complet au modèle.
  • Ajoute la réponse de l’assistant à l’historique pour le tour suivant.
while (true)
{
    Console.Write("You: ");
    var userInput = Console.ReadLine();
    if (string.IsNullOrWhiteSpace(userInput) ||
        userInput.Equals("quit", StringComparison.OrdinalIgnoreCase) ||
        userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
    {
        break;
    }

    // Add the user's message to conversation history
    messages.Add(new ChatMessage { Role = "user", Content = userInput });

    // Stream the response token by token
    Console.Write("Assistant: ");
    var fullResponse = string.Empty;
    var streamingResponse = chatClient.CompleteChatStreamingAsync(messages, ct);
    await foreach (var chunk in streamingResponse)
    {
        var content = chunk.Choices[0].Message.Content;
        if (!string.IsNullOrEmpty(content))
        {
            Console.Write(content);
            Console.Out.Flush();
            fullResponse += content;
        }
    }
    Console.WriteLine("\n");

    // Add the complete response to conversation history
    messages.Add(new ChatMessage { Role = "assistant", Content = fullResponse });
}

Chaque appel pour CompleteChatAsync reçoit l’historique complet des messages. Il s’agit de la façon dont le modèle « se souvient » des tours précédents : il ne stocke pas l’état entre les appels.

Ajouter des réponses en flux

La diffusion en continu imprime chaque jeton au fur et à mesure qu’il est généré, ce qui rend l’assistant plus réactif. Remplacez l’appel CompleteChatAsync par CompleteChatStreamingAsync le flux du jeton de réponse par jeton.

Mettez à jour la boucle de conversation pour utiliser la diffusion en continu :

// Stream the response token by token
Console.Write("Assistant: ");
var fullResponse = string.Empty;
var streamingResponse = chatClient.CompleteChatStreamingAsync(messages, ct);
await foreach (var chunk in streamingResponse)
{
    var content = chunk.Choices[0].Message.Content;
    if (!string.IsNullOrEmpty(content))
    {
        Console.Write(content);
        Console.Out.Flush();
        fullResponse += content;
    }
}
Console.WriteLine("\n");

La version de diffusion en continu accumule la réponse complète afin qu’elle puisse être ajoutée à l’historique des conversations une fois le flux terminé.

Code complet

Remplacez le contenu de Program.cs par le code complet suivant :

using Microsoft.AI.Foundry.Local;
using Betalgo.Ranul.OpenAI.ObjectModels.RequestModels;
using Microsoft.Extensions.Logging;

CancellationToken ct = CancellationToken.None;

var config = new Configuration
{
    AppName = "foundry_local_samples",
    LogLevel = Microsoft.AI.Foundry.Local.LogLevel.Information
};

using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information);
});
var logger = loggerFactory.CreateLogger<Program>();

// Initialize the singleton instance
await FoundryLocalManager.CreateAsync(config, logger);
var mgr = FoundryLocalManager.Instance;

// Download and register all execution providers.
var currentEp = "";
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
{
    if (epName != currentEp)
    {
        if (currentEp != "") Console.WriteLine();
        currentEp = epName;
    }
    Console.Write($"\r  {epName.PadRight(30)}  {percent,6:F1}%");
});
if (currentEp != "") Console.WriteLine();

// Select and load a model from the catalog
var catalog = await mgr.GetCatalogAsync();
var model = await catalog.GetModelAsync("qwen2.5-0.5b")
    ?? throw new Exception("Model not found");

await model.DownloadAsync(progress =>
{
    Console.Write($"\rDownloading model: {progress:F2}%");
    if (progress >= 100f) Console.WriteLine();
});

await model.LoadAsync();
Console.WriteLine("Model loaded and ready.");

// Get a chat client
var chatClient = await model.GetChatClientAsync();

// Start the conversation with a system prompt
var messages = new List<ChatMessage>
{
    new ChatMessage
    {
        Role = "system",
        Content = "You are a helpful, friendly assistant. Keep your responses " +
                  "concise and conversational. If you don't know something, say so."
    }
};

Console.WriteLine("\nChat assistant ready! Type 'quit' to exit.\n");

while (true)
{
    Console.Write("You: ");
    var userInput = Console.ReadLine();
    if (string.IsNullOrWhiteSpace(userInput) ||
        userInput.Equals("quit", StringComparison.OrdinalIgnoreCase) ||
        userInput.Equals("exit", StringComparison.OrdinalIgnoreCase))
    {
        break;
    }

    // Add the user's message to conversation history
    messages.Add(new ChatMessage { Role = "user", Content = userInput });

    // Stream the response token by token
    Console.Write("Assistant: ");
    var fullResponse = string.Empty;
    var streamingResponse = chatClient.CompleteChatStreamingAsync(messages, ct);
    await foreach (var chunk in streamingResponse)
    {
        var content = chunk.Choices[0].Message.Content;
        if (!string.IsNullOrEmpty(content))
        {
            Console.Write(content);
            Console.Out.Flush();
            fullResponse += content;
        }
    }
    Console.WriteLine("\n");

    // Add the complete response to conversation history
    messages.Add(new ChatMessage { Role = "assistant", Content = fullResponse });
}

// Clean up - unload the model
await model.UnloadAsync();
Console.WriteLine("Model unloaded. Goodbye!");

Exécutez l'assistant de chat :

dotnet run

Vous voyez un résultat similaire à :

Downloading model: 100.00%
Model loaded and ready.

Chat assistant ready! Type 'quit' to exit.

You: What is photosynthesis?
Assistant: Photosynthesis is the process plants use to convert sunlight, water, and carbon
dioxide into glucose and oxygen. It mainly happens in the leaves, inside structures
called chloroplasts.

You: Why is it important for other living things?
Assistant: It's essential because photosynthesis produces the oxygen that most living things
breathe. It also forms the base of the food chain — animals eat plants or eat other
animals that depend on plants for energy.

You: quit
Model unloaded. Goodbye!

Notez comment l’assistant mémorise le contexte des tours précédents , quand vous demandez « Pourquoi est-il important pour d’autres choses vivantes ? », il sait que vous parlez toujours de photosynthèse.

Référentiel d’exemples

L’exemple de code complet de cet article est disponible dans le dépôt Foundry Local GitHub. Pour cloner le référentiel et accéder à l’exemple d’utilisation :

git clone https://github.com/microsoft/Foundry-Local.git
cd Foundry-Local/samples/js/tutorial-chat-assistant

Installer des packages

Si vous développez ou expédiez sur Windows, sélectionnez l'onglet Windows. Le package Windows s’intègre au runtime Windows ML . Il fournit la même surface d’surface d’API avec une étendue plus large d’accélération matérielle.

npm install foundry-local-sdk-winml openai

Parcourir le catalogue et sélectionner un modèle

Le SDK Local Foundry fournit un catalogue de modèles qui répertorie tous les modèles disponibles. Dans cette étape, vous initialisez le Kit de développement logiciel (SDK) et sélectionnez un modèle pour votre assistant conversation.

  1. Créez un fichier appelé index.js.

  2. Ajoutez le code suivant pour initialiser le Kit de développement logiciel (SDK) et sélectionnez un modèle :

    // Initialize the Foundry Local SDK
    const manager = FoundryLocalManager.create({
        appName: 'foundry_local_samples',
        logLevel: 'info'
    });
    
    // Download and register all execution providers.
    let currentEp = '';
    await manager.downloadAndRegisterEps((epName, percent) => {
        if (epName !== currentEp) {
            if (currentEp !== '') process.stdout.write('\n');
            currentEp = epName;
        }
        process.stdout.write(`\r  ${epName.padEnd(30)}  ${percent.toFixed(1).padStart(5)}%`);
    });
    if (currentEp !== '') process.stdout.write('\n');
    
    // Select and load a model from the catalog
    const model = await manager.catalog.getModel('qwen2.5-0.5b');
    
    await model.download((progress) => {
        process.stdout.write(`\rDownloading model: ${progress.toFixed(2)}%`);
    });
    console.log('\nModel downloaded.');
    
    await model.load();
    console.log('Model loaded and ready.');
    
    // Create a chat client
    const chatClient = model.createChatClient();
    

    La getModel méthode accepte un alias de modèle, qui est un nom convivial court qui correspond à un modèle spécifique dans le catalogue. La download méthode extrait les pondérations du modèle dans votre cache local et load rend le modèle prêt pour l’inférence.

Définir une invite système

Une invite système définit la personnalité et le comportement de l’assistant. Il s’agit du premier message de l’historique des conversations et du modèle le référence tout au long de la conversation.

Ajoutez une invite système pour mettre en forme la façon dont l’Assistant répond :

// Start the conversation with a system prompt
const messages = [
    {
        role: 'system',
        content: 'You are a helpful, friendly assistant. Keep your responses ' +
                 'concise and conversational. If you don\'t know something, say so.'
    }
];

Conseil / Astuce

Expérimentez avec différentes invites système pour modifier le comportement de l’assistant. Par exemple, vous pouvez lui demander de répondre en tant que pirate, enseignant ou expert de domaine.

Implémenter une conversation multitour

Un assistant de conversation doit maintenir le contexte entre plusieurs échanges. Pour ce faire, conservez la liste de tous les messages (système, utilisateur et Assistant) et envoyez la liste complète avec chaque requête. Le modèle utilise cet historique pour générer des réponses contextuellement pertinentes.

Ajoutez une boucle de conversation qui :

  • Lit l’entrée utilisateur à partir de la console.
  • Ajoute le message utilisateur à l’historique.
  • Envoie l’historique complet au modèle.
  • Ajoute la réponse de l’assistant à l’historique pour le tour suivant.
while (true) {
    const userInput = await askQuestion('You: ');
    if (userInput.trim().toLowerCase() === 'quit' ||
        userInput.trim().toLowerCase() === 'exit') {
        break;
    }

    // Add the user's message to conversation history
    messages.push({ role: 'user', content: userInput });

    // Stream the response token by token
    process.stdout.write('Assistant: ');
    let fullResponse = '';
    for await (const chunk of chatClient.completeStreamingChat(messages)) {
        const content = chunk.choices?.[0]?.delta?.content;
        if (content) {
            process.stdout.write(content);
            fullResponse += content;
        }
    }
    console.log('\n');

    // Add the complete response to conversation history
    messages.push({ role: 'assistant', content: fullResponse });
}

Chaque appel pour completeChat reçoit l’historique complet des messages. Il s’agit de la façon dont le modèle « se souvient » des tours précédents : il ne stocke pas l’état entre les appels.

Ajouter des réponses en flux

La diffusion en continu imprime chaque jeton au fur et à mesure qu’il est généré, ce qui rend l’assistant plus réactif. Remplacez l'appel completeChat par completeStreamingChat pour diffuser le flux du jeton de réponse par jeton.

Mettez à jour la boucle de conversation pour utiliser la diffusion en continu :

// Stream the response token by token
process.stdout.write('Assistant: ');
let fullResponse = '';
for await (const chunk of chatClient.completeStreamingChat(messages)) {
    const content = chunk.choices?.[0]?.delta?.content;
    if (content) {
        process.stdout.write(content);
        fullResponse += content;
    }
}
console.log('\n');

La version de diffusion en continu accumule la réponse complète afin qu’elle puisse être ajoutée à l’historique des conversations une fois le flux terminé.

Code complet

Créez un fichier nommé index.js et ajoutez le code complet suivant :

import { FoundryLocalManager } from 'foundry-local-sdk';
import * as readline from 'readline';

// Initialize the Foundry Local SDK
const manager = FoundryLocalManager.create({
    appName: 'foundry_local_samples',
    logLevel: 'info'
});

// Download and register all execution providers.
let currentEp = '';
await manager.downloadAndRegisterEps((epName, percent) => {
    if (epName !== currentEp) {
        if (currentEp !== '') process.stdout.write('\n');
        currentEp = epName;
    }
    process.stdout.write(`\r  ${epName.padEnd(30)}  ${percent.toFixed(1).padStart(5)}%`);
});
if (currentEp !== '') process.stdout.write('\n');

// Select and load a model from the catalog
const model = await manager.catalog.getModel('qwen2.5-0.5b');

await model.download((progress) => {
    process.stdout.write(`\rDownloading model: ${progress.toFixed(2)}%`);
});
console.log('\nModel downloaded.');

await model.load();
console.log('Model loaded and ready.');

// Create a chat client
const chatClient = model.createChatClient();

// Start the conversation with a system prompt
const messages = [
    {
        role: 'system',
        content: 'You are a helpful, friendly assistant. Keep your responses ' +
                 'concise and conversational. If you don\'t know something, say so.'
    }
];

// Set up readline for console input
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

const askQuestion = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));

console.log('\nChat assistant ready! Type \'quit\' to exit.\n');

while (true) {
    const userInput = await askQuestion('You: ');
    if (userInput.trim().toLowerCase() === 'quit' ||
        userInput.trim().toLowerCase() === 'exit') {
        break;
    }

    // Add the user's message to conversation history
    messages.push({ role: 'user', content: userInput });

    // Stream the response token by token
    process.stdout.write('Assistant: ');
    let fullResponse = '';
    for await (const chunk of chatClient.completeStreamingChat(messages)) {
        const content = chunk.choices?.[0]?.delta?.content;
        if (content) {
            process.stdout.write(content);
            fullResponse += content;
        }
    }
    console.log('\n');

    // Add the complete response to conversation history
    messages.push({ role: 'assistant', content: fullResponse });
}

// Clean up - unload the model
await model.unload();
console.log('Model unloaded. Goodbye!');
rl.close();

Exécutez l'assistant de chat :

node index.js

Vous voyez un résultat similaire à :

Downloading model: 100.00%
Model downloaded.
Model loaded and ready.

Chat assistant ready! Type 'quit' to exit.

You: What is photosynthesis?
Assistant: Photosynthesis is the process plants use to convert sunlight, water, and carbon
dioxide into glucose and oxygen. It mainly happens in the leaves, inside structures
called chloroplasts.

You: Why is it important for other living things?
Assistant: It's essential because photosynthesis produces the oxygen that most living things
breathe. It also forms the base of the food chain — animals eat plants or eat other
animals that depend on plants for energy.

You: quit
Model unloaded. Goodbye!

Notez comment l’assistant mémorise le contexte des tours précédents , quand vous demandez « Pourquoi est-il important pour d’autres choses vivantes ? », il sait que vous parlez toujours de photosynthèse.

Référentiel d’exemples

L’exemple de code complet de cet article est disponible dans le dépôt Foundry Local GitHub. Pour cloner le référentiel et accéder à l’exemple d’utilisation :

git clone https://github.com/microsoft/Foundry-Local.git
cd Foundry-Local/samples/python/tutorial-chat-assistant

Installer des packages

Si vous développez ou expédiez sur Windows, sélectionnez l'onglet Windows. Le package Windows s’intègre au runtime Windows ML . Il fournit la même surface d’surface d’API avec une étendue plus large d’accélération matérielle.

pip install foundry-local-sdk-winml openai

Parcourir le catalogue et sélectionner un modèle

Le SDK Local Foundry fournit un catalogue de modèles qui répertorie tous les modèles disponibles. Dans cette étape, vous initialisez le Kit de développement logiciel (SDK) et sélectionnez un modèle pour votre assistant conversation.

  1. Créez un fichier appelé main.py.

  2. Ajoutez le code suivant pour initialiser le Kit de développement logiciel (SDK) et sélectionnez un modèle :

    # Initialize the Foundry Local SDK
    config = Configuration(app_name="foundry_local_samples")
    FoundryLocalManager.initialize(config)
    manager = FoundryLocalManager.instance
    
    # Download and register all execution providers.
    current_ep = ""
    def ep_progress(ep_name: str, percent: float):
        nonlocal current_ep
        if ep_name != current_ep:
            if current_ep:
                print()
            current_ep = ep_name
        print(f"\r  {ep_name:<30}  {percent:5.1f}%", end="", flush=True)
    
    manager.download_and_register_eps(progress_callback=ep_progress)
    if current_ep:
        print()
    
    # Select and load a model from the catalog
    model = manager.catalog.get_model("qwen2.5-0.5b")
    model.download(lambda progress: print(f"\rDownloading model: {progress:.2f}%", end="", flush=True))
    print()
    model.load()
    print("Model loaded and ready.")
    
    # Get a chat client
    client = model.get_chat_client()
    

    La get_model méthode accepte un alias de modèle, qui est un nom convivial court qui correspond à un modèle spécifique dans le catalogue. La download méthode extrait les pondérations du modèle dans votre cache local et load rend le modèle prêt pour l’inférence.

Définir une invite système

Une invite système définit la personnalité et le comportement de l’assistant. Il s’agit du premier message de l’historique des conversations et du modèle le référence tout au long de la conversation.

Ajoutez une invite système pour mettre en forme la façon dont l’Assistant répond :

# Start the conversation with a system prompt
messages = [
    {
        "role": "system",
        "content": "You are a helpful, friendly assistant. Keep your responses "
                   "concise and conversational. If you don't know something, say so."
    }
]

Conseil / Astuce

Expérimentez avec différentes invites système pour modifier le comportement de l’assistant. Par exemple, vous pouvez lui demander de répondre en tant que pirate, enseignant ou expert de domaine.

Implémenter une conversation multitour

Un assistant de conversation doit maintenir le contexte entre plusieurs échanges. Pour ce faire, conservez la liste de tous les messages (système, utilisateur et Assistant) et envoyez la liste complète avec chaque requête. Le modèle utilise cet historique pour générer des réponses contextuellement pertinentes.

Ajoutez une boucle de conversation qui :

  • Lit l’entrée utilisateur à partir de la console.
  • Ajoute le message utilisateur à l’historique.
  • Envoie l’historique complet au modèle.
  • Ajoute la réponse de l’assistant à l’historique pour le tour suivant.
while True:
    user_input = input("You: ")
    if user_input.strip().lower() in ("quit", "exit"):
        break

    # Add the user's message to conversation history
    messages.append({"role": "user", "content": user_input})

    # Stream the response token by token
    print("Assistant: ", end="", flush=True)
    full_response = ""
    for chunk in client.complete_streaming_chat(messages):
        content = chunk.choices[0].delta.content
        if content:
            print(content, end="", flush=True)
            full_response += content
    print("\n")

    # Add the complete response to conversation history
    messages.append({"role": "assistant", "content": full_response})

Chaque appel pour complete_chat reçoit l’historique complet des messages. Il s’agit de la façon dont le modèle « se souvient » des tours précédents : il ne stocke pas l’état entre les appels.

Ajouter des réponses en flux

La diffusion en continu imprime chaque jeton au fur et à mesure qu’il est généré, ce qui rend l’assistant plus réactif. Remplacez l'appel complete_chat par complete_streaming_chat pour diffuser le flux du jeton de réponse par jeton.

Mettez à jour la boucle de conversation pour utiliser la diffusion en continu :

# Stream the response token by token
print("Assistant: ", end="", flush=True)
full_response = ""
for chunk in client.complete_streaming_chat(messages):
    content = chunk.choices[0].delta.content
    if content:
        print(content, end="", flush=True)
        full_response += content
print("\n")

La version de diffusion en continu accumule la réponse complète afin qu’elle puisse être ajoutée à l’historique des conversations une fois le flux terminé.

Code complet

Créez un fichier nommé main.py et ajoutez le code complet suivant :

from foundry_local_sdk import Configuration, FoundryLocalManager


def main():
    # Initialize the Foundry Local SDK
    config = Configuration(app_name="foundry_local_samples")
    FoundryLocalManager.initialize(config)
    manager = FoundryLocalManager.instance

    # Download and register all execution providers.
    current_ep = ""
    def ep_progress(ep_name: str, percent: float):
        nonlocal current_ep
        if ep_name != current_ep:
            if current_ep:
                print()
            current_ep = ep_name
        print(f"\r  {ep_name:<30}  {percent:5.1f}%", end="", flush=True)

    manager.download_and_register_eps(progress_callback=ep_progress)
    if current_ep:
        print()

    # Select and load a model from the catalog
    model = manager.catalog.get_model("qwen2.5-0.5b")
    model.download(lambda progress: print(f"\rDownloading model: {progress:.2f}%", end="", flush=True))
    print()
    model.load()
    print("Model loaded and ready.")

    # Get a chat client
    client = model.get_chat_client()

    # Start the conversation with a system prompt
    messages = [
        {
            "role": "system",
            "content": "You are a helpful, friendly assistant. Keep your responses "
                       "concise and conversational. If you don't know something, say so."
        }
    ]

    print("\nChat assistant ready! Type 'quit' to exit.\n")

    while True:
        user_input = input("You: ")
        if user_input.strip().lower() in ("quit", "exit"):
            break

        # Add the user's message to conversation history
        messages.append({"role": "user", "content": user_input})

        # Stream the response token by token
        print("Assistant: ", end="", flush=True)
        full_response = ""
        for chunk in client.complete_streaming_chat(messages):
            content = chunk.choices[0].delta.content
            if content:
                print(content, end="", flush=True)
                full_response += content
        print("\n")

        # Add the complete response to conversation history
        messages.append({"role": "assistant", "content": full_response})

    # Clean up - unload the model
    model.unload()
    print("Model unloaded. Goodbye!")


if __name__ == "__main__":
    main()

Exécutez l'assistant de chat :

python main.py

Vous voyez un résultat similaire à :

Downloading model: 100.00%
Model loaded and ready.

Chat assistant ready! Type 'quit' to exit.

You: What is photosynthesis?
Assistant: Photosynthesis is the process plants use to convert sunlight, water, and carbon
dioxide into glucose and oxygen. It mainly happens in the leaves, inside structures
called chloroplasts.

You: Why is it important for other living things?
Assistant: It's essential because photosynthesis produces the oxygen that most living things
breathe. It also forms the base of the food chain — animals eat plants or eat other
animals that depend on plants for energy.

You: quit
Model unloaded. Goodbye!

Notez comment l’assistant mémorise le contexte des tours précédents , quand vous demandez « Pourquoi est-il important pour d’autres choses vivantes ? », il sait que vous parlez toujours de photosynthèse.

Référentiel d’exemples

L’exemple de code complet de cet article est disponible dans le dépôt Foundry Local GitHub. Pour cloner le référentiel et accéder à l’exemple d’utilisation :

git clone https://github.com/microsoft/Foundry-Local.git
cd Foundry-Local/samples/rust/tutorial-chat-assistant

Installer des packages

Si vous développez ou expédiez sur Windows, sélectionnez l'onglet Windows. Le package Windows s’intègre au runtime Windows ML . Il fournit la même surface d’surface d’API avec une étendue plus large d’accélération matérielle.

cargo add foundry-local-sdk --features winml
cargo add tokio --features full
cargo add tokio-stream anyhow

Parcourir le catalogue et sélectionner un modèle

Le SDK Local Foundry fournit un catalogue de modèles qui répertorie tous les modèles disponibles. Dans cette étape, vous initialisez le Kit de développement logiciel (SDK) et sélectionnez un modèle pour votre assistant conversation.

  • Ouvrez et remplacez src/main.rs son contenu par le code suivant pour initialiser le Kit de développement logiciel (SDK) et sélectionnez un modèle :

    // Initialize the Foundry Local SDK
    let manager = FoundryLocalManager::create(FoundryLocalConfig::new("chat-assistant"))?;
    
    // Download and register all execution providers.
    manager
        .download_and_register_eps_with_progress(None, {
            let mut current_ep = String::new();
            move |ep_name: &str, percent: f64| {
                if ep_name != current_ep {
                    if !current_ep.is_empty() {
                        println!();
                    }
                    current_ep = ep_name.to_string();
                }
                print!("\r  {:<30}  {:5.1}%", ep_name, percent);
                io::stdout().flush().ok();
            }
        })
        .await?;
    println!();
    
    // Select and load a model from the catalog
    let model = manager.catalog().get_model("qwen2.5-0.5b").await?;
    
    if !model.is_cached().await? {
        println!("Downloading model...");
        model
            .download(Some(|progress: f64| {
                print!("\r  {progress:.1}%");
                io::stdout().flush().ok();
            }))
            .await?;
        println!();
    }
    
    model.load().await?;
    println!("Model loaded and ready.");
    
    // Create a chat client
    let client = model.create_chat_client().temperature(0.7).max_tokens(512);
    

    La get_model méthode accepte un alias de modèle, qui est un nom convivial court qui correspond à un modèle spécifique dans le catalogue. La download méthode extrait les pondérations du modèle dans votre cache local et load rend le modèle prêt pour l’inférence.

Définir une invite système

Une invite système définit la personnalité et le comportement de l’assistant. Il s’agit du premier message de l’historique des conversations et du modèle le référence tout au long de la conversation.

Ajoutez une invite système pour mettre en forme la façon dont l’Assistant répond :

// Start the conversation with a system prompt
let mut messages: Vec<ChatCompletionRequestMessage> = vec![
    ChatCompletionRequestSystemMessage::from(
        "You are a helpful, friendly assistant. Keep your responses \
         concise and conversational. If you don't know something, say so.",
    )
    .into(),
];

Conseil / Astuce

Expérimentez avec différentes invites système pour modifier le comportement de l’assistant. Par exemple, vous pouvez lui demander de répondre en tant que pirate, enseignant ou expert de domaine.

Implémenter une conversation multitour

Un assistant de conversation doit maintenir le contexte entre plusieurs échanges. Pour ce faire, conservez un vecteur de tous les messages (système, utilisateur et Assistant) et envoyez la liste complète avec chaque requête. Le modèle utilise cet historique pour générer des réponses contextuellement pertinentes.

Ajoutez une boucle de conversation qui :

  • Lit l’entrée utilisateur à partir de la console.
  • Ajoute le message utilisateur à l’historique.
  • Envoie l’historique complet au modèle.
  • Ajoute la réponse de l’assistant à l’historique pour le tour suivant.
loop {
    print!("You: ");
    io::stdout().flush()?;

    let mut input = String::new();
    stdin.lock().read_line(&mut input)?;
    let input = input.trim();

    if input.eq_ignore_ascii_case("quit") || input.eq_ignore_ascii_case("exit") {
        break;
    }

    // Add the user's message to conversation history
    messages.push(ChatCompletionRequestUserMessage::from(input).into());

    // Stream the response token by token
    print!("Assistant: ");
    io::stdout().flush()?;
    let mut full_response = String::new();
    let mut stream = client.complete_streaming_chat(&messages, None).await?;
    while let Some(chunk) = stream.next().await {
        let chunk = chunk?;
        if let Some(choice) = chunk.choices.first() {
            if let Some(ref content) = choice.delta.content {
                print!("{content}");
                io::stdout().flush()?;
                full_response.push_str(content);
            }
        }
    }
    println!("\n");

    // Add the complete response to conversation history
    let assistant_msg: ChatCompletionRequestMessage = serde_json::from_value(
        serde_json::json!({"role": "assistant", "content": full_response}),
    )?;
    messages.push(assistant_msg);
}

Chaque appel pour complete_chat reçoit l’historique complet des messages. Il s’agit de la façon dont le modèle « se souvient » des tours précédents : il ne stocke pas l’état entre les appels.

Ajouter des réponses en flux

La diffusion en continu imprime chaque jeton au fur et à mesure qu’il est généré, ce qui rend l’assistant plus réactif. Remplacez l'appel complete_chat par complete_streaming_chat pour diffuser le flux du jeton de réponse par jeton.

Mettez à jour la boucle de conversation pour utiliser la diffusion en continu :

// Stream the response token by token
print!("Assistant: ");
io::stdout().flush()?;
let mut full_response = String::new();
let mut stream = client.complete_streaming_chat(&messages, None).await?;
while let Some(chunk) = stream.next().await {
    let chunk = chunk?;
    if let Some(choice) = chunk.choices.first() {
        if let Some(ref content) = choice.delta.content {
            print!("{content}");
            io::stdout().flush()?;
            full_response.push_str(content);
        }
    }
}
println!("\n");

La version de diffusion en continu accumule la réponse complète afin qu’elle puisse être ajoutée à l’historique des conversations une fois le flux terminé.

Code complet

Remplacez le contenu de src/main.rs par le code complet suivant :

use foundry_local_sdk::{
    ChatCompletionRequestMessage,
    ChatCompletionRequestSystemMessage, ChatCompletionRequestUserMessage,
    FoundryLocalConfig, FoundryLocalManager,
};
use std::io::{self, BufRead, Write};
use tokio_stream::StreamExt;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Initialize the Foundry Local SDK
    let manager = FoundryLocalManager::create(FoundryLocalConfig::new("chat-assistant"))?;

    // Download and register all execution providers.
    manager
        .download_and_register_eps_with_progress(None, {
            let mut current_ep = String::new();
            move |ep_name: &str, percent: f64| {
                if ep_name != current_ep {
                    if !current_ep.is_empty() {
                        println!();
                    }
                    current_ep = ep_name.to_string();
                }
                print!("\r  {:<30}  {:5.1}%", ep_name, percent);
                io::stdout().flush().ok();
            }
        })
        .await?;
    println!();

    // Select and load a model from the catalog
    let model = manager.catalog().get_model("qwen2.5-0.5b").await?;

    if !model.is_cached().await? {
        println!("Downloading model...");
        model
            .download(Some(|progress: f64| {
                print!("\r  {progress:.1}%");
                io::stdout().flush().ok();
            }))
            .await?;
        println!();
    }

    model.load().await?;
    println!("Model loaded and ready.");

    // Create a chat client
    let client = model.create_chat_client().temperature(0.7).max_tokens(512);

    // Start the conversation with a system prompt
    let mut messages: Vec<ChatCompletionRequestMessage> = vec![
        ChatCompletionRequestSystemMessage::from(
            "You are a helpful, friendly assistant. Keep your responses \
             concise and conversational. If you don't know something, say so.",
        )
        .into(),
    ];

    println!("\nChat assistant ready! Type 'quit' to exit.\n");

    let stdin = io::stdin();
    loop {
        print!("You: ");
        io::stdout().flush()?;

        let mut input = String::new();
        stdin.lock().read_line(&mut input)?;
        let input = input.trim();

        if input.eq_ignore_ascii_case("quit") || input.eq_ignore_ascii_case("exit") {
            break;
        }

        // Add the user's message to conversation history
        messages.push(ChatCompletionRequestUserMessage::from(input).into());

        // Stream the response token by token
        print!("Assistant: ");
        io::stdout().flush()?;
        let mut full_response = String::new();
        let mut stream = client.complete_streaming_chat(&messages, None).await?;
        while let Some(chunk) = stream.next().await {
            let chunk = chunk?;
            if let Some(choice) = chunk.choices.first() {
                if let Some(ref content) = choice.delta.content {
                    print!("{content}");
                    io::stdout().flush()?;
                    full_response.push_str(content);
                }
            }
        }
        println!("\n");

        // Add the complete response to conversation history
        let assistant_msg: ChatCompletionRequestMessage = serde_json::from_value(
            serde_json::json!({"role": "assistant", "content": full_response}),
        )?;
        messages.push(assistant_msg);
    }

    // Clean up - unload the model
    model.unload().await?;
    println!("Model unloaded. Goodbye!");

    Ok(())
}

Exécutez l'assistant de chat :

cargo run

Vous voyez un résultat similaire à :

Downloading model: 100.00%
Model loaded and ready.

Chat assistant ready! Type 'quit' to exit.

You: What is photosynthesis?
Assistant: Photosynthesis is the process plants use to convert sunlight, water, and carbon
dioxide into glucose and oxygen. It mainly happens in the leaves, inside structures
called chloroplasts.

You: Why is it important for other living things?
Assistant: It's essential because photosynthesis produces the oxygen that most living things
breathe. It also forms the base of the food chain — animals eat plants or eat other
animals that depend on plants for energy.

You: quit
Model unloaded. Goodbye!

Notez comment l’assistant mémorise le contexte des tours précédents , quand vous demandez « Pourquoi est-il important pour d’autres choses vivantes ? », il sait que vous parlez toujours de photosynthèse.

Nettoyer les ressources

Les poids du modèle restent dans votre cache local après avoir déchargé un modèle. Cela signifie que la prochaine fois que vous exécutez l’application, l’étape de téléchargement est ignorée et le modèle se charge plus rapidement. Aucun nettoyage supplémentaire n’est nécessaire, sauf si vous souhaitez récupérer de l’espace disque.