次の方法で共有


チュートリアル: ドキュメント サマライザーを作成する

テキスト ファイルを読み取り、簡潔な概要を生成するアプリケーションを、完全にデバイス上に構築します。 これは、ドキュメントの内容を完全に読まずにすばやく理解する必要がある場合や、コンピューターから離れてはならない機密情報がドキュメントに含まれている場合に便利です。

このチュートリアルでは、以下の内容を学習します。

  • プロジェクトを設定し、Foundry Local SDK をインストールする
  • ファイル システムからテキスト ドキュメントを読み取る
  • モデルを読み込んで概要を生成する
  • システム プロンプトを使用して概要出力を制御する
  • 複数のドキュメントをバッチで処理する
  • リソースをクリーンアップする

前提条件

  • 8 GB 以上の RAM を搭載したWindows、macOS、または Linux コンピューター。

パッケージをインストールする

サンプル リポジトリ

この記事の完全なサンプル コードは、Foundry Local GitHub リポジトリで入手できます。 リポジトリを複製し、サンプルに移動するには、次を使用します。

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

Windowsで開発または出荷する場合は、Windows タブを選択します。Windows パッケージは、Windows ML ランタイムと統合され、同じ API サーフェス領域に幅広いハードウェア アクセラレーションを提供します。

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

GitHub リポジトリの C# サンプルは、事前構成済みのプロジェクトです。 最初からビルドする場合は、Foundry Local を使用して C# プロジェクトを設定する方法の詳細については、 Foundry Local SDK リファレンスを参照 してください。

テキスト ドキュメントの読み取り

何かを要約する前に、使用するサンプル ドキュメントが必要です。 プロジェクト ディレクトリに document.txt という名前のファイルを作成し、次の内容を追加します。

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 を開き、次のコードを追加してドキュメントを読みます。

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

このコードでは、オプションのファイル パスをコマンド ライン引数として受け取り、指定されていない場合は document.txt にフォールバックします。

概要を生成する

Foundry Local SDK を初期化し、モデルを読み込み、要約するようにモデルに指示するシステム プロンプトと共にドキュメントコンテンツを送信します。

Program.cs の内容を次のコードに置き換えます。

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

GetModelAsync メソッドは、モデルエイリアスを受け入れます。これは、カタログ内の特定のモデルにマップされる短いフレンドリ名です。 DownloadAsync メソッドはモデルの重みをローカルキャッシュにフェッチし、すでにキャッシュされている場合はダウンロードをスキップします。そして、LoadAsync メソッドはモデルを推論の準備が整うようにします。 システム プロンプトは、主要なアイデアに重点を置いた箇条書きの概要を生成するようにモデルに指示します。

概要出力を管理する

状況によって、概要スタイルが異なります。 システム プロンプトを変更して、モデルによる出力の構造を制御できます。 便利な 3 つのバリエーションを次に示します。

箇条書き (前のステップのデフォルトの値):

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

1 段落の概要:

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

重要なポイント:

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

別のスタイルを試すには、システム メッセージの Content 値をいずれかのプロンプトに置き換えます。 モデルは、システム プロンプトの指示に従って、概要の形式と深さを整形します。

複数のドキュメントを処理する

アプリケーションを拡張して、ディレクトリ内のすべての .txt ファイルを要約します。 これは、すべてのサマリーが必要なドキュメントのフォルダーがある場合に便利です。

次のメソッドは、特定のディレクトリ内のすべての .txt ファイルを反復処理し、それぞれを要約します。

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

各ファイルは読み取られ、同じシステム プロンプトとペアになり、モデルに個別に送信されます。 モデルはファイル間のコンテキストを伝達しないため、各概要は自己完結型です。

完成したコード

Program.csの内容を次の完全なコードに置き換えます。

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

1 つのファイルを要約します。

dotnet run -- document.txt

または、ディレクトリ内のすべての .txt ファイルを要約します。

dotnet run -- ./docs

次のような出力が表示されます。

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!

パッケージをインストールする

サンプル リポジトリ

この記事の完全なサンプル コードは、Foundry Local GitHub リポジトリで入手できます。 リポジトリを複製し、サンプルに移動するには、次を使用します。

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

Windowsで開発または出荷する場合は、Windows タブを選択します。Windows パッケージは、Windows ML ランタイムと統合され、同じ API サーフェス領域に幅広いハードウェア アクセラレーションを提供します。

npm install foundry-local-sdk-winml openai

テキスト ドキュメントの読み取り

何かを要約する前に、使用するサンプル ドキュメントが必要です。 プロジェクト ディレクトリに document.txt という名前のファイルを作成し、次の内容を追加します。

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.

次に、 index.js という名前のファイルを作成し、次のコードを追加してドキュメントを読み取ります。

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

スクリプトは、オプションのファイル パスをコマンド ライン引数として受け取り、指定されていない場合は document.txt にフォールバックします。

概要を生成する

Foundry Local SDK を初期化し、モデルを読み込み、要約するようにモデルに指示するシステム プロンプトと共にドキュメントコンテンツを送信します。

index.js の内容を次のコードに置き換えます。

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

getModel メソッドは、モデルエイリアスを受け入れます。これは、カタログ内の特定のモデルにマップされる短いフレンドリ名です。 download メソッドは、モデルの重みをローカルキャッシュにフェッチし (既にキャッシュされている場合はダウンロードをスキップします)、load メソッドでモデルを推論に使用できるようにします。 システム プロンプトは、主要なアイデアに重点を置いた箇条書きの概要を生成するようにモデルに指示します。

概要出力をコントロールする

状況によって、概要スタイルが異なります。 システム プロンプトを変更して、モデルによる出力の構造を制御できます。 便利な 3 つのバリエーションを次に示します。

箇条書き (前の手順の既定値):

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

1 段落の概要:

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

重要なポイント:

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

別のスタイルを試すには、システム メッセージの content 値をいずれかのプロンプトに置き換えます。 モデルは、システム プロンプトの指示に従って、概要の形式と深さを整形します。

複数のドキュメントを処理する

アプリケーションを拡張して、ディレクトリ内のすべての .txt ファイルを要約します。 これは、すべてのサマリーが必要なドキュメントのフォルダーがある場合に便利です。

次の関数は、特定のディレクトリ内のすべての .txt ファイルを反復処理し、それぞれを要約します。

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

各ファイルは読み取られ、同じシステム プロンプトとペアになり、モデルに個別に送信されます。 モデルはファイル間のコンテキストを伝達しないため、各概要は自己完結型です。

完成したコード

index.jsという名前のファイルを作成し、次の完全なコードを追加します。

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

1 つのファイルを要約します。

node index.js document.txt

または、ディレクトリ内のすべての .txt ファイルを要約します。

node index.js ./docs

次のような出力が表示されます。

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!

パッケージをインストールする

サンプル リポジトリ

この記事の完全なサンプル コードは、Foundry Local GitHub リポジトリで入手できます。 リポジトリを複製し、サンプルに移動するには、次を使用します。

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

Windowsで開発または出荷する場合は、Windows タブを選択します。Windows パッケージは、Windows ML ランタイムと統合され、同じ API サーフェス領域に幅広いハードウェア アクセラレーションを提供します。

pip install foundry-local-sdk-winml openai

テキスト ドキュメントの読み取り

何かを要約する前に、使用するサンプル ドキュメントが必要です。 プロジェクト ディレクトリに document.txt という名前のファイルを作成し、次の内容を追加します。

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.

次に、 main.py という名前のファイルを作成し、次のコードを追加してドキュメントを読み取ります。

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

スクリプトは、オプションのファイル パスをコマンド ライン引数として受け取り、指定されていない場合は document.txt にフォールバックします。 Path.read_text メソッドは、ファイル全体を文字列に読み取ります。

概要を生成する

Foundry Local SDK を初期化し、モデルを読み込み、要約するようにモデルに指示するシステム プロンプトと共にドキュメントコンテンツを送信します。

main.py の内容を次のコードに置き換えます。

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)

get_model メソッドは、モデルエイリアスを受け入れます。これは、カタログ内の特定のモデルにマップされる短いフレンドリ名です。 download メソッドはモデルの重みをローカルキャッシュにフェッチします(既にキャッシュされている場合はダウンロードをスキップします)、そしてload はモデルを推論に使用できるように準備します。 システム プロンプトは、主要なアイデアに重点を置いた箇条書きの概要を生成するようにモデルに指示します。

概要出力を制御する

状況によって、概要スタイルが異なります。 システム プロンプトを変更して、モデルによる出力の構造を制御できます。 便利な 3 つのバリエーションを次に示します。

箇条書き (前のステップのデフォルトの値):

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

1 段落の概要:

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

重要なポイント:

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

別のスタイルを試すには、システム メッセージの "content" 値をいずれかのプロンプトに置き換えます。 モデルは、システム プロンプトの指示に従って、概要の形式と深さを整形します。

複数のドキュメントを処理する

アプリケーションを拡張して、ディレクトリ内のすべての .txt ファイルを要約します。 これは、すべてのサマリーが必要なドキュメントのフォルダーがある場合に便利です。

次の関数は、特定のディレクトリ内のすべての .txt ファイルを反復処理し、それぞれを要約します。

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

各ファイルは読み取られ、同じシステム プロンプトとペアになり、モデルに個別に送信されます。 モデルはファイル間のコンテキストを伝達しないため、各概要は自己完結型です。

完成したコード

main.pyという名前のファイルを作成し、次の完全なコードを追加します。

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

1 つのファイルを要約します。

python main.py document.txt

または、ディレクトリ内のすべての .txt ファイルを要約します。

python main.py ./docs

次のような出力が表示されます。

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!

パッケージをインストールする

サンプル リポジトリ

この記事の完全なサンプル コードは、Foundry Local GitHub リポジトリで入手できます。 リポジトリを複製し、サンプルに移動するには、次を使用します。

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

Windowsで開発または出荷する場合は、Windows タブを選択します。Windows パッケージは、Windows ML ランタイムと統合され、同じ API サーフェス領域に幅広いハードウェア アクセラレーションを提供します。

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

テキスト ドキュメントの読み取り

何かを要約する前に、使用するサンプル ドキュメントが必要です。 プロジェクト ディレクトリに document.txt という名前のファイルを作成し、次の内容を追加します。

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 を開き、次のコードを追加してドキュメントを読みます。

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

このコードでは、オプションのファイル パスをコマンド ライン引数として受け取り、指定されていない場合は document.txt にフォールバックします。

概要を生成する

Foundry Local SDK を初期化し、モデルを読み込み、要約するようにモデルに指示するシステム プロンプトと共にドキュメントコンテンツを送信します。

src/main.rs の内容を次のコードに置き換えます。

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

get_model メソッドは、モデルエイリアスを受け入れます。これは、カタログ内の特定のモデルにマップされる短いフレンドリ名です。 download メソッドはモデルの重みをローカルキャッシュにフェッチし、すでにキャッシュされている場合はダウンロードをスキップします。そして、load メソッドはモデルを推論の準備が整うようにします。 システム プロンプトは、主要なアイデアに重点を置いた箇条書きの概要を生成するようにモデルに指示します。

概要出力を管理する

状況によって、概要スタイルが異なります。 システム プロンプトを変更して、モデルによる出力の構造を制御できます。 便利な 3 つのバリエーションを次に示します。

箇条書き (前のステップのデフォルトの値):

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

1 段落の概要:

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

重要なポイント:

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

別のスタイルを試すには、システム メッセージの内容をいずれかのプロンプトに置き換えます。 モデルは、システム プロンプトの指示に従って、概要の形式と深さを整形します。

複数のドキュメントを処理する

アプリケーションを拡張して、ディレクトリ内のすべての .txt ファイルを要約します。 これは、すべてのサマリーが必要なドキュメントのフォルダーがある場合に便利です。

次の関数は、特定のディレクトリ内のすべての .txt ファイルを反復処理し、それぞれを要約します。

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

各ファイルは読み取られ、同じシステム プロンプトとペアになり、モデルに個別に送信されます。 モデルはファイル間のコンテキストを伝達しないため、各概要は自己完結型です。

完成したコード

src/main.rsの内容を次の完全なコードに置き換えます。

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

1 つのファイルを要約します。

cargo run -- document.txt

または、ディレクトリ内のすべての .txt ファイルを要約します。

cargo run -- ./docs

次のような出力が表示されます。

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!

リソースをクリーンアップする

モデルの重みは、モデルをアンロードした後もローカル キャッシュに残ります。 つまり、次にアプリケーションを実行すると、ダウンロード手順がスキップされ、モデルの読み込みが速くなります。 ディスク領域を再利用しない限り、追加のクリーンアップは必要ありません。