Esercitazione: Creare un assistente chat a più turni con Foundry Local

In questa esercitazione si crea un assistente chat interattivo che viene eseguito interamente nel dispositivo. L'assistente gestisce il contesto della conversazione tra più scambi, quindi ricorda ciò che hai discusso in precedenza nella conversazione. Si usa Foundry Local SDK per selezionare un modello, definire una richiesta di sistema e trasmettere le risposte token per token.

In questa esercitazione apprenderai a:

  • Configurare un progetto e installare Foundry Local SDK
  • Esplorare il catalogo dei modelli e selezionare un modello
  • Definire un prompt di sistema per modellare il comportamento dell'assistente
  • Implementare una conversazione a più turni con la cronologia dei messaggi
  • Trasmettere risposte per un'esperienza reattiva
  • Ripulire le risorse al termine della conversazione

Prerequisiti

  • Un computer Windows, macOS o Linux con almeno 8 GB di RAM.

Repository di esempi

Il codice di esempio completo per questo articolo è disponibile nel repository GitHub Foundry Local. Per clonare il repository e navigare all'esempio:

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

Installare i pacchetti

Se si sviluppa o si esegue la spedizione in Windows, selezionare la scheda Windows. Il pacchetto Windows si integra con il runtime Windows ML, che fornisce la stessa area di attacco API con un'ampiezza più ampia di accelerazione hardware.

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

Gli esempi C# nel repository GitHub sono progetti preconfigurati. Se stai creando da zero, dovresti leggere il riferimento del Foundry Local SDK per ulteriori dettagli su come configurare il tuo progetto C# con Foundry Local.

Esplorare il catalogo e selezionare un modello

Foundry Local SDK fornisce un catalogo di modelli che elenca tutti i modelli disponibili. In questo passaggio si inizializza l'SDK e si seleziona un modello per l'assistente chat.

  • Aprire Program.cs e sostituire il relativo contenuto con il codice seguente per inizializzare l'SDK e selezionare un modello:

    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();
    

    Il GetModelAsync metodo accetta un alias del modello, cioè un nome breve e amichevole che corrisponde a un modello specifico nel catalogo. Il DownloadAsync metodo recupera i pesi del modello nella cache locale e LoadAsync rende il modello pronto per l'inferenza.

Definire un prompt di sistema

Una richiesta di sistema imposta la personalità e il comportamento dell'assistente. Si tratta del primo messaggio nella cronologia delle conversazioni e il modello vi fa riferimento in tutta la conversazione.

Aggiungere una richiesta di sistema per modellare la risposta dell'assistente:

// 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."
    }
};

Suggerimento

Sperimentare con richieste di sistema diverse per modificare il comportamento dell'assistente. Ad esempio, è possibile indicare di rispondere come pirata, insegnante o esperto di dominio.

Implementare una conversazione a più turni

Un assistente chat deve mantenere il contesto tra più scambi. A tale scopo, mantenere un elenco di tutti i messaggi (sistema, utente e assistente) e inviare l'elenco completo con ogni richiesta. Il modello usa questa cronologia per generare risposte contestualmente pertinenti.

Aggiungere un ciclo di conversazione che:

  • Legge l'input dell'utente dalla console.
  • Aggiunge il messaggio utente alla cronologia.
  • Invia la cronologia completa al modello.
  • Aggiunge la risposta dell'assistente alla cronologia per il turno successivo.
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 });
}

Ogni chiamata a CompleteChatAsync riceve la cronologia completa dei messaggi. Questo è il modo in cui il modello "ricorda" i turni precedenti: non archivia lo stato tra le chiamate.

Aggiungere risposte di streaming

Lo streaming stampa ogni token man mano che viene generato, il che rende l'assistente più reattivo. Sostituire la CompleteChatAsync chiamata con CompleteChatStreamingAsync per trasmettere il token di risposta uno per uno.

Aggiornare il ciclo di conversazione per usare lo streaming:

// 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 versione di streaming accumula la risposta completa in modo che possa essere aggiunta alla cronologia della conversazione al termine del flusso.

Codice completo

Sostituire il contenuto di Program.cs con il codice completo seguente:

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!");

Avvia l'assistente chat:

dotnet run

Viene visualizzato un output simile al seguente:

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!

Si noti che l'assistente ricorda il contesto dei turni precedenti: quando chiedi "Perché è importante per altre cose viventi?", sa che stai ancora parlando di fotosintesi.

Repository di esempi

Il codice di esempio completo per questo articolo è disponibile nel repository GitHub Foundry Local. Per clonare il repository e navigare all'esempio:

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

Installare i pacchetti

Se si sviluppa o si esegue la spedizione in Windows, selezionare la scheda Windows. Il pacchetto Windows si integra con il runtime Windows ML, che fornisce la stessa area di attacco API con un'ampiezza più ampia di accelerazione hardware.

npm install foundry-local-sdk-winml openai

Esplorare il catalogo e selezionare un modello

Foundry Local SDK fornisce un catalogo di modelli che elenca tutti i modelli disponibili. In questo passaggio si inizializza l'SDK e si seleziona un modello per l'assistente chat.

  1. Creare un file denominato index.js.

  2. Aggiungere il codice seguente per inizializzare l'SDK e selezionare un modello:

    // 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();
    

    Il getModel metodo accetta un alias del modello, cioè un nome breve e amichevole che corrisponde a un modello specifico nel catalogo. Il download metodo recupera i pesi del modello nella cache locale e load rende il modello pronto per l'inferenza.

Definire un prompt di sistema

Una richiesta di sistema imposta la personalità e il comportamento dell'assistente. Si tratta del primo messaggio nella cronologia delle conversazioni e il modello vi fa riferimento in tutta la conversazione.

Aggiungere una richiesta di sistema per modellare la risposta dell'assistente:

// 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.'
    }
];

Suggerimento

Sperimentare con richieste di sistema diverse per modificare il comportamento dell'assistente. Ad esempio, è possibile indicare di rispondere come pirata, insegnante o esperto di dominio.

Implementare una conversazione a più turni

Un assistente chat deve mantenere il contesto tra più scambi. A tale scopo, mantenere un elenco di tutti i messaggi (sistema, utente e assistente) e inviare l'elenco completo con ogni richiesta. Il modello usa questa cronologia per generare risposte contestualmente pertinenti.

Aggiungere un ciclo di conversazione che:

  • Legge l'input dell'utente dalla console.
  • Aggiunge il messaggio utente alla cronologia.
  • Invia la cronologia completa al modello.
  • Aggiunge la risposta dell'assistente alla cronologia per il turno successivo.
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 });
}

Ogni chiamata a completeChat riceve la cronologia completa dei messaggi. Questo è il modo in cui il modello "ricorda" i turni precedenti: non archivia lo stato tra le chiamate.

Aggiungere risposte di streaming

Lo streaming stampa ogni token man mano che viene generato, il che rende l'assistente più reattivo. Sostituire la completeChat chiamata con completeStreamingChat per trasmettere il token di risposta uno per uno.

Aggiornare il ciclo di conversazione per usare lo streaming:

// 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 versione di streaming accumula la risposta completa in modo che possa essere aggiunta alla cronologia della conversazione al termine del flusso.

Codice completo

Creare un file denominato index.js e aggiungere il codice completo seguente:

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();

Avvia l'assistente chat:

node index.js

Viene visualizzato un output simile al seguente:

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!

Si noti che l'assistente ricorda il contesto dei turni precedenti: quando chiedi "Perché è importante per altre cose viventi?", sa che stai ancora parlando di fotosintesi.

Repository di esempi

Il codice di esempio completo per questo articolo è disponibile nel repository GitHub Foundry Local. Per clonare il repository e navigare all'esempio:

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

Installare i pacchetti

Se si sviluppa o si esegue la spedizione in Windows, selezionare la scheda Windows. Il pacchetto Windows si integra con il runtime Windows ML, che fornisce la stessa area di attacco API con un'ampiezza più ampia di accelerazione hardware.

pip install foundry-local-sdk-winml openai

Esplorare il catalogo e selezionare un modello

Foundry Local SDK fornisce un catalogo di modelli che elenca tutti i modelli disponibili. In questo passaggio si inizializza l'SDK e si seleziona un modello per l'assistente chat.

  1. Creare un file denominato main.py.

  2. Aggiungere il codice seguente per inizializzare l'SDK e selezionare un modello:

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

    Il get_model metodo accetta un alias del modello, cioè un nome breve e amichevole che corrisponde a un modello specifico nel catalogo. Il download metodo recupera i pesi del modello nella cache locale e load rende il modello pronto per l'inferenza.

Definire un prompt di sistema

Una richiesta di sistema imposta la personalità e il comportamento dell'assistente. Si tratta del primo messaggio nella cronologia delle conversazioni e il modello vi fa riferimento in tutta la conversazione.

Aggiungere una richiesta di sistema per modellare la risposta dell'assistente:

# 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."
    }
]

Suggerimento

Sperimentare con richieste di sistema diverse per modificare il comportamento dell'assistente. Ad esempio, è possibile indicare di rispondere come pirata, insegnante o esperto di dominio.

Implementare una conversazione a più turni

Un assistente chat deve mantenere il contesto tra più scambi. A tale scopo, mantenere un elenco di tutti i messaggi (sistema, utente e assistente) e inviare l'elenco completo con ogni richiesta. Il modello usa questa cronologia per generare risposte contestualmente pertinenti.

Aggiungere un ciclo di conversazione che:

  • Legge l'input dell'utente dalla console.
  • Aggiunge il messaggio utente alla cronologia.
  • Invia la cronologia completa al modello.
  • Aggiunge la risposta dell'assistente alla cronologia per il turno successivo.
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})

Ogni chiamata a complete_chat riceve la cronologia completa dei messaggi. Questo è il modo in cui il modello "ricorda" i turni precedenti: non archivia lo stato tra le chiamate.

Aggiungere risposte di streaming

Lo streaming stampa ogni token man mano che viene generato, il che rende l'assistente più reattivo. Sostituire la complete_chat chiamata con complete_streaming_chat per trasmettere il token di risposta uno per uno.

Aggiornare il ciclo di conversazione per usare lo streaming:

# 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 versione di streaming accumula la risposta completa in modo che possa essere aggiunta alla cronologia della conversazione al termine del flusso.

Codice completo

Creare un file denominato main.py e aggiungere il codice completo seguente:

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

Avvia l'assistente chat:

python main.py

Viene visualizzato un output simile al seguente:

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!

Si noti che l'assistente ricorda il contesto dei turni precedenti: quando chiedi "Perché è importante per altre cose viventi?", sa che stai ancora parlando di fotosintesi.

Repository di esempi

Il codice di esempio completo per questo articolo è disponibile nel repository GitHub Foundry Local. Per clonare il repository e navigare all'esempio:

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

Installare i pacchetti

Se si sviluppa o si esegue la spedizione in Windows, selezionare la scheda Windows. Il pacchetto Windows si integra con il runtime Windows ML, che fornisce la stessa area di attacco API con un'ampiezza più ampia di accelerazione hardware.

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

Esplorare il catalogo e selezionare un modello

Foundry Local SDK fornisce un catalogo di modelli che elenca tutti i modelli disponibili. In questo passaggio si inizializza l'SDK e si seleziona un modello per l'assistente chat.

  • Aprire src/main.rs e sostituire il relativo contenuto con il codice seguente per inizializzare l'SDK e selezionare un modello:

    // 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);
    

    Il get_model metodo accetta un alias del modello, cioè un nome breve e amichevole che corrisponde a un modello specifico nel catalogo. Il download metodo recupera i pesi del modello nella cache locale e load rende il modello pronto per l'inferenza.

Definire un prompt di sistema

Una richiesta di sistema imposta la personalità e il comportamento dell'assistente. Si tratta del primo messaggio nella cronologia delle conversazioni e il modello vi fa riferimento in tutta la conversazione.

Aggiungere una richiesta di sistema per modellare la risposta dell'assistente:

// 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(),
];

Suggerimento

Sperimentare con richieste di sistema diverse per modificare il comportamento dell'assistente. Ad esempio, è possibile indicare di rispondere come pirata, insegnante o esperto di dominio.

Implementare una conversazione a più turni

Un assistente chat deve mantenere il contesto tra più scambi. A tale scopo, mantenere un vettore di tutti i messaggi (sistema, utente e assistente) e inviare l'elenco completo con ogni richiesta. Il modello usa questa cronologia per generare risposte contestualmente pertinenti.

Aggiungere un ciclo di conversazione che:

  • Legge l'input dell'utente dalla console.
  • Aggiunge il messaggio utente alla cronologia.
  • Invia la cronologia completa al modello.
  • Aggiunge la risposta dell'assistente alla cronologia per il turno successivo.
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);
}

Ogni chiamata a complete_chat riceve la cronologia completa dei messaggi. Questo è il modo in cui il modello "ricorda" i turni precedenti: non archivia lo stato tra le chiamate.

Aggiungere risposte di streaming

Lo streaming stampa ogni token man mano che viene generato, il che rende l'assistente più reattivo. Sostituire la complete_chat chiamata con complete_streaming_chat per trasmettere il token di risposta uno per uno.

Aggiornare il ciclo di conversazione per usare lo streaming:

// 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 versione di streaming accumula la risposta completa in modo che possa essere aggiunta alla cronologia della conversazione al termine del flusso.

Codice completo

Sostituire il contenuto di src/main.rs con il codice completo seguente:

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(())
}

Avvia l'assistente chat:

cargo run

Viene visualizzato un output simile al seguente:

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!

Si noti che l'assistente ricorda il contesto dei turni precedenti: quando chiedi "Perché è importante per altre cose viventi?", sa che stai ancora parlando di fotosintesi.

Pulire le risorse

I pesi del modello rimangono nella cache locale dopo lo scaricamento di un modello. Ciò significa che la volta successiva che si esegue l'applicazione, il passaggio di download viene ignorato e il modello viene caricato più velocemente. Non è necessaria alcuna pulizia aggiuntiva, a meno che non si voglia recuperare spazio su disco.