Compartir a través de


Tutorial: Creación de un asistente de chat de varios turnos con Foundry Local

En este tutorial, creará un asistente de chat interactivo que se ejecuta completamente en el dispositivo. El asistente mantiene el contexto de la conversación a lo largo de varios intercambios, así que recuerda lo que discutieron anteriormente. Use el SDK Local de Foundry para seleccionar un modelo, definir un mensaje del sistema y transmitir las respuestas token por token.

En este tutorial aprenderá a:

  • Configuración de un proyecto e instalación del SDK local de Foundry
  • Examinar el catálogo de modelos y seleccionar un modelo
  • Definir un mensaje del sistema para configurar el comportamiento del asistente
  • Implemente conversaciones de varios turnos con historial de mensajes
  • Transmitir respuestas para una experiencia receptiva
  • Limpie los recursos cuando finalice la conversación

Prerrequisitos

  • Un equipo Windows, macOS o Linux con al menos 8 GB de RAM.

Repositorio de ejemplos

El código de ejemplo completo de este artículo está disponible en el repositorio de Foundry Local GitHub. Cómo clonar el repositorio y acceder al directorio de ejemplo:

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

Instalación de paquetes

Si está desarrollando o distribuyendo en Windows, seleccione la pestaña Windows. El paquete de Windows se integra con el entorno de ejecución Windows ML y ofrece la misma superficie de API con una gama más amplia de aceleración de hardware.

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

Los ejemplos de C# del repositorio de GitHub son proyectos preconfigurados. Si va a compilar desde cero, debe leer la referencia del SDK local de Foundry para obtener más detalles sobre cómo configurar el proyecto de C# con Foundry Local.

Examinar el catálogo y seleccionar un modelo

El SDK local de Foundry proporciona un catálogo de modelos que muestra todos los modelos disponibles. En este paso, inicializa el SDK y selecciona un modelo para el asistente de chat.

  • Abra Program.cs y reemplace su contenido por el código siguiente para inicializar el SDK y seleccionar un 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();
    

    El GetModelAsync método acepta un alias de modelo, que es un nombre descriptivo corto que se asigna a un modelo específico del catálogo. El DownloadAsync método captura los pesos del modelo en la memoria caché local y LoadAsync hace que el modelo esté listo para la inferencia.

Definir un mensaje del sistema

Un mensaje del sistema establece la personalidad y el comportamiento del asistente. Es el primer mensaje del historial de conversaciones y el modelo hace referencia a él a lo largo de la conversación.

Agregue un símbolo del sistema para dar forma a cómo responde el asistente:

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

Sugerencia

Experimente con diferentes avisos del sistema para cambiar el comportamiento del asistente. Por ejemplo, puede indicarle que responda como un pirata, un profesor o un experto en dominio.

Implementar una conversación multiturno

Un asistente de chat debe mantener el contexto en varios intercambios. Para ello, mantenga una lista de todos los mensajes (sistema, usuario y asistente) y envíe la lista completa con cada solicitud. El modelo usa este historial para generar respuestas contextualmente relevantes.

Agregue un bucle de conversación que:

  • Lee la entrada del usuario desde la consola.
  • Anexa el mensaje de usuario al historial.
  • Envía el historial completo al modelo.
  • Anexa la respuesta del asistente al historial para el siguiente 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 llamada a CompleteChatAsync recibe el historial de mensajes completo. Así es como el modelo "recuerda" los turnos anteriores, no almacena el estado entre las llamadas.

Agrega respuestas de streaming

El streaming muestra cada token a medida que se genera, lo que hace que el asistente parezca más receptivo. Reemplazar la llamada CompleteChatAsync con CompleteChatStreamingAsync para transmitir la respuesta, token por token.

Actualice el bucle de conversación para usar el streaming:

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

La versión de streaming acumula la respuesta completa para que se pueda agregar al historial de conversaciones una vez completada la secuencia.

Código completo

Reemplace el contenido de Program.cs por el código completo siguiente:

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

Ejecute el asistente de chat:

dotnet run

Verá una salida similar a la siguiente:

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 cómo el asistente recuerda el contexto de los turnos anteriores, cuando se pregunta "¿Por qué es importante para otras cosas vivientes?", sabe que sigue hablando de fotosynthesis.

Repositorio de ejemplos

El código de ejemplo completo de este artículo está disponible en el repositorio de Foundry Local GitHub. Cómo clonar el repositorio y acceder al directorio de ejemplo:

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

Instalación de paquetes

Si está desarrollando o distribuyendo en Windows, seleccione la pestaña Windows. El paquete de Windows se integra con el entorno de ejecución Windows ML y ofrece la misma superficie de API con una gama más amplia de aceleración de hardware.

npm install foundry-local-sdk-winml openai

Examinar el catálogo y seleccionar un modelo

El SDK local de Foundry proporciona un catálogo de modelos que muestra todos los modelos disponibles. En este paso, inicializa el SDK y selecciona un modelo para el asistente de chat.

  1. Cree un archivo denominado index.js.

  2. Agregue el código siguiente para inicializar el SDK y seleccionar un 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();
    

    El getModel método acepta un alias de modelo, que es un nombre descriptivo corto que se asigna a un modelo específico del catálogo. El download método captura los pesos del modelo en la memoria caché local y load hace que el modelo esté listo para la inferencia.

Definir un mensaje del sistema

Un mensaje del sistema establece la personalidad y el comportamiento del asistente. Es el primer mensaje del historial de conversaciones y el modelo hace referencia a él a lo largo de la conversación.

Agregue un símbolo del sistema para dar forma a cómo responde el asistente:

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

Sugerencia

Experimente con diferentes avisos del sistema para cambiar el comportamiento del asistente. Por ejemplo, puede indicarle que responda como un pirata, un profesor o un experto en dominio.

Implementar una conversación multiturno

Un asistente de chat debe mantener el contexto en varios intercambios. Para ello, mantenga una lista de todos los mensajes (sistema, usuario y asistente) y envíe la lista completa con cada solicitud. El modelo usa este historial para generar respuestas contextualmente relevantes.

Agregue un bucle de conversación que:

  • Lee la entrada del usuario desde la consola.
  • Anexa el mensaje de usuario al historial.
  • Envía el historial completo al modelo.
  • Anexa la respuesta del asistente al historial para el siguiente 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 llamada a completeChat recibe el historial de mensajes completo. Así es como el modelo "recuerda" los turnos anteriores, no almacena el estado entre las llamadas.

Agrega respuestas de streaming

El streaming muestra cada token a medida que se genera, lo que hace que el asistente parezca más receptivo. Reemplazar la llamada completeChat con completeStreamingChat para transmitir la respuesta, token por token.

Actualice el bucle de conversación para usar el streaming:

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

La versión de streaming acumula la respuesta completa para que se pueda agregar al historial de conversaciones una vez completada la secuencia.

Código completo

Cree un archivo denominado index.js y agregue el siguiente 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();

Ejecute el asistente de chat:

node index.js

Verá una salida similar a la siguiente:

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 cómo el asistente recuerda el contexto de los turnos anteriores, cuando se pregunta "¿Por qué es importante para otras cosas vivientes?", sabe que sigue hablando de fotosynthesis.

Repositorio de ejemplos

El código de ejemplo completo de este artículo está disponible en el repositorio de Foundry Local GitHub. Cómo clonar el repositorio y acceder al directorio de ejemplo:

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

Instalación de paquetes

Si está desarrollando o distribuyendo en Windows, seleccione la pestaña Windows. El paquete de Windows se integra con el entorno de ejecución Windows ML y ofrece la misma superficie de API con una gama más amplia de aceleración de hardware.

pip install foundry-local-sdk-winml openai

Examinar el catálogo y seleccionar un modelo

El SDK local de Foundry proporciona un catálogo de modelos que muestra todos los modelos disponibles. En este paso, inicializa el SDK y selecciona un modelo para el asistente de chat.

  1. Cree un archivo denominado main.py.

  2. Agregue el código siguiente para inicializar el SDK y seleccionar un 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()
    

    El get_model método acepta un alias de modelo, que es un nombre descriptivo corto que se asigna a un modelo específico del catálogo. El download método captura los pesos del modelo en la memoria caché local y load hace que el modelo esté listo para la inferencia.

Definir un mensaje del sistema

Un mensaje del sistema establece la personalidad y el comportamiento del asistente. Es el primer mensaje del historial de conversaciones y el modelo hace referencia a él a lo largo de la conversación.

Agregue un símbolo del sistema para dar forma a cómo responde el asistente:

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

Sugerencia

Experimente con diferentes avisos del sistema para cambiar el comportamiento del asistente. Por ejemplo, puede indicarle que responda como un pirata, un profesor o un experto en dominio.

Implementar una conversación multiturno

Un asistente de chat debe mantener el contexto en varios intercambios. Para ello, mantenga una lista de todos los mensajes (sistema, usuario y asistente) y envíe la lista completa con cada solicitud. El modelo usa este historial para generar respuestas contextualmente relevantes.

Agregue un bucle de conversación que:

  • Lee la entrada del usuario desde la consola.
  • Anexa el mensaje de usuario al historial.
  • Envía el historial completo al modelo.
  • Anexa la respuesta del asistente al historial para el siguiente 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 llamada a complete_chat recibe el historial de mensajes completo. Así es como el modelo "recuerda" los turnos anteriores, no almacena el estado entre las llamadas.

Agrega respuestas de streaming

El streaming muestra cada token a medida que se genera, lo que hace que el asistente parezca más receptivo. Reemplazar la llamada complete_chat con complete_streaming_chat para transmitir la respuesta, token por token.

Actualice el bucle de conversación para usar el streaming:

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

La versión de streaming acumula la respuesta completa para que se pueda agregar al historial de conversaciones una vez completada la secuencia.

Código completo

Cree un archivo denominado main.py y agregue el siguiente 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()

Ejecute el asistente de chat:

python main.py

Verá una salida similar a la siguiente:

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 cómo el asistente recuerda el contexto de los turnos anteriores, cuando se pregunta "¿Por qué es importante para otras cosas vivientes?", sabe que sigue hablando de fotosynthesis.

Repositorio de ejemplos

El código de ejemplo completo de este artículo está disponible en el repositorio de Foundry Local GitHub. Cómo clonar el repositorio y acceder al directorio de ejemplo:

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

Instalación de paquetes

Si está desarrollando o distribuyendo en Windows, seleccione la pestaña Windows. El paquete de Windows se integra con el entorno de ejecución Windows ML y ofrece la misma superficie de API con una gama más amplia de aceleración de hardware.

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

Examinar el catálogo y seleccionar un modelo

El SDK local de Foundry proporciona un catálogo de modelos que muestra todos los modelos disponibles. En este paso, inicializa el SDK y selecciona un modelo para el asistente de chat.

  • Abra src/main.rs y reemplace su contenido por el código siguiente para inicializar el SDK y seleccionar un 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);
    

    El get_model método acepta un alias de modelo, que es un nombre descriptivo corto que se asigna a un modelo específico del catálogo. El download método captura los pesos del modelo en la memoria caché local y load hace que el modelo esté listo para la inferencia.

Defina un mensaje de sistema

Un mensaje de sistema establece la personalidad y el comportamiento del asistente. Es el primer mensaje del historial de conversaciones y el modelo hace referencia a él a lo largo de la conversación.

Agregue un mensaje de sistema para configurar cómo responde el asistente.

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

Sugerencia

Experimente con diferentes avisos del sistema para cambiar el comportamiento del asistente. Por ejemplo, puede indicarle que responda como un pirata, un profesor o un experto en dominio.

Implementar una conversación multiturno

Un asistente de chat debe mantener el contexto en varios intercambios. Para ello, mantenga un vector de todos los mensajes (sistema, usuario y asistente) y envíe la lista completa con cada solicitud. El modelo usa este historial para generar respuestas contextualmente relevantes.

Agregue un bucle de conversación que:

  • Lee la entrada del usuario desde la consola.
  • Anexa el mensaje de usuario al historial.
  • Envía el historial completo al modelo.
  • Anexa la respuesta del asistente al historial para el siguiente 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 llamada a complete_chat recibe el historial de mensajes completo. Así es como el modelo "recuerda" los turnos anteriores, no almacena el estado entre las llamadas.

Agrega respuestas de streaming

El streaming muestra cada token a medida que se genera, lo que hace que el asistente parezca más receptivo. Reemplazar la llamada complete_chat con complete_streaming_chat para transmitir la respuesta, token por token.

Actualice el bucle de conversación para usar el streaming:

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

La versión de streaming acumula la respuesta completa para que se pueda agregar al historial de conversaciones una vez completada la secuencia.

Código completo

Reemplace el contenido de src/main.rs por el código completo siguiente:

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

Ejecute el asistente de chat:

cargo run

Verá una salida similar a la siguiente:

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 cómo el asistente recuerda el contexto de los turnos anteriores, cuando se pregunta "¿Por qué es importante para otras cosas vivientes?", sabe que sigue hablando de fotosynthesis.

Limpieza de recursos

Los parámetros de ponderación del modelo se quedan almacenados en la memoria caché local después de descargar un modelo. Esto significa que la próxima vez que ejecute la aplicación, se omite el paso de descarga y el modelo se carga más rápido. No se necesita ninguna limpieza adicional a menos que quiera reclamar espacio en disco.