Как я начал писать своё автопротоколирование. llama.. llama. python.. llama. python. whisper.. llama. python. whisper. автоматизация.. llama. python. whisper. автоматизация. искусственный интеллект.. llama. python. whisper. автоматизация. искусственный интеллект. прототип.

Дисклеймер

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

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

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

Пообщавшись с несколькими вендорами, в целом сложилась картина, что решения готовые есть, что их на рынке не мало, но что они используют, неизвестно. Собранные результаты я передал начальнику и начал ждать. Основное требование у всех вендоров было наличие сервера с 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, потому что код, написанный дома — твой код (вот ссылка, если вдруг интересно).

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

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

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

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

Автор: virus3908

Источник

Rambler's Top100