Tutorial: Criar um resumo de documentos

Crie um aplicativo que lê arquivos de texto e gere resumos concisos – inteiramente em seu dispositivo. Isso é útil quando você precisa entender rapidamente o conteúdo dos documentos sem lê-los na íntegra e quando os documentos contêm informações confidenciais que não devem sair do computador.

Neste tutorial, você aprenderá como:

  • Configurar um projeto e instalar o SDK Local do Foundry
  • Ler um documento de texto do sistema de arquivos
  • Carregar um modelo e gerar um resumo
  • Controle a saída do resumo por meio de prompts do sistema
  • Processar vários documentos em um lote
  • Limpar os recursos

Pré-requisitos

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

Instalar pacotes

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-document-summarizer

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.

Ler um documento de texto

Antes de resumir qualquer coisa, você precisa de um documento de exemplo para trabalhar. Crie um arquivo chamado document.txt no diretório do projeto e adicione o seguinte conteúdo:

Automated testing is a practice in software development where tests are written and executed
by specialized tools rather than performed manually. There are several categories of automated
tests, including unit tests, integration tests, and end-to-end tests. Unit tests verify that
individual functions or methods behave correctly in isolation. Integration tests check that
multiple components work together as expected. End-to-end tests simulate real user workflows
across the entire application.

Adopting automated testing brings measurable benefits to a development team. It catches
regressions early, before they reach production. It reduces the time spent on repetitive
manual verification after each code change. It serves as living documentation of expected
behavior, which helps new team members understand the codebase. Continuous integration
pipelines rely on automated tests to gate deployments and maintain release quality.

Effective test suites follow a few guiding principles. Tests should be deterministic, meaning
they produce the same result every time they run. Tests should be independent, so that one
failing test does not cascade into false failures elsewhere. Tests should run fast, because
slow tests discourage developers from running them frequently. Finally, tests should be
maintained alongside production code so they stay accurate as the application evolves.

Agora, abra Program.cs e adicione o seguinte código para ler o documento:

var target = args.Length > 0 ? args[0] : "document.txt";

O código aceita um caminho de arquivo opcional como um argumento de linha de comando e volta para document.txt se nenhum for fornecido.

Gerar um resumo

Inicialize o SDK Local do Foundry, carregue um modelo e envie o conteúdo do documento juntamente com um prompt do sistema que instrui o modelo a resumir.

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

var systemPrompt =
    "Summarize the following document into concise bullet points. " +
    "Focus on the key points and main ideas.";

var target = args.Length > 0 ? args[0] : "document.txt";

if (Directory.Exists(target))
{
    await SummarizeDirectoryAsync(chatClient, target, systemPrompt, ct);
}
else
{
    Console.WriteLine($"--- {Path.GetFileName(target)} ---");
    await SummarizeFileAsync(chatClient, target, systemPrompt, ct);
}

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 ignora o download se eles já estiverem armazenados em cache) e LoadAsync prepara o modelo para inferência. O aviso do sistema instrui o modelo a produzir resumos em tópicos focados em ideias principais.

Sumário de saída de controle

Situações diferentes exigem estilos de resumo diferentes. Você pode alterar o prompt do sistema para controlar como o modelo estrutura sua saída. Aqui estão três variações úteis:

Lista de tópicos (padrão da etapa anterior):

var systemPrompt =
    "Summarize the following document into concise bullet points. " +
    "Focus on the key points and main ideas.";

Resumo de um parágrafo:

var systemPrompt =
    "Summarize the following document in a single, concise paragraph. " +
    "Capture the main argument and supporting points.";

Principais observações:

var systemPrompt =
    "Extract the three most important takeaways from the following document. " +
    "Number each takeaway and keep each to one or two sentences.";

Para tentar um estilo diferente, substitua o valor de Content na mensagem do sistema por uma das sugestões. O modelo segue as instruções no prompt do sistema para moldar o formato e a profundidade do resumo.

Processar vários documentos

Estenda o aplicativo para resumir cada .txt arquivo em um diretório. Isso é útil quando você tem uma pasta de documentos que todos precisam de resumos.

O método a seguir itera em todos os .txt arquivos em um determinado diretório e resume cada um deles:

async Task SummarizeDirectoryAsync(
    dynamic chatClient,
    string directory,
    string systemPrompt,
    CancellationToken ct)
{
    var txtFiles = Directory.GetFiles(directory, "*.txt")
        .OrderBy(f => f)
        .ToArray();

    if (txtFiles.Length == 0)
    {
        Console.WriteLine($"No .txt files found in {directory}");
        return;
    }

    foreach (var txtFile in txtFiles)
    {
        var fileContent = await File.ReadAllTextAsync(txtFile, ct);
        var msgs = new List<ChatMessage>
        {
            new ChatMessage { Role = "system", Content = systemPrompt },
            new ChatMessage { Role = "user", Content = fileContent }
        };

        Console.WriteLine($"--- {Path.GetFileName(txtFile)} ---");
        var resp = await chatClient.CompleteChatAsync(msgs, ct);
        Console.WriteLine(resp.Choices[0].Message.Content);
        Console.WriteLine();
    }
}

Cada arquivo é lido, emparelhado com o mesmo prompt do sistema e enviado para o modelo de forma independente. O modelo não carrega contexto entre arquivos, portanto, cada resumo é independente.

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.\n");

// Get a chat client
var chatClient = await model.GetChatClientAsync();

var systemPrompt =
    "Summarize the following document into concise bullet points. " +
    "Focus on the key points and main ideas.";

var target = args.Length > 0 ? args[0] : "document.txt";

if (Directory.Exists(target))
{
    await SummarizeDirectoryAsync(chatClient, target, systemPrompt, ct);
}
else
{
    Console.WriteLine($"--- {Path.GetFileName(target)} ---");
    await SummarizeFileAsync(chatClient, target, systemPrompt, ct);
}

// Clean up
await model.UnloadAsync();
Console.WriteLine("\nModel unloaded. Done!");

async Task SummarizeFileAsync(
    dynamic client,
    string filePath,
    string prompt,
    CancellationToken token)
{
    var fileContent = await File.ReadAllTextAsync(filePath, token);
    var messages = new List<ChatMessage>
    {
        new ChatMessage { Role = "system", Content = prompt },
        new ChatMessage { Role = "user", Content = fileContent }
    };

    var response = await client.CompleteChatAsync(messages, token);
    Console.WriteLine(response.Choices[0].Message.Content);
}

async Task SummarizeDirectoryAsync(
    dynamic client,
    string directory,
    string prompt,
    CancellationToken token)
{
    var txtFiles = Directory.GetFiles(directory, "*.txt")
        .OrderBy(f => f)
        .ToArray();

    if (txtFiles.Length == 0)
    {
        Console.WriteLine($"No .txt files found in {directory}");
        return;
    }

    foreach (var txtFile in txtFiles)
    {
        Console.WriteLine($"--- {Path.GetFileName(txtFile)} ---");
        await SummarizeFileAsync(client, txtFile, prompt, token);
        Console.WriteLine();
    }
}

Resumir um único arquivo:

dotnet run -- document.txt

Ou resuma cada arquivo .txt em um diretório:

dotnet run -- ./docs

Você vê uma saída semelhante a:

Downloading model: 100.00%
Model loaded and ready.

--- document.txt ---
- Automated testing uses specialized tools to execute tests instead of manual verification.
- Tests fall into three main categories: unit tests (individual functions), integration tests
  (component interactions), and end-to-end tests (full user workflows).
- Key benefits include catching regressions early, reducing manual effort, serving as living
  documentation, and gating deployments through continuous integration pipelines.
- Effective test suites should be deterministic, independent, fast, and maintained alongside
  production code.

Model unloaded. Done!

Instalar pacotes

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-document-summarizer

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

Ler um documento de texto

Antes de resumir qualquer coisa, você precisa de um documento de exemplo para trabalhar. Crie um arquivo chamado document.txt no diretório do projeto e adicione o seguinte conteúdo:

Automated testing is a practice in software development where tests are written and executed
by specialized tools rather than performed manually. There are several categories of automated
tests, including unit tests, integration tests, and end-to-end tests. Unit tests verify that
individual functions or methods behave correctly in isolation. Integration tests check that
multiple components work together as expected. End-to-end tests simulate real user workflows
across the entire application.

Adopting automated testing brings measurable benefits to a development team. It catches
regressions early, before they reach production. It reduces the time spent on repetitive
manual verification after each code change. It serves as living documentation of expected
behavior, which helps new team members understand the codebase. Continuous integration
pipelines rely on automated tests to gate deployments and maintain release quality.

Effective test suites follow a few guiding principles. Tests should be deterministic, meaning
they produce the same result every time they run. Tests should be independent, so that one
failing test does not cascade into false failures elsewhere. Tests should run fast, because
slow tests discourage developers from running them frequently. Finally, tests should be
maintained alongside production code so they stay accurate as the application evolves.

Agora, crie um arquivo chamado index.js e adicione o seguinte código para ler o documento:

const target = process.argv[2] || 'document.txt';

O script aceita um caminho de arquivo opcional como um argumento de linha de comando e volta para document.txt se nenhum for fornecido.

Gerar um resumo

Inicialize o SDK Local do Foundry, carregue um modelo e envie o conteúdo do documento juntamente com um prompt do sistema que instrui o modelo a resumir.

Substitua o conteúdo de index.js pelo seguinte código:

const systemPrompt =
    'Summarize the following document into concise bullet points. ' +
    'Focus on the key points and main ideas.';

const target = process.argv[2] || 'document.txt';

try {
    const stats = statSync(target);
    if (stats.isDirectory()) {
        await summarizeDirectory(chatClient, target, systemPrompt);
    } else {
        console.log(`--- ${basename(target)} ---`);
        await summarizeFile(chatClient, target, systemPrompt);
    }
} catch {
    console.log(`--- ${basename(target)} ---`);
    await summarizeFile(chatClient, target, systemPrompt);
}

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 ignora o download se eles já estiverem armazenados em cache) e load prepara o modelo para inferência. O aviso do sistema instrui o modelo a produzir resumos em tópicos focados em ideias principais.

Sumário de saída de controle

Situações diferentes exigem estilos de resumo diferentes. Você pode alterar o prompt do sistema para controlar como o modelo estrutura sua saída. Aqui estão três variações úteis:

Lista de tópicos (padrão da etapa anterior):

const systemPrompt =
    'Summarize the following document into concise bullet points. ' +
    'Focus on the key points and main ideas.';

Resumo de um parágrafo:

const systemPrompt =
    'Summarize the following document in a single, concise paragraph. ' +
    'Capture the main argument and supporting points.';

Principais observações:

const systemPrompt =
    'Extract the three most important takeaways from the following document. ' +
    'Number each takeaway and keep each to one or two sentences.';

Para tentar um estilo diferente, substitua o valor de content na mensagem do sistema por uma das sugestões. O modelo segue as instruções no prompt do sistema para moldar o formato e a profundidade do resumo.

Processar vários documentos

Estenda o aplicativo para resumir cada .txt arquivo em um diretório. Isso é útil quando você tem uma pasta de documentos que todos precisam de resumos.

A função a seguir itera sobre todos os arquivos .txt em um determinado diretório e resume cada um:

import { readdirSync } from 'fs';
import { join, basename } from 'path';

async function summarizeDirectory(chatClient, directory, systemPrompt) {
    const txtFiles = readdirSync(directory)
        .filter(f => f.endsWith('.txt'))
        .sort();

    if (txtFiles.length === 0) {
        console.log(`No .txt files found in ${directory}`);
        return;
    }

    for (const fileName of txtFiles) {
        const fileContent = readFileSync(join(directory, fileName), 'utf-8');
        const msgs = [
            { role: 'system', content: systemPrompt },
            { role: 'user', content: fileContent }
        ];

        console.log(`--- ${fileName} ---`);
        const resp = await chatClient.completeChat(msgs);
        console.log(resp.choices[0]?.message?.content);
        console.log();
    }
}

Cada arquivo é lido, emparelhado com o mesmo prompt do sistema e enviado para o modelo de forma independente. O modelo não carrega contexto entre arquivos, portanto, cada resumo é independente.

Código completo

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

import { FoundryLocalManager } from 'foundry-local-sdk';
import { readFileSync, readdirSync, statSync } from 'fs';
import { join, basename } from 'path';

async function summarizeFile(chatClient, filePath, systemPrompt) {
    const content = readFileSync(filePath, 'utf-8');
    const messages = [
        { role: 'system', content: systemPrompt },
        { role: 'user', content: content }
    ];

    const response = await chatClient.completeChat(messages);
    console.log(response.choices[0]?.message?.content);
}

async function summarizeDirectory(chatClient, directory, systemPrompt) {
    const txtFiles = readdirSync(directory)
        .filter(f => f.endsWith('.txt'))
        .sort();

    if (txtFiles.length === 0) {
        console.log(`No .txt files found in ${directory}`);
        return;
    }

    for (const fileName of txtFiles) {
        console.log(`--- ${fileName} ---`);
        await summarizeFile(chatClient, join(directory, fileName), systemPrompt);
        console.log();
    }
}

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

// Create a chat client
const chatClient = model.createChatClient();

const systemPrompt =
    'Summarize the following document into concise bullet points. ' +
    'Focus on the key points and main ideas.';

const target = process.argv[2] || 'document.txt';

try {
    const stats = statSync(target);
    if (stats.isDirectory()) {
        await summarizeDirectory(chatClient, target, systemPrompt);
    } else {
        console.log(`--- ${basename(target)} ---`);
        await summarizeFile(chatClient, target, systemPrompt);
    }
} catch {
    console.log(`--- ${basename(target)} ---`);
    await summarizeFile(chatClient, target, systemPrompt);
}

// Clean up
await model.unload();
console.log('\nModel unloaded. Done!');

Resumir um único arquivo:

node index.js document.txt

Ou resuma cada arquivo .txt em um diretório:

node index.js ./docs

Você vê uma saída semelhante a:

Downloading model: 100.00%
Model downloaded.
Model loaded and ready.

--- document.txt ---
- Automated testing uses specialized tools to execute tests instead of manual verification.
- Tests fall into three main categories: unit tests (individual functions), integration tests
  (component interactions), and end-to-end tests (full user workflows).
- Key benefits include catching regressions early, reducing manual effort, serving as living
  documentation, and gating deployments through continuous integration pipelines.
- Effective test suites should be deterministic, independent, fast, and maintained alongside
  production code.

Model unloaded. Done!

Instalar pacotes

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-document-summarizer

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

Ler um documento de texto

Antes de resumir qualquer coisa, você precisa de um documento de exemplo para trabalhar. Crie um arquivo chamado document.txt no diretório do projeto e adicione o seguinte conteúdo:

Automated testing is a practice in software development where tests are written and executed
by specialized tools rather than performed manually. There are several categories of automated
tests, including unit tests, integration tests, and end-to-end tests. Unit tests verify that
individual functions or methods behave correctly in isolation. Integration tests check that
multiple components work together as expected. End-to-end tests simulate real user workflows
across the entire application.

Adopting automated testing brings measurable benefits to a development team. It catches
regressions early, before they reach production. It reduces the time spent on repetitive
manual verification after each code change. It serves as living documentation of expected
behavior, which helps new team members understand the codebase. Continuous integration
pipelines rely on automated tests to gate deployments and maintain release quality.

Effective test suites follow a few guiding principles. Tests should be deterministic, meaning
they produce the same result every time they run. Tests should be independent, so that one
failing test does not cascade into false failures elsewhere. Tests should run fast, because
slow tests discourage developers from running them frequently. Finally, tests should be
maintained alongside production code so they stay accurate as the application evolves.

Agora, crie um arquivo chamado main.py e adicione o seguinte código para ler o documento:

target = sys.argv[1] if len(sys.argv) > 1 else "document.txt"
target_path = Path(target)

O script aceita um caminho de arquivo opcional como um argumento de linha de comando e volta para document.txt se nenhum for fornecido. O Path.read_text método lê todo o arquivo em uma cadeia de caracteres.

Gerar um resumo

Inicialize o SDK Local do Foundry, carregue um modelo e envie o conteúdo do documento juntamente com um prompt do sistema que instrui o modelo a resumir.

Substitua o conteúdo de main.py pelo seguinte código:

system_prompt = (
    "Summarize the following document into concise bullet points. "
    "Focus on the key points and main ideas."
)

target = sys.argv[1] if len(sys.argv) > 1 else "document.txt"
target_path = Path(target)

if target_path.is_dir():
    summarize_directory(client, target_path, system_prompt)
else:
    print(f"--- {target_path.name} ---")
    summarize_file(client, target_path, system_prompt)

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 ignora o download se eles já estiverem armazenados em cache) e load prepara o modelo para inferência. O aviso do sistema instrui o modelo a produzir resumos em tópicos focados em ideias principais.

Sumário de saída de controle

Situações diferentes exigem estilos de resumo diferentes. Você pode alterar o prompt do sistema para controlar como o modelo estrutura sua saída. Aqui estão três variações úteis:

Lista de tópicos (padrão da etapa anterior):

system_prompt = (
    "Summarize the following document into concise bullet points. "
    "Focus on the key points and main ideas."
)

Resumo de um parágrafo:

system_prompt = (
    "Summarize the following document in a single, concise paragraph. "
    "Capture the main argument and supporting points."
)

Principais observações:

system_prompt = (
    "Extract the three most important takeaways from the following document. "
    "Number each takeaway and keep each to one or two sentences."
)

Para tentar um estilo diferente, substitua o valor de "content" na mensagem do sistema por uma das sugestões. O modelo segue as instruções no prompt do sistema para moldar o formato e a profundidade do resumo.

Processar vários documentos

Estenda o aplicativo para resumir cada .txt arquivo em um diretório. Isso é útil quando você tem uma pasta de documentos que todos precisam de resumos.

A função a seguir itera sobre todos os arquivos .txt em um determinado diretório e resume cada um:

async def summarize_directory(client, directory):
    txt_files = sorted(Path(directory).glob("*.txt"))

    if not txt_files:
        print(f"No .txt files found in {directory}")
        return

    for txt_file in txt_files:
        content = txt_file.read_text(encoding="utf-8")
        messages = [
            {
                "role": "system",
                "content": "Summarize the following document into concise bullet points. "
                           "Focus on the key points and main ideas."
            },
            {"role": "user", "content": content}
        ]

        print(f"--- {txt_file.name} ---")
        response = client.complete_chat(messages)
        print(response.choices[0].message.content)
        print()

Cada arquivo é lido, emparelhado com o mesmo prompt do sistema e enviado para o modelo de forma independente. O modelo não carrega contexto entre arquivos, portanto, cada resumo é independente.

Código completo

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

import sys
from pathlib import Path
from foundry_local_sdk import Configuration, FoundryLocalManager


def summarize_file(client, file_path, system_prompt):
    """Summarize a single file and print the result."""
    content = Path(file_path).read_text(encoding="utf-8")
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": content}
    ]
    response = client.complete_chat(messages)
    print(response.choices[0].message.content)


def summarize_directory(client, directory, system_prompt):
    """Summarize all .txt files in a directory."""
    txt_files = sorted(Path(directory).glob("*.txt"))

    if not txt_files:
        print(f"No .txt files found in {directory}")
        return

    for txt_file in txt_files:
        print(f"--- {txt_file.name} ---")
        summarize_file(client, txt_file, system_prompt)
        print()


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 p: print(f"\rDownloading model: {p:.2f}%", end="", flush=True))
    print()
    model.load()
    print("Model loaded and ready.\n")

    # Get a chat client
    client = model.get_chat_client()

    system_prompt = (
        "Summarize the following document into concise bullet points. "
        "Focus on the key points and main ideas."
    )

    target = sys.argv[1] if len(sys.argv) > 1 else "document.txt"
    target_path = Path(target)

    if target_path.is_dir():
        summarize_directory(client, target_path, system_prompt)
    else:
        print(f"--- {target_path.name} ---")
        summarize_file(client, target_path, system_prompt)

    # Clean up
    model.unload()
    print("\nModel unloaded. Done!")


if __name__ == "__main__":
    main()

Resumir um único arquivo:

python main.py document.txt

Ou resuma cada arquivo .txt em um diretório:

python main.py ./docs

Você vê uma saída semelhante a:

Downloading model: 100.00%
Model loaded and ready.

--- document.txt ---
- Automated testing uses specialized tools to execute tests instead of manual verification.
- Tests fall into three main categories: unit tests (individual functions), integration tests
  (component interactions), and end-to-end tests (full user workflows).
- Key benefits include catching regressions early, reducing manual effort, serving as living
  documentation, and gating deployments through continuous integration pipelines.
- Effective test suites should be deterministic, independent, fast, and maintained alongside
  production code.

Model unloaded. Done!

Instalar pacotes

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-document-summarizer

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

Ler um documento de texto

Antes de resumir qualquer coisa, você precisa de um documento de exemplo para trabalhar. Crie um arquivo chamado document.txt no diretório do projeto e adicione o seguinte conteúdo:

Automated testing is a practice in software development where tests are written and executed
by specialized tools rather than performed manually. There are several categories of automated
tests, including unit tests, integration tests, and end-to-end tests. Unit tests verify that
individual functions or methods behave correctly in isolation. Integration tests check that
multiple components work together as expected. End-to-end tests simulate real user workflows
across the entire application.

Adopting automated testing brings measurable benefits to a development team. It catches
regressions early, before they reach production. It reduces the time spent on repetitive
manual verification after each code change. It serves as living documentation of expected
behavior, which helps new team members understand the codebase. Continuous integration
pipelines rely on automated tests to gate deployments and maintain release quality.

Effective test suites follow a few guiding principles. Tests should be deterministic, meaning
they produce the same result every time they run. Tests should be independent, so that one
failing test does not cascade into false failures elsewhere. Tests should run fast, because
slow tests discourage developers from running them frequently. Finally, tests should be
maintained alongside production code so they stay accurate as the application evolves.

Agora, abra src/main.rs e adicione o seguinte código para ler o documento:

let target = env::args()
    .nth(1)
    .unwrap_or_else(|| "document.txt".to_string());
let target_path = Path::new(&target);

O código aceita um caminho de arquivo opcional como um argumento de linha de comando e volta para document.txt se nenhum for fornecido.

Gerar um resumo

Inicialize o SDK Local do Foundry, carregue um modelo e envie o conteúdo do documento juntamente com um prompt do sistema que instrui o modelo a resumir.

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

let system_prompt = "Summarize the following document \
     into concise bullet points. Focus on the key \
     points and main ideas.";

let target = env::args()
    .nth(1)
    .unwrap_or_else(|| "document.txt".to_string());
let target_path = Path::new(&target);

if target_path.is_dir() {
    summarize_directory(
        &client,
        target_path,
        system_prompt,
    )
    .await?;
} else {
    let file_name = target_path
        .file_name()
        .map(|n| n.to_string_lossy().to_string())
        .unwrap_or_else(|| target.clone());
    println!("--- {} ---", file_name);
    summarize_file(
        &client,
        target_path,
        system_prompt,
    )
    .await?;
}

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 ignora o download se eles já estiverem armazenados em cache) e load prepara o modelo para inferência. O aviso do sistema instrui o modelo a produzir resumos em tópicos focados em ideias principais.

Sumário de saída de controle

Situações diferentes exigem estilos de resumo diferentes. Você pode alterar o prompt do sistema para controlar como o modelo estrutura sua saída. Aqui estão três variações úteis:

Lista de tópicos (padrão da etapa anterior):

let system_prompt =
    "Summarize the following document into concise bullet points. \
     Focus on the key points and main ideas.";

Resumo de um parágrafo:

let system_prompt =
    "Summarize the following document in a single, concise paragraph. \
     Capture the main argument and supporting points.";

Principais observações:

let system_prompt =
    "Extract the three most important takeaways from the following document. \
     Number each takeaway and keep each to one or two sentences.";

Para tentar um estilo diferente, substitua o conteúdo da mensagem do sistema por um dos prompts. O modelo segue as instruções no prompt do sistema para moldar o formato e a profundidade do resumo.

Processar vários documentos

Estenda o aplicativo para resumir cada .txt arquivo em um diretório. Isso é útil quando você tem uma pasta de documentos que todos precisam de resumos.

A função a seguir itera sobre todos os arquivos .txt em um determinado diretório e resume cada um:

use std::path::Path;

async fn summarize_directory(
    client: &foundry_local_sdk::ChatClient,
    directory: &Path,
    system_prompt: &str,
) -> anyhow::Result<()> {
    let mut txt_files: Vec<_> = fs::read_dir(directory)?
        .filter_map(|entry| entry.ok())
        .filter(|entry| {
            entry.path().extension()
                .map(|ext| ext == "txt")
                .unwrap_or(false)
        })
        .collect();

    txt_files.sort_by_key(|e| e.path());

    if txt_files.is_empty() {
        println!("No .txt files found in {}", directory.display());
        return Ok(());
    }

    for entry in &txt_files {
        let file_content = fs::read_to_string(entry.path())?;
        let messages: Vec<ChatCompletionRequestMessage> = vec![
            ChatCompletionRequestSystemMessage::new(system_prompt).into(),
            ChatCompletionRequestUserMessage::new(&file_content).into(),
        ];

        let file_name = entry.file_name();
        println!("--- {} ---", file_name.to_string_lossy());
        let resp = client.complete_chat(&messages, None).await?;
        let text = resp.choices[0]
            .message
            .content
            .as_deref()
            .unwrap_or("");
        println!("{}\n", text);
    }

    Ok(())
}

Cada arquivo é lido, emparelhado com o mesmo prompt do sistema e enviado para o modelo de forma independente. O modelo não carrega contexto entre arquivos, portanto, cada resumo é independente.

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, Write};
use std::path::Path;
use std::{env, fs};

async fn summarize_file(
    client: &foundry_local_sdk::openai::ChatClient,
    file_path: &Path,
    system_prompt: &str,
) -> anyhow::Result<()> {
    let content = fs::read_to_string(file_path)?;
    let messages: Vec<ChatCompletionRequestMessage> = vec![
        ChatCompletionRequestSystemMessage::from(system_prompt)
            .into(),
        ChatCompletionRequestUserMessage::from(content.as_str())
            .into(),
    ];

    let response =
        client.complete_chat(&messages, None).await?;
    let summary = response.choices[0]
        .message
        .content
        .as_deref()
        .unwrap_or("");
    println!("{}", summary);
    Ok(())
}

async fn summarize_directory(
    client: &foundry_local_sdk::openai::ChatClient,
    directory: &Path,
    system_prompt: &str,
) -> anyhow::Result<()> {
    let mut txt_files: Vec<_> = fs::read_dir(directory)?
        .filter_map(|entry| entry.ok())
        .filter(|entry| {
            entry
                .path()
                .extension()
                .map(|ext| ext == "txt")
                .unwrap_or(false)
        })
        .collect();

    txt_files.sort_by_key(|e| e.path());

    if txt_files.is_empty() {
        println!(
            "No .txt files found in {}",
            directory.display()
        );
        return Ok(());
    }

    for entry in &txt_files {
        let file_name = entry.file_name();
        println!(
            "--- {} ---",
            file_name.to_string_lossy()
        );
        summarize_file(
            client,
            &entry.path(),
            system_prompt,
        )
        .await?;
        println!();
    }

    Ok(())
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Initialize the Foundry Local SDK
    let manager = FoundryLocalManager::create(
        FoundryLocalConfig::new("doc-summarizer"),
    )?;

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

    // Create a chat client
    let client = model
        .create_chat_client()
        .temperature(0.7)
        .max_tokens(512);

    let system_prompt = "Summarize the following document \
         into concise bullet points. Focus on the key \
         points and main ideas.";

    let target = env::args()
        .nth(1)
        .unwrap_or_else(|| "document.txt".to_string());
    let target_path = Path::new(&target);

    if target_path.is_dir() {
        summarize_directory(
            &client,
            target_path,
            system_prompt,
        )
        .await?;
    } else {
        let file_name = target_path
            .file_name()
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_else(|| target.clone());
        println!("--- {} ---", file_name);
        summarize_file(
            &client,
            target_path,
            system_prompt,
        )
        .await?;
    }

    // Clean up
    model.unload().await?;
    println!("\nModel unloaded. Done!");

    Ok(())
}

Resumir um único arquivo:

cargo run -- document.txt

Ou resuma cada arquivo .txt em um diretório:

cargo run -- ./docs

Você vê uma saída semelhante a:

Downloading model: 100.00%
Model loaded and ready.

--- document.txt ---
- Automated testing uses specialized tools to execute tests instead of manual verification.
- Tests fall into three main categories: unit tests (individual functions), integration tests
  (component interactions), and end-to-end tests (full user workflows).
- Key benefits include catching regressions early, reducing manual effort, serving as living
  documentation, and gating deployments through continuous integration pipelines.
- Effective test suites should be deterministic, independent, fast, and maintained alongside
  production code.

Model unloaded. Done!

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.