Samouczek: tworzenie asystenta czatu z wieloma trybami za pomocą rozwiązania Foundry Local

W tym samouczku utworzysz interaktywnego asystenta czatu działającego w całości na urządzeniu. Asystent utrzymuje kontekst konwersacji w wielu wymianach, dlatego pamięta to, co omówiliśmy wcześniej w konwersacji. Lokalny zestaw SDK usługi Foundry służy do wybierania modelu, definiowania monitu systemowego i przesyłania odpowiedzi strumieniowo, token po tokenie.

W tym poradniku nauczysz się, jak:

  • Konfigurowanie projektu i instalowanie lokalnego zestawu SDK rozwiązania Foundry
  • Przeglądanie wykazu modeli i wybieranie modelu
  • Zdefiniuj systemowy monit dla kształtowania zachowania asystenta
  • Implementowanie wieloełowej konwersacji z historią komunikatów
  • Przesyłanie strumieniowe odpowiedzi dla responsywnego doświadczenia
  • Czyszczenie zasobów po zakończeniu konwersacji

Wymagania wstępne

  • Komputer Windows, macOS lub Linux z co najmniej 8 GB pamięci RAM.

Repozytorium przykładów

Pełny kod przykładowy do tego artykułu można znaleźć w repozytorium GitHub przykładów platformy Foundry. Aby sklonować repozytorium i przejść do przykładu, użyj następującego polecenia:

git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/csharp/foundry-local/tutorial-chat-assistant

Instalowanie pakietów

Jeśli programujesz lub wysyłasz na Windows, wybierz kartę Windows. Pakiet Windows integruje się z środowiskiem uruchomieniowym Windows ML — zapewnia ten sam obszar powierzchni interfejsu API z szerszym zakresem przyspieszania sprzętowego.

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

Przykłady języka C# w repozytorium GitHub to wstępnie skonfigurowane projekty. Jeśli tworzysz od podstaw, zapoznaj się z dokumentacją zestawu SDK lokalnego rozwiązania Foundry , aby uzyskać więcej informacji na temat sposobu konfigurowania projektu w języku C# przy użyciu rozwiązania Foundry Local.

Przeglądanie wykazu i wybieranie modelu

Lokalny zestaw SDK usługi Foundry udostępnia wykaz modeli, który zawiera listę wszystkich dostępnych modeli. W tym kroku zainicjujesz zestaw SDK i wybierzesz model asystenta czatu.

  • Otwórz Program.cs i zastąp jego zawartość następującym kodem, aby zainicjować zestaw SDK i wybrać model:

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

    Metoda GetModelAsync akceptuje alias modelu, który jest krótką, przyjazną nazwą odnoszącą się do określonego modelu w wykazie. Metoda DownloadAsync pobiera wagi modelu do lokalnej pamięci podręcznej i LoadAsync przygotowuje model do wnioskowania.

Definiowanie monitu systemowego

Monit systemowy ustawia osobowość i zachowanie asystenta. Jest to pierwsza wiadomość w historii konwersacji i model odwołuje się do niej w całej konwersacji.

Dodaj monit systemowy, aby ukształtować sposób reagowania asystenta:

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

Wskazówka

Poeksperymentuj z różnymi monitami systemowymi o zmianę zachowania asystenta. Możesz na przykład poinstruować go, aby odpowiedział jako pirat, nauczyciel lub ekspert domeny.

Implementowanie konwersacji wieloełowej

Asystent czatu musi zachować kontekst w wielu wymianach. Można to osiągnąć, utrzymując listę wszystkich komunikatów (system, użytkownika i asystenta) i wysyłając pełną listę z każdym żądaniem. Model używa tej historii do generowania kontekstowo odpowiednich odpowiedzi.

Dodaj pętlę konwersacji, która:

  • Odczytuje dane wejściowe użytkownika z konsoli.
  • Dołącza komunikat użytkownika do historii.
  • Wysyła pełną historię do modelu.
  • Dołącza odpowiedź asystenta do historii na następną turę.
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 });
}

Każde wywołanie CompleteChatAsync otrzymuje pełną historię komunikatów. W ten sposób model "zapamiętuje" poprzednie interakcje — nie przechowuje stanu między wywołaniami.

Dodawanie odpowiedzi przesyłania strumieniowego

Przesyłanie strumieniowe wyświetla każdy token w miarę jego generowania, co sprawia, że asystent wydaje się bardziej responsywny. Zastąp wywołanie CompleteChatAsync na CompleteChatStreamingAsync, aby przesyłać strumieniowo odpowiedź, token po tokenie.

Zaktualizuj pętlę konwersacji, aby używać przesyłania strumieniowego:

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

Wersja przesyłania strumieniowego gromadzi pełną odpowiedź, aby można ją było dodać do historii konwersacji po zakończeniu transmisji strumieniowej.

Kompletny kod

Zastąp zawartość Program.cs następującym kompletnym kodem:

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

Uruchom asystenta czatu:

dotnet run

Zobaczysz dane wyjściowe podobne do:

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!

Zwróć uwagę, że asystent zapamiętuje kontekst z poprzednich rozmów — gdy pytasz "Dlaczego to jest ważne dla innych organizmów żywych?", wie, że nadal mówisz o fotosyntezie.

Repozytorium przykładów

Kompletny przykładowy kod dla tego artykułu jest dostępny w repozytorium foundry-samples GitHub. Aby sklonować repozytorium i przejść do przykładowego użycia:

git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/javascript/foundry-local/tutorial-chat-assistant

Instalowanie pakietów

Jeśli programujesz lub wysyłasz na Windows, wybierz kartę Windows. Pakiet Windows integruje się z środowiskiem uruchomieniowym Windows ML — zapewnia ten sam obszar powierzchni interfejsu API z szerszym zakresem przyspieszania sprzętowego.

npm install foundry-local-sdk-winml openai

Przeglądanie wykazu i wybieranie modelu

Lokalny zestaw SDK usługi Foundry udostępnia wykaz modeli, który zawiera listę wszystkich dostępnych modeli. W tym kroku zainicjujesz zestaw SDK i wybierzesz model asystenta czatu.

  1. Utwórz plik o nazwie index.js.

  2. Dodaj następujący kod, aby zainicjować zestaw SDK i wybrać model:

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

    Metoda getModel akceptuje alias modelu, który jest krótką, przyjazną nazwą odnoszącą się do określonego modelu w wykazie. Metoda download pobiera wagi modelu do lokalnej pamięci podręcznej i load przygotowuje model do wnioskowania.

Definiowanie monitu systemowego

Monit systemowy ustawia osobowość i zachowanie asystenta. Jest to pierwsza wiadomość w historii konwersacji i model odwołuje się do niej w całej konwersacji.

Dodaj monit systemowy, aby ukształtować sposób reagowania asystenta:

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

Wskazówka

Poeksperymentuj z różnymi monitami systemowymi o zmianę zachowania asystenta. Możesz na przykład poinstruować go, aby odpowiedział jako pirat, nauczyciel lub ekspert domeny.

Implementowanie konwersacji wieloełowej

Asystent czatu musi zachować kontekst w wielu wymianach. Można to osiągnąć, utrzymując listę wszystkich komunikatów (system, użytkownika i asystenta) i wysyłając pełną listę z każdym żądaniem. Model używa tej historii do generowania kontekstowo odpowiednich odpowiedzi.

Dodaj pętlę konwersacji, która:

  • Odczytuje dane wejściowe użytkownika z konsoli.
  • Dołącza komunikat użytkownika do historii.
  • Wysyła pełną historię do modelu.
  • Dołącza odpowiedź asystenta do historii na następną turę.
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 });
}

Każde wywołanie completeChat otrzymuje pełną historię komunikatów. W ten sposób model "zapamiętuje" poprzednie interakcje — nie przechowuje stanu między wywołaniami.

Dodawanie odpowiedzi przesyłania strumieniowego

Przesyłanie strumieniowe wyświetla każdy token w miarę jego generowania, co sprawia, że asystent wydaje się bardziej responsywny. Zastąp wywołanie completeChat na completeStreamingChat, aby przesyłać strumieniowo odpowiedź, token po tokenie.

Zaktualizuj pętlę konwersacji, aby używać przesyłania strumieniowego:

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

Wersja przesyłania strumieniowego gromadzi pełną odpowiedź, aby można ją było dodać do historii konwersacji po zakończeniu transmisji strumieniowej.

Kompletny kod

Utwórz plik o nazwie index.js i dodaj następujący kompletny kod:

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) => {
    if (rl.closed) return resolve('quit');
    const onClose = () => resolve('quit');
    rl.once('close', onClose);
    try {
        rl.question(prompt, (answer) => {
            rl.off('close', onClose);
            resolve(answer);
        });
    } catch {
        rl.off('close', onClose);
        resolve('quit');
    }
});

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

Uruchom asystenta czatu:

node index.js

Zobaczysz dane wyjściowe podobne do:

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!

Zwróć uwagę, że asystent zapamiętuje kontekst z poprzednich rozmów — gdy pytasz "Dlaczego to jest ważne dla innych organizmów żywych?", wie, że nadal mówisz o fotosyntezie.

Repozytorium przykładów

Kompletny przykładowy kod dla tego artykułu jest dostępny w repozytorium foundry-samples GitHub. Aby sklonować repozytorium i przejść do przykładowego użycia:

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

Instalowanie pakietów

Jeśli programujesz lub wysyłasz na Windows, wybierz kartę Windows. Pakiet Windows integruje się z środowiskiem uruchomieniowym Windows ML — zapewnia ten sam obszar powierzchni interfejsu API z szerszym zakresem przyspieszania sprzętowego.

pip install foundry-local-sdk-winml openai

Przeglądanie wykazu i wybieranie modelu

Lokalny zestaw SDK usługi Foundry udostępnia wykaz modeli, który zawiera listę wszystkich dostępnych modeli. W tym kroku zainicjujesz zestaw SDK i wybierzesz model asystenta czatu.

  1. Utwórz plik o nazwie main.py.

  2. Dodaj następujący kod, aby zainicjować zestaw SDK i wybrać model:

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

    Metoda get_model akceptuje alias modelu, który jest krótką, przyjazną nazwą odnoszącą się do określonego modelu w wykazie. Metoda download pobiera wagi modelu do lokalnej pamięci podręcznej i load przygotowuje model do wnioskowania.

Definiowanie monitu systemowego

Monit systemowy ustawia osobowość i zachowanie asystenta. Jest to pierwsza wiadomość w historii konwersacji i model odwołuje się do niej w całej konwersacji.

Dodaj monit systemowy, aby ukształtować sposób reagowania asystenta:

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

Wskazówka

Poeksperymentuj z różnymi monitami systemowymi o zmianę zachowania asystenta. Możesz na przykład poinstruować go, aby odpowiedział jako pirat, nauczyciel lub ekspert domeny.

Implementowanie konwersacji wieloełowej

Asystent czatu musi zachować kontekst w wielu wymianach. Można to osiągnąć, utrzymując listę wszystkich komunikatów (system, użytkownika i asystenta) i wysyłając pełną listę z każdym żądaniem. Model używa tej historii do generowania kontekstowo odpowiednich odpowiedzi.

Dodaj pętlę konwersacji, która:

  • Odczytuje dane wejściowe użytkownika z konsoli.
  • Dołącza komunikat użytkownika do historii.
  • Wysyła pełną historię do modelu.
  • Dołącza odpowiedź asystenta do historii na następną turę.
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):
        if not chunk.choices:
            continue
        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})

Każde wywołanie complete_chat otrzymuje pełną historię komunikatów. W ten sposób model "zapamiętuje" poprzednie interakcje — nie przechowuje stanu między wywołaniami.

Dodawanie odpowiedzi przesyłania strumieniowego

Przesyłanie strumieniowe wyświetla każdy token w miarę jego generowania, co sprawia, że asystent wydaje się bardziej responsywny. Zastąp wywołanie complete_chat na complete_streaming_chat, aby przesyłać strumieniowo odpowiedź, token po tokenie.

Zaktualizuj pętlę konwersacji, aby używać przesyłania strumieniowego:

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

Wersja przesyłania strumieniowego gromadzi pełną odpowiedź, aby można ją było dodać do historii konwersacji po zakończeniu transmisji strumieniowej.

Kompletny kod

Utwórz plik o nazwie main.py i dodaj następujący kompletny kod:

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):
            if not chunk.choices:
                continue
            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()

Uruchom asystenta czatu:

python main.py

Zobaczysz dane wyjściowe podobne do:

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!

Zwróć uwagę, że asystent zapamiętuje kontekst z poprzednich rozmów — gdy pytasz "Dlaczego to jest ważne dla innych organizmów żywych?", wie, że nadal mówisz o fotosyntezie.

Repozytorium przykładów

Kompletny przykładowy kod dla tego artykułu jest dostępny w repozytorium foundry-samples GitHub. Aby sklonować repozytorium i przejść do przykładowego użycia:

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

Instalowanie pakietów

Jeśli programujesz lub wysyłasz na Windows, wybierz kartę Windows. Pakiet Windows integruje się z środowiskiem uruchomieniowym Windows ML — zapewnia ten sam obszar powierzchni interfejsu API z szerszym zakresem przyspieszania sprzętowego.

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

Przeglądanie wykazu i wybieranie modelu

Lokalny zestaw SDK usługi Foundry udostępnia wykaz modeli, który zawiera listę wszystkich dostępnych modeli. W tym kroku zainicjujesz zestaw SDK i wybierzesz model asystenta czatu.

  • Otwórz src/main.rs i zastąp jego zawartość następującym kodem, aby zainicjować zestaw SDK i wybrać model:

    // Initialize the Foundry Local SDK
    let manager = FoundryLocalManager::create(FoundryLocalConfig::new("foundry_local_samples"))?;
    
    // 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);
    

    Metoda get_model akceptuje alias modelu, który jest krótką, przyjazną nazwą odnoszącą się do określonego modelu w wykazie. Metoda download pobiera wagi modelu do lokalnej pamięci podręcznej i load przygotowuje model do wnioskowania.

Definiowanie monitu systemowego

Monit systemowy ustawia osobowość i zachowanie asystenta. Jest to pierwsza wiadomość w historii konwersacji i model odwołuje się do niej w całej konwersacji.

Dodaj monit systemowy, aby ukształtować sposób reagowania asystenta:

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

Wskazówka

Poeksperymentuj z różnymi monitami systemowymi o zmianę zachowania asystenta. Możesz na przykład poinstruować go, aby odpowiedział jako pirat, nauczyciel lub ekspert domeny.

Implementowanie konwersacji wieloełowej

Asystent czatu musi zachować kontekst w wielu wymianach. Można to osiągnąć, zachowując wektor wszystkich komunikatów (system, użytkownik i asystent) i wysyłając pełną listę przy każdym żądaniu. Model używa tej historii do generowania kontekstowo odpowiednich odpowiedzi.

Dodaj pętlę konwersacji, która:

  • Odczytuje dane wejściowe użytkownika z konsoli.
  • Dołącza komunikat użytkownika do historii.
  • Wysyła pełną historię do modelu.
  • Dołącza odpowiedź asystenta do historii na następną turę.
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);
}

Każde wywołanie complete_chat otrzymuje pełną historię komunikatów. W ten sposób model "zapamiętuje" poprzednie interakcje — nie przechowuje stanu między wywołaniami.

Dodawanie odpowiedzi przesyłania strumieniowego

Przesyłanie strumieniowe wyświetla każdy token w miarę jego generowania, co sprawia, że asystent wydaje się bardziej responsywny. Zastąp wywołanie complete_chat na complete_streaming_chat, aby przesyłać strumieniowo odpowiedź, token po tokenie.

Zaktualizuj pętlę konwersacji, aby używać przesyłania strumieniowego:

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

Wersja przesyłania strumieniowego gromadzi pełną odpowiedź, aby można ją było dodać do historii konwersacji po zakończeniu transmisji strumieniowej.

Kompletny kod

Zastąp zawartość src/main.rs następującym kompletnym kodem:

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("foundry_local_samples"))?;

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

Uruchom asystenta czatu:

cargo run

Zobaczysz dane wyjściowe podobne do:

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!

Zwróć uwagę, że asystent zapamiętuje kontekst z poprzednich rozmów — gdy pytasz "Dlaczego to jest ważne dla innych organizmów żywych?", wie, że nadal mówisz o fotosyntezie.

Uprzątnij zasoby

Wagi modelu są przechowywane w Twojej lokalnej pamięci podręcznej po rozładowaniu modelu. Oznacza to, że przy następnym uruchomieniu aplikacji krok pobierania zostanie pominięty, a model ładuje się szybciej. Nie jest wymagane żadne dodatkowe czyszczenie, chyba że chcesz odzyskać miejsce na dysku.