Ваш микрофон - ваш замок. Пора забрать его у облаков
Каждый раз, когда вы говорите 'Окей, 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}")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 для симуляции клавиатуры. Но это опасно: ассистент может начать печатать не там, где нужно.
- Плагины. Вынесите действия в отдельные модули, чтобы добавлять новые команды без переписывания основного кода.
Главное - не переусердствуйте. Локальный голосовой ввод должен решать ваши задачи, а не становиться новым хобби по поддержке легаси-кода. Начните с простого: включить музыку, записать мысль, открыть сайт. Остальное приложится.
И последнее: этот стек не требует интернета, но требует ресурсов. На слабом ноутбуке Whisper base может думать 2-3 секунды. Это нормально. Если хочется быстрее, купите NVIDIA GPU или используйте облако? Нет, шучу. Используйте более легкие модели из статьи про голосовой терминал. Там Claude Code CLI уже оптимизирован для команд, а не для общего диалога.
Ваш голос должен работать на вас. Начните с этого скрипта, подгоните под свои нужды и забудьте о том, что кто-то слушает.