В продолжение предыдущей заметки давайте сделаем ещё один шаг и добавим возможность боту отвечать текстом на голосовые сообщения. Для распознования голоса будем использовать python обертку над whisper.cpp.

Более подробно как работает бот смотрите предыдущий пост, тут я сделаю небольшой рефакторинг и добавлю новый обработчик для голосовых.

Устанавливаем зависимости

deep-translator==1.11.4
llama_cpp_python==0.2.77
loguru==0.7.2
python-dotenv==1.0.1
requests==2.32.3
telebot==0.0.5
whisper_cpp_python==0.2.0
import datetime
import os

from uuid import uuid4

import requests
import telebot
from deep_translator import GoogleTranslator
from dotenv import load_dotenv
from llama_cpp import Llama
from loguru import logger
from telebot.types import Message
from whisper_cpp_python import Whisper

load_dotenv()

TG_TOKEN = os.getenv("TG_TOKEN")
bot = telebot.TeleBot(TG_TOKEN)

# Загружаем llama-3 модель
llm = Llama(
    model_path="./models/llama3/Meta-Llama-3-8B-Instruct-Q4_K_M.gguf",
    chat_format="llama-3",
    verbose=False,
    # n_gpu_layers=-1, # Uncomment to use GPU acceleration
    # seed=1337, # Uncomment to set a specific seed
    # n_ctx=2048, # Uncomment to increase the context window
)

# Загружаем whisper.cpp модель
whisper_model = Whisper(model_path="./models/whisper/ggml-base.bin")

# Словарь для хранения историй сообщений
user_message_history = {}

# Стартовое сообщение бота
@bot.message_handler(commands=["start", "help"])
def send_welcome(message: Message):
    bot.send_message(message.chat.id, "Я ИИ бот на базе llama-3. Можешь отправить мне текстовое или голосовое сообщение.")


# Вынесем логику работы с историей сообщений и llama моделью в отдельную функцию
def create_chat_completion(user_id, text):
    # Получаем историю сообщений текущего пользователя
    user_history = user_message_history.get(user_id, [])
    user_history.append({"role": "user", "content": text})

    #  Добавим в контекст текущую дату и время
    current_date_time = datetime.datetime.now().strftime("%d %B %Y, %H:%M MSK")
    messages = [
        {
            "role": "system",
            "content": f"Ты полезный ИИ помощник.\nТекущая дата: {current_date_time}",
        },
    ]

    for msg in user_history[-10:]:
        messages.append(msg)

    out = llm.create_chat_completion(messages)
    reply = out["choices"][0]["message"]["content"]
    logger.info(f"assistant: {reply}")

    # Добавляем ответ бота в историю текущего пользователя
    user_history.append({"role": "assistant", "content": reply})
    user_message_history[user_id] = user_history[-20:]

    return reply

# Обработчик текстовых сообщений
@bot.message_handler(content_types=["text"])
def message_handler(message: Message):
    chat_id = message.chat.id
    user_id = message.from_user.id
    logger.info(f"user {user_id}: {message.text}")

    bot.send_chat_action(chat_id, "typing")
    reply = create_chat_completion(user_id, message.text)
    # Отправляем ответ пользователю
    bot.send_message(chat_id, reply)

Добавляем обработчик голосовых сообщений

@bot.message_handler(content_types=["voice"])
def voice_handler(message: Message):
    chat_id = message.chat.id
    user_id = message.from_user.id
    logger.info(f"get voice message from {user_id}")
    logger.info(message.voice)

    # Получаем url фала голосового сообщения
    file_info = bot.get_file(message.voice.file_id)
    file_path = file_info.file_path
    file_url = f"https://api.telegram.org/file/bot{TG_TOKEN}/{file_path}"

    # Скачиваем OGG файл
    response = requests.get(file_url)
    ogg_file_path = f"voice-{user_id}-{message.voice.file_id}.ogg"
    with open(ogg_file_path, "wb") as f:
        f.write(response.content)

    bot.send_chat_action(chat_id, "typing")

    # Используем whisper.cpp для преобразования аудио в текст
    data = whisper_model.transcribe(ogg_file_path)
    logger.info(f"whisper out: {data}")
    text = data["text"]

    # Используем llama для ответа на сообщение
    reply = create_chat_completion(user_id, text)
    reply = GoogleTranslator(source="auto", target="ru").translate(reply)

    # Отправляем ответ в чат
    bot.send_message(chat_id, reply)

    # Удаляем временный OGG файл
    os.remove(ogg_file_path)
    pass


logger.info("bot is ready")
bot.infinity_polling()

Обратите внимание, whisper преобразует русскую речь в текст на английском если не указать параметр language. Т.к. перевод получается вполне коррекнтым и llama-3 лучше работает с английским - оставляем как есть без указания языка и передаем llama-3 именно английский вариант транскрипции. А вот уже ответ полученный от llama-3 переводим на русский используя GoogleTranslator.

Теперь бот должен уметь отвечать и на текстовые сообщения, и на голосовые. Спрашиваю как у бота дела:

В логах бота:

INFO | __main__:voice_handler:42 - get voice message from .....
INFO | __main__:voice_handler:43 - {'file_id': 'AwACBAAIDZ2ZkM4zzsj-tdSkAAUv0iDDv24QEgAACFk8AAragIUvSSFN8mxv-kDUE', 'file_unique_id': 'AgvDFe8AAragIUs', 'duration': 2, 'performer': None, 'title': None, 'file_name': None, 'mime_type': 'audio/ogg', 'file_size': 52877, 'thumbnail': None}
INFO | __main__:voice_handler:58 - {'text': 'Hello, how are you doing?'}
INFO | __main__:create_chat_completion:89 - assistant: I'm just an AI, I don't have feelings like humans do, but I'm functioning properly and ready to help with any questions or tasks you may have! It's great to chat with you. How about you? How's your day going so far?