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.

Repositório de exemplos

Você pode encontrar o código de exemplo completo deste artigo no repositório Foundry samples no GitHub. Para clonar o repositório e navegar até o exemplo, use:

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

Instalar pacotes

dotnet add package Microsoft.AI.Foundry.Local
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(model, target, systemPrompt, ct);
}
else
{
    Console.WriteLine($"--- {Path.GetFileName(target)} ---");
    await SummarizeFileAsync(model, 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 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");


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(model, target, systemPrompt, ct);
}
else
{
    Console.WriteLine($"--- {Path.GetFileName(target)} ---");
    await SummarizeFileAsync(model, target, systemPrompt, ct);
}

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

async Task SummarizeFileAsync(
    IModel loadedModel,
    string filePath,
    string prompt,
    CancellationToken token)
{
    var fileContent = await File.ReadAllTextAsync(filePath, token);
    using var session = new ChatSession(loadedModel);
    using var request = new Request();
    request.AddItem(MessageItem.System(prompt));
    request.AddItem(MessageItem.User(fileContent));
    using var response = await session.ProcessRequestAsync(request, token);
    Console.WriteLine(string.Concat(
        response.OfType<MessageItem>()
            .SelectMany(message => message.Parts)
            .OfType<TextItem>()
            .Where(text => text.Type == TextItemType.Default)
            .Select(text => text.Text)));
}

async Task SummarizeDirectoryAsync(
    IModel loadedModel,
    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(loadedModel, 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!

Repositório de exemplos

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

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

Instalar pacotes

npm install foundry-local-sdk 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(model, target, systemPrompt);
    } else {
        console.log(`--- ${basename(target)} ---`);
        const session = new ChatSession(model);
        await summarizeFile(session, target, systemPrompt);
        session.dispose();
    }
} catch {
    console.log(`--- ${basename(target)} ---`);
    const session = new ChatSession(model);
    await summarizeFile(session, target, systemPrompt);
    session.dispose();
}

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 {
    ChatSession,
    FoundryLocalManager,
    Item,
    Request
} from 'foundry-local-sdk';
import { readFileSync, readdirSync, statSync } from 'fs';
import { join, basename } from 'path';

async function summarizeFile(chatSession, filePath, systemPrompt) {
    const content = readFileSync(filePath, 'utf-8');
    const request = new Request()
        .addItem(Item.systemMessage(systemPrompt))
        .addItem(Item.userMessage(content));
    const response = await chatSession.processRequest(request);
    const text = response.output
        .map((item) => item.type === 'message'
            ? item.content ?? item.parts?.map((part) => part.text ?? '').join('') ?? ''
            : item.type === 'text' ? item.text : '')
        .join('');
    console.log(text);
}

async function summarizeDirectory(model, 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} ---`);
        const session = new ChatSession(model);
        await summarizeFile(session, join(directory, fileName), systemPrompt);
        session.dispose();
        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');


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(model, target, systemPrompt);
    } else {
        console.log(`--- ${basename(target)} ---`);
        const session = new ChatSession(model);
        await summarizeFile(session, target, systemPrompt);
        session.dispose();
    }
} catch {
    console.log(`--- ${basename(target)} ---`);
    const session = new ChatSession(model);
    await summarizeFile(session, target, systemPrompt);
    session.dispose();
}

// 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!

Repositório de exemplos

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

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

Instalar pacotes

pip install foundry-local-sdk 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(model, target_path, system_prompt)
else:
    print(f"--- {target_path.name} ---")
    summarize_file(model, 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 (
    ChatSession,
    Configuration,
    FoundryLocalManager,
    MessageItem,
    Request,
    RequestOptions,
    SearchOptions,
    TextItem,
)



def get_text(response):
    parts = []
    for item in response:
        if isinstance(item, TextItem):
            parts.append(item.text)
        elif isinstance(item, MessageItem):
            parts.extend(part.text for part in item.parts if isinstance(part, TextItem))
    return "".join(parts)


def summarize_file(model, file_path, system_prompt):
    """Summarize a single file and print the result."""
    content = Path(file_path).read_text(encoding="utf-8")
    with ChatSession(model) as session:
        session.set_options(
            RequestOptions(
                search=SearchOptions(temperature=0.2, max_output_tokens=512)
            )
        )
        with (
            Request()
            .add_item(MessageItem.system(system_prompt))
            .add_item(MessageItem.user(content)) as request
        ):
            with session.process_request(request) as response:
                print(get_text(response))


def summarize_directory(model, 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(model, 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

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

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

    try:
        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(model, target_path, system_prompt)
        else:
            print(f"--- {target_path.name} ---")
            summarize_file(model, target_path, system_prompt)
    finally:
        # Clean up
        if model.is_loaded:
            model.unload()
        manager.close()
        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!

Repositório de exemplos

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

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

Instalar pacotes

cargo add foundry-local-sdk
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(&model, target_path, system_prompt).await?;
} else {
    let file_name = target_path
        .file_name()
        .map(|name| name.to_string_lossy().to_string())
        .unwrap_or_else(|| target.clone());
    println!("--- {file_name} ---");
    summarize_file(&model, 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::{
    ChatSession, FoundryLocalConfig, FoundryLocalManager, Item, Model, Request, RequestOptions,
    SearchOptions,
};
use std::io::{self, Write};
use std::path::Path;
use std::{env, fs};

async fn summarize_file(
    model: &Model,
    file_path: &Path,
    system_prompt: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let content = fs::read_to_string(file_path)?;
    let session = ChatSession::new(model).await?;
    session
        .set_options(RequestOptions {
            search: SearchOptions {
                temperature: Some(0.7),
                max_output_tokens: Some(512),
                ..Default::default()
            },
            ..Default::default()
        })
        .await?;

    let response = session
        .process_request(Request::from_items(vec![
            Item::system_message(vec![Item::text(system_prompt)]),
            Item::user_message(vec![Item::text(content)]),
        ]))
        .await?;

    for item in &response.items {
        if let Some(message) = item.as_message() {
            print!("{}", message.text());
        } else if let Some(text) = item.as_text() {
            print!("{text}");
        }
    }
    println!();
    drop(session);
    Ok(())
}

async fn summarize_directory(
    model: &Model,
    directory: &Path,
    system_prompt: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut txt_files: Vec<_> = fs::read_dir(directory)?
        .filter_map(Result::ok)
        .filter(|entry| entry.path().extension().is_some_and(|ext| ext == "txt"))
        .collect();

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

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

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

    Ok(())
}

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

    manager
        .download_and_register_eps_with_progress(None, {
            let mut current_ep = String::new();
            move |ep_name: &str, percent: f64| {
                if ep_name != current_ep {
                    if !current_ep.is_empty() {
                        println!();
                    }
                    current_ep = ep_name.to_string();
                }
                print!("\r  {ep_name:<30}  {percent:5.1}%");
                io::stdout().flush().ok();
            }
        })
        .await?;
    println!();

    let model = manager.catalog().get_model("qwen2.5-0.5b").await?;
    if !model.is_cached().await? {
        println!("Downloading model...");
        model
            .download(Some(|progress: f64| {
                print!("\r  {progress:.1}%");
                io::stdout().flush().ok();
            }))
            .await?;
        println!();
    }

    model.load().await?;
    println!("Model loaded and ready.\n");

    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(&model, target_path, system_prompt).await?;
    } else {
        let file_name = target_path
            .file_name()
            .map(|name| name.to_string_lossy().to_string())
            .unwrap_or_else(|| target.clone());
        println!("--- {file_name} ---");
        summarize_file(&model, target_path, system_prompt).await?;
    }

    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!

Repositório de exemplos

Clone o repositório GitHub de exemplos do Foundry e vá para o exemplo em C++:

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

Instalar pacotes

Os exemplos do C++ exigem:

Configure e compile o exemplo:

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

Durante a configuração, o exemplo restaura a versão 2.2.1 do Microsoft.AI.Foundry.Local.Runtime pacote NuGet para build/_packages. O pacote contém os cabeçalhos C++ e bibliotecas nativas para Windows, Linux e macOS. Você não precisa de um pacote WinML separado.

A configuração do CMake seleciona os ativos de runtime para seu sistema operacional e arquitetura e copia as bibliotecas nativas necessárias ao lado do executável. No macOS e no Linux, execute o executável a partir de build. No Windows, execute o arquivo .exe direto de build.

Ler um documento de texto

Leia um arquivo de texto a partir de um caminho da linha de comando:

const std::string path = argc > 1 ? argv[1] : "document.txt";
std::ifstream input(path);
if (!input) {
  throw std::runtime_error("Could not open document: " + path);
}
const std::string document{
    std::istreambuf_iterator<char>(input),
    std::istreambuf_iterator<char>(),
};
if (document.empty()) {
  throw std::runtime_error("The document is empty.");
}

Gerar um resumo

Carregue um modelo de chat e solicite-o para resumir o documento:

{
  ChatSession session(*model);
  Request request{
      SystemMessage(
          "Summarize the supplied document using a short overview and three bullet points."),
      UserMessage(document),
  };
  request.SetOptions({
      .search = {.temperature = 0.2F, .max_output_tokens = 512, .seed = 42},
  });
  std::cout << ResponseText(session.ProcessRequest(request)) << "\n";
}

Código completo

#include <foundry_local/foundry_local_cpp.h>

#include <fstream>
#include <iostream>
#include <iterator>
#include <string>

using namespace foundry_local;

std::string ResponseText(const Response& response) {
  std::string text;
  for (const Item& item : response.GetItems()) {
    if (item.GetType() != FOUNDRY_LOCAL_ITEM_MESSAGE) {
      continue;
    }
    for (const Item& part : item.GetMessage().parts) {
      if (part.GetType() == FOUNDRY_LOCAL_ITEM_TEXT) {
        text.append(part.GetText().text);
      }
    }
  }
  return text;
}

int main(int argc, char* argv[]) {
  try {
    Configuration config("foundry_local_cpp_document_summarizer");
    Manager manager(std::move(config));
    auto model = manager.GetCatalog().GetModel("qwen2.5-0.5b");
    if (!model) {
      throw std::runtime_error("Model 'qwen2.5-0.5b' was not found in the catalog.");
    }
    if (!model->IsCached()) {
      model->Download([](float progress) {
        std::cout << "\rDownloading model: " << static_cast<int>(progress) << "%" << std::flush;
        return 0;
      });
      std::cout << "\n";
    }
    model->Load();

    const std::string path = argc > 1 ? argv[1] : "document.txt";
    std::ifstream input(path);
    if (!input) {
      throw std::runtime_error("Could not open document: " + path);
    }
    const std::string document{
        std::istreambuf_iterator<char>(input),
        std::istreambuf_iterator<char>(),
    };
    if (document.empty()) {
      throw std::runtime_error("The document is empty.");
    }

    {
      ChatSession session(*model);
      Request request{
          SystemMessage(
              "Summarize the supplied document using a short overview and three bullet points."),
          UserMessage(document),
      };
      request.SetOptions({
          .search = {.temperature = 0.2F, .max_output_tokens = 512, .seed = 42},
      });
      std::cout << ResponseText(session.ProcessRequest(request)) << "\n";
    }

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

  return 0;
}

Resumir um arquivo:

./build/tutorial-document-summarizer document.txt

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.