Samouczek: tworzenie aplikacji do notatek głosowych zamieniającej mowę na tekst

Skompiluj aplikację, która konwertuje dźwięk mówiony na zorganizowane notatki — w całości na urządzeniu. Aplikacja najpierw transkrybuje plik audio przy użyciu modelu zamiany mowy na tekst, a następnie używa modelu czatu do podsumowania i organizowania transkrypcji w czyste notatki.

W tym poradniku nauczysz się, jak:

  • Konfigurowanie projektu i instalowanie lokalnego zestawu SDK rozwiązania Foundry
  • Ładowanie modelu zamiany mowy na tekst i transkrypcja pliku audio
  • Ładowanie modelu czatu i podsumowywanie transkrypcji
  • Łączenie transkrypcji i podsumowania w pełną aplikację
  • Uprzątnij zasoby

Wymagania wstępne

  • Komputer Windows, macOS lub Linux z co najmniej 8 GB pamięci RAM.
  • .wav Plik audio do transkrypcji (w tym samouczku jest używany przykładowy plik).

Instalowanie pakietów

Repozytorium przykładów

Pełny kod przykładowy do tego artykułu można znaleźć w repozytorium GitHub przykładów platformy Foundry. Aby sklonować repozytorium i przejść do przykładu, użyj następującego polecenia:

git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/csharp/foundry-local/tutorial-voice-to-text
dotnet add package Microsoft.AI.Foundry.Local
dotnet add package OpenAI

Przykłady języka C# w repozytorium GitHub to wstępnie skonfigurowane projekty. Jeśli tworzysz od podstaw, zapoznaj się z dokumentacją zestawu SDK lokalnego rozwiązania Foundry , aby uzyskać więcej informacji na temat sposobu konfigurowania projektu w języku C# przy użyciu rozwiązania Foundry Local.

Transkrypcja pliku audio

W tym kroku załadujesz model zamiany mowy na tekst i transkrybujesz plik audio. Lokalny zestaw SDK Foundry używa aliasu whisper modelu, aby wybrać najlepszy wariant Whisper dla twojego urządzenia.

  • Otwórz Program.cs i zastąp jego zawartość następującym kodem, aby zainicjować zestaw SDK, załadować model mowy i transkrypować plik audio:

    // Load the speech-to-text model
    var speechModel = await catalog.GetModelAsync("whisper-tiny")
        ?? throw new Exception("Speech model not found");
    
    await speechModel.DownloadAsync(progress =>
    {
        Console.Write($"\rDownloading speech model: {progress:F2}%");
        if (progress >= 100f) Console.WriteLine();
    });
    
    await speechModel.LoadAsync();
    Console.WriteLine("Speech model loaded.");
    
    // Transcribe the audio file with the native v2 audio session.
    var transcriptionText = new StringBuilder();
    using var audioSession = new AudioSession(speechModel);
    audioSession.SetStreaming(true);
    using var audioRequest = new Request();
    audioRequest.AddItem(new AudioItem("meeting-notes.wav"));
    
    Console.WriteLine("\nTranscription:");
    await using var audioResponse = audioSession.ProcessStreamingRequestAsync(audioRequest, ct);
    await foreach (var item in audioResponse)
    {
        using (item)
        {
            if (item is SpeechSegmentItem segment)
            {
                Console.Write(segment.Text);
                transcriptionText.Append(segment.Text);
            }
        }
    }
    using var finalTranscription = await audioResponse.FinalResponse;
    Console.WriteLine();
    
    // Unload the speech model to free memory
    finalTranscription.Dispose();
    await audioResponse.DisposeAsync();
    audioRequest.Dispose();
    audioSession.Dispose();
    await speechModel.UnloadAsync();
    

    Metoda GetAudioClientAsync zwraca klienta dla operacji audio. Metoda TranscribeAudioStreamingAsync przesyła strumieniowo fragmenty transkrypcji w miarę ich dostępności. Zakumulujesz tekst, aby móc przekazać go do modelu czatu w następnym kroku.

Uwaga / Notatka

Zastąp "meeting-notes.wav" ścieżką do pliku audio. Obsługiwane formaty to WAV, MP3 i FLAC.

Podsumowanie transkrypcji

Teraz użyj modelu czatu, aby uporządkować surowe transkrypcje w uporządkowane notatki. Załaduj qwen2.5-0.5b model i wyślij transkrypcję jako kontekst z monitem systemowym, który nakazuje modelowi tworzenie czystych, podsumowanych notatek.

Dodaj następujący kod po kroku transkrypcji:

// Load the chat model for summarization
var chatModel = await catalog.GetModelAsync("qwen2.5-0.5b")
    ?? throw new Exception("Chat model not found");

await chatModel.DownloadAsync(progress =>
{
    Console.Write($"\rDownloading chat model: {progress:F2}%");
    if (progress >= 100f) Console.WriteLine();
});

await chatModel.LoadAsync();
Console.WriteLine("Chat model loaded.");

// Summarize the transcription into organized notes.
using var chatSession = new ChatSession(chatModel);
using var chatRequest = new Request();
chatRequest.AddItem(MessageItem.System(
    "You are a note-taking assistant. Summarize the following transcription " +
    "into organized, concise notes with bullet points."));
chatRequest.AddItem(MessageItem.User(transcriptionText.ToString()));
using var chatResponse = await chatSession.ProcessRequestAsync(chatRequest, ct);
var summary = string.Concat(
    chatResponse.OfType<MessageItem>()
        .SelectMany(message => message.Parts)
        .OfType<TextItem>()
        .Where(text => text.Type == TextItemType.Default)
        .Select(text => text.Text));
Console.WriteLine($"\nSummary:\n{summary}");

// Clean up
chatResponse.Dispose();
chatRequest.Dispose();
chatSession.Dispose();
await chatModel.UnloadAsync();
Console.WriteLine("\nDone. Models unloaded.");

Monit systemowy kształtuje format danych wyjściowych modelu. Poinstruując go, aby utworzyć "zorganizowane, zwięzłe notatki z punktami punktowymi", otrzymujesz ustrukturyzowaną zawartość, a nie nieprzetworzone parafrazy.

Połącz w kompletną aplikację

Zastąp zawartość Program.cs następującym kompletnym kodem, który transkrypuje plik audio i podsumowuje transkrypcję:

using Microsoft.AI.Foundry.Local;
using Microsoft.Extensions.Logging;
using System.Text;

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

var catalog = await mgr.GetCatalogAsync();

// Load the speech-to-text model
var speechModel = await catalog.GetModelAsync("whisper-tiny")
    ?? throw new Exception("Speech model not found");

await speechModel.DownloadAsync(progress =>
{
    Console.Write($"\rDownloading speech model: {progress:F2}%");
    if (progress >= 100f) Console.WriteLine();
});

await speechModel.LoadAsync();
Console.WriteLine("Speech model loaded.");

// Transcribe the audio file with the native v2 audio session.
var transcriptionText = new StringBuilder();
using var audioSession = new AudioSession(speechModel);
audioSession.SetStreaming(true);
using var audioRequest = new Request();
audioRequest.AddItem(new AudioItem("meeting-notes.wav"));

Console.WriteLine("\nTranscription:");
await using var audioResponse = audioSession.ProcessStreamingRequestAsync(audioRequest, ct);
await foreach (var item in audioResponse)
{
    using (item)
    {
        if (item is SpeechSegmentItem segment)
        {
            Console.Write(segment.Text);
            transcriptionText.Append(segment.Text);
        }
    }
}
using var finalTranscription = await audioResponse.FinalResponse;
Console.WriteLine();

// Unload the speech model to free memory
finalTranscription.Dispose();
await audioResponse.DisposeAsync();
audioRequest.Dispose();
audioSession.Dispose();
await speechModel.UnloadAsync();

// Load the chat model for summarization
var chatModel = await catalog.GetModelAsync("qwen2.5-0.5b")
    ?? throw new Exception("Chat model not found");

await chatModel.DownloadAsync(progress =>
{
    Console.Write($"\rDownloading chat model: {progress:F2}%");
    if (progress >= 100f) Console.WriteLine();
});

await chatModel.LoadAsync();
Console.WriteLine("Chat model loaded.");

// Summarize the transcription into organized notes.
using var chatSession = new ChatSession(chatModel);
using var chatRequest = new Request();
chatRequest.AddItem(MessageItem.System(
    "You are a note-taking assistant. Summarize the following transcription " +
    "into organized, concise notes with bullet points."));
chatRequest.AddItem(MessageItem.User(transcriptionText.ToString()));
using var chatResponse = await chatSession.ProcessRequestAsync(chatRequest, ct);
var summary = string.Concat(
    chatResponse.OfType<MessageItem>()
        .SelectMany(message => message.Parts)
        .OfType<TextItem>()
        .Where(text => text.Type == TextItemType.Default)
        .Select(text => text.Text));
Console.WriteLine($"\nSummary:\n{summary}");

// Clean up
chatResponse.Dispose();
chatRequest.Dispose();
chatSession.Dispose();
await chatModel.UnloadAsync();
Console.WriteLine("\nDone. Models unloaded.");

Uwaga / Notatka

Zastąp "meeting-notes.wav" ścieżką do pliku audio. Obsługiwane formaty to WAV, MP3 i FLAC.

Uruchom program do wykonywania notatek:

dotnet run

Zobaczysz dane wyjściowe podobne do:

Downloading speech model: 100.00%
Speech model loaded.

Transcription:
OK so let's get started with the weekly sync. First, the backend
API is nearly done. Sarah finished the authentication endpoints
yesterday. We still need to add rate limiting before we go to
staging. On the frontend, the dashboard redesign is about seventy
percent complete. Jake, can you walk us through the new layout?
Great. The charts look good. I think we should add a filter for
date range though. For testing, we have about eighty percent code
coverage on the API. We need to write integration tests for the
new auth flow before Friday. Let's plan to do a full regression
test next Tuesday before the release. Any blockers? OK, sounds
like we are in good shape. Let's wrap up.

Downloading chat model: 100.00%
Chat model loaded.

Summary:
- **Backend API**: Authentication endpoints complete. Rate limiting
  still needed before staging deployment.
- **Frontend**: Dashboard redesign 70% complete. New chart layout
  reviewed. Action item: add a date range filter.
- **Testing**: API code coverage at 80%. Integration tests for the
  auth flow due Friday. Full regression test scheduled for next
  Tuesday before release.
- **Status**: No blockers reported. Team is on track.

Done. Models unloaded.

Aplikacja najpierw transkrybuje zawartość audio z danymi wyjściowymi przesyłania strumieniowego, a następnie przekazuje skumulowany tekst do modelu czatu, który wyodrębnia kluczowe punkty i organizuje je w notatki ustrukturyzowane.

Instalowanie pakietów

Repozytorium przykładów

Kompletny przykładowy kod dla tego artykułu jest dostępny w repozytorium foundry-samples GitHub. Aby sklonować repozytorium i przejść do przykładowego projektu, użyj:

git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/javascript/foundry-local/tutorial-voice-to-text
npm install foundry-local-sdk openai

Transkrypcja pliku audio

W tym kroku załadujesz model zamiany mowy na tekst i transkrybujesz plik audio. Lokalny zestaw SDK Foundry używa aliasu whisper modelu, aby wybrać najlepszy wariant Whisper dla twojego urządzenia.

  1. Utwórz plik o nazwie app.js.

  2. Dodaj następujący kod w celu zainicjowania zestawu SDK, załadowania modelu mowy i transkrypcji pliku audio:

    // Load the speech-to-text model
    const speechModel = await manager.catalog.getModel('whisper-tiny');
    await speechModel.download((progress) => {
        process.stdout.write(
            `\rDownloading speech model: ${progress.toFixed(2)}%`
        );
    });
    console.log('\nSpeech model downloaded.');
    
    await speechModel.load();
    console.log('Speech model loaded.');
    
    // Transcribe the audio file
    const audioSession = new AudioSession(speechModel);
    const audioFile = path.join(__dirname, 'meeting-notes.wav');
    const transcriptionResponse = await audioSession.processRequest(
        new Request().addItem(Item.audioFromUri(audioFile))
    );
    const transcription = transcriptionResponse.output.find(
        (item) => item.type === 'speechResult'
    );
    if (!transcription) {
        throw new Error('The speech model returned no transcription.');
    }
    console.log(`\nTranscription:\n${transcription.text}`);
    
    // Unload the speech model to free memory
    audioSession.dispose();
    await speechModel.unload();
    

    Metoda createAudioClient zwraca klienta dla operacji audio. Metoda transcribe akceptuje ścieżkę pliku i zwraca obiekt z właściwością text zawierającą treść transkrypcji.

Uwaga / Notatka

Zastąp './meeting-notes.wav' ścieżką do pliku audio. Obsługiwane formaty to WAV, MP3 i FLAC.

Podsumowanie transkrypcji

Teraz użyj modelu czatu, aby uporządkować surowe transkrypcje w uporządkowane notatki. Załaduj qwen2.5-0.5b model i wyślij transkrypcję jako kontekst z monitem systemowym, który nakazuje modelowi tworzenie czystych, podsumowanych notatek.

Dodaj następujący kod po kroku transkrypcji:

// Load the chat model for summarization
const chatModel = await manager.catalog.getModel('qwen2.5-0.5b');
await chatModel.download((progress) => {
    process.stdout.write(
        `\rDownloading chat model: ${progress.toFixed(2)}%`
    );
});
console.log('\nChat model downloaded.');

await chatModel.load();
console.log('Chat model loaded.');

// Summarize the transcription into organized notes
const chatSession = new ChatSession(chatModel);
const request = new Request()
    .addItem(Item.systemMessage(
        'You are a note-taking assistant. Summarize ' +
        'the following transcription into organized, ' +
        'concise notes with bullet points.'
    ))
    .addItem(Item.userMessage(transcription.text));

const response = await chatSession.processRequest(request);
const summary = response.output
    .map((item) => item.type === 'message'
        ? item.content ?? item.parts?.map((part) => part.text ?? '').join('') ?? ''
        : item.type === 'text' ? item.text : '')
    .join('');
console.log(`\nSummary:\n${summary}`);

// Clean up
chatSession.dispose();
await chatModel.unload();
console.log('\nDone. Models unloaded.');

Monit systemowy kształtuje format danych wyjściowych modelu. Poinstruując go, aby utworzyć "zorganizowane, zwięzłe notatki z punktami punktowymi", otrzymujesz ustrukturyzowaną zawartość, a nie nieprzetworzone parafrazy.

Połącz w kompletną aplikację

Utwórz plik o nazwie app.js i dodaj następujący pełny kod, który transkrypuje plik audio i podsumowuje transkrypcję:

import {
    AudioSession,
    ChatSession,
    FoundryLocalManager,
    Item,
    Request
} from 'foundry-local-sdk';
import { fileURLToPath } from 'url';
import path from 'path';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

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

// Load the speech-to-text model
const speechModel = await manager.catalog.getModel('whisper-tiny');
await speechModel.download((progress) => {
    process.stdout.write(
        `\rDownloading speech model: ${progress.toFixed(2)}%`
    );
});
console.log('\nSpeech model downloaded.');

await speechModel.load();
console.log('Speech model loaded.');

// Transcribe the audio file
const audioSession = new AudioSession(speechModel);
const audioFile = path.join(__dirname, 'meeting-notes.wav');
const transcriptionResponse = await audioSession.processRequest(
    new Request().addItem(Item.audioFromUri(audioFile))
);
const transcription = transcriptionResponse.output.find(
    (item) => item.type === 'speechResult'
);
if (!transcription) {
    throw new Error('The speech model returned no transcription.');
}
console.log(`\nTranscription:\n${transcription.text}`);

// Unload the speech model to free memory
audioSession.dispose();
await speechModel.unload();

// Load the chat model for summarization
const chatModel = await manager.catalog.getModel('qwen2.5-0.5b');
await chatModel.download((progress) => {
    process.stdout.write(
        `\rDownloading chat model: ${progress.toFixed(2)}%`
    );
});
console.log('\nChat model downloaded.');

await chatModel.load();
console.log('Chat model loaded.');

// Summarize the transcription into organized notes
const chatSession = new ChatSession(chatModel);
const request = new Request()
    .addItem(Item.systemMessage(
        'You are a note-taking assistant. Summarize ' +
        'the following transcription into organized, ' +
        'concise notes with bullet points.'
    ))
    .addItem(Item.userMessage(transcription.text));

const response = await chatSession.processRequest(request);
const summary = response.output
    .map((item) => item.type === 'message'
        ? item.content ?? item.parts?.map((part) => part.text ?? '').join('') ?? ''
        : item.type === 'text' ? item.text : '')
    .join('');
console.log(`\nSummary:\n${summary}`);

// Clean up
chatSession.dispose();
await chatModel.unload();
console.log('\nDone. Models unloaded.');

Uwaga / Notatka

Zastąp './meeting-notes.wav' ścieżką do pliku audio. Obsługiwane formaty to WAV, MP3 i FLAC.

Uruchom program do wykonywania notatek:

node app.js

Zobaczysz dane wyjściowe podobne do:

Downloading speech model: 100.00%
Speech model downloaded.
Speech model loaded.

Transcription:
OK so let's get started with the weekly sync. First, the backend
API is nearly done. Sarah finished the authentication endpoints
yesterday. We still need to add rate limiting before we go to
staging. On the frontend, the dashboard redesign is about seventy
percent complete. Jake, can you walk us through the new layout?
Great. The charts look good. I think we should add a filter for
date range though. For testing, we have about eighty percent code
coverage on the API. We need to write integration tests for the
new auth flow before Friday. Let's plan to do a full regression
test next Tuesday before the release. Any blockers? OK, sounds
like we are in good shape. Let's wrap up.

Downloading chat model: 100.00%
Chat model downloaded.
Chat model loaded.

Summary:
- **Backend API**: Authentication endpoints complete. Rate limiting
  still needed before staging deployment.
- **Frontend**: Dashboard redesign 70% complete. New chart layout
  reviewed. Action item: add a date range filter.
- **Testing**: API code coverage at 80%. Integration tests for the
  auth flow due Friday. Full regression test scheduled for next
  Tuesday before release.
- **Status**: No blockers reported. Team is on track.

Done. Models unloaded.

Aplikacja najpierw transkrybuje zawartość audio, a następnie przekazuje ten tekst do modelu czatu, który wyodrębnia kluczowe punkty i organizuje je w notatki ustrukturyzowane.

Instalowanie pakietów

Repozytorium przykładów

Kompletny przykładowy kod dla tego artykułu jest dostępny w repozytorium foundry-samples GitHub. Aby sklonować repozytorium i przejść do przykładowego projektu, użyj:

git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/python/foundry-local/tutorial-voice-to-text
pip install foundry-local-sdk openai

Transkrypcja pliku audio

W tym kroku załadujesz model zamiany mowy na tekst i transkrybujesz plik audio. Lokalny zestaw SDK Foundry używa aliasu whisper modelu, aby wybrać najlepszy wariant Whisper dla twojego urządzenia.

  1. Utwórz plik o nazwie app.py.

  2. Dodaj następujący kod w celu zainicjowania zestawu SDK, załadowania modelu mowy i transkrypcji pliku audio:

    # Load the speech-to-text model.
    speech_model = manager.catalog.get_model("whisper-tiny")
    if speech_model is None:
        raise RuntimeError("Model 'whisper-tiny' was not found in the catalog.")
    download_if_needed(speech_model, "speech")
    speech_model.load()
    print("Speech model loaded.")
    
    try:
        # Transcribe the audio file with a native audio session.
        with AudioSession(speech_model) as session:
            with Request().add_item(AudioItem.from_uri(audio_path)) as request:
                with session.process_request(request) as response:
                    transcription = get_text(response)
        print(f"\nTranscription:\n{transcription}")
    finally:
        # Unload the speech model to free memory.
        if speech_model.is_loaded:
            speech_model.unload()
    

    Metoda get_audio_client zwraca klienta dla operacji audio. Metoda transcribe akceptuje ścieżkę pliku i zwraca obiekt z właściwością text zawierającą treść transkrypcji.

Uwaga / Notatka

Zastąp "meeting-notes.wav" ścieżką do pliku audio. Obsługiwane formaty to WAV, MP3 i FLAC.

Podsumowanie transkrypcji

Teraz użyj modelu czatu, aby uporządkować surowe transkrypcje w uporządkowane notatki. Załaduj qwen2.5-0.5b model i wyślij transkrypcję jako kontekst z monitem systemowym, który nakazuje modelowi tworzenie czystych, podsumowanych notatek.

Dodaj następujący kod po kroku transkrypcji:

# Load the chat model for summarization.
chat_model = manager.catalog.get_model("qwen2.5-0.5b")
if chat_model is None:
    manager.close()
    raise RuntimeError("Model 'qwen2.5-0.5b' was not found in the catalog.")
download_if_needed(chat_model, "chat")
chat_model.load()
print("Chat model loaded.")

try:
    # Summarize the transcription into organized notes.
    with ChatSession(chat_model) as session:
        session.set_options(
            RequestOptions(
                search=SearchOptions(temperature=0.2, max_output_tokens=512)
            )
        )
        with (
            Request()
            .add_item(
                MessageItem.system(
                    "You are a note-taking assistant. Summarize the following "
                    "transcription into organized, concise notes with bullet points."
                )
            )
            .add_item(MessageItem.user(transcription)) as request
        ):
            with session.process_request(request) as response:
                summary = get_text(response)
    print(f"\nSummary:\n{summary}")
finally:
    # Clean up.
    if chat_model.is_loaded:
        chat_model.unload()
    manager.close()
    print("\nDone. Models unloaded.")

Monit systemowy kształtuje format danych wyjściowych modelu. Poinstruując go, aby utworzyć "zorganizowane, zwięzłe notatki z punktami punktowymi", otrzymujesz ustrukturyzowaną zawartość, a nie nieprzetworzone parafrazy.

Połącz w kompletną aplikację

Utwórz plik o nazwie app.py i dodaj następujący pełny kod, który transkrypuje plik audio i podsumowuje transkrypcję:

import sys

from foundry_local_sdk import (
    AudioItem,
    AudioSession,
    ChatSession,
    Configuration,
    FoundryLocalManager,
    MessageItem,
    Request,
    RequestOptions,
    SearchOptions,
    SpeechResultItem,
    TextItem,
)



def download_if_needed(model, label):
    if model.is_cached:
        print(f"Using cached {label} model {model.id}.")
        return
    model.download(
        lambda progress: print(
            f"\rDownloading {label} model: {progress:.2f}%",
            end="",
            flush=True,
        )
    )
    print()


def get_text(response):
    parts = []
    for item in response:
        if isinstance(item, SpeechResultItem):
            parts.append(item.text)
        elif 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 main():
    # Initialize the Foundry Local SDK
    config = Configuration(app_name="foundry_local_samples")
    FoundryLocalManager.initialize(config)
    manager = FoundryLocalManager.instance

    audio_path = sys.argv[1] if len(sys.argv) > 1 else "meeting-notes.wav"

    # Load the speech-to-text model.
    speech_model = manager.catalog.get_model("whisper-tiny")
    if speech_model is None:
        raise RuntimeError("Model 'whisper-tiny' was not found in the catalog.")
    download_if_needed(speech_model, "speech")
    speech_model.load()
    print("Speech model loaded.")

    try:
        # Transcribe the audio file with a native audio session.
        with AudioSession(speech_model) as session:
            with Request().add_item(AudioItem.from_uri(audio_path)) as request:
                with session.process_request(request) as response:
                    transcription = get_text(response)
        print(f"\nTranscription:\n{transcription}")
    finally:
        # Unload the speech model to free memory.
        if speech_model.is_loaded:
            speech_model.unload()

    # Load the chat model for summarization.
    chat_model = manager.catalog.get_model("qwen2.5-0.5b")
    if chat_model is None:
        manager.close()
        raise RuntimeError("Model 'qwen2.5-0.5b' was not found in the catalog.")
    download_if_needed(chat_model, "chat")
    chat_model.load()
    print("Chat model loaded.")

    try:
        # Summarize the transcription into organized notes.
        with ChatSession(chat_model) as session:
            session.set_options(
                RequestOptions(
                    search=SearchOptions(temperature=0.2, max_output_tokens=512)
                )
            )
            with (
                Request()
                .add_item(
                    MessageItem.system(
                        "You are a note-taking assistant. Summarize the following "
                        "transcription into organized, concise notes with bullet points."
                    )
                )
                .add_item(MessageItem.user(transcription)) as request
            ):
                with session.process_request(request) as response:
                    summary = get_text(response)
        print(f"\nSummary:\n{summary}")
    finally:
        # Clean up.
        if chat_model.is_loaded:
            chat_model.unload()
        manager.close()
        print("\nDone. Models unloaded.")


if __name__ == "__main__":
    main()

Uwaga / Notatka

Zastąp "meeting-notes.wav" ścieżką do pliku audio. Obsługiwane formaty to WAV, MP3 i FLAC.

Uruchom program do wykonywania notatek:

python app.py

Zobaczysz dane wyjściowe podobne do:

Downloading speech model: 100.00%
Speech model loaded.

Transcription:
OK so let's get started with the weekly sync. First, the backend
API is nearly done. Sarah finished the authentication endpoints
yesterday. We still need to add rate limiting before we go to
staging. On the frontend, the dashboard redesign is about seventy
percent complete. Jake, can you walk us through the new layout?
Great. The charts look good. I think we should add a filter for
date range though. For testing, we have about eighty percent code
coverage on the API. We need to write integration tests for the
new auth flow before Friday. Let's plan to do a full regression
test next Tuesday before the release. Any blockers? OK, sounds
like we are in good shape. Let's wrap up.

Downloading chat model: 100.00%
Chat model loaded.

Summary:
- **Backend API**: Authentication endpoints complete. Rate limiting
  still needed before staging deployment.
- **Frontend**: Dashboard redesign 70% complete. New chart layout
  reviewed. Action item: add a date range filter.
- **Testing**: API code coverage at 80%. Integration tests for the
  auth flow due Friday. Full regression test scheduled for next
  Tuesday before release.
- **Status**: No blockers reported. Team is on track.

Done. Models unloaded.

Aplikacja najpierw transkrybuje zawartość audio, a następnie przekazuje ten tekst do modelu czatu, który wyodrębnia kluczowe punkty i organizuje je w notatki ustrukturyzowane.

Instalowanie pakietów

Repozytorium przykładów

Kompletny przykładowy kod dla tego artykułu jest dostępny w repozytorium foundry-samples GitHub. Aby sklonować repozytorium i przejść do przykładowego projektu, użyj:

git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/rust/foundry-local/tutorial-voice-to-text
cargo add foundry-local-sdk
cargo add tokio --features full
cargo add tokio-stream anyhow

Transkrypcja pliku audio

W tym kroku załadujesz model zamiany mowy na tekst i transkrybujesz plik audio. Lokalny zestaw SDK Foundry używa aliasu whisper modelu, aby wybrać najlepszy wariant Whisper dla twojego urządzenia.

  • Otwórz src/main.rs i zastąp jego zawartość następującym kodem, aby zainicjować zestaw SDK, załadować model mowy i transkrypować plik audio:

    let speech_model = manager.catalog().get_model("whisper-tiny").await?;
    
    if !speech_model.is_cached().await? {
        println!("Downloading speech model...");
        speech_model
            .download(Some(|progress: f64| {
                print!("\r  {progress:.1}%");
                io::stdout().flush().ok();
            }))
            .await?;
        println!();
    }
    
    speech_model.load().await?;
    println!("Speech model loaded.");
    
    let audio_session = AudioSession::new(&speech_model).await?;
    let audio_path = std::fs::canonicalize("meeting-notes.wav")?;
    let audio_uri = Url::from_file_path(&audio_path)
        .map_err(|_| format!("Could not convert {} to a file URI.", audio_path.display()))?;
    let audio = Item::audio_uri(audio_uri.as_str(), Some("wav"), 0, 0);
    let transcription = audio_session.transcribe(audio).await?;
    println!("\nTranscription:\n{transcription}");
    
    drop(audio_session);
    speech_model.unload().await?;
    

    Metoda create_audio_client zwraca klienta dla operacji audio. Metoda transcribe akceptuje ścieżkę pliku i zwraca obiekt z polem text zawierającym zawartość transkrypcji.

Uwaga / Notatka

Zastąp "meeting-notes.wav" ścieżką do pliku audio. Obsługiwane formaty to WAV, MP3 i FLAC.

Podsumowanie transkrypcji

Teraz użyj modelu czatu, aby uporządkować surowe transkrypcje w uporządkowane notatki. Załaduj qwen2.5-0.5b model i wyślij transkrypcję jako kontekst z monitem systemowym, który nakazuje modelowi tworzenie czystych, podsumowanych notatek.

Dodaj następujący kod wewnątrz funkcji main po kroku transkrypcji:

let chat_model = manager.catalog().get_model("qwen2.5-0.5b").await?;

if !chat_model.is_cached().await? {
    println!("Downloading chat model...");
    chat_model
        .download(Some(|progress: f64| {
            print!("\r  {progress:.1}%");
            io::stdout().flush().ok();
        }))
        .await?;
    println!();
}

chat_model.load().await?;
println!("Chat model loaded.");

let chat_session = ChatSession::new(&chat_model).await?;
chat_session
    .set_options(RequestOptions {
        search: SearchOptions {
            temperature: Some(0.7),
            max_output_tokens: Some(512),
            ..Default::default()
        },
        ..Default::default()
    })
    .await?;

let response = chat_session
    .process_request(Request::from_items(vec![
        Item::system_message(vec![Item::text(
            "You are a note-taking assistant. Summarize the following transcription into \
             organized, concise notes with bullet points.",
        )]),
        Item::user_message(vec![Item::text(transcription)]),
    ]))
    .await?;

println!("\nSummary:");
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(chat_session);
chat_model.unload().await?;
println!("\nDone. Models unloaded.");

Monit systemowy kształtuje format danych wyjściowych modelu. Poinstruując go, aby utworzyć "zorganizowane, zwięzłe notatki z punktami punktowymi", otrzymujesz ustrukturyzowaną zawartość, a nie nieprzetworzone parafrazy.

Połącz w kompletną aplikację

Zastąp zawartość src/main.rs następującym kompletnym kodem, który transkrypuje plik audio i podsumowuje transkrypcję:

use foundry_local_sdk::{
    AudioSession, ChatSession, FoundryLocalConfig, FoundryLocalManager, Item, Request,
    RequestOptions, SearchOptions,
};
use std::io::{self, Write};
use url::Url;

#[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 speech_model = manager.catalog().get_model("whisper-tiny").await?;

    if !speech_model.is_cached().await? {
        println!("Downloading speech model...");
        speech_model
            .download(Some(|progress: f64| {
                print!("\r  {progress:.1}%");
                io::stdout().flush().ok();
            }))
            .await?;
        println!();
    }

    speech_model.load().await?;
    println!("Speech model loaded.");

    let audio_session = AudioSession::new(&speech_model).await?;
    let audio_path = std::fs::canonicalize("meeting-notes.wav")?;
    let audio_uri = Url::from_file_path(&audio_path)
        .map_err(|_| format!("Could not convert {} to a file URI.", audio_path.display()))?;
    let audio = Item::audio_uri(audio_uri.as_str(), Some("wav"), 0, 0);
    let transcription = audio_session.transcribe(audio).await?;
    println!("\nTranscription:\n{transcription}");

    drop(audio_session);
    speech_model.unload().await?;

    let chat_model = manager.catalog().get_model("qwen2.5-0.5b").await?;

    if !chat_model.is_cached().await? {
        println!("Downloading chat model...");
        chat_model
            .download(Some(|progress: f64| {
                print!("\r  {progress:.1}%");
                io::stdout().flush().ok();
            }))
            .await?;
        println!();
    }

    chat_model.load().await?;
    println!("Chat model loaded.");

    let chat_session = ChatSession::new(&chat_model).await?;
    chat_session
        .set_options(RequestOptions {
            search: SearchOptions {
                temperature: Some(0.7),
                max_output_tokens: Some(512),
                ..Default::default()
            },
            ..Default::default()
        })
        .await?;

    let response = chat_session
        .process_request(Request::from_items(vec![
            Item::system_message(vec![Item::text(
                "You are a note-taking assistant. Summarize the following transcription into \
                 organized, concise notes with bullet points.",
            )]),
            Item::user_message(vec![Item::text(transcription)]),
        ]))
        .await?;

    println!("\nSummary:");
    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(chat_session);
    chat_model.unload().await?;
    println!("\nDone. Models unloaded.");

    Ok(())
}

Uwaga / Notatka

Zastąp "meeting-notes.wav" ścieżką do pliku audio. Obsługiwane formaty to WAV, MP3 i FLAC.

Uruchom program do wykonywania notatek:

cargo run

Zobaczysz dane wyjściowe podobne do:

Downloading speech model: 100.00%
Speech model loaded.

Transcription:
OK so let's get started with the weekly sync. First, the backend
API is nearly done. Sarah finished the authentication endpoints
yesterday. We still need to add rate limiting before we go to
staging. On the frontend, the dashboard redesign is about seventy
percent complete. Jake, can you walk us through the new layout?
Great. The charts look good. I think we should add a filter for
date range though. For testing, we have about eighty percent code
coverage on the API. We need to write integration tests for the
new auth flow before Friday. Let's plan to do a full regression
test next Tuesday before the release. Any blockers? OK, sounds
like we are in good shape. Let's wrap up.

Downloading chat model: 100.00%
Chat model loaded.

Summary:
- **Backend API**: Authentication endpoints complete. Rate limiting
  still needed before staging deployment.
- **Frontend**: Dashboard redesign 70% complete. New chart layout
  reviewed. Action item: add a date range filter.
- **Testing**: API code coverage at 80%. Integration tests for the
  auth flow due Friday. Full regression test scheduled for next
  Tuesday before release.
- **Status**: No blockers reported. Team is on track.

Done. Models unloaded.

Aplikacja najpierw transkrybuje zawartość audio, a następnie przekazuje ten tekst do modelu czatu, który wyodrębnia kluczowe punkty i organizuje je w notatki ustrukturyzowane.

Repozytorium przykładów

Sklonuj repozytorium GitHub z przykładami Foundry i przejdź do przykładu języka C++:

git clone https://github.com/microsoft-foundry/foundry-samples.git
cd foundry-samples/samples/cpp/foundry-local/tutorial-voice-to-text

Instalowanie pakietów

Przykłady języka C++ wymagają:

Skonfiguruj i skompiluj przykład:

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

Podczas konfiguracji przykład przywraca pakiet NuGet w wersji 2.2.1 Microsoft.AI.Foundry.Local.Runtime do build/_packages. Pakiet zawiera nagłówki języka C++ i biblioteki natywne dla Windows, Linux i macOS. Nie potrzebujesz oddzielnego pakietu WinML.

Konfiguracja narzędzia CMake wybiera zasoby środowiska uruchomieniowego dla systemu operacyjnego i architektury oraz kopiuje wymagane biblioteki natywne obok pliku wykonywalnego. W systemach macOS i Linux uruchom plik wykonywalny z build. W systemie Windows uruchom plik .exe z build.

Transkrypcja pliku audio

Załaduj model mowy i transkrybuj plik audio:

auto speech_model = manager.GetCatalog().GetModel("whisper-tiny");
if (!speech_model) {
  throw std::runtime_error("Model 'whisper-tiny' was not found in the catalog.");
}
DownloadIfNeeded(*speech_model, "speech");
speech_model->Load();

std::string transcript;
{
  AudioSession session(*speech_model);
  Request request{Item::AudioFromUri(argv[1])};
  transcript = ResponseText(session.ProcessRequest(request));
}
speech_model->Unload();
std::cout << "Transcription:\n" << transcript << "\n";

Podsumowanie transkrypcji

Załaduj model czatu i zmień transkrypcję na notatki ustrukturyzowane:

auto chat_model = manager.GetCatalog().GetModel("qwen2.5-0.5b");
if (!chat_model) {
  throw std::runtime_error("Model 'qwen2.5-0.5b' was not found in the catalog.");
}
DownloadIfNeeded(*chat_model, "chat");
chat_model->Load();

{
  ChatSession session(*chat_model);
  Request request{
      SystemMessage(
          "Turn the transcription into concise meeting notes with action items."),
      UserMessage(transcript),
  };
  request.SetOptions({
      .search = {.temperature = 0.2F, .max_output_tokens = 512, .seed = 42},
  });
  std::cout << "Summary:\n" << ResponseText(session.ProcessRequest(request)) << "\n";
}
chat_model->Unload();

Połącz kroki

#include <foundry_local/foundry_local_cpp.h>

#include <iostream>
#include <string>

using namespace foundry_local;

void DownloadIfNeeded(IModel& model, const std::string& label) {
  if (model.IsCached()) {
    std::cout << "Using cached " << label << " model.\n";
    return;
  }
  model.Download([&label](float progress) {
    std::cout << "\rDownloading " << label << " model: " << static_cast<int>(progress) << "%"
              << std::flush;
    return 0;
  });
  std::cout << "\n";
}

std::string ResponseText(const Response& response) {
  std::string text;
  for (const Item& item : response.GetItems()) {
    if (item.GetType() == FOUNDRY_LOCAL_ITEM_SPEECH_RESULT) {
      text.append(item.GetSpeechResult().text);
    } else if (item.GetType() == FOUNDRY_LOCAL_ITEM_TEXT) {
      text.append(item.GetText().text);
    } else if (item.GetType() == FOUNDRY_LOCAL_ITEM_MESSAGE) {
      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[]) {
  if (argc != 2) {
    std::cerr << "Usage: tutorial-voice-to-text <audio-file>\n";
    return 1;
  }

  try {
    Configuration config("foundry_local_cpp_voice_to_text");
    Manager manager(std::move(config));

    auto speech_model = manager.GetCatalog().GetModel("whisper-tiny");
    if (!speech_model) {
      throw std::runtime_error("Model 'whisper-tiny' was not found in the catalog.");
    }
    DownloadIfNeeded(*speech_model, "speech");
    speech_model->Load();

    std::string transcript;
    {
      AudioSession session(*speech_model);
      Request request{Item::AudioFromUri(argv[1])};
      transcript = ResponseText(session.ProcessRequest(request));
    }
    speech_model->Unload();
    std::cout << "Transcription:\n" << transcript << "\n";

    auto chat_model = manager.GetCatalog().GetModel("qwen2.5-0.5b");
    if (!chat_model) {
      throw std::runtime_error("Model 'qwen2.5-0.5b' was not found in the catalog.");
    }
    DownloadIfNeeded(*chat_model, "chat");
    chat_model->Load();

    {
      ChatSession session(*chat_model);
      Request request{
          SystemMessage(
              "Turn the transcription into concise meeting notes with action items."),
          UserMessage(transcript),
      };
      request.SetOptions({
          .search = {.temperature = 0.2F, .max_output_tokens = 512, .seed = 42},
      });
      std::cout << "Summary:\n" << ResponseText(session.ProcessRequest(request)) << "\n";
    }
    chat_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;
}

Uruchom program do robienia notatek ze ścieżką pliku audio:

./build/tutorial-voice-to-text path/to/audio.wav

Uprzątnij zasoby

Wagi modelu są przechowywane w Twojej lokalnej pamięci podręcznej po rozładowaniu modelu. Oznacza to, że przy następnym uruchomieniu aplikacji krok pobierania zostanie pominięty, a model ładuje się szybciej. Nie jest wymagane żadne dodatkowe czyszczenie, chyba że chcesz odzyskać miejsce na dysku.