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

dotnet add package Microsoft.AI.Foundry.Local
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.");
    
    // Create one stateful native chat session for the conversation.
    using var chatSession = new ChatSession(model);
    chatSession.SetStreaming(true);
    

    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 systemPrompt = "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;
    }

    using var request = new Request();
    if (chatSession.TurnCount == 0)
    {
        request.AddItem(MessageItem.System(systemPrompt));
    }
    request.AddItem(MessageItem.User(userInput));

    // Stream the response token by token
    Console.Write("Assistant: ");
    await using var streamingResponse = chatSession.ProcessStreamingRequestAsync(request, ct);
    await foreach (var item in streamingResponse)
    {
        using (item)
        {
            if (item is TextItem { Type: TextItemType.Default } text)
            {
                Console.Write(text.Text);
                Console.Out.Flush();
            }
        }
    }
    using var finalResponse = await streamingResponse.FinalResponse;
    Console.WriteLine("\n");
}

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: ");
await using var streamingResponse = chatSession.ProcessStreamingRequestAsync(request, ct);
await foreach (var item in streamingResponse)
{
    using (item)
    {
        if (item is TextItem { Type: TextItemType.Default } text)
        {
            Console.Write(text.Text);
            Console.Out.Flush();
        }
    }
}
using var finalResponse = await streamingResponse.FinalResponse;
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 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.");

// Create one stateful native chat session for the conversation.
using var chatSession = new ChatSession(model);
chatSession.SetStreaming(true);

// Start the conversation with a system prompt
var systemPrompt = "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;
    }

    using var request = new Request();
    if (chatSession.TurnCount == 0)
    {
        request.AddItem(MessageItem.System(systemPrompt));
    }
    request.AddItem(MessageItem.User(userInput));

    // Stream the response token by token
    Console.Write("Assistant: ");
    await using var streamingResponse = chatSession.ProcessStreamingRequestAsync(request, ct);
    await foreach (var item in streamingResponse)
    {
        using (item)
        {
            if (item is TextItem { Type: TextItemType.Default } text)
            {
                Console.Write(text.Text);
                Console.Out.Flush();
            }
        }
    }
    using var finalResponse = await streamingResponse.FinalResponse;
    Console.WriteLine("\n");
}

// Clean up - unload the model
chatSession.Dispose();
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

npm install foundry-local-sdk 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 stateful native chat session
    const chatSession = new ChatSession(model);
    chatSession.setOptions({
        search: { temperature: 0.7, maxOutputTokens: 512 }
    });
    

    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 systemPrompt =
    'You are a helpful, friendly assistant. Keep your responses ' +
    'concise and conversational. If you don\'t know something, say so.';
let firstTurn = true;

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;
    }

    const request = new Request();
    if (firstTurn) {
        request.addItem(Item.systemMessage(systemPrompt));
        firstTurn = false;
    }
    request.addItem(Item.userMessage(userInput));

    // Stream the response token by token
    process.stdout.write('Assistant: ');
    const stream = chatSession.processStreamingRequest(request);
    for await (const item of stream) {
        if (item.type === 'text' && item.textType === 'default') {
            process.stdout.write(item.text);
        }
    }
    await stream.response;
    console.log('\n');
}

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: ');
const stream = chatSession.processStreamingRequest(request);
for await (const item of stream) {
    if (item.type === 'text' && item.textType === 'default') {
        process.stdout.write(item.text);
    }
}
await stream.response;
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 {
    ChatSession,
    FoundryLocalManager,
    Item,
    Request
} 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 stateful native chat session
const chatSession = new ChatSession(model);
chatSession.setOptions({
    search: { temperature: 0.7, maxOutputTokens: 512 }
});

// Start the conversation with a system prompt
const systemPrompt =
    'You are a helpful, friendly assistant. Keep your responses ' +
    'concise and conversational. If you don\'t know something, say so.';
let firstTurn = true;

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

    const request = new Request();
    if (firstTurn) {
        request.addItem(Item.systemMessage(systemPrompt));
        firstTurn = false;
    }
    request.addItem(Item.userMessage(userInput));

    // Stream the response token by token
    process.stdout.write('Assistant: ');
    const stream = chatSession.processStreamingRequest(request);
    for await (const item of stream) {
        if (item.type === 'text' && item.textType === 'default') {
            process.stdout.write(item.text);
        }
    }
    await stream.response;
    console.log('\n');
}

// Clean up - unload the model
chatSession.dispose();
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

pip install foundry-local-sdk 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
    
    # Discover and register execution providers that are not already available.
    missing_eps = [ep.name for ep in manager.discover_eps() if not ep.is_registered]
    if missing_eps:
        manager.download_and_register_eps(missing_eps)
    
    # Select and load a model from the catalog.
    model = manager.catalog.get_model("qwen2.5-0.5b")
    if model is None:
        raise RuntimeError("Model 'qwen2.5-0.5b' was not found in the catalog.")
    if not model.is_cached:
        model.download(
            lambda progress: print(
                f"\rDownloading model: {progress:.2f}%", end="", flush=True
            )
        )
        print()
    model.load()
    print("Model loaded and ready.")
    

    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.
system_prompt = (
    "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

    # The stateful session retains earlier turns automatically.
    with Request() as request:
        if first_turn:
            request.add_item(MessageItem.system(system_prompt))
            first_turn = False
        request.add_item(MessageItem.user(user_input))
        print("Assistant: ", end="", flush=True)
        with session.process_streaming_request(request) as stream:
            for item in stream:
                if (
                    isinstance(item, TextItem)
                    and item.type == TextItemType.DEFAULT
                ):
                    print(item.text, end="", flush=True)
        print("\n")

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:

# The stateful session retains earlier turns automatically.
with Request() as request:
    if first_turn:
        request.add_item(MessageItem.system(system_prompt))
        first_turn = False
    request.add_item(MessageItem.user(user_input))
    print("Assistant: ", end="", flush=True)
    with session.process_streaming_request(request) as stream:
        for item in stream:
            if (
                isinstance(item, TextItem)
                and item.type == TextItemType.DEFAULT
            ):
                print(item.text, end="", flush=True)
    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 (
    ChatSession,
    Configuration,
    FoundryLocalManager,
    MessageItem,
    Request,
    RequestOptions,
    SearchOptions,
    TextItem,
    TextItemType,
)



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

    # Discover and register execution providers that are not already available.
    missing_eps = [ep.name for ep in manager.discover_eps() if not ep.is_registered]
    if missing_eps:
        manager.download_and_register_eps(missing_eps)

    # Select and load a model from the catalog.
    model = manager.catalog.get_model("qwen2.5-0.5b")
    if model is None:
        raise RuntimeError("Model 'qwen2.5-0.5b' was not found in the catalog.")
    if not model.is_cached:
        model.download(
            lambda progress: print(
                f"\rDownloading model: {progress:.2f}%", end="", flush=True
            )
        )
        print()
    model.load()
    print("Model loaded and ready.")

    try:
        # Start the conversation with a system prompt.
        system_prompt = (
            "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")

        with ChatSession(model) as session:
            session.set_options(
                RequestOptions(
                    search=SearchOptions(temperature=0.7, max_output_tokens=512)
                )
            )
            session.set_streaming(True)
            first_turn = True

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

                # The stateful session retains earlier turns automatically.
                with Request() as request:
                    if first_turn:
                        request.add_item(MessageItem.system(system_prompt))
                        first_turn = False
                    request.add_item(MessageItem.user(user_input))
                    print("Assistant: ", end="", flush=True)
                    with session.process_streaming_request(request) as stream:
                        for item in stream:
                            if (
                                isinstance(item, TextItem)
                                and item.type == TextItemType.DEFAULT
                            ):
                                print(item.text, end="", flush=True)
                    print("\n")
    finally:
        # Clean up - unload the model
        if model.is_loaded:
            model.unload()
        manager.close()
        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

cargo add foundry-local-sdk
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"))?;
    
    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  {ep_name:<30}  {percent:5.1}%");
                io::stdout().flush().ok();
            }
        })
        .await?;
    println!();
    
    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?;
    let session = ChatSession::new(&model).await?;
    session
        .set_options(RequestOptions {
            search: SearchOptions {
                temperature: Some(0.7),
                max_output_tokens: Some(512),
                ..Default::default()
            },
            ..Default::default()
        })
        .await?;
    println!("Model loaded and ready.");
    

    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:

let system_prompt = "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ąć, 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;
    }
    if input.is_empty() {
        continue;
    }

    let mut items = Vec::new();
    if session.turn_count() == 0 {
        items.push(Item::system_message(vec![Item::text(system_prompt)]));
    }
    items.push(Item::user_message(vec![Item::text(input)]));

    print!("Assistant: ");
    io::stdout().flush()?;
    let mut stream = session.process_streaming_request(Request::from_items(items));
    while let Some(item) = stream.next().await {
        if let Item::Text {
            text,
            kind: TextKind::Default,
        } = item?
        {
            print!("{text}");
            io::stdout().flush()?;
        }
    }
    println!("\n");
}

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:

print!("Assistant: ");
io::stdout().flush()?;
let mut stream = session.process_streaming_request(Request::from_items(items));
while let Some(item) = stream.next().await {
    if let Item::Text {
        text,
        kind: TextKind::Default,
    } = item?
    {
        print!("{text}");
        io::stdout().flush()?;
    }
}
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::{
    ChatSession, FoundryLocalConfig, FoundryLocalManager, Item, Request, RequestOptions,
    SearchOptions, TextKind,
};
use std::io::{self, BufRead, Write};
use tokio_stream::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize the Foundry Local SDK
    let manager = FoundryLocalManager::create(FoundryLocalConfig::new("foundry_local_samples"))?;

    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  {ep_name:<30}  {percent:5.1}%");
                io::stdout().flush().ok();
            }
        })
        .await?;
    println!();

    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?;
    let session = ChatSession::new(&model).await?;
    session
        .set_options(RequestOptions {
            search: SearchOptions {
                temperature: Some(0.7),
                max_output_tokens: Some(512),
                ..Default::default()
            },
            ..Default::default()
        })
        .await?;
    println!("Model loaded and ready.");

    let system_prompt = "You are a helpful, friendly assistant. Keep your responses concise and \
                         conversational. If you don't know something, say so.";

    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;
        }
        if input.is_empty() {
            continue;
        }

        let mut items = Vec::new();
        if session.turn_count() == 0 {
            items.push(Item::system_message(vec![Item::text(system_prompt)]));
        }
        items.push(Item::user_message(vec![Item::text(input)]));

        print!("Assistant: ");
        io::stdout().flush()?;
        let mut stream = session.process_streaming_request(Request::from_items(items));
        while let Some(item) = stream.next().await {
            if let Item::Text {
                text,
                kind: TextKind::Default,
            } = item?
            {
                print!("{text}");
                io::stdout().flush()?;
            }
        }
        println!("\n");
    }

    drop(session);
    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.

Repozytorium przykładów

Sklonuj repozytorium GitHub z przykładami Foundry i przejdź do przykładu języka C++:

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

Instalowanie pakietów

Przykłady języka C++ wymagają:

Skonfiguruj i skompiluj przykład:

cmake -S . -B build
cmake --build build --config Release

Podczas konfiguracji przykład powoduje przywrócenie pakietu NuGet Microsoft.AI.Foundry.Local.Runtime w wersji 2.2.1 do build/_packages. Pakiet zawiera nagłówki języka C++ i biblioteki natywne dla Windows, Linux i macOS. Nie potrzebujesz oddzielnego pakietu WinML.

Konfiguracja narzędzia CMake wybiera zasoby środowiska uruchomieniowego dla Twojego systemu operacyjnego i architektury oraz kopiuje wymagane biblioteki natywne obok pliku wykonywalnego. W systemach macOS i Linux uruchom plik wykonywalny z build. W systemie Windows uruchom plik .exe z build.

Przeglądanie wykazu i wybieranie modelu

Zainicjuj zestaw SDK, wybierz model z katalogu i pobierz i załaduj go:

Configuration config("foundry_local_cpp_tutorial_chat");
Manager manager(std::move(config));
auto model = SelectChatModel(manager.GetCatalog());
if (!model) {
  throw std::runtime_error("No supported CPU chat model was found.");
}
if (!model->IsCached()) {
  model->Download([](float progress) {
    std::cout << "\rDownloading model: " << static_cast<int>(progress) << "%" << std::flush;
    return 0;
  });
  std::cout << "\n";
}
model->Load();

Definiowanie monitu systemowego

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

Request system_prompt{
    SystemMessage(
        "You are a concise, friendly local assistant. State when you are unsure."),
};
session.ProcessRequest(system_prompt);

Implementowanie konwersacji wieloełowej

Zachowaj historię konwersacji i wyślij ją przy użyciu każdego żądania:

std::cout << "Enter a message, or type 'exit'.\n";
for (std::string input; std::cout << "You: " && std::getline(std::cin, input);) {
  if (input == "exit" || input == "quit") {
    break;
  }
  if (input.empty()) {
    continue;
  }

  EnableStreaming(session);
  Request request{UserMessage(input)};
  std::cout << "Assistant: ";
  session.ProcessRequest(request);
  std::cout << "\n";
}

Dodawanie odpowiedzi przesyłania strumieniowego

Przesyłanie strumieniowe tekstu odpowiedzi podczas generowania go przez model:

void EnableStreaming(ChatSession& session) {
  session.SetStreamingCallback([](Item item) {
    if (item.GetType() == FOUNDRY_LOCAL_ITEM_TEXT &&
        item.GetText().type == FOUNDRY_LOCAL_TEXT_ITEM_TYPE_DEFAULT) {
      std::cout << item.GetText().text << std::flush;
    }
    return 0;
  });
}

Kompletny kod

#include <foundry_local/foundry_local_cpp.h>

#include <iostream>
#include <memory>
#include <string>

using namespace foundry_local;

std::unique_ptr<IModel> SelectChatModel(ICatalog& catalog) {
  for (const std::string alias : {"qwen2.5-0.5b", "qwen3.5-0.8b"}) {
    auto model = catalog.GetModel(alias);
    if (!model) {
      continue;
    }
    for (const auto& variant : model->GetVariants()) {
      const auto info = variant->GetInfo();
      if (info.DeviceType() == FOUNDRY_LOCAL_DEVICE_CPU &&
          (info.Task() == "chat-completion" || info.Task() == "coding")) {
        model->SelectVariant(*variant);
        return model;
      }
    }
  }
  return nullptr;
}

void EnableStreaming(ChatSession& session);

int main() {
  try {
    Configuration config("foundry_local_cpp_tutorial_chat");
    Manager manager(std::move(config));
    auto model = SelectChatModel(manager.GetCatalog());
    if (!model) {
      throw std::runtime_error("No supported CPU chat model was found.");
    }
    if (!model->IsCached()) {
      model->Download([](float progress) {
        std::cout << "\rDownloading model: " << static_cast<int>(progress) << "%" << std::flush;
        return 0;
      });
      std::cout << "\n";
    }
    model->Load();

    {
      ChatSession session(*model);
      session.SetOptions({
          .search = {.temperature = 0.2F, .max_output_tokens = 512, .seed = 42},
      });

      Request system_prompt{
          SystemMessage(
              "You are a concise, friendly local assistant. State when you are unsure."),
      };
      session.ProcessRequest(system_prompt);

      std::cout << "Enter a message, or type 'exit'.\n";
      for (std::string input; std::cout << "You: " && std::getline(std::cin, input);) {
        if (input == "exit" || input == "quit") {
          break;
        }
        if (input.empty()) {
          continue;
        }

        EnableStreaming(session);
        Request request{UserMessage(input)};
        std::cout << "Assistant: ";
        session.ProcessRequest(request);
        std::cout << "\n";
      }
    }

    model->Unload();
    manager.Shutdown();
  } catch (const Error& error) {
    std::cerr << "Foundry Local error [" << error.Code() << "]: " << error.what() << "\n";
    return 1;
  } catch (const std::exception& error) {
    std::cerr << "Error: " << error.what() << "\n";
    return 1;
  }

  return 0;
}

void EnableStreaming(ChatSession& session) {
  session.SetStreamingCallback([](Item item) {
    if (item.GetType() == FOUNDRY_LOCAL_ITEM_TEXT &&
        item.GetText().type == FOUNDRY_LOCAL_TEXT_ITEM_TYPE_DEFAULT) {
      std::cout << item.GetText().text << std::flush;
    }
    return 0;
  });
}

Uruchom asystenta czatu:

./build/tutorial-chat-assistant

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.