Содержание статьи +
- Кратко
- Почему это важно
- Что вы получаете
- Точка входа: `agent.py`
- Сердце: `transcriber.py`
- Интеллект, применённый один раз: `notes.py`
- Опциональное дополнение: `recording.py`
- Как ввести агента в звонок: `token_server.py`
- Доказать, что ядро работает: `tests/test_notes.py`
- Запуск и стоимость
- Строить на этом или купить готового бота?
- Где здесь Фора Софт
- Главное
- Что почитать дальше
Кратко
Этот урок предлагает готовый рабочий open-source проект: ноутейкер на базе LiveKit Agents, который подключается к любой встрече как молчаливый дополнительный участник, распознаёт речь каждого спикера с указанием имён и временных меток, а по окончании звонка преобразует транскрипт в структурированные заметки – резюме, решения, задачи – и сохраняет их в форматах Markdown и JSON. Мы подробно разберём все шесть исходных файлов простым языком, чтобы вы не только поняли, что делает каждый из них, но и почему он устроен именно так. Дизайн намеренно ориентирован на прослушивание: агент никогда не говорит, что позволяет исключить самый дорогой компонент и сделать его незаметным. К концу урока вы сможете склонировать репозиторий, запустить проект на своём сервере LiveKit примерно за пять минут, интегрировать его из своего приложения и развернуть в LiveKit Cloud или Docker.
Почему это важно
Большинство команд, которым нужен ИИ-ноутейкер, начинают с интеграции API транскрипции и LLM, но быстро сталкиваются с серьёзными проблемами: как подключить бота к звонку, как разделять речь каждого участника, когда генерировать резюме и сколько всё это будет стоить. Этот урок устраняет неопределённость, предлагая готовую реализацию, которая решает эти задачи так же, как собственные примеры LiveKit, но с дополнительными продакшен-компонентами, которых в них нет: диспетчеризация, запись, структурированные заметки и тесты. Если вы – основатель или продуктовый лидер, вы получаете конкретный артефакт, по которому можно оценивать и масштабировать. Если вы – инженер, вы получаете код, который легко читается от начала до конца и быстро адаптируется под ваш продукт.
Что вы получаете
Загрузка ниже – это весь проект под лицензией Apache-2.0, как и сам LiveKit, проверенный по версии livekit-agents 1.х на июнь 2026 года. Он намеренно небольшой – шесть исходных файлов, один тестовый и обвязка для деплоя – потому что цель здесь не в создании фреймворка, который нужно учить, а в понимании.
Перед кодом – напоминание из опорного урока по LiveKit: ассистент встреч – это не просто функция, «прикрученная» к экрану одного человека. Это полноценный участник, созданный программно. Он подключается к той же комнате, что и люди, слушает общее аудио и выдаёт результат в виде текста, записи или речи. Всё, что есть в этом репозитории, вытекает из этой одной идеи. Если термины LiveKit ниже – комната, участник, диспатч, агент – кажутся незнакомыми, сначала ознакомьтесь с опорным уроком; здесь он считается известным.
Вот весь проект целиком, прежде чем мы откроем хоть один файл.
Пять исходных файлов в src/ чётко распределяют работу, а файл тестов фиксирует ту единственную часть, которую стоит тестировать без подключения к сети. Таблица ниже – это карта, по которой мы пройдём весь оставшийся урок.
| Файл | За что отвечает | Что запомнить |
|---|---|---|
| src/agent.py | Точка входа – связывает всё вместе | Сервер агентов передаёт управление вашему entrypoint на каждый звонок |
| src/transcriber.py | По одной STT-сессии на участника | Раздельные сессии держат слова каждого спикера атрибутированными |
| src/notes.py | Собрать строки, резюмировать один раз, записать файлы | Резюме – один проход LLM в конце, а не во время звонка |
| src/recording.py | Опциональная запись через LiveKit Egress | Запись – отдельная задача от транскрипции |
| src/token_server.py | Выдать токен пользователю и диспатчить агента | Явный диспатч ставит агента только туда, куда нужно |
| tests/test_notes.py | Юнит-тесты слоя заметок | Детерминированное ядро тестируется без сети |
Точка входа: `agent.py`
У каждого агента LiveKit есть одна входная дверь, и этот файл – она. Представьте эту дверь как стойку ресепшена в здании: долгоживущая программа ждёт там, и каждый раз, когда встрече нужен ассистент, ресепшен выдаёт посетителю – вашему коду – ключ от одной конкретной комнаты.
В фреймворке LiveKit стойка ресепшена – AgentServer, а передача ключа – функция, помеченная декоратором. Декоратор – это однострочная пометка, написанная символом @ над функцией, которая говорит фреймворку: «вызови эту функцию, когда придёт задача». Вот её форма, сокращённая до сути:
server = AgentServer()
def prewarm(proc: JobProcess) -> None:
# Загрузить модель активности голоса один раз на процесс и переиспользовать.
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
@server.rtc_session(agent_name="meeting-notetaker")
async def entrypoint(ctx: JobContext) -> None:
...
if __name__ == "__main__":
cli.run_app(server)Три детали в этом маленьком блоке имеют реальное значение. Первая: prewarm выполняется один раз при запуске процесса-воркера – до назначения ему встречи – и загружает небольшую модель voice activity detection, сокращённо VAD. Это лёгкая модель, которая просто определяет, говорит ли кто-то в данный момент. Загрузив её один раз и используя для всех участников в рамках одного процесса, мы экономим время запуска на каждом звонке. Поскольку фреймворк выполняет каждую задачу в отдельном процессе операционной системы ради изоляции, «прогрев» модели оказывается выгодным именно на уровне процесса.
Вторая: аргумент agent_name="meeting-notetaker" – не косметика. В LiveKit имя агента в декораторе сессии переключает его в режим явного диспатча: агент будет входить только в те комнаты, куда вы его конкретно отправили, и никогда – во все автоматически. Это как раз то, что нужно для продукта встреч, поэтому token-сервер обязан далее запрашивать агента по тому же имени.
Третья: cli.run_app(server) превращает файл в программу командной строки с тремя режимами. Запуск python src/agent.py console позволяет общаться с агентом в терминале без использования фронтенда. Режим dev запускает воркер против вашего сервера LiveKit с горячей перезагрузкой и понятными логами. Режим start – продакшен-режим, использующий Dockerfile и выводящий машиночитаемые JSON-логи.
Теперь тело точки входа – это код, который выполняется при каждой встрече:
@server.rtc_session(agent_name="meeting-notetaker")
async def entrypoint(ctx: JobContext) -> None:
store = TranscriptStore(room_name=ctx.room.name)
transcriber = MultiUserTranscriber(ctx, store, stt_model=STT_MODEL)
egress_id = await start_room_recording(ctx.room.name)
# Подписаться только на аудио — ноутейкеру никогда не нужны видеотреки.
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)
transcriber.start()
async def on_shutdown() -> None:
await transcriber.aclose()
await stop_recording(egress_id)
if store.is_empty:
return
summary = await summarize_transcript(store)
write_notes(NOTES_DIR, store, summary)
ctx.add_shutdown_callback(on_shutdown)Читайте это как короткий рассказ. Агент создаёт пустую тетрадь (store), нанимает транскрайбера, который будет в неё писать, и при необходимости запускает запись. Затем подключается к комнате, запрашивая только аудио – видео не нужно, ведь ноутейкеру изображение не требуется, а отказ от видео экономит трафик, за который вы иначе платите. Запускает транскрипцию. Наконец, регистрирует колбэк завершения – функцию, которую фреймворк обязуется выполнить, когда все покинут комнату. Этот колбэк закрывает транскрайбера, останавливает запись и – только если что-то действительно было сказано – запускает резюме и сохраняет заметки. Реальный файл оборачивает резюме в try/except, чтобы в случае сбоя резюмирования вы не потеряли исходный транскрипт; потерять слова из-за поломки резюме было бы худшим исходом.
Форма, которую стоит усвоить: во время звонка агент почти ничего умного не делает – он просто собирает информацию. Интеллект проявляется один раз, в конце. У этого выбора есть последствия для стоимости и качества, к которым мы вернёмся.
Сердце: `transcriber.py`
Это файл, который делает ноутейкером ноутейкера, и на нём стоит сбавить темп. Задача, которую он решает, звучит просто, но на деле оказывается сложной: как в звонке с пятью участниками получить пять чётко разделённых потоков «кто что сказал», а не один общий, перепутанный транскрипт?
Наивный подход – направить один транскрайбер на смешанное аудио комнаты – даёт сплошной текст без возможности надёжно определить, кто из спикеров что сказал. В этом случае потребуется дополнительная система, называемая диаризацией спикеров, чтобы постфактум определить, кто произнёс каждую реплику; более сложный путь мы подробно разбираем в уроке по диаризации Pyannote. Этот репозиторий полностью обходит эту проблему более чистым решением: каждому участнику даётся собственный транскрайбер.
Это работает благодаря особенностям доставки аудио в LiveKit. Аудиопоток каждого участника поступает как отдельный трек – так называемый медиапоток, который пересылает медиасервер, описанный в сравнении WebRTC и video SDK. Поскольку потоки изначально разделены на источнике, к каждому можно привязать отдельную STT-сессию, и каждая расшифрованная строка автоматически атрибутируется нужному человеку – без необходимости угадывать, кто говорит. Это канонический паттерн LiveKit «multi-user transcriber», и репозиторий следует официальному примеру, добавляя лишь одну деталь, которую он опускает: отправку финализированных строк в общее хранилище.
Вклад самого агента в диалог минимальный. Это агент STT-только, который при завершении высказывания спикера записывает строку и затем намеренно молчит:
class ParticipantTranscriber(Agent):
def __init__(self, *, participant_identity, store, stt_model):
super().__init__(instructions="not-needed", stt=inference.STT(stt_model))
self.participant_identity = participant_identity
self._store = store
async def on_user_turn_completed(self, chat_ctx, new_message) -> None:
text = (new_message.text_content or "").strip()
if text:
self._store.add(speaker=self.participant_identity, text=text)
# Подавить ответ по умолчанию: этот агент только слушает.
raise StopResponse()Самая важная строка – raise StopResponse(). По умолчанию агент LiveKit, услышав завершённую реплику, попытается на неё ответить – запустить языковую модель и произнести вслух. Для ноутейкера это была бы катастрофа: бот начал бы отвечать всем подряд. raise StopResponse() – это способ сообщить фреймворку: «Я услышал, я закончил, не генерируй ответ». Именно так разговорный агент превращается в чистого слушателя. Вызов inference.STT(stt_model) направляет аудио через LiveKit Inference – брокерский способ подключения к провайдеру вроде Deepgram – с моделью, заданной в одной переменной окружения (deepgram/nova-3 по умолчанию); урок по потоковому ASR сравнивает варианты провайдеров.
Окружающий класс MultiUserTranscriber – это менеджер. Он отслеживает два события в комнате: вход и выход участников, и поддерживает словарь, в котором для каждого человека хранится отдельная сессия транскрипции:
def start(self) -> None:
self.ctx.room.on("participant_connected", self._on_participant_connected)
self.ctx.room.on("participant_disconnected", self._on_participant_disconnected)
# Обработать всех, кто уже в комнате, когда агент входит.
for participant in self.ctx.room.remote_participants.values():
self._on_participant_connected(participant)Последний цикл важнее, чем может показаться. События срабатывают только для будущего: если в комнате уже три человека, когда приходит агент, ни одно событие «connected» для них не произойдёт. Перебор существующих участников при старте – это способ, которым агент «подхватывает» всех, кто уже был. Забыть этот цикл – классическая ошибка: агент идеально обрабатывает опоздавших, но молча игнорирует тех, кто пришёл раньше.
Когда участник подключается, менеджер создаёт для него сессию, в настройках которой устанавливается режим прослушивания – listen-only.
await session.start(
agent=ParticipantTranscriber(...),
room=self.ctx.room,
room_options=room_io.RoomOptions(
audio_input=True, # слушать этого участника
text_output=True, # публиковать живые субтитры обратно в комнату
audio_output=False, # никогда не говорить — это ноутейкер
text_input=False,
participant_identity=participant.identity,
),
)Читайте эти четыре флага как контракт агента с комнатой. Он получает аудио на вход, может публиковать текст наружу (чтобы встреча отображала живые субтитры, если интерфейс того требует), никогда не передаёт аудио наружу и привязан строго к одному участнику. Поставьте audio_output=True – и у вас будет говорящий бот; оставьте False – и весь смысл пропадёт. Когда встреча завершается, aclose() отменяет фоновые задачи и аккуратно завершает каждую сессию, чтобы ни одна незавершённая строка не была потеряна.
Интеллект, применённый один раз: `notes.py`
Этот файл выполняет две задачи, которые легко перепутать: сбор транскрипта в ходе звонка и создание краткого резюме в конце. Разделение этих функций делает архитектуру простой и предсказуемой.
Сборщик – небольшое хранилище в оперативной памяти. Каждая запись содержит информацию о том, кто сказал, что сказал и сколько секунд прошло с начала встречи:
@dataclass
class TranscriptLine:
speaker: str
text: str
t: float # секунды от начала встречи
def add(self, speaker: str, text: str) -> None:
self.lines.append(
TranscriptLine(speaker=speaker, text=text, t=time.monotonic() - self.started_at)
)Таймстамп использует монотонные часы – те, что идут только вперёд и не реагируют на изменение системного времени во время звонка, – поэтому прошедшее время никогда не идёт назад, даже если серверные часы сдвинуты. Хранилище также возвращает список спикеров без дубликатов и текстовую версию полного транскрипта, используемую как промпт для создания резюме и как читаемый человеком блок в файле заметок.
Резюме – это один вызов языковой модели, сделанный после встречи, а не во время неё. Это решение оказывает наибольшее влияние на стоимость во всём репозитории, поэтому стоит подчеркнуть это прямо. Агент не просит LLM «постоянно резюмировать» в процессе разговора. Он ждёт окончания звонка, затем отправляет весь транскрипт модели ровно один раз и запрашивает структурированный вывод:
resp = await client.chat.completions.create(
model=model,
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": SUMMARY_SYSTEM_PROMPT},
{"role": "user", "content": user_prompt},
],
)Настройка response_format={"type": "json_object"} требует от модели возвращать строгий JSON, а не свободную прозу, чтобы результат можно было сразу использовать в остальной программе без хрупкого парсинга текста. Промпт указывает ровно пять ключей, которые должны быть возвращены: краткий обзор, темы, решения, задачи (каждая с владельцем и опциональным сроком) и любые открытые follow-up’ы – и строго запрещает модели выдумывать факты, не подтверждённые транскриптом. Код всё равно оборачивает обработку в защиту: если модель когда-либо вернёт что-то, не являющееся валидным JSON, строка json.loads(raw) падает мягко, и сырой текст сохраняется, а не ломает выполнение.
Запись вывода – последний шаг. Репозиторий генерирует два файла на каждую встречу, названные по имени комнаты и таймстампу: Markdown-файл с разделами для человека – Резюме, Решения, Задачи, Follow-up’ы – и полный транскрипт, а также JSON-файл с тем же содержанием, но в формате, пригодном для обработки программами. Выдавать оба – намеренно: Markdown предназначен для участников встречи, а JSON – для системы, которая создаст задачи в вашем трекере. Имя файла санитизируется, чтобы комната с названием Q3 / planning не могла «вырваться» за пределы папки с заметками – это небольшая, но полезная мера безопасности, которую стоит перенять.
Опциональное дополнение: `recording.py`
Запись выглядит так, будто она должна быть внутри транскрайбера, но одно из самых чистых решений в этом репозитории – в том, что это не так. Транскрипция и запись реализованы как две отдельные задачи, потому что именно так устроен LiveKit, и именно так LiveKit Cloud взимает плату за эти функции.
Запись осуществляется с помощью компонента LiveKit под названием Egress – части системы, отвечающей за экспорт или повторную трансляцию содержимого комнаты. Агент никогда не пытается самостоятельно захватывать медиа; вместо этого он просит Egress записать композит комнаты (объединение всех участников в один файл) и загрузить его в ваше собственное хранилище:
request = api.RoomCompositeEgressRequest(
room_name=room_name,
layout="speaker",
audio_only=False,
file_outputs=[file_output], # внимание: список, а не одиночное 'output'
)
async with api.LiveKitAPI() as lkapi:
info = await lkapi.egress.start_room_composite_egress(request)
return info.egress_idВся функциональность скрыта за одной переменной окружения, RECORD_MEETINGS, и отключена по умолчанию – ведь запись влечёт за собой обязательства по приватности и согласию, в которые стоит входить осознанно, а не случайно. Если функция включена, но хранилище не настроено, код выводит предупреждение и пропускает запись, не прерывая встречу: ноутейкер, который ничего не записал, всё ещё полезен; тот, что «умер» из-за отсутствия бакета, – уже нет.
«Частая ловушка: пропущенное поле output. Реальный баг, с которым сталкивались многие команды LiveKit, – это отправка запроса записи с одиночным полем output= и последующее получение от LiveKit Cloud ошибки invalid_argument: missing field: output. Текущий, правильный API ожидает список – file_outputs=[...] – и именно такой формат используется в этом репозитории намеренно. Если при адаптации кода запись падает в Cloud с этой ошибкой, значит, вы случайно вернулись к устаревшему формату с одиночным полем. Та же логика разделения ответственности применима и к более тонкой ловушке: не пытайтесь перехватывать аудиофреймы внутри агента, чтобы «сохранить запись» – это смешивает два тарифицируемых ресурса и два режима отказа в один хрупкий и ненадёжный путь. Пусть Egress отвечает за запись, а агент – за расшифровку.»
Приятное свойство, о котором стоит знать: запись композита комнаты привязана к её жизненному циклу и автоматически останавливается, когда покидает последний участник. Репозиторий всё равно явно вызывает stop_recording при завершении – это безвредная подстраховка, делающая намерение очевидным для следующего, кто будет читать код.
Как ввести агента в звонок: `token_server.py`
Пока агент может работать, но его ещё не назначили на конкретную встречу. Это задача последнего исходного файла – он выполняет две операции в одном HTTP-запросе.
Приложению пользователя нужны две вещи, чтобы войти в комнату LiveKit: адрес сервера и подписанный токен доступа – короткоживущее удостоверение, как нумерованный билет, говорящее «этому человеку можно войти в эту конкретную комнату». Token-сервер чеканит этот билет. Но в том же запросе, до выдачи билета, он также диспатчит ноутейкер в эту комнату:
@app.get("/token")
async def token(room: str, identity: str) -> dict:
# 1. Диспатчить агента-ноутейкер в комнату.
async with api.LiveKitAPI() as lkapi:
await lkapi.agent_dispatch.create_dispatch(
api.CreateAgentDispatchRequest(agent_name=AGENT_NAME, room=room)
)
# 2. Выдать токен входа человеку.
grant = api.VideoGrants(room_join=True, room=room)
jwt = (
api.AccessToken(os.environ["LIVEKIT_API_KEY"], os.environ["LIVEKIT_API_SECRET"])
.with_identity(identity).with_name(identity).with_grants(grant).to_jwt()
)
return {"serverUrl": os.environ["LIVEKIT_URL"], "roomName": room, "token": jwt}Это явный диспатч – и это рекомендуемый паттерн по причине, описанной в опорном уроке по LiveKit: он отправляет агента только в те комнаты, которые вы выбираете, и позволяет прикрепить метаданные – например, какая это встреча, кто хост – чтобы агент понимал свой контекст. Альтернатива – диспатч по токену – срабатывает только при первом создании комнаты и молча ничего не делает, если комната уже существует; у явного диспатча такой ловушки нет. Обратите внимание: ключ и секрет API берутся из переменных окружения и никогда не передаются в браузер – сервер подписывает токен, клиент лишь получает его.
Ваш фронтенд вызывает один эндпоинт, получает адрес сервера и токен, после чего подключается. Ноутейкер уже в пути внутрь – обычно он помещается в комнату быстрее, чем за пятую долю секунды. С точки зрения пользователя ассистент просто появляется, как только начинается звонок.
Доказать, что ядро работает: `tests/test_notes.py`
Замечание о тестировании, потому что оно показывает, как нужно мыслить о надёжности в ИИ-функциях. Те части, что зависят от живой сети и платной модели – транскрипция и резюмирование LLM, – сложно поддаются юнит-тестированию. А вот те, которые никогда не должны ломаться – сбор строк, атрибуция спикеров, рендеринг файла заметок, – основаны на чистой логике, и именно их проверяет тестовый файл:
def test_render_markdown_has_sections():
summary = {
"summary": "The team agreed to ship on Friday.",
"decisions": ["Ship the note-taker on Friday."],
"action_items": [{"owner": "bob", "task": "Write the deployment guide.", "due": None}],
"follow_ups": [],
}
md = render_notes_markdown(_store(), summary)
assert "## Summary" in md
assert "**bob**" in mdТесты запускаются без API-ключей и без интернета, и проходят заметно быстрее секунды. Такое разделение – жёстко тестировать детерминированное ядро, а модель рассматривать как внешнюю зависимость, которую можно замокать или пропускать – является правильным стандартом для любой ИИ-функции. Именно поэтому слой заметок реализован как простые функции над простой структурой данных, а не вплетён в живого агента.
Запуск и стоимость
Пять шагов помогут вам перейти от клона к разговору с агентом. Установите зависимости через uv sync, скопируйте .env.example в .env.local и введите ключи LiveKit, Deepgram и OpenAI. Один раз скачайте веса модели VAD через python src/agent.py download-files, затем запустите python src/agent.py console, чтобы общаться с агентом в терминале, или python src/agent.py dev, чтобы запустить его как воркер на сервере. Войдите в комнату с любого фронтенда LiveKit, поговорите и выйдите – заметки появятся в notes/.
Деплой в LiveKit Cloud – всего две команды, если livekit.toml и Dockerfile настроены: lk agent create – впервые, чтобы зарегистрировать агента и записать его ID в livekit.toml, а затем lk agent deploy – для каждой последующей версии. LiveKit Cloud собирает образ контейнера автоматически и запускает его. Либо соберите Docker-образ самостоятельно и запустите на любой инфраструктуре через docker run --env-file .env.local meeting-notetaker.
Структура затрат – это то, что стоит учитывать при планировании. Поскольку речь идёт о ноутбуке с функцией прослушивания (listen-only), это относительно дешёвое решение. В расчёт входят три счётчика и модель распознавания речи (speech-to-text). Рассмотрим пример: 2 000 встреч в месяц, продолжительностью по 45 минут, с участием пяти человек и одного агента.
Время подключения тарифицируется за каждого участника, включая агента:
2 000 встреч × 45 минут × 6 участников
= 540 000 минут участников WebRTC / месяцСобственное время работы агента тарифицируется отдельно – только за самого агента:
2 000 встреч × 45 минут × 1 агент
= 90 000 минут сессии агента / месяцИ распознавание речи по представительной тарифной ставке около 0,006 доллара за минуту расшифрованного аудио:
2 000 встреч × 45 минут × $0.006
= $540 / месяц на speech-to-textУрок в пропорциях, а не в итогах. Поскольку агент не говорит, нет этапа text-to-speech – а он обычно самый затратный слой, часто обходящийся примерно в $0.03 за минуту. Отсутствие этого этапа – причина, по которой listen-only-ассистент может стоить менее половины того, что стоил бы говорящий копилот. Полные тарифы по уровням с лимитами Build, Ship и Scale – в опорном уроке по архитектуре и ценам LiveKit, а юнит-экономику по функциям мы моделируем в реальной стоимости ИИ в видеопродуктах.
Строить на этом или купить готового бота?
Этот репозиторий – пример «build», и он подходит, когда ассистент является частью вашего продукта или должен работать в среде, которую вы уже контролируете: ваше приложение для конференций, телемедицинская платформа, комната для e-learning. Вы контролируете поведение, модели и путь данных.
Если всё, что нужно, – «дать пользователям транскрипт их звонка в Zoom», то купить готовый сервис-бота встреч вроде Recall.ai – бота под ключ, который входит в Zoom, Google Meet или Teams и возвращает транскрипты через API примерно за $0.50 за час записи – быстрее и дешевле для запуска, чем держать любую инфраструктуру. Компромисс обычный: чем более готовый продукт вы покупаете, тем меньше можете менять. Этот репозиторий существует для случая, когда менять – и есть смысл.
| Путь | Что вы делаете | Лучше всего, когда |
|---|---|---|
| Этот репо на LiveKit Cloud | Запускаете агента; LiveKit хостит медиа | Ноутейкер живёт внутри вашего приложения |
| Этот репо на self-host | Запускаете open-source сервер и агента | Строгая приватность или резидентность данных |
| Купить бота встреч (напр. Recall.ai) | Зовёте API; бот входит в Zoom/Meet/Teams | Транскрипт – товарное дополнение |
Где здесь Фора Софт
Мы разрабатываем продукты для видеосвязи в реальном времени с 2005 года – видеоконференции, e-learning, телемедицину и инструменты совместной работы – и ноутейкер сегодня почти стандартный запрос в проектах по конференциям и телемедицине, которые мы прорабатываем. Мы выпускали агентов, присоединявшихся к звонкам как участники, чтобы расшифровывать и резюмировать содержание, и архитектура в этом репозитории – та же, что мы используем, когда искусственный интеллект должен работать внутри продукта клиента, а не через стороннего бота. В телемедицине этот подход превращается в ИИ-скрайба, который составляет заметки о приёме; в e-learning – в резюме сессии; в конференциях – в ноутейкера, которого пользователи уже ожидают. Важное инженерное решение – согласовать дизайн с реальной потребностью: сделать ноутейкера только слушающим, чтобы его не воспринимали – и не тарифицировали – как говорящего помощника.
Главное
- Репозиторий – готовый рабочий ноутбук на основе LiveKit Agents под лицензией Apache-2.0, состоящий из шести полностью разобранных файлов.
- Одна STT-сессия на участника обеспечивает атрибутированные транскрипты без необходимости отдельного этапа диаризации.
- raise StopResponse() – это то, что превращает агента в режим прослушивания без ответов.
- Резюме генерируется за один проход LLM в конце встречи и возвращается в виде строгого JSON, сохранённого также в форматах Markdown и JSON.
- Запись – отдельная задача Egress, отключена по умолчанию; используйте file_outputs=[...], а не одиночный output.
- Режим listen-only исключает text-to-speech – самый ресурсоёмкий и дорогой компонент – поэтому работает за долю стоимости полноценного копилота.