Partager via


Tutoriel : Créer un récapitulateur de documents

Créez une application qui lit les fichiers texte et génère des résumés concis sur votre appareil. Cela est utile lorsque vous devez rapidement comprendre le contenu des documents sans les lire en intégralité, et lorsque les documents contiennent des informations sensibles qui ne doivent pas quitter votre ordinateur.

Dans ce tutoriel, vous allez apprendre à :

  • Configurer un projet et installer le Kit de développement logiciel (SDK) Local Foundry
  • Lire un document texte à partir du système de fichiers
  • Charger un modèle et générer un résumé
  • Contrôler la sortie de synthèse avec les invites système
  • Traiter plusieurs documents dans un lot
  • Nettoyer les ressources

Prerequisites

  • Un ordinateur Windows, macOS ou Linux avec au moins 8 Go de RAM.

Installer des packages

Référentiel d’exemples

L’exemple de code complet de cet article est disponible dans le dépôt Foundry Local GitHub. Pour cloner le référentiel et accéder à l’exemple d’utilisation :

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

Si vous développez ou expédiez sur Windows, sélectionnez l'onglet Windows. Le package Windows s’intègre au runtime Windows ML . Il fournit la même surface d’surface d’API avec une étendue plus large d’accélération matérielle.

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

Les exemples C# dans le référentiel GitHub sont des projets préconfigurés. Si vous développez ex nihilo, vous devez lire la référence de Foundry Local SDK pour plus d’informations sur la configuration de votre projet C# avec Foundry Local.

Lire un document texte

Avant de résumer quoi que ce soit, vous avez besoin d’un exemple de document à utiliser. Créez un fichier appelé document.txt dans votre répertoire de projet et ajoutez le contenu suivant :

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.

Program.cs Ouvrez et ajoutez maintenant le code suivant pour lire le document :

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

Le code accepte un chemin de fichier facultatif en tant qu’argument de ligne de commande et utilise document.txt par défaut si aucun n’est fourni.

Générer un résumé

Initialisez le SDK local Foundry, chargez un modèle et envoyez le contenu du document avec une invite système qui indique au modèle de résumer.

Remplacez le contenu de Program.cs par le code suivant :

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

La GetModelAsync méthode accepte un alias de modèle, qui est un nom convivial court qui correspond à un modèle spécifique dans le catalogue. La DownloadAsync méthode extrait les pondérations du modèle dans votre cache local (et ignore le téléchargement s’ils sont déjà mis en cache) et LoadAsync rend le modèle prêt pour l’inférence. L'invite système demande au modèle de produire des résumés sous forme de points, centrés sur les idées clés.

Sortie du résumé de contrôle

Différentes situations appellent différents styles de résumé. Vous pouvez modifier l’invite système pour contrôler la façon dont le modèle structure sa sortie. Voici trois variantes utiles :

Puces ( valeur par défaut de l’étape précédente) :

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

Résumé d’un paragraphe :

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

Points clés :

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

Pour essayer un autre style, remplacez la valeur Content dans le message système par l’un des appels déclencheurs. Le modèle suit les instructions de l'invitation système pour structurer le format et la profondeur du résumé.

Traiter plusieurs documents

Étendez l’application pour résumer chaque .txt fichier dans un répertoire. Cela est utile lorsque vous disposez d’un dossier de documents qui ont tous besoin de résumés.

La méthode suivante itère sur tous les .txt fichiers d’un répertoire donné et récapitule chacune d’elles :

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

Chaque fichier est lu, associé à la même invite système, puis envoyé indépendamment au modèle. Le modèle ne comporte pas de contexte entre les fichiers. Chaque résumé est donc autonome.

Code complet

Remplacez le contenu de Program.cs par le code complet suivant :

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

Résumez un seul fichier :

dotnet run -- document.txt

Ou résumez chaque .txt fichier dans un répertoire :

dotnet run -- ./docs

Vous voyez un résultat similaire à :

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!

Installer des packages

Référentiel d’exemples

L’exemple de code complet de cet article est disponible dans le dépôt Foundry Local GitHub. Pour cloner le référentiel et accéder à l’exemple d’utilisation :

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

Si vous développez ou expédiez sur Windows, sélectionnez l'onglet Windows. Le package Windows s’intègre au runtime Windows ML . Il fournit la même surface d’surface d’API avec une étendue plus large d’accélération matérielle.

npm install foundry-local-sdk-winml openai

Lire un document texte

Avant de résumer quoi que ce soit, vous avez besoin d’un exemple de document à utiliser. Créez un fichier appelé document.txt dans votre répertoire de projet et ajoutez le contenu suivant :

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.

Créez maintenant un fichier appelé index.js et ajoutez le code suivant pour lire le document :

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

Le script accepte un chemin d'accès de fichier facultatif en tant qu'argument de ligne de commande et utilise document.txt par défaut si aucun n'est fourni.

Générer un résumé

Initialisez le SDK local Foundry, chargez un modèle et envoyez le contenu du document avec une invite système qui indique au modèle de résumer.

Remplacez le contenu de index.js par le code suivant :

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

La getModel méthode accepte un alias de modèle, qui est un nom convivial court qui correspond à un modèle spécifique dans le catalogue. La download méthode extrait les pondérations du modèle dans votre cache local (et ignore le téléchargement s’ils sont déjà mis en cache) et load rend le modèle prêt pour l’inférence. L'invite de commande indique au modèle de produire des résumés sous forme de liste axés sur les idées clés.

Résumé de la sortie de contrôle

Différentes situations appellent différents styles de résumé. Vous pouvez modifier l’invite système pour contrôler la façon dont le modèle structure sa sortie. Voici trois variantes utiles :

Puces ( valeur par défaut provenant de l’étape précédente) :

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

Résumé d’un paragraphe :

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

Points clés :

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

Pour essayer un autre style, remplacez la content valeur dans le message système par l’une des invites. Le modèle suit les instructions de l'invitation du système pour définir la structure et la profondeur du résumé.

Traiter plusieurs documents

Étendez l’application pour résumer chaque .txt fichier dans un répertoire. Cela est utile lorsque vous disposez d’un dossier de documents qui ont tous besoin de résumés.

La fonction suivante itère sur tous les .txt fichiers d’un répertoire donné et récapitule chacune d’elles :

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

Chaque fichier est lu, associé à la même invite système et envoyé au modèle indépendamment. Le modèle ne comporte pas de contexte entre les fichiers. Chaque résumé est donc autonome.

Code complet

Créez un fichier nommé index.js et ajoutez le code complet suivant :

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

Résumez un seul fichier :

node index.js document.txt

Ou résumez chaque .txt fichier dans un répertoire :

node index.js ./docs

Vous voyez un résultat similaire à :

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!

Installer des packages

Référentiel d’exemples

L’exemple de code complet de cet article est disponible dans le dépôt Foundry Local GitHub. Pour cloner le référentiel et accéder à l’exemple d’utilisation :

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

Si vous développez ou expédiez sur Windows, sélectionnez l'onglet Windows. Le package Windows s’intègre au runtime Windows ML . Il fournit la même surface d’surface d’API avec une étendue plus large d’accélération matérielle.

pip install foundry-local-sdk-winml openai

Lire un document texte

Avant de résumer quoi que ce soit, vous avez besoin d’un exemple de document à utiliser. Créez un fichier appelé document.txt dans votre répertoire de projet et ajoutez le contenu suivant :

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.

Créez maintenant un fichier appelé main.py et ajoutez le code suivant pour lire le document :

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

Le script accepte un chemin d’accès de fichier facultatif en tant qu’argument de ligne de commande et revient par défaut à document.txt si aucun n’est fourni. La Path.read_text méthode lit l’intégralité du fichier dans une chaîne.

Générer un résumé

Initialisez le SDK local Foundry, chargez un modèle et envoyez le contenu du document avec une invite système qui indique au modèle de résumer.

Remplacez le contenu de main.py par le code suivant :

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)

La get_model méthode accepte un alias de modèle, qui est un nom convivial court qui correspond à un modèle spécifique dans le catalogue. La download méthode extrait les pondérations du modèle dans votre cache local (et ignore le téléchargement s’ils sont déjà mis en cache) et load rend le modèle prêt pour l’inférence. L’invite système indique au modèle de produire des résumés à puces axés sur les idées clés.

Sortie récapitulative de contrôle

Différentes situations appellent différents styles de résumé. Vous pouvez modifier l’invite système pour contrôler la façon dont le modèle organise ses résultats. Voici trois variantes utiles :

Points de liste (valeur par défaut de l’étape précédente) :

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

Résumé d’un paragraphe :

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

Points clés :

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

Pour essayer un autre style, remplacez la valeur "content" dans le message système par l'une des invitations. Le modèle suit les instructions de l’invite système pour déterminer le format et la profondeur du résumé.

Traiter plusieurs documents

Étendez l’application pour résumer chaque .txt fichier dans un répertoire. Cela est utile lorsque vous disposez d’un dossier de documents qui ont tous besoin de résumés.

La fonction suivante itère sur tous les .txt fichiers d’un répertoire donné et récapitule chacune d’elles :

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

Chaque fichier est lu, associé à la même invite système, puis envoyé indépendamment au modèle. Le modèle ne comporte pas de contexte entre les fichiers. Chaque résumé est donc autonome.

Code complet

Créez un fichier nommé main.py et ajoutez le code complet suivant :

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

Résumez un seul fichier :

python main.py document.txt

Ou résumez chaque .txt fichier dans un répertoire :

python main.py ./docs

Vous voyez un résultat similaire à :

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!

Installer des packages

Référentiel d’exemples

L’exemple de code complet de cet article est disponible dans le dépôt Foundry Local GitHub. Pour cloner le référentiel et accéder à l’exemple d’utilisation :

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

Si vous développez ou expédiez sur Windows, sélectionnez l'onglet Windows. Le package Windows s’intègre au runtime Windows ML . Il fournit la même surface d’surface d’API avec une étendue plus large d’accélération matérielle.

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

Lire un document texte

Avant de résumer quoi que ce soit, vous avez besoin d’un exemple de document à utiliser. Créez un fichier appelé document.txt dans votre répertoire de projet et ajoutez le contenu suivant :

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.

src/main.rs Ouvrez et ajoutez maintenant le code suivant pour lire le document :

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

Le code accepte un chemin de fichier facultatif en tant qu’argument de ligne de commande et utilise document.txt par défaut si aucun n’est fourni.

Générer un résumé

Initialisez le SDK local Foundry, chargez un modèle et envoyez le contenu du document avec une invite système qui indique au modèle de résumer.

Remplacez le contenu de src/main.rs par le code suivant :

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

La get_model méthode accepte un alias de modèle, qui est un nom convivial court qui correspond à un modèle spécifique dans le catalogue. La download méthode extrait les pondérations du modèle dans votre cache local (et ignore le téléchargement s’ils sont déjà mis en cache) et load rend le modèle prêt pour l’inférence. L'invite système demande au modèle de produire des résumés sous forme de points, centrés sur les idées clés.

Sortie du résumé de contrôle

Différentes situations appellent différents styles de résumé. Vous pouvez modifier l’invite système pour contrôler la façon dont le modèle structure sa sortie. Voici trois variantes utiles :

Puces ( valeur par défaut de l’étape précédente) :

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

Résumé d’un paragraphe :

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

Points clés :

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

Pour essayer un autre style, remplacez le contenu du message système par l’une des instructions. Le modèle suit les instructions de l'invitation système pour structurer le format et la profondeur du résumé.

Traiter plusieurs documents

Étendez l’application pour résumer chaque .txt fichier dans un répertoire. Cela est utile lorsque vous disposez d’un dossier de documents qui ont tous besoin de résumés.

La fonction suivante itère sur tous les .txt fichiers d’un répertoire donné et récapitule chacune d’elles :

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

Chaque fichier est lu, associé à la même invite système, puis envoyé indépendamment au modèle. Le modèle ne comporte pas de contexte entre les fichiers. Chaque résumé est donc autonome.

Code complet

Remplacez le contenu de src/main.rs par le code complet suivant :

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

Résumez un seul fichier :

cargo run -- document.txt

Ou résumez chaque .txt fichier dans un répertoire :

cargo run -- ./docs

Vous voyez un résultat similaire à :

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!

Nettoyer les ressources

Les poids du modèle restent dans votre cache local après avoir déchargé un modèle. Cela signifie que la prochaine fois que vous exécutez l’application, l’étape de téléchargement est ignorée et le modèle se charge plus rapidement. Aucun nettoyage supplémentaire n’est nécessaire, sauf si vous souhaitez récupérer de l’espace disque.