Локальный голосовой ввод на Python с Whisper и Ollama | AiManual
AiManual Logo Ai / Manual.
12 Мар 2026 Гайд

Собираем локальный голосовой ввод на Python: Whisper + Ollama для Windows, macOS и Linux

Полный гайд по сборке системы голосового ввода без интернета. Используем Whisper для транскрипции и Ollama для обработки текста на Windows, macOS и Linux. Готов

Ваш микрофон - ваш замок. Пора забрать его у облаков

Каждый раз, когда вы говорите 'Окей, Google' или 'Привет, Сири', ваши слова улетают на сервер, записываются, анализируются и, возможно, используются для чего-то, о чем вы не догадываетесь. Это 2026 год, и мы до сих пор платим за голосовые ассистенты своей приватностью. Абсурд.

Но есть другой путь. Ваш компьютер достаточно мощный, чтобы понимать вас самому. Комбинация Whisper от OpenAI для распознавания речи и Ollama для запуска локальных языковых моделей создает систему, которая работает в полном офлайне. Никаких API-ключей, счетов за запросы или внезапных обновлений политик конфиденциальности. Только ваш код, ваше железо и ваш голос.

Актуально на 12.03.2026: Whisper v4 (выпущена в конце 2025) работает в 2-3 раза быстрее предыдущих версий на CPU. Ollama поддерживает модели до 128B параметров, но для нашей задачи хватит и 7B. Faster-whisper обновился до версии 1.0, а mlx-whisper стал стабильным для macOS на Apple Silicon.

Архитектура за минуту: как звук становится действием

Система состоит из трех ключевых звеньев, которые работают как конвейер. Если одно ломается, все падает. Поэтому мы разберем каждое до винтика.

КомпонентЧто делаетАльтернативы
Микрофон + PrebufferЗаписывает аудиопоток, хранит последние 2-3 секунды в буфере, чтобы не пропустить начало фразы.SoundDevice, PyAudio, PortAudio
Whisper (транскрипция)Превращает аудио в текст. Используем локально, модель 'tiny' или 'base' для скорости.Faster-whisper (оптимизированный), MLX-whisper (для Apple Silicon), Whisper.cpp
Ollama (обработка текста)Берет текст от Whisper, понимает команду, генерирует ответ или действие. Например, 'открой браузер' → запуск Firefox.LM Studio, llama.cpp, прямые вызовы через API модели.

Самое узкое место здесь - скорость транскрипции. Whisper large может быть точнее, но ждать 5 секунд после каждой фразы - нереально для интерактивного использования. Поэтому мы выберем баланс между скоростью и качеством. К счастью, в статье про локальную STT мы уже разбирали, какая реализация быстрее на каком железе.

1 Готовим окружение: Python, виртуальное окружение и зависимости

Первое, что ломается у 80% людей - конфликты версий библиотек. Не ставьте ничего в глобальный Python. Создаем виртуальное окружение и живем в нем.

# Для Windows (PowerShell или CMD)
python -m venv voice_env
voice_env\Scripts\activate

# Для macOS и Linux
python3 -m venv voice_env
source voice_env/bin/activate

Теперь ставим базовые зависимости. Мы будем использовать sounddevice для захвата аудио - она проще PyAudio и лучше работает на всех ОС. Но если у вас Windows 11 и проблемы с драйверами, придется копать глубже.

pip install sounddevice numpy openai-whisper ollama

Внимание: openai-whisper установит torch. Если у вас NVIDIA GPU и вы хотите использовать CUDA, установите torch отдельно с официального сайта PyTorch под вашу версию CUDA (актуально на март 2026 - CUDA 13.2). Иначе Whisper будет работать на CPU, что медленнее, но все равно работает.

2 Устанавливаем и запускаем Ollama

Ollama - это не Python-библиотека, а отдельный демон. Его нужно установить с официального сайта или через скрипт.

# macOS и Linux
curl -fsSL https://ollama.ai/install.sh | sh

# После установки запускаем демон в фоне
ollama serve &

# Загружаем модель, например, Llama 3.2 7B (актуальная на март 2026)
ollama pull llama3.2:7b

Для Windows скачайте установщик с официального сайта Ollama и запустите. После установки Ollama будет работать как служба. Проверьте, что он доступен:

ollama list

Если видите модель llama3.2:7b, значит все готово. Эта модель достаточно умна, чтобы понимать простые команды, и достаточно легкая для работы в реальном времени. Для более сложных задач можете взять модель больше, но учтите, что она будет медленнее.

3 Пишем скрипт захвата аудио с пребуфером

Проблема стандартного захвата: вы начинаете говорить, скрипт начинает запись, но первые 100-200 мс теряются. Решение - кольцевой буфер, который постоянно записывает аудио, а когда вы заканчиваете говорить, мы берем последние N секунд из этого буфера. Это называется prebuffer или voice activity detection с историей.

Вот как это выглядит в коде. Не копируйте слепо, разберитесь, почему буфер именно такой размерности.

import sounddevice as sd
import numpy as np
import queue
import threading
from collections import deque

class AudioBuffer:
    def __init__(self, samplerate=16000, buffer_duration=3):
        """
        samplerate: частота дискретизации, Whisper работает на 16кГц
        buffer_duration: сколько секунд хранить в буфере (пребуфер)
        """
        self.samplerate = samplerate
        self.buffer = deque(maxlen=int(samplerate * buffer_duration))
        self.recording = []
        self.is_recording = False
        self.stream = None
        self.audio_queue = queue.Queue()
        
    def callback(self, indata, frames, time, status):
        """Этот callback вызывается порциями аудио от микрофона"""
        if status:
            print(f"Audio status: {status}")
        # Добавляем данные в кольцевой буфер (всегда)
        self.buffer.extend(indata[:, 0])  # берем один канал
        # Если идет запись, сохраняем в отдельный список
        if self.is_recording:
            self.recording.extend(indata[:, 0])
    
    def start_stream(self):
        """Запускаем поток аудио"""
        self.stream = sd.InputStream(
            samplerate=self.samplerate,
            channels=1,
            callback=self.callback,
            dtype='float32'
        )
        self.stream.start()
    
    def start_recording(self):
        """Начинаем запись: копируем пребуфер и начинаем сохранять новое"""
        self.recording = list(self.buffer)  # копируем последние N секунд из буфера
        self.is_recording = True
    
    def stop_recording(self):
        """Останавливаем запись и возвращаем аудио как numpy array"""
        self.is_recording = False
        audio_data = np.array(self.recording, dtype=np.float32)
        self.audio_queue.put(audio_data)
        return audio_data

# Пример использования
buffer = AudioBuffer()
buffer.start_stream()
# Когда детектор голоса обнаружил начало речи
buffer.start_recording()
# ... ждем конца речи
# audio = buffer.stop_recording()
# Теперь audio можно отправить в Whisper

Этот класс - сердце системы. Он гарантирует, что мы не потеряем начало команды, даже если нажали кнопку или детектор сработал с задержкой. Для детектирования голоса (VAD) можно использовать простой порог по громкости или библиотеку like webrtcvad, но это уже тема для отдельной статьи. В нашем случае, для простоты, будем использовать кнопку или горячую клавишу.

4 Интегрируем Whisper для транскрипции

Теперь, когда у нас есть аудио, нужно превратить его в текст. Используем Whisper. Но открывать модель для каждого аудиофрагмента - долго. Лучше загрузить модель один раз и использовать многократно.

import whisper

# Выбираем модель. tiny - самая быстрая, base - баланс, large - точнее но медленнее
model = whisper.load_model("base")  # или "tiny", "small", "medium", "large-v3"

def transcribe_audio(audio_numpy, samplerate=16000):
    """
    Принимает numpy array с аудио (частота 16кГц), возвращает текст.
    """
    # Whisper ожидает аудио в формате 16кГц, моно, что у нас уже есть
    result = model.transcribe(audio_numpy, fp16=False)  # fp16=False для CPU
    return result["text"].strip()

# Пример
# text = transcribe_audio(audio_data)
# print(f"Распознано: {text}")
💡
Whisper v4 (обновление 2025) добавила поддержку потоковой транскрипции, но она все еще экспериментальная. Для реального времени лучше использовать faster-whisper (основан на CTranslate2) или mlx-whisper для Mac. Установите их: pip install faster-whisper или для macOS pip install mlx-whisper. Скорость вырастет в 2-5 раз.

5 Отправляем текст в Ollama для выполнения команд

Теперь у нас есть текст. Например, 'открой браузер'. Нужно превратить это в действие. Можно было бы написать кучу if-else, но тогда для каждой новой команды придется переписывать код. Вместо этого используем Ollama: пусть языковая модель сама решает, что делать, и генерирует команду или ответ.

import ollama
import json
import subprocess
import platform

def process_command(text):
    """
    Отправляем текст в Ollama, получаем JSON с командой и выполняем.
    """
    # Системный промпт, который объясняет модели, что мы хотим
    system_prompt = """Ты - голосовой ассистент, который понимает команды пользователя и возвращает JSON.
    Доступные действия:
    - open_browser: открыть браузер (Chrome, Firefox)
    - open_terminal: открыть терминал
    - say_text: произнести текст (генерирует ответ)
    - run_command: выполнить shell команду (только безопасные)
    - write_text: написать текст в активное окно (симулирует ввод с клавиатуры)
    
    Верни JSON в формате: {"action": "название_действия", "data": "параметр"}
    Пример: пользователь говорит 'открой браузер' -> {"action": "open_browser", "data": "firefox"}
    Пользователь говорит 'скажи привет' -> {"action": "say_text", "data": "Привет! Я ваш локальный ассистент."}
    Если команда неясна, верни action: 'say_text' с data: 'Не понял команду.'"""
    
    response = ollama.chat(
        model='llama3.2:7b',  # или ваша модель
        messages=[
            {'role': 'system', 'content': system_prompt},
            {'role': 'user', 'content': text}
        ],
        format='json'  # просим ответ в JSON
    )
    
    try:
        result = json.loads(response['message']['content'])
        action = result.get('action')
        data = result.get('data', '')
        
        if action == 'open_browser':
            if platform.system() == 'Windows':
                subprocess.Popen(['start', 'chrome', data if data else ''], shell=True)
            elif platform.system() == 'Darwin':  # macOS
                subprocess.Popen(['open', '-a', data if data else 'Google Chrome'])
            else:  # Linux
                subprocess.Popen(['xdg-open', 'http://google.com'])
            return f"Открываю браузер {data}"
        
        elif action == 'say_text':
            # Здесь можно подключить TTS, например, Silero
            print(f"Ассистент: {data}")
            return data
            
        elif action == 'run_command' and 'rm' not in data:  # ограничиваем опасные команды
            subprocess.run(data, shell=True, check=True)
            return f"Выполнил команду: {data}"
            
        else:
            return f"Действие {action} не реализовано"
            
    except json.JSONDecodeError:
        return "Ошибка: модель не вернула JSON"

# Пример
# command_result = process_command("открой браузер")
# print(command_result)

Этот подход гибкий: вы можете добавлять новые действия, просто описав их в system_prompt. Модель поймет. Конечно, для production-системы нужно больше валидации и безопасности, но для персонального использования сгодится. Если хотите более сложного диалога, посмотрите гайд по сборке голосового ассистента на LangChain.

Собираем все вместе: главный цикл

Теперь осталось связать аудио-буфер, Whisper и Ollama в один цикл. Мы будем использовать горячую клавишу для активации записи (например, Ctrl+Space). Это проще, чем детектор голоса, и надежнее.

import keyboard  # Установите: pip install keyboard

buffer = AudioBuffer()
buffer.start_stream()

print("Голосовой ассистент запущен. Нажмите и держите ПРОБЕЛ для записи...")

while True:
    # Ждем, пока нажмут пробел
    keyboard.wait('space')
    print("Записываю...")
    buffer.start_recording()
    
    # Ждем, пока отпустят пробел
    keyboard.wait('space')
    audio = buffer.stop_recording()
    print("Обработка...")
    
    # Транскрипция
    text = transcribe_audio(audio)
    print(f"Вы сказали: {text}")
    
    # Обработка команды
    if text:
        result = process_command(text)
        print(f"Результат: {result}")

Библиотека keyboard может требовать прав администратора на Linux или macOS. Если не работает, используйте pynput или просто зацикленный input() для тестов. В продакшене лучше использовать глобальные хоткеи через системные API, но это выходит за рамки гайда.

Почему это ломается и как чинить

Я собрал десятки таких систем и знаю все типичные грабли. Вот что чаще всего идет не так:

  • Whisper молчит или возвращает ерунду. Проверьте samplerate аудио. Whisper ждет 16кГц. Если вы даете 48кГц, он попытается ресемплировать, но может сломаться. Конвертируйте в 16кГц явно:
    import librosa
    audio_16k = librosa.resample(audio, orig_sr=samplerate, target_sr=16000)
  • Ollama не отвечает. Убедитесь, что демон запущен: ollama list должен работать. Если нет, перезапустите: ollama serve. На Windows проверьте службу 'Ollama' в services.msc.
  • Звук с микрофона шумный. Whisper устойчив к шумам, но не идеально. Попробуйте простой noise gate в коде: обрезайте тихие части аудио перед отправкой в Whisper.
  • Медленная транскрипция. Перейдите на faster-whisper. Замените model = whisper.load_model("base") на:
    from faster_whisper import WhisperModel
    model = WhisperModel("base", device="cuda", compute_type="float16")  # или device="cpu"
    segments, info = model.transcribe(audio_numpy)
    text = " ".join([segment.text for segment in segments])
  • Модель Ollama не понимает команды. Уточните system_prompt. Дайте больше примеров. Или используйте модель побольше, например, llama3.2:12b или mistral:latest.

Если вы хотите не просто выполнять команды, а вести диалог, вам понадобится хранить историю сообщений. Добавьте список messages и обновляйте его после каждого ответа модели. Но будьте осторожны: контекст модели ограничен (обычно 4096 или 8192 токена), и история может переполниться.

Что дальше? От скрипта к полноценному ассистенту

Этот скрипт - скелет. На его основе можно построить что-то большее. Вот идеи:

  • Детектор голоса (VAD) вместо пробела. Используйте webrtcvad или простой порог по энергии. Но будьте готовы к ложным срабатываниям от кашля или стука.
  • Текст-в-речь (TTS). Чтобы ассистент отвечал голосом, подключите Silero TTS или Coqui TTS. Они работают локально и быстро. В статье про умную колонку на Raspberry Pi есть пример.
  • Интеграция с активным окном. Команда 'напиши письмо' должна вводить текст в активное окно. Используйте pyautogui или pynput для симуляции клавиатуры. Но это опасно: ассистент может начать печатать не там, где нужно.
  • Плагины. Вынесите действия в отдельные модули, чтобы добавлять новые команды без переписывания основного кода.

Главное - не переусердствуйте. Локальный голосовой ввод должен решать ваши задачи, а не становиться новым хобби по поддержке легаси-кода. Начните с простого: включить музыку, записать мысль, открыть сайт. Остальное приложится.

💡
Если вы разработчик, который хочет сделать из этого коммерческий продукт, посмотрите на Voxtral-Mini 4B Realtime. Это специализированная модель для потоковой транскрипции с задержкой менее 500ms. Она дороже в вычислительном смысле, но качество и скорость того стоят.

И последнее: этот стек не требует интернета, но требует ресурсов. На слабом ноутбуке Whisper base может думать 2-3 секунды. Это нормально. Если хочется быстрее, купите NVIDIA GPU или используйте облако? Нет, шучу. Используйте более легкие модели из статьи про голосовой терминал. Там Claude Code CLI уже оптимизирован для команд, а не для общего диалога.

Ваш голос должен работать на вас. Начните с этого скрипта, подгоните под свои нужды и забудьте о том, что кто-то слушает.

Подписаться на канал