Как стать автором
Обновить

Как я начал писать своё автопротоколирование

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров867

Дисклеймер

Всем привет, это мой первый пост, если вдруг будет интересно, продолжу писать на эту тематику. Я не являюсь опытным и профессиональным разработчиком, поэтому буду делиться тем, что узнал сам и по какому пути шел. Мой путь не является правильным да и пишу в первый раз, поэтому судите «строго»:‑)

Этап первый - появление задачи

В прошлом году на работе появилась задача для нашего отдела эксплуатации, где я работаю, о поиске сервиса, в котором можно подводить итоги совещания. Это в целом — стандартная для нас задача, а именно общение с вендором и определение, возможно ли их продукт внедрить в наш контур.

Пообщавшись с несколькими вендорами, в целом сложилась картина, что решения готовые есть, что их на рынке не мало, но что они используют, неизвестно. Собранные результаты я передал начальнику и начал ждать. Основное требование у всех вендоров было наличие сервера с GPU, что крайне не дешево, а так же, большинство из них, хоть и продают систему во внутренний контур, но при этом есть ограничение на кол‑во часов совещаний, что не сильно классно.

Спустя 2 месяца бюрократическая машина заработала, и начали что-то делать, но всё как-то заглохло, а люди, которым этот сервис был очень нужен, не переставали к нам ходить, поэтому появилась идея, а что есть в open source...

Этап второй - поиск open source

Пообщавшись с вендорами, я понял, что есть несколько этапов создания протокола совещания, а именно:

  • Диаризация — разбиение аудиофайла на сегменты по участникам (На самом деле опоционально, если есть агент для ВКС, но в случае с записью — необходим)

  • Транскрибация — превращение голоса в текст

  • Формирование отчета — создание из полученной транскрипции некого протокола

В целом это все основные этапы, которы нужны для этого. Ну и куда же можно пойти в 25 году для поиска? В GPT.) Знаю, знаю, плохо злоупотреблять моделями, но, блин, гуглить? Слишком лень, при том что я делал это в не рабочее время.

Быстро нашел модельку Whisper от open‑ai, поставил себе на ноут, записал короткую аудиозапись, проверил, в целом прикольно, работает.

Дальше задался вопросом о локальных LLM, начал с deepseek, но, как оказалось, дистиллированные модели — крайне не очень работают на русском, да и как дальше оказалось, итоги подводит так себе, поэтому глаз зацепился за llama3, потестил, в целом нормально, ее дальше и начал использовать.

Что по поводу диаризации — тут уже сложнее, потому что запустить её из терминала, не выходит, нужно писать код (в целом нужно написать код для нормальной работы, но это чуть дальше), а для просто результатов, что можно получить, перечисленного выше, хватает.

Ну я собрал результаты, скинул начальнику, он сказал: «Прикольно, а что по поводу целикового продукта?» Я сказал, что хз, нужно время. Он сказал, что ок, попробуй. Ну я и начал пробовать в свободное время, почему нет

Этап третий - первые попытки

Для начала, решил попробовать всё собрать на python (В целом вся логика на нем и осталась). Скачал либы, начал играться, и понял, что к перечисленным выше этапам, при работе с аудиофайлом, нужен еще один — конвертация. Для конвертации решил взять ffmpeg, просто и быстро. Сделал вот такую штуку:

ffmpeg
.input(mp4_file)
.filter("anlmdn")
.output(wav_file, format='wav', acodec='pcm_s16le', ac=1, ar='16k')
.run(quiet=True, overwrite_output=True)

Просто решил конвертировать, обрезать частоты, слегка подавить шум и все, готово, wav формат. Да, конвертирую до сих пор только с MP4, но мне пока что больше и не надо.

Конвертация готова, дальше диаризация, что делать с ней... Решил взять pyannote, показалась простой штукой, куда можно закинуть аудиофайл и получить сегменты в float, плюс слегка пострадав, удалось подключить CUDA что ускорило в целом все.

pipeline = SpeakerDiarization.from_pretrained(
    "pyannote/speaker-diarization-3.1",
    use_auth_token=settings.hf_token
)
pipeline.to(torch.device(device))

diarization = pipeline(wav_file_path)
for turn, _, speaker in diarization.itertracks(yield_label=True):
    duration = turn.end - turn.start
    if duration < 0.2:
        continue

    if speaker not in speaker_map:
        speaker_map[speaker] = next_speaker_id
        next_speaker_id += 1

    segments.append((speaker_map[speaker], turn.start, turn.end))

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

Следующий этап — транскрипция, там я уже потыкался с разными моделями large/medium/turbo, в целом очень понравилась турбо, быстрая и качественная, но есть нюансы в виде «Субтитры предоставил...», зато узнал, откуда взялся этот бред в голосовухах тг, сейчас правда не знаю, есть ли оно всё еще :-)

model = whisper.load_model("turbo", device=device)
result = model.transcribe(
    segment_path,
    language="ru",
    beam_size=10,
    temperature=cycle * 0.2,
)
text = result.get("text", "").strip()

В целом таких настроек мне хватило, и текст получался более менее норм, суть уловить можно, да и от косяков обезопасился костылями в виде if, они, конечно, всё равно проскакивают, но куда меньше.

Ну и дальше последний этап — это создание протокола, для этого решил взять ollama с llama3, (когда запускал на работе, взял llama3.3, т.к. оперативка позволяла не ждать пол года генерации, а всего пол часа) ну и сделал простого агента для обращения к нему.

message_payload = [
    {'role': 'system', 'content': prompt},
    {'role': 'user', 'content': message}
]
response: ChatResponse = chat(
    model=settings.ollama_model,
    messages=message_payload,
    options={'num_ctx': int(settings.ollama_num_context)}
)
content = response.message.content.strip()

Добавил расширение контекста, потому что по умолчанию он 2к, а этого мало (но это сделал уже сильно позже, вначале не понимал, почему ответы становились хуже с размером сообщения).

Получил готовый черновой вариант (Большую часть кода упустил, потому что это же не урок, а просто описание моего пути) и начал его использовать на работе. Мне присылали видеозапись совещания, я запускал этого монстра, получал транскрипцию, отправлял назад, мне писали какой спикер — кто, я запускал запрос в LLM, получал отчет, отдавал назад.

Дебажил получившуюся штуковину, разбирался в LLM, узнал про ограничение контекста, узнал, начал делить совещание перед отправкой в LLM на сегменты по 10 минут совещания, дальше понял, что полученные итоги лучше прогнать еще раз, чтобы получить готовый протокол и так и работали месяца два. Да, на CPU запускать долго, зато бесплатно.:-)

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

Пообщавшись с разработчиком, он решил прикрутить мое решение как доп модуль к существующему у них решению, что я так же одобряю, но это моя разработка, поэтому мне стало интересно сделать что‑то полностью своё.

Этап четвертый - создание полноценного приложения

В целом, данный этап идет до сих пор.

Я решил, что хочу подразобраться в различных технологиях, поэтому решил писать фронт на реакте с тайпскриптом и сделать некую прослойку, которая будет общаться с базой и ставить задачу в бэк. Для этого решил взять golang, потому что круто, модно, молодежно (Ну и есть друг го разраб)

Тут я столкнулся с серьезной проблемой — отсутствие опыта, из‑за которого я сильно пострадал на архитектуре, рефакторинге и т. п., но это уже история на следующий раз, который может быть, а может и не быть.

Всё что я пишу, выкладываю на gitHub, потому что код, написанный дома — твой код (вот ссылка, если вдруг интересно).

А вот вам картинка интерфейса который сейчас есть, так скажем, затравка на будущее, если вдруг кто‑то прочитает.

Картинка ради картинки :-)
Картинка ради картинки :-)

Всем кто дочитал — спасибо! :-)

Теги:
Хабы:
+6
Комментарии6

Публикации

Работа

Data Scientist
49 вакансий

Ближайшие события