Tutorial: Criar um assistente de chat de vários turnos com o Foundry Local

Neste tutorial, você criará um assistente de chat interativo que é executado inteiramente em seu dispositivo. O assistente mantém o contexto de conversa em várias trocas, portanto, ele se lembra do que você discutiu anteriormente na conversa. Você usa o Foundry Local SDK para selecionar um modelo, definir um prompt do sistema e transmitir as respostas token por token.

Neste tutorial, você aprenderá como:

  • Configurar um projeto e instalar o SDK Local do Foundry
  • Navegue pelo catálogo de modelos e selecione um modelo
  • Definir um prompt do sistema para moldar o comportamento do assistente
  • Implementar uma conversa de vários turnos com histórico de mensagens
  • Transmitir respostas para uma experiência responsiva
  • Liberar recursos quando a conversa terminar

Pré-requisitos

  • Um computador Windows, macOS ou Linux com pelo menos 8 GB de RAM.

Repositório de exemplos

O código de exemplo completo deste artigo está disponível no repositório Foundry Local GitHub. Para clonar o repositório e acessar o exemplo, use:

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

Instalar pacotes

Se você estiver desenvolvendo ou enviando em Windows, selecione a guia Windows. O pacote Windows integra-se ao runtime Windows ML – ele fornece a mesma área de superfície de API com uma amplitude maior de aceleração de hardware.

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

Os exemplos de C# no repositório GitHub são projetos pré-configurados. Se você estiver criando do zero, leia a referência do SDK Local do Foundry para obter mais detalhes sobre como configurar seu projeto em C# com o Foundry Local.

Navegue pelo catálogo e selecione um modelo

O SDK Local do Foundry fornece um catálogo de modelos que lista todos os modelos disponíveis. Nesta etapa, você inicializa o SDK e seleciona um modelo para seu assistente de chat.

  • Abra Program.cs e substitua seu conteúdo pelo seguinte código para inicializar o SDK e selecionar um modelo:

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

    O método GetModelAsync aceita um apelido de modelo, que é um nome amigável curto que corresponde a um modelo específico no catálogo. O DownloadAsync método busca os pesos do modelo para o cache local e LoadAsync prepara o modelo para inferência.

Definir um prompt do sistema

Um prompt do sistema define a personalidade e o comportamento do assistente. É a primeira mensagem no histórico de conversas e o modelo faz referência a ela durante toda a conversa.

Adicione um prompt do sistema para moldar como o assistente responde:

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

Dica

Experimente diferentes prompts do sistema para alterar o comportamento do assistente. Por exemplo, você pode instruí-lo a responder como um pirata, um professor ou um especialista em domínio.

Implementar diálogo em várias etapas

Um assistente de chat precisa manter o contexto em várias trocas. Você consegue isso mantendo uma lista de todas as mensagens (sistema, usuário e assistente) e enviando a lista completa com cada solicitação. O modelo usa esse histórico para gerar respostas contextualmente relevantes.

Adicione um loop de conversa que:

  • Lê a entrada do usuário do console.
  • Acrescenta a mensagem do usuário ao histórico.
  • Envia o histórico completo para o modelo.
  • Adiciona a resposta do assistente ao histórico para o próximo turno.
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 });
}

Cada chamada para CompleteChatAsync recebe o histórico completo de mensagens. É assim que o modelo "lembra" de turnos anteriores — ele não armazena o estado entre as chamadas.

Adicionar respostas de streaming

O streaming imprime cada token conforme ele é gerado, o que faz com que o assistente se sinta mais responsivo. Substitua a chamada CompleteChatAsync por CompleteChatStreamingAsync para transmitir a resposta token por token.

Atualize o loop de conversa para usar o 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");

A versão de streaming acumula a resposta completa para que ela possa ser adicionada ao histórico de conversas após a conclusão do fluxo.

Código completo

Substitua o conteúdo de Program.cs pelo seguinte código completo:

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

Execute o assistente de chat:

dotnet run

Você vê uma saída semelhante a:

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!

Observe como o assistente se lembra do contexto de turnos anteriores – quando você pergunta "Por que é importante para outros seres vivos?", ele sabe que você ainda está falando sobre fotossíntese.

Repositório de exemplos

O código de exemplo completo deste artigo está disponível no repositório Foundry Local GitHub. Para clonar o repositório e acessar o exemplo, use:

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

Instalar pacotes

Se você estiver desenvolvendo ou enviando em Windows, selecione a guia Windows. O pacote Windows integra-se ao runtime Windows ML – ele fornece a mesma área de superfície de API com uma amplitude maior de aceleração de hardware.

npm install foundry-local-sdk-winml openai

Navegue pelo catálogo e selecione um modelo

O SDK Local do Foundry fornece um catálogo de modelos que lista todos os modelos disponíveis. Nesta etapa, você inicializa o SDK e seleciona um modelo para seu assistente de chat.

  1. Crie um ficheiro chamado index.js.

  2. Adicione o seguinte código para inicializar o SDK e selecione um modelo:

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

    O método getModel aceita um apelido de modelo, que é um nome amigável curto que corresponde a um modelo específico no catálogo. O download método busca os pesos do modelo para o cache local e load prepara o modelo para inferência.

Definir um prompt do sistema

Um prompt do sistema define a personalidade e o comportamento do assistente. É a primeira mensagem no histórico de conversas e o modelo faz referência a ela durante toda a conversa.

Adicione um prompt do sistema para moldar como o assistente responde:

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

Dica

Experimente diferentes prompts do sistema para alterar o comportamento do assistente. Por exemplo, você pode instruí-lo a responder como um pirata, um professor ou um especialista em domínio.

Implementar diálogo em várias etapas

Um assistente de chat precisa manter o contexto em várias trocas. Você consegue isso mantendo uma lista de todas as mensagens (sistema, usuário e assistente) e enviando a lista completa com cada solicitação. O modelo usa esse histórico para gerar respostas contextualmente relevantes.

Adicione um loop de conversa que:

  • Lê a entrada do usuário do console.
  • Acrescenta a mensagem do usuário ao histórico.
  • Envia o histórico completo para o modelo.
  • Adiciona a resposta do assistente ao histórico para o próximo turno.
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 });
}

Cada chamada para completeChat recebe o histórico completo de mensagens. É assim que o modelo "lembra" de turnos anteriores — ele não armazena o estado entre as chamadas.

Adicionar respostas de streaming

O streaming imprime cada token conforme ele é gerado, o que faz com que o assistente se sinta mais responsivo. Substitua a chamada completeChat por completeStreamingChat para transmitir a resposta token por token.

Atualize o loop de conversa para usar o 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');

A versão de streaming acumula a resposta completa para que ela possa ser adicionada ao histórico de conversas após a conclusão do fluxo.

Código completo

Crie um arquivo nomeado index.js e adicione o seguinte código completo:

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

Execute o assistente de chat:

node index.js

Você vê uma saída semelhante a:

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!

Observe como o assistente se lembra do contexto de turnos anteriores – quando você pergunta "Por que é importante para outros seres vivos?", ele sabe que você ainda está falando sobre fotossíntese.

Repositório de exemplos

O código de exemplo completo deste artigo está disponível no repositório Foundry Local GitHub. Para clonar o repositório e acessar o exemplo, use:

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

Instalar pacotes

Se você estiver desenvolvendo ou enviando em Windows, selecione a guia Windows. O pacote Windows integra-se ao runtime Windows ML – ele fornece a mesma área de superfície de API com uma amplitude maior de aceleração de hardware.

pip install foundry-local-sdk-winml openai

Navegue pelo catálogo e selecione um modelo

O SDK Local do Foundry fornece um catálogo de modelos que lista todos os modelos disponíveis. Nesta etapa, você inicializa o SDK e seleciona um modelo para seu assistente de chat.

  1. Crie um ficheiro chamado main.py.

  2. Adicione o seguinte código para inicializar o SDK e selecione um modelo:

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

    O método get_model aceita um apelido de modelo, que é um nome amigável curto que corresponde a um modelo específico no catálogo. O download método busca os pesos do modelo para o cache local e load prepara o modelo para inferência.

Definir um prompt do sistema

Um prompt do sistema define a personalidade e o comportamento do assistente. É a primeira mensagem no histórico de conversas e o modelo faz referência a ela durante toda a conversa.

Adicione um prompt do sistema para moldar como o assistente responde:

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

Dica

Experimente diferentes prompts do sistema para alterar o comportamento do assistente. Por exemplo, você pode instruí-lo a responder como um pirata, um professor ou um especialista em domínio.

Implementar diálogo em várias etapas

Um assistente de chat precisa manter o contexto em várias trocas. Você consegue isso mantendo uma lista de todas as mensagens (sistema, usuário e assistente) e enviando a lista completa com cada solicitação. O modelo usa esse histórico para gerar respostas contextualmente relevantes.

Adicione um loop de conversa que:

  • Lê a entrada do usuário do console.
  • Acrescenta a mensagem do usuário ao histórico.
  • Envia o histórico completo para o modelo.
  • Adiciona a resposta do assistente ao histórico para o próximo turno.
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})

Cada chamada para complete_chat recebe o histórico completo de mensagens. É assim que o modelo "lembra" de turnos anteriores — ele não armazena o estado entre as chamadas.

Adicionar respostas de streaming

O streaming imprime cada token conforme ele é gerado, o que faz com que o assistente se sinta mais responsivo. Substitua a chamada complete_chat por complete_streaming_chat para transmitir a resposta token por token.

Atualize o loop de conversa para usar o 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")

A versão de streaming acumula a resposta completa para que ela possa ser adicionada ao histórico de conversas após a conclusão do fluxo.

Código completo

Crie um arquivo nomeado main.py e adicione o seguinte código completo:

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

Execute o assistente de chat:

python main.py

Você vê uma saída semelhante a:

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!

Observe como o assistente se lembra do contexto de turnos anteriores – quando você pergunta "Por que é importante para outros seres vivos?", ele sabe que você ainda está falando sobre fotossíntese.

Repositório de exemplos

O código de exemplo completo deste artigo está disponível no repositório Foundry Local GitHub. Para clonar o repositório e acessar o exemplo, use:

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

Instalar pacotes

Se você estiver desenvolvendo ou enviando em Windows, selecione a guia Windows. O pacote Windows integra-se ao runtime Windows ML – ele fornece a mesma área de superfície de API com uma amplitude maior de aceleração de hardware.

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

Navegue pelo catálogo e selecione um modelo

O SDK Local do Foundry fornece um catálogo de modelos que lista todos os modelos disponíveis. Nesta etapa, você inicializa o SDK e seleciona um modelo para seu assistente de chat.

  • Abra src/main.rs e substitua seu conteúdo pelo seguinte código para inicializar o SDK e selecionar um modelo:

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

    O método get_model aceita um apelido de modelo, que é um nome amigável curto que corresponde a um modelo específico no catálogo. O download método busca os pesos do modelo para o cache local e load prepara o modelo para inferência.

Definir um prompt do sistema

Um prompt do sistema define a personalidade e o comportamento do assistente. É a primeira mensagem no histórico de conversas e o modelo faz referência a ela durante toda a conversa.

Adicione um prompt do sistema para moldar como o assistente responde:

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

Dica

Experimente diferentes prompts do sistema para alterar o comportamento do assistente. Por exemplo, você pode instruí-lo a responder como um pirata, um professor ou um especialista em domínio.

Implementar diálogo em várias etapas

Um assistente de chat precisa manter o contexto em várias trocas. Você consegue isso mantendo um vetor de todas as mensagens (sistema, usuário e assistente) e enviando a lista completa com cada solicitação. O modelo usa esse histórico para gerar respostas contextualmente relevantes.

Adicione um loop de conversa que:

  • Lê a entrada do usuário do console.
  • Acrescenta a mensagem do usuário ao histórico.
  • Envia o histórico completo para o modelo.
  • Adiciona a resposta do assistente ao histórico para o próximo turno.
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);
}

Cada chamada para complete_chat recebe o histórico completo de mensagens. É assim que o modelo "lembra" de turnos anteriores — ele não armazena o estado entre as chamadas.

Adicionar respostas de streaming

O streaming imprime cada token conforme ele é gerado, o que faz com que o assistente se sinta mais responsivo. Substitua a chamada complete_chat por complete_streaming_chat para transmitir a resposta token por token.

Atualize o loop de conversa para usar o 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");

A versão de streaming acumula a resposta completa para que ela possa ser adicionada ao histórico de conversas após a conclusão do fluxo.

Código completo

Substitua o conteúdo de src/main.rs pelo seguinte código completo:

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

Execute o assistente de chat:

cargo run

Você vê uma saída semelhante a:

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!

Observe como o assistente se lembra do contexto de turnos anteriores – quando você pergunta "Por que é importante para outros seres vivos?", ele sabe que você ainda está falando sobre fotossíntese.

Limpar os recursos

Os pesos do modelo permanecem no cache local depois que você descarrega um modelo. Isso significa que, na próxima vez que você executar o aplicativo, a etapa de download será ignorada e o modelo será carregado mais rapidamente. Nenhuma limpeza extra é necessária, a menos que você queira recuperar espaço em disco.