
Полная версия
ChatGPT на вашем ноутбуке: бесплатно, анонимно, навсегда
yield from self.stream_chat(
messages=[{"role": "user", "content": user_message}],
system_prompt=system_prompt,
temperature=temperature,
max_tokens=max_tokens
)
Важные замечания по промпт-инжинирингу
1. Чёткие правила в системе. Модели Llama (и другие открытые) хорошо следуют инструкциям, если они сформулированы пронумерованным списком. Размытые просьбы («отвечай хорошо») работают хуже.
2. Низкая температура. Для задач, требующих фактологической точности, temperature=0.2..0.3 даёт лучшие результаты, чем 0.7. Модель меньше фантазирует.
3. Обработка противоречий. Иногда в разных чанках может быть противоречивая информация. Правило 3 в нашем промпте помогает модели не запутаться и явно указать на расхождение.
4. Ограничение контекста. Если чанков слишком много или они слишком длинные, они могут не влезть в n_ctx. Наш n_results=4 и chunk_size=500 дают около 2000 токенов контекста — достаточно для окна в 2048 токенов с запасом под ответ. При увеличении окна до 4096 можно увеличить n_results.
Возможные проблемы при работес RAG.
Ответ слишком короткий или неполный. Модель может обрезать ответ, если параметр max_tokens установлен слишком низко. Увеличьте его с 512 до 1024 или даже 2048 для сложных вопросов. Другой способ — явно попросить развёрнутый ответ в системном промпте: «Отвечай подробно, с примерами из документов, не менее трёх предложений». Модели Llama и Qwen хорошо следуют таким инструкциям.
Модель игнорирует документы и фантазирует. Это случается при высокой температуре — модель начинает «творчески» дополнять ответ. Понизьте temperature до 0.1 или 0.2 — в таком режиме модель становится строже и меньше отклоняется от предоставленных фактов. Также ужесточите системный промпт, добавив фразу: «Категорически запрещено использовать знания, не содержащиеся в документах. Если ответа нет — так и скажи». Это работает как дополнительный барьер от галлюцинаций.
Ответ на русском, но с английскими вкраплениями. Многие модели обучались в основном на английском корпусе и могут вставлять английские слова в русский текст — особенно термины. Лучшее решение — использовать модель, изначально оптимизированную под русский язык, например Qwen 2.5 7B или Qwen 3 14B. Если вы привязаны к Llama, добавьте в системный промпт прямое указание: «Отвечай на чистом русском языке, без английских слов». Обычно этого достаточно для 8B-модели.
Поиск не находит релевантные чанки. Возможно, вы указали слишком маленькое значение n_results — увеличьте его с 4 до 8, чтобы захватить больше кандидатов. Если это не помогает, проблема может быть в embedding-модели: она плохо понимает вашу специфическую терминологию. Попробуйте заменить модель на более крупную или специализированную под вашу область. Также проверьте размер чанков — слишком маленькие теряют контекст, слишком большие размывают релевантность.
Цитаты указываются, но название файла обрезано. В метаданных, которые мы сохраняем при индексации, есть поле file_name, но оно содержит только имя файла без пути. Если вам нужно видеть полный путь к документу, добавьте в indexer сохранение relative_path — относительного пути от корневой папки. Тогда в ответе модели будет фигурировать не просто contract.pdf, а договоры/2024/contract.pdf, что гораздо информативнее.
Что дальше
Мы завершили Часть 4. Наш ИИ-помощник теперь умеет:
· Индексировать папку с документами.
· Векторизовать их и хранить в локальной ChromaDB.
· Отвечать на вопросы, опираясь на факты из этих документов.
· Честно признаваться, если ответа нет.
· Указывать источники.
В Части 5 мы добавим голосовой интерфейс: научим помощника слушать и говорить, оставаясь полностью офлайн.
Часть 5. Голосовойинтерфейс
Глава 5.1.Офлайн-распознавание речи: Whisper tiny/base
Наш локальный ИИ уже умеет анализировать документы и отвечать на вопросы, но пока общение с ним — это набор текста на клавиатуре. Теперь мы сделаем следующий шаг: дадим помощнику «уши». Он будет слушать нас через микрофон, преобразовывать речь в текст и передавать его в языковую модель. И всё это — без интернета, с помощью моделей Whisper, которые работают прямо на вашем процессоре.
Что такое Whisper и почему он работает офлайн
Whisper — это система распознавания речи, разработанная OpenAI и опубликованная с открытыми весами. Она обучена на 680 000 часах аудио на разных языках и способна распознавать русскую речь на достойном уровне. Благодаря открытой лицензии модель можно скачать и запускать локально.
Whisper существует в нескольких размерах: от крошечной tiny (39 миллионов параметров, около 75 МБ на диске) до гигантской large-v3 (1.5 миллиарда параметров, около 3 ГБ). Для нашего персонального помощника идеально подойдут tiny или base. Почему:
· tiny — самая быстрая, способна работать в реальном времени даже на слабом процессоре. Качество распознавания русского языка приемлемое для команд и простых вопросов.
· base — чуть больше и точнее, особенно на русском. Требует немного больше ресурсов, но всё ещё легко запускается на любом современном ноутбуке.
Обе модели работают полностью локально, не отправляя аудио во внешние серверы.
Выбор между faster-whisper и whisper.cpp
Оригинальный Whisper от OpenAI написан на Python, но довольно медленный и прожорливый. К счастью, сообщество создало оптимизированные реализации:
· faster-whisper — переписанный на C++ движок (CTranslate2), который значительно быстрее оригинала. Отлично работает через Python, прост в установке и поддерживает все размеры моделей.
· whisper.cpp — порт на C++ без зависимостей Python. Ещё быстрее, но требует либо компиляции, либо использования отдельных биндингов. Для интеграции в наше Python-приложение менее удобен.
Мы выберем faster-whisper. Он даёт отличный баланс скорости и простоты: устанавливается через pip, работает как обычная Python-библиотека и поддерживает потоковую обработку аудио.
Установка faster-whisper и запись с микрофона
Установим две библиотеки:
bash
pip install faster-whisper sounddevice numpy
· faster-whisper — сам движок распознавания.
· sounddevice — кроссплатформенная библиотека для захвата аудио с микрофона (работает на Windows, macOS, Linux без дополнительных драйверов).
· numpy — потребуется для преобразования аудиоданных.
На Linux может понадобиться установить системную библиотеку portaudio:
bash
sudo apt install portaudio19-dev # Debian/Ubuntu
Пишем функцию распознавания речи
Создадим модуль speech_recognition.py. Функция recognize_speech() будет слушать микрофон до наступления тишины и возвращать распознанный текст.
python
# speech_recognition.py
import queue
import numpy as np
import sounddevice as sd
from faster_whisper import WhisperModel
# Глобальные константы
SAMPLE_RATE = 16000 # Частота дискретизации (требуется для Whisper)
SILENCE_THRESHOLD = 0.01 # Уровень громкости, ниже которого считаем тишиной
SILENCE_DURATION = 1.5 # Сколько секунд тишины нужно, чтобы закончить запись
MAX_RECORD_SECONDS = 30 # Максимальная длительность записи
class SpeechRecognizer:
"""
Локальное распознавание речи с помощью faster-whisper.
"""
def __init__(self, model_size: str = "tiny", device: str = "cpu"):
"""
:param model_size: размер модели: "tiny", "base", "small", "medium", "large-v3"
:param device: "cpu" или "cuda" (если есть GPU NVIDIA)
"""
print(f"Загрузка модели Whisper ({model_size})...")
self.model = WhisperModel(model_size, device=device, compute_type="int8")
print("Модель готова.")
def record_until_silence(self) -> np.ndarray:
"""
Записывает аудио с микрофона до наступления SILENCE_DURATION секунд тишины
или до MAX_RECORD_SECONDS. Возвращает массив float32 с частотой 16 кГц.
"""
q = queue.Queue()
recorded_chunks = []
def callback(indata, frames, time, status):
if status:
print(f"Предупреждение: {status}")
q.put(indata.copy())
print("Слушаю... (говорите)")
with sd.InputStream(
samplerate=SAMPLE_RATE,
channels=1,
callback=callback,
dtype=np.float32
):
silence_start = None
while True:
chunk = q.get()
recorded_chunks.append(chunk)
# Определяем громкость текущего чанка
volume = np.abs(chunk).mean()
# Проверяем на тишину
if volume < SILENCE_THRESHOLD:
if silence_start is None:
silence_start = len(recorded_chunks) * chunk.shape[0] / SAMPLE_RATE
else:
silence_dur = (len(recorded_chunks) * chunk.shape[0] / SAMPLE_RATE) - silence_start
if silence_dur >= SILENCE_DURATION:
print("Тишина, завершаю запись.")
break
else:
silence_start = None # сброс, если снова звук
# Проверяем максимальную длительность
total_sec = len(recorded_chunks) * chunk.shape[0] / SAMPLE_RATE
if total_sec >= MAX_RECORD_SECONDS:
print("Достигнут лимит записи.")
break
# Объединяем чанки в один массив
audio = np.concatenate(recorded_chunks, axis=0).flatten()
return audio
def transcribe(self, audio: np.ndarray) -> str:
"""
Преобразует аудиомассив в текст с помощью Whisper.
"""
segments, info = self.model.transcribe(audio, language="ru", beam_size=5)
text = " ".join([seg.text for seg in segments])
return text.strip()
def recognize_speech(self) -> str:
"""
Основная функция: слушает микрофон и возвращает распознанный текст.
Если ничего не распознано — пустая строка.
"""
audio = self.record_until_silence()
if audio is None or len(audio) == 0:
return ""
text = self.transcribe(audio)
return text
Пояснения к коду:
· Параметры микрофона: Whisper ожидает аудио с частотой 16 кГц в моно. Мы настраиваем sd.InputStream соответственно.
· Обнаружение тишины: Мы вычисляем среднюю громкость каждого аудиочанка. Если громкость падает ниже SILENCE_THRESHOLD, запускаем счётчик. Когда тишина длится SILENCE_DURATION секунд подряд, запись останавливается. Если пользователь снова начинает говорить, счётчик сбрасывается.
· Ограничение записи: MAX_RECORD_SECONDS предотвращает бесконечную запись, если алгоритм ошибётся с определением тишины.
· Параметр language="ru" ускоряет распознавание для русского языка. Если вы говорите на смеси языков, можно указать language=None — модель определит язык сама, но это чуть медленнее.
Тестирование распознавания
Напишем простой тест:
python
# test_speech.py
from speech_recognition import SpeechRecognizer
# Создаём распознаватель с моделью tiny
rec = SpeechRecognizer(model_size="tiny")
print("Нажмите Enter, чтобы начать запись...")
input()
text = rec.recognize_speech()
if text:
print(f"Распознано: {text}")
else:
print("Не удалось распознать речь.")
print("Не удалось распознать речь.")
Запустите, нажмите Enter и скажите несколько фраз. После полутора секунд молчания запись завершится, и вы увидите текст.
Выбор размера модели Whisper
Качество распознавания речи напрямую зависит от размера модели. Давайте сравним три основные версии на одной и той же русской фразе.
Tiny. Самая маленькая и быстрая модель — загружается около 2 секунд, распознаёт 3 секунды аудио всего за полсекунды. Занимает примерно 75 мегабайт на диске. Качество распознавания русского языка удовлетворительное: модель может путать окончания слов и ошибаться в шумной обстановке. Идеальный выбор для простых голосовых команд, когда важна скорость, а не дословная точность.
Base. Умеренный размер — загрузка около 3 секунд, распознавание того же аудио за 0.8 секунды. Занимает около 150 мегабайт. Качество русского языка хорошее, подходит для повседневной речи: модель правильно распознаёт большинство фраз в тихой комнате. Рекомендуемый выбор для домашнего голосового ассистента.
Small. Самая точная из компактных моделей — загружается около 5 секунд, распознавание за 2 секунды. Занимает около 500 мегабайт. Качество русского языка отличное: модель хорошо справляется даже с быстрой речью и лёгким фоновым шумом. Для большинства домашних сценариев может быть избыточной — разница с base заметна только в сложных условиях.
Практический совет: Начните с tiny — её возможностей хватит для голосовых команд и заметок. Если точность не устраивает, переключитесь на base. Модель small имеет смысл использовать только если вы планируете много работать с голосом в шумной обстановке или вам важна максимальная точность распознавания. Замена модели не требует изменений в коде — достаточно указать другой размер в конструкторе SpeechRecognizer.
Для нашего проекта я рекомендую tiny как основную, а base — если ваш процессор позволяет и вы хотите более точного распознавания. В коде достаточно поменять одну строку: SpeechRecognizer(model_size="base").
Интеграция в основной проект
Функция recognize_speech() будет использоваться в будущем GUI следующим образом:
python
recognizer = SpeechRecognizer(model_size="tiny")
text = recognizer.recognize_speech()
if text:
# Отправляем text в LocalModel.chat() или ask_documents()
Мы не стали добавлять recognize_speech внутрь класса LocalModel, чтобы сохранить разделение обязанностей. LocalModel управляет языковой моделью, SpeechRecognizer — аудио. В главе 5.3 мы объединим их в едином голосовом ассистенте.
Возможные проблемы сраспознаванием речи
Ошибка «PortAudioError: Error opening InputStream». Микрофон не подключён физически или занят другим приложением — например, мессенджером в фоновом режиме. Проверьте настройки звука в системе: зайдите в параметры записи и убедитесь, что микрофон отображается и не отключён. На Linux может потребоваться установка или запуск звукового сервера — pulseaudio --start или активация pipewire. После этого перезапустите приложение.
Модель не распознаёт русскую речь. Убедитесь, что в методе transcribe указан параметр language="ru" — без него Whisper пытается определить язык автоматически и может ошибиться, особенно на коротких фразах. Модели tiny и base понимают русский, но могут путать слова в шумной обстановке или при быстрой речи. Если качество не устраивает, попробуйте модель base вместо tiny — разница заметна.
Запись обрывается слишком рано. Вы не успеваете договорить фразу, а запись уже завершилась. Это происходит, когда алгоритм обнаружения тишины слишком чувствительный. Увеличьте SILENCE_DURATION с 1.5 до 2.0 секунд — это даст больше времени на паузы между словами. Альтернативно, уменьшите SILENCE_THRESHOLD до 0.005 — это сделает детектор тишины менее чувствительным к тихим звукам.
Распознавание очень медленное. На процессоре распознавание даже короткой фразы может занимать несколько секунд. Попробуйте самую маленькую модель — tiny вместо base, разница в скорости существенна, а качество падает незначительно. Если у вас есть видеокарта NVIDIA, используйте device="cuda" и compute_type="float16" при создании SpeechRecognizer — это ускорит распознавание в разы.
Шум на записи. Встроенные микрофоны ноутбуков часто записывают шум вентиляторов и эхо комнаты. Самое простое решение — использовать гарнитуру или внешний микрофон, качество распознавания вырастет dramatically. Если хотите остаться со встроенным микрофоном, можно добавить программную фильтрацию шума через библиотеку noisereduce, но это выходит за рамки базового проекта.
Что дальше
Теперь наш помощник умеет слышать. В следующей главе мы научим его говорить — добавим локальный синтез речи. Глава 5.2 «Озвучивание ответа: piper-tts и silero» покажет, как превратить текст ответа в голос, который можно слушать, не глядя на экран.
Глава 5.2. Озвучиваниеответа: piper-tts и silero.
Мы научили нашего ИИ-помощника слышать. Теперь пора дать ему голос. Когда вы задаёте вопрос, ответ может появляться на экране, но иногда хочется услышать его — например, когда вы готовите кофе или смотрите в окно. Мы добавим локальный синтез речи (Text-to-Speech, TTS), который превратит текст ответа в звук, воспроизводимый через колонки или наушники. И разумеется, всё будет работать без интернета.
На начало 2026 года есть два основных открытых решения для качественного синтеза речи на русском языке, которые можно запустить локально: piper-tts и silero. Давайте сравним их и выберем то, которое лучше впишется в наш проект.
Сравнение piper-tts и silero
Piper-tts
Piper — это быстрый движок синтеза речи, написанный на C++ с Python-биндингами. Он создавался для домашней автоматизации и голосовых ассистентов, работающих на слабом железе вроде Raspberry Pi. Piper использует модели, обученные на различных языках, включая русский.
Плюсы:
· Очень быстрый, работает в реальном времени даже на CPU.
· Низкое потребление памяти.
· Есть готовые русские голоса (например, ru_RU-ruslan-medium).
Минусы:
· Требует установки системных библиотек (libportaudio, espeak-ng) и отдельно скачиваемых моделей.
· Python-интерфейс (piper-tts) не всегда стабилен на Windows.
· Качество русского голоса среднее: иногда слышны «роботизированные» нотки, хотя для утилитарного помощника терпимо.
Silero
Silero — это набор моделей для синтеза и распознавания речи, разработанный российскими исследователями. Модели TTS от Silero стали стандартом де-факто для локального синтеза на русском языке благодаря высокому качеству и простоте использования.
Плюсы:
· Отличное качество русского голоса: интонации, паузы, естественное звучание. Доступны несколько голосов (мужские и женские).
· Простота интеграции: модель загружается напрямую через PyTorch Hub одной строкой, не нужно вручную скачивать файлы.
· Не требует внешних зависимостей: всё работает внутри Python. Установил torch — и готово.
· Активное сообщество: модель обновляется, есть примеры под разные платформы.
Минусы:
· Требует PyTorch, что добавляет около 200 МБ зависимостей при первой установке (но для проекта с LLM это уже не проблема).
· Чуть медленнее piper-tts на CPU (хотя всё равно быстрее реального времени).
Что мы выбираем
Я выбираю Silero. Качество русского голоса здесь на голову выше piper-tts, а простота использования (одна строка для загрузки модели) идеально вписывается в философию «одной кнопки». PyTorch у нас всё равно может пригодиться в будущем для экспериментов с моделями, а его размер — небольшая плата за естественно звучащую речь.
Если для вас критична минимальная производительность (например, вы запускаетесь на очень слабом одноплатнике), вы всегда сможете заменить Silero на Piper, следуя тому же интерфейсу. Мы напишем обёртку, которую будет легко адаптировать.
Установка
Нам понадобятся PyTorch и библиотека для воспроизведения звука. Установим их:
bash
pip install torch sounddevice
Модели Silero загружаются автоматически при первом обращении, никакие файлы вручную скачивать не нужно.
Пишем функцию синтеза речи
Создадим модуль speech_synthesis.py. В нём будет класс TextToSpeech, который загружает модель Silero и воспроизводит переданный текст.
python
# speech_synthesis.py
import os
import torch
import sounddevice as sd
class TextToSpeech:
"""
Локальный синтез речи с помощью модели Silero.
"""
def __init__(self, speaker: str = "xenia", device: str = "cpu"):
"""
:param speaker: голос (список доступных голосов появится при первом запуске)
:param device: "cpu" или "cuda"
"""
self.device = torch.device(device)
print("Загрузка модели Silero TTS...")
# Загружаем модель и список доступных голосов из PyTorch Hub
self.model, self.symbols, self.sample_rate, _, self.apply_tts = torch.hub.load(
repo_or_dir='snakers4/silero-models',
model='silero_tts',
language='ru',
speaker='ru_v3'
)
self.model = self.model.to(self.device)
self.speaker = speaker
print(f"Модель готова. Выбран голос: {self.speaker}")
def speak(self, text: str):
"""
Синтезирует текст и воспроизводит его через звуковое устройство.
"""
if not text.strip():
return
# Синтез: получаем тензор с аудио
audio = self.apply_tts(
texts=[text],
model=self.model,
symbol=self.symbols,
device=self.device,
speaker=self.speaker
)[0] # Берем первый (и единственный) элемент батча
# Переводим тензор в numpy-массив float32
audio_np = audio.cpu().numpy()
# Воспроизводим
sd.play(audio_np, samplerate=self.sample_rate)
sd.wait() # Ждём окончания воспроизведения
Пояснения:
· Загрузка модели: torch.hub.load скачивает модель Silero (если её нет локально) и возвращает все необходимые компоненты: саму модель, словарь символов, частоту дискретизации (обычно 22050 Гц для русских голосов), пути к примерам и функцию apply_tts. Эта функция и делает синтез — принимает список строк и возвращает список аудиотензоров.
· Выбор голоса: Параметр speaker может принимать значения: 'xenia' (женский, мягкий), 'aidar' (мужской), 'baya' (женский, более высокий), 'kseniya' (женский), 'eugene' (мужской). В коде выше мы используем 'xenia'. Вы можете посмотреть полный список, заглянув в документацию Silero или просто попробовав разные варианты.












