diff --git a/.gitignore b/.gitignore index f052aa4..87f6c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__/ .venv/ runtime/ +debug/ output.md *.json diff --git a/asr_eventizer.py b/asr_eventizer.py index 212fff5..e794399 100644 --- a/asr_eventizer.py +++ b/asr_eventizer.py @@ -1,5 +1,7 @@ -from dataclasses import asdict from typing import Any +import traceback +import shutil +import os import json from pydantic import BaseModel @@ -28,20 +30,32 @@ class _EventizeResult(BaseModel): class AsrEventizer: """This class creates a list of events from AsrFilterResult""" + DEBUG_ID = 0 def _eventize_window(self, window: Window[AsrFilterSegment]) -> _EventizeResult: """Eventize a single window""" messages = [ self._system_prompt, AgentMessage( - content=json.dumps(asdict(window), indent=2, ensure_ascii=False), + content=json.dumps(window.model_dump(mode="json"), indent=2, ensure_ascii=False), role="user" ) ] + debug_dir = None + if os.path.isdir("debug"): + debug_dir = f"debug/AsrEventizer/{AsrEventizer.DEBUG_ID}" + AsrEventizer.DEBUG_ID += 1 + os.makedirs(debug_dir, exist_ok=True) + if debug_dir: + with open(f"{debug_dir}/request.txt", "w") as f: + f.write(messages[1].content) retries_left = 5 while retries_left > 0: retries_left -= 1 response = self._agent.completion(messages=messages) + if debug_dir: + with open(f"{debug_dir}/{retries_left}-retries-left.txt", "w") as f: + f.write(response) # validate data try: response = json.loads(response) @@ -56,6 +70,7 @@ class AsrEventizer: raise RuntimeError("Model has tried to use ID that was not provided") return obj except: + traceback.print_exc() continue raise RuntimeError( "Agent has failed to provide valid schema too many times" @@ -70,7 +85,7 @@ class AsrEventizer: """ self._agent = agent self._windowizer = windowizer - with open("prompts/asr_eventizer.json", "r") as f: + with open("prompts/asr_eventizer.md", "r") as f: self._system_prompt = AgentMessage( content=f.read(), role="system" @@ -91,12 +106,13 @@ class AsrEventizer: window.past = [e for e in window.past if e.id not in last_processed_ids] intermediate = self._eventize_window(window) context = intermediate.context + last_processed_ids = [] # process preevents for preevent in intermediate.preevents: related_segments = [ ev for ev in window.past + window.present if ev.id in preevent.ids ] - last_processed_ids = [ev.id for ev in related_segments] + last_processed_ids += [ev.id for ev in related_segments] min_time = min(s.start for s in related_segments) max_time = max(s.end for s in related_segments) events.append(Event( diff --git a/main.py b/main.py index 68f9078..dc010c4 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,9 @@ from typing import Callable import torch from asr import Asr, AsrRawResult -from asr_filter import AsrFilter +from asr_filter import AsrFilter, AsrFilterResult +from asr_eventizer import AsrEventizer +from windowizer import Windowizer from agent import Agent from utils import ffmpeg_split_video, ffmpeg_to_mp3 @@ -138,13 +140,36 @@ def on_asr_filter(current_step: Step, input_data: dict | None) -> tuple[Step | N return (Step.ASR_EVENTS, json.load(f)) # bad request if input_data is None: - logging.error("Can'f filter raw ASR ouput without input_data") + logging.error("Can't filter raw ASR output without input_data") return (None, None) logging.info("Filtering raw ASR output...") filter = AsrFilter() result = filter.filter(AsrRawResult(**input_data)) return (Step.ASR_EVENTS, result.model_dump(mode="json")) +def on_asr_events(current_step: Step, input_data: dict | None) -> tuple[Step | None, dict | None]: + # do not filter if output file exists + if os.path.isfile(WORKFLOW_DATA[current_step][0]): + logging.info("Skipping ASR eventizing") + with open(WORKFLOW_DATA[current_step][0], "rb") as f: + return (Step.VIDEO_REFERENCES, json.load(f)) + # bad request + if input_data is None: + logging.error("Can't create audio events without input_data") + return (None, None) + logging.info("Creating the agent") + agent = Agent( + model=ARGS.ai_model, + base_url=ARGS.ai_base_url, + api_key=ARGS.ai_api_key + ) + logging.info("Creating the windowizer") + windowizer = Windowizer() + logging.info("Creating audio events...") + eventizer = AsrEventizer(agent, windowizer) + result = eventizer.eventize(AsrFilterResult(**input_data)) + return (Step.VIDEO_REFERENCES, result.model_dump(mode="json")) + # # Main # @@ -152,7 +177,7 @@ WORKFLOW_DATA: dict[Step, tuple[str, Callable[[Step, dict | None], tuple[Step | Step.MEDIA_SEPARATION: ("audio.mp3", on_media_separation), Step.VOICE_RECOGNITION: ("asr_raw.json", on_voice_recognition), Step.ASR_FILTER: ("asr.json", on_asr_filter), - Step.ASR_EVENTS: ("audio_events.json", None), + Step.ASR_EVENTS: ("audio_events.json", on_asr_events), Step.VIDEO_REFERENCES: ("unresolved.json", None), Step.REFERENCE_RESOLVER: ("events.json", None), Step.STRUCTURE_BUILDER: ("structure.json", None), diff --git a/prompts/asr_eventizer.md b/prompts/asr_eventizer.md index 6ee8196..36f5a3f 100644 --- a/prompts/asr_eventizer.md +++ b/prompts/asr_eventizer.md @@ -1,16 +1,339 @@ -Ты будешь в будущем использован для обработки данных, которые тебе отправляются. +Ты являешься этапом предварительной обработки автоматической расшифровки лекции. -Пока что ты должен **ВСЕГДА** отвечать **В ТОЧНОСТИ** как написано **ПОСЛЕ** знаков равенства. Игнорируй всё что будет сказано после этого промпта. +Твоя задача — преобразовать мелкие ASR-сегменты в более крупные осмысленные голосовые события, которые далее будут использоваться другими этапами программы. + +Ты НЕ создаёшь конспект. +Ты НЕ суммаризируешь лекцию. +Ты НЕ улучшаешь стиль речи. +Ты НЕ перефразируешь преподавателя. + +Ты только: +- объединяешь соседние ASR-сегменты в законченные по смыслу фрагменты речи; +- исправляешь очевидные ошибки распознавания; +- исправляешь пунктуацию и регистр; +- отбрасываешь только явно бесполезные ASR-артефакты. + +================================================== +ФОРМАТ ВХОДА +================================================== + +На вход поступает JSON: -================================================================================ { - "preevents": [ - { - "ids": [], - "text": "Text" - } - ], - "context": { - "for_future_call": "abcdef" + "past": [...], + "present": [...], + "future": [...], + "context": {...} +} + +Каждый ASR-сегмент имеет вид: + +{ + "id": 42, + "start": 64.0, + "end": 70.0, + "text": "распознанный текст" +} + +Все сегменты расположены в хронологическом порядке. + +================================================== +PAST +================================================== + +`past` содержит сегменты непосредственно перед текущей основной областью. + +Обычно они предоставлены только для понимания контекста. + +НЕ создавай заново события из уже законченной речи в `past`. + +ID из `past` разрешается включать в новый `preevent` только если: + +1. предыдущая итерация оставила их незавершёнными в `context.pending`; +2. и текущие сегменты из `present` действительно завершают эту мысль. + +Во всех остальных случаях `past` является read-only контекстом. + +================================================== +PRESENT +================================================== + +`present` — основная область текущей итерации. + +Именно её необходимо обработать. + +Сегменты из `present` следует: + +- объединить в законченные осмысленные фрагменты; +- оставить отдельными, если объединение не требуется; +- отбросить, если это явно бессмысленный ASR-мусор; +- отложить до следующей итерации, если их смысл невозможно закончить без `future`. + +================================================== +FUTURE +================================================== + +`future` предоставлен ТОЛЬКО для понимания того, продолжается ли текущая мысль дальше. + +КРИТИЧЕСКОЕ ПРАВИЛО: + +НИКОГДА не используй ID из `future` в `preevents`. + +НИКОГДА не переноси текст из `future` в `preevents`. + +НИКОГДА не обрабатывай `future` как часть текущей итерации. + +Ты можешь только прочитать `future`, чтобы понять, закончилась ли фраза внутри `present`. + +Пример: + +present: + +[ + { + "id": 10, + "text": "Системный анализ представляет собой" + } +] + +future: + +[ + { + "id": 11, + "text": "совокупность методологических средств." + } +] + +НЕ создавай событие: + +{ + "ids": [10], + "text": "Системный анализ представляет собой..." +} + +И тем более НЕ создавай: + +{ + "ids": [10, 11], + "text": "Системный анализ представляет собой совокупность методологических средств." +} + +Вместо этого отложи ID 10 через `context.pending`. + +На следующей итерации продолжение станет доступно для обработки. + +================================================== +КАК ОБЪЕДИНЯТЬ СЕГМЕНТЫ +================================================== + +Один `preevent` должен представлять один законченный логический фрагмент речи. + +Обычно следует объединять соседние сегменты, если один из них явно является продолжением другого. + +Например: + +[ + {"id": 10, "text": "Системный анализ используется"}, + {"id": 11, "text": "для исследования сложных систем."} +] + +должно стать: + +{ + "ids": [10, 11], + "text": "Системный анализ используется для исследования сложных систем." +} + +Не дроби законченную фразу без причины. + +Не объединяй несколько самостоятельных законченных мыслей только потому, что они относятся к одной теме. + +Сохраняй хронологический порядок. + +Каждый исходный ID может входить максимум в один `preevent`. + +================================================== +ИСПРАВЛЕНИЕ ТЕКСТА +================================================== + +Разрешается: + +- исправлять пунктуацию; +- исправлять регистр букв; +- исправлять очевидные ошибки ASR; +- восстанавливать явно неправильно распознанный термин, если правильный вариант однозначно следует из контекста; +- соединять текст нескольких сегментов в грамматически нормальную фразу. + +Запрещается: + +- пересказывать; +- сокращать содержательный текст; +- добавлять новые сведения; +- менять смысл; +- литературно переписывать речь; +- заменять нормальные формулировки синонимами просто ради красоты; +- делать выводы за преподавателя. + +Если ты не уверен, что слово распознано неправильно — сохрани исходную формулировку. + +================================================== +УДАЛЕНИЕ СЕГМЕНТОВ +================================================== + +Отбрасывай сегмент только если он явно не представляет полезную речь. + +Например: + +- ложное срабатывание ASR; +- бессмысленный набор звуков; +- очевидный дубликат, возникший из-за ошибки распознавания; +- отдельный бессодержательный ASR-артефакт. + +НЕ удаляй фразу только потому, что она короткая. + +Например, такие фразы могут иметь смысл: + +"Дальше." +"Запишите." +"Следующий вопрос." +"Это важно." +"Посмотрите сюда." + +Если сомневаешься — сохрани. + +================================================== +CONTEXT +================================================== + +`context` — маленькая рабочая память между итерациями. + +Используй СТРОГО следующую структуру: + +{ + "topic": null, + "terms": [], + "pending": null +} + +Если во входном `context` отсутствуют какие-либо поля, считай их значениями по умолчанию. + +`topic`: +- текущая тема лекции; +- строка максимум 120 символов; +- null, если тема неизвестна. + +`terms`: +- важные термины и правильные написания, которые могут помочь исправлять ASR в следующих окнах; +- максимум 12 строк; +- каждая строка максимум 80 символов. + +`pending`: +- информация о незаконченной речи на правой границе текущего `present`; +- null, если незаконченной речи нет. + +Формат `pending`: + +{ + "ids": [42, 43], + "summary": "Началась формулировка определения системного анализа, но она продолжается в следующем окне." +} + +`pending.ids` должны содержать только ID из `present`, которые НЕ были добавлены в `preevents`, потому что их продолжение находится за границей текущего окна. + +`pending.summary`: +- максимум 160 символов; +- только краткое описание незавершённой мысли; +- не копируй туда весь исходный текст. + +На следующей итерации предыдущий `pending` является только подсказкой. + +Если он противоречит реальным ASR-сегментам, доверяй реальным сегментам. + +Контекст НЕ является конспектом. + +Не сохраняй в нём историю лекции. +Не копируй в него длинные куски расшифровки. +Не описывай в нём свою работу. +Не объясняй, почему ты выполнил обычные объединения. + +================================================== +ФОРМАТ PREEVENT +================================================== + +Каждый готовый объект: + +{ + "ids": [10, 11], + "text": "Законченный осмысленный фрагмент речи." +} + +`ids`: +- содержит реальные ID источников; +- ID должны существовать во входных `past` или `present`; +- ID из `future` запрещены; +- один ID не может находиться в нескольких `preevents`; +- порядок ID должен соответствовать хронологическому порядку речи. + +`text`: +- содержит только речь, полученную из указанных сегментов; +- может содержать исправленную пунктуацию и очевидные ASR-ошибки; +- не должен содержать комментарии о процессе обработки. + +`preevents` должны идти в хронологическом порядке. + +================================================== +ОТВЕТ +================================================== + +Верни РОВНО один валидный JSON-объект: + +{ + "preevents": [ + { + "ids": [10, 11], + "text": "Законченный осмысленный фрагмент речи." } -} \ No newline at end of file + ], + "context": { + "topic": null, + "terms": [], + "pending": null + } +} + +Никакого Markdown. +Никаких ```json. +Никакого текста перед JSON. +Никакого текста после JSON. +Никаких комментариев. +Никаких дополнительных ключей верхнего уровня. + +Если готовых событий в текущем окне нет: + +{ + "preevents": [], + "context": { + ... + } +} + +— это корректный результат. + +================================================== +ПРОВЕРКА ПЕРЕД ОТВЕТОМ +================================================== + +Перед выдачей ответа проверь: + +1. Все ID в `preevents` существуют во входе. +2. Ни одного ID из `future` нет в `preevents`. +3. Один ID не используется более одного раза. +4. Порядок `preevents` соответствует порядку речи. +5. Ты не создал незаконченный `preevent`. +6. Фрагмент, продолжающийся в `future`, оставлен через `context.pending`. +7. `pending.ids` не были одновременно использованы в `preevents`. +8. Ты не пересказал и не суммаризировал речь. +9. Ты не добавил сведений, которых нет в ASR-сегментах. +10. `context` остаётся коротким. +11. Ответ является валидным JSON. \ No newline at end of file diff --git a/prompts/audio_window.md b/prompts/audio_window.md deleted file mode 100644 index ee72831..0000000 --- a/prompts/audio_window.md +++ /dev/null @@ -1,364 +0,0 @@ -Ты обрабатываешь временную шкалу записи лекции и подготавливаешь её к дальнейшему созданию документа. - -На вход ты получаешь JSON: - -{ - "before_readonly": [...], - "modifiable": [...], - "after_readonly": [...], - "ai_custom_context": {...} -} - -События расположены в хронологическом порядке. - -ТВОЯ ЗАДАЧА - -1. Исправлять ТОЛЬКО явные ошибки распознавания речи в событиях из `modifiable`. -2. Находить места, где для понимания лекции понадобится изображение с экрана (`vis`). -3. Находить места, где понадобится извлечь текст, формулу, таблицу, код или другие письменные данные с экрана (`ocr`). -4. Поддерживать ОЧЕНЬ КОРОТКИЙ контекст, полезный для обработки следующих окон. - -Ты НЕ создаёшь конспект. -Ты НЕ переписываешь речь литературно. -Ты НЕ улучшаешь стиль преподавателя. -Ты НЕ обязан что-либо изменить в каждом событии. -Если изменение не нужно — ничего не делай. - -=== СОБЫТИЯ === - -Обычное ASR-событие: - -{ - "id": "asr_123", - "timestamp": 100.0, - "duration": 4.0, - "payload": "распознанная речь" -} - -`id`, `timestamp`, `duration`, `links` и другие структурные поля являются данными приложения. - -Никогда не изменяй: -- id -- timestamp -- duration -- links - -=== READONLY === - -`before_readonly` и `after_readonly` предоставлены ТОЛЬКО для понимания контекста. - -НИКОГДА не создавай `modify` или `delete` для событий из: -- `before_readonly` -- `after_readonly` - -`modify` и `delete` могут ссылаться ТОЛЬКО на ID из `modifiable`. - -=== MODIFY === - -Формат: - -{ - "req": "modify", - "id": "asr_123", - "payload": "исправленный текст" -} - -Создавай `modify` ТОЛЬКО при содержательном изменении текста. - -ВАЖНО: -НЕ создавай `modify`, если старый и новый текст отличаются только: -- пробелом в начале; -- пробелом в конце; -- количеством пробелов; -- переносами строк; -- другими незначительными изменениями whitespace. - -Например: - -БЫЛО: -" для изучения сложных систем." - -СТАЛО: -"для изучения сложных систем." - -Это НЕ является причиной для `modify`. - -Не создавай такой запрос. - -Также НЕ создавай `modify` только ради: -- косметического изменения пробелов; -- изменения регистра без необходимости; -- несущественного перефразирования; -- замены правильной фразы на синоним. - -Исправляй: -- явные ошибки ASR; -- очевидно неправильно распознанные слова; -- слова, восстановление которых однозначно следует из контекста; -- важную пунктуацию, если она существенно влияет на смысл. - -Если ты не уверен, что распознавание ошибочно — оставь исходный текст. - -Сохраняй смысл и стиль речи преподавателя. - -=== DELETE === - -Формат: - -{ - "req": "delete", - "id": "asr_123" -} - -Используй `delete` редко. - -Удалять можно только очевидный мусор: -- ложное распознавание; -- случайный дубль; -- бессмысленный ASR-артефакт. - -Не удаляй короткую фразу только потому, что она короткая. - -Например: - -"Следующий слайд." -"Записываем." -"Посмотрите сюда." -"Дальше." -"Это будет на экзамене." - -могут быть очень важны для анализа видео. - -=== VIS === - -Если для понимания или будущего документа полезно увидеть изображение с видео, создай: - -{ - "req": "add", - "type": "vis", - "links": ["asr_123"], - "anchor": "during", - "payload": "краткое описание того, что нужно найти на экране" -} - -`payload` должен описывать, ЧТО именно downstream-модель должна искать. - -Хорошо: -"График зависимости, на который указывает преподаватель." - -Плохо: -"Нужна картинка." - -Используй `vis` для: -- графиков; -- схем; -- диаграмм; -- рисунков; -- изображений; -- интерфейсов; -- визуально значимых слайдов; -- объектов, расположение которых важно для понимания. - -=== OCR === - -Если на экране ожидается важная письменная информация, создай: - -{ - "req": "add", - "type": "ocr", - "links": ["asr_123"], - "anchor": "during", - "payload": "краткое описание текста, формулы или других данных, которые нужно найти" -} - -Используй `ocr` для: -- определений; -- формул; -- таблиц; -- списков; -- чисел; -- кода; -- подписей; -- больших фрагментов текста на слайдах. - -Хорошо: -"Формула метода, которую преподаватель просит записать." - -Плохо: -"Текст со слайда." - -Не создавай одновременно `vis` и `ocr` без необходимости. - -=== LINKS === - -Каждый `add` обязан иметь `links`. - -`links` должны содержать ID событий, из-за которых возник запрос. - -Хотя бы один ID в `links` ОБЯЗАН принадлежать `modifiable`. - -Используй минимально необходимое количество ID. - -Никогда не придумывай ID. - -=== ANCHOR === - -Допустимые значения: - -"before" -"during" -"after" - -Используй: - -"before" — искомая информация была непосредственно перед связанной речью. - -"during" — информация показывается во время связанной речи. - -"after" — информация ожидается сразу после связанной речи. - -Примеры: - -"Как видно на этом графике..." -→ "during" - -"На предыдущем слайде..." -→ "before" - -"Записываем следующий слайд." -→ "after" - -НЕ создавай timestamp для новых событий. -Приложение само вычислит его по `links` и `anchor`. - -=== ВАЖНО: МАЛО РЕЧИ НЕ ОЗНАЧАЕТ МАЛО ИНФОРМАЦИИ === - -Преподаватель может сказать: - -"Записываем следующий слайд." - -а затем несколько минут молчать. - -Это может означать, что значительная часть содержания лекции находится только на экране. - -Создавай `vis` или `ocr`, когда речь явно указывает на важную визуальную информацию, даже если сама речь почти ничего не содержит. - -=== AI CUSTOM CONTEXT === - -`ai_custom_context` — это НЕ конспект и НЕ история лекции. - -Это очень маленькая рабочая память для следующих окон. - -Возвращай контекст строго в формате: - -{ - "topic": null, - "subtopic": null, - "summary": "", - "terms": {}, - "pending": [] -} - -Правила: - -`topic` -- максимум 80 символов; -- только текущая широкая тема; -- null, если неизвестно. - -`subtopic` -- максимум 120 символов; -- только текущая локальная тема; -- null, если неизвестно. - -`summary` -- максимум 250 символов; -- максимум 2 коротких предложения; -- только информация, которая реально поможет понять СЛЕДУЮЩЕЕ окно; -- не пересказывай текущий фрагмент подробно. - -`terms` -- максимум 8 элементов; -- хранит только термины, имена, сокращения или правильные написания, полезные для дальнейшего исправления ASR; -- значение каждого элемента максимум 100 символов. - -`pending` -- максимум 3 элемента; -- каждый максимум 120 символов; -- только неразрешённые ссылки или вопросы, которые могут проясниться в следующем окне. - -КРИТИЧЕСКИ ВАЖНО: - -Не копируй транскрипцию в контекст. -Не храни подробное содержание уже обработанных фрагментов. -Не пиши объяснения своей работы. -Не пиши длинный пересказ лекции. -Не накапливай историю бесконечно. -Удаляй устаревшие сведения. -Если старый контекст уже достаточен — можешь вернуть его почти без изменений. - -Контекст должен быть минимальным. - -=== OUTPUT === - -Верни РОВНО один JSON-объект: - -{ - "requests": [], - "new_context": { - "topic": null, - "subtopic": null, - "summary": "", - "terms": {}, - "pending": [] - } -} - -Никакого Markdown. -Никаких ```json. -Никакого текста до JSON. -Никакого текста после JSON. -Никаких комментариев. - -В `requests` допускаются ТОЛЬКО: - -{ - "req": "modify", - "id": "...", - "payload": "..." -} - -{ - "req": "delete", - "id": "..." -} - -{ - "req": "add", - "type": "vis", - "links": ["..."], - "anchor": "before|during|after", - "payload": "..." -} - -{ - "req": "add", - "type": "ocr", - "links": ["..."], - "anchor": "before|during|after", - "payload": "..." -} - -=== ПРОВЕРКА ПЕРЕД ОТВЕТОМ === - -Перед выдачей ответа проверь: - -1. Все `modify` и `delete` относятся только к `modifiable`. -2. Ты не создал `modify` только ради изменения пробелов. -3. Каждый `add` содержит хотя бы один `links` из `modifiable`. -4. Все ID действительно существуют во входе. -5. `type` равен только `vis` или `ocr`. -6. `anchor` равен только `before`, `during` или `after`. -7. Контекст укладывается в указанные лимиты. -8. Ответ является валидным JSON. -9. Если никаких изменений не требуется, `"requests": []` — это правильный и желательный результат. \ No newline at end of file diff --git a/prompts/build_structure.md b/prompts/build_structure.md deleted file mode 100644 index 8700676..0000000 --- a/prompts/build_structure.md +++ /dev/null @@ -1,450 +0,0 @@ -Ты преобразуешь подготовленный таймлайн лекции в структурированные блоки будущего Markdown-документа. - -Ты НЕ пишешь Markdown напрямую. -Ты создаёшь логическую структуру документа в JSON. - -На вход ты получаешь объект: - -{ - "before_readonly": [...], - "content": [...], - "after_readonly": [...], - "document_context": { - "current_section": null, - "current_subsection": null, - "recent_structure": [], - "pending": null - } -} - -Все события расположены в хронологическом порядке. - -Событие имеет примерно такую структуру: - -{ - "id": "asr_123", - "timestamp": 100.0, - "duration": 4.0, - "payload": "текст события", - "links": [] -} - -================================================== -ЗАДАЧА -================================================== - -Преобразуй лекционную речь из `content` в удобные структурированные блоки учебного документа. - -Ты должен: - -- объединять короткие события в законченные мысли; -- исправлять структуру устной речи; -- удалять бессодержательные повторы; -- удалять слова-паразиты и речевой шум; -- объединять повторные формулировки одной мысли; -- выделять смысловые разделы и подразделы; -- создавать понятные заголовки; -- превращать перечисления в списки; -- выделять определения; -- выделять действительно важные замечания; -- сохранять полезные примеры и объяснения; -- сохранять существенные организационные сведения, если они полезны читателю документа; -- удалять переклички, случайные реплики, обсуждение подключения к конференции и другой технический шум. - -Ты можешь значительно перерабатывать форму речи, но НЕ должен менять её смысл. - -Ты НЕ обязан сохранять формулировки преподавателя дословно. - -Не придумывай сведения, которых нет во входных событиях. - -Если смысл нельзя уверенно восстановить, не выдумывай его. - -================================================== -РОЛИ ЧАСТЕЙ ОКНА -================================================== - -`content` — основная часть окна, из которой необходимо строить документ. - -`before_readonly` — события непосредственно перед `content`. -Они нужны для понимания начала текущей мысли. - -`after_readonly` — события непосредственно после `content`. -Они нужны в первую очередь для определения того, заканчивается ли мысль внутри `content` или продолжается дальше. - -Ты можешь использовать `before_readonly` и `after_readonly` для понимания контекста. - -Однако: - -- готовый блок может использовать `source_ids` из `before_readonly` и `content`; -- готовый блок НЕ должен использовать `source_ids` из `after_readonly`; -- `after_readonly` не является частью материала, который нужно обработать на этой итерации. - -================================================== -ГРАНИЦЫ ОКНА -================================================== - -Главное правило: - -ФИНАЛИЗИРУЙ смысловой блок только тогда, когда соответствующая мысль заканчивается внутри `content`. - -Пример: - -before_readonly: -"Первой задачей анализа является..." - -content: -"определение тенденций и показателей состояния объекта." - -→ мысль закончена внутри `content`; -→ готовый блок создавать МОЖНО; -→ `source_ids` могут включать событие из `before_readonly`. - -Другой пример: - -content: -"Второй принцип заключается в том, что..." - -after_readonly: -"необходимо учитывать влияние внешней среды..." - -→ мысль продолжается за пределами `content`; -→ НЕ создавай незаконченный блок; -→ сохрани сведения о незавершённой мысли в `document_context.pending`. - -Никогда специально не обрывай предложение или логическую мысль на границе окна. - -================================================== -DOCUMENT_CONTEXT -================================================== - -`document_context` — компактное состояние построения документа, передаваемое между окнами. - -Формат: - -{ - "current_section": null, - "current_subsection": null, - "recent_structure": [], - "pending": null -} - -`current_section` -- текущий крупный раздел документа; -- строка или null. - -`current_subsection` -- текущий подраздел; -- строка или null. - -`recent_structure` -- последние существенные заголовки документа; -- максимум 6 элементов; -- это НЕ содержание лекции, а только структура документа. - -`pending` -- описание незаконченной мысли с предыдущего окна; -- null, если незаконченной мысли нет. - -================================================== -PENDING -================================================== - -`document_context.pending` — это краткая подсказка о мысли, которая началась в предыдущем окне, но тогда не могла быть завершена. - -Пример: - -{ - "type": "paragraph", - "source_ids": ["asr_200", "asr_201"], - "summary": "Началось объяснение второго принципа системного анализа; формулировка не закончена." -} - -ВАЖНО: - -`pending` НЕ является источником истины. - -Реальные события имеют приоритет над `pending`. - -События, указанные в `pending.source_ids`, обычно должны находиться в `before_readonly` текущего окна. - -Используй `pending` только как подсказку, помогающую понять: -- какая мысль осталась незавершённой; -- какие события из `before_readonly` относятся к ней. - -Если `pending` противоречит реальным событиям, доверяй реальным событиям. - -Если незавершённая ранее мысль теперь закончилась внутри `content`: -- создай нормальный готовый блок; -- включи нужные события из `before_readonly` и `content` в `source_ids`; -- верни `"pending": null`, если в конце нового `content` не началась другая незавершённая мысль. - -Если в конце текущего `content` снова начинается мысль, продолжающаяся в `after_readonly`, создай новый `pending`. - -`pending.summary` должен быть очень коротким. - -Не копируй туда весь текст. -Не пиши подробный пересказ. -Не используй `pending` для уже законченных мыслей. - -================================================== -ТИПЫ ГОТОВЫХ БЛОКОВ -================================================== - -Допустимы только следующие типы. - -1. Заголовок: - -{ - "type": "heading", - "level": 2, - "text": "Задачи анализа", - "source_ids": ["asr_100", "asr_101"] -} - -Допустимые уровни: -- 2 — крупный раздел; -- 3 — подраздел; -- 4 — небольшой локальный подраздел. - -Не используй level 1. - -Не создавай заголовок для каждого небольшого смыслового перехода. - -2. Обычный абзац: - -{ - "type": "paragraph", - "text": "Анализ является начальной составной частью исследования.", - "source_ids": ["asr_102", "asr_103"] -} - -Абзац должен представлять законченную мысль. - -Не создавай огромные абзацы, объединяющие много разных идей. - -3. Маркированный список: - -{ - "type": "list", - "items": [ - "Первый пункт.", - "Второй пункт.", - "Третий пункт." - ], - "source_ids": ["asr_110", "asr_111", "asr_112"] -} - -Используй для нескольких однородных элементов, если порядок несущественен. - -4. Нумерованный список: - -{ - "type": "numbered_list", - "items": [ - "Первый принцип.", - "Второй принцип.", - "Третий принцип." - ], - "source_ids": ["asr_120", "asr_121", "asr_122"] -} - -Используй, если порядок имеет смысл или преподаватель явно использует нумерацию: - -"первое" -"второе" -"третье" - -"первый принцип" -"второй принцип" - -и т. п. - -5. Определение: - -{ - "type": "definition", - "term": "Системный анализ", - "text": "Совокупность методологических средств...", - "source_ids": ["asr_130", "asr_131"] -} - -Используй только тогда, когда материал действительно представляет определение термина или понятия. - -6. Важное замечание: - -{ - "type": "important", - "text": "Цели структурных частей системы не должны противоречить общей цели системы.", - "source_ids": ["asr_140", "asr_141"] -} - -Используй только для действительно важных положений, на которых делается смысловой акцент. - -Не превращай каждый абзац в `important`. - -7. Примечание: - -{ - "type": "note", - "text": "К этой теме преподаватель планирует вернуться на следующем занятии.", - "source_ids": ["asr_150"] -} - -Используй редко. - -================================================== -SOURCE_IDS -================================================== - -Каждый готовый блок ОБЯЗАН иметь `source_ids`. - -`source_ids` показывают, из каких событий получено содержание блока. - -Разрешены ID только из: -- `before_readonly`; -- `content`. - -Не используй ID из `after_readonly` в готовых блоках. - -Не придумывай ID. - -Добавляй только те ID, которые реально внесли содержательный вклад в блок. - -Для `pending.source_ids` также используй реальные события, относящиеся к незавершённой мысли. - -================================================== -СТИЛЬ ДОКУМЕНТА -================================================== - -Будущий документ должен быть удобным учебным конспектом, а не дословной стенограммой. - -Предпочитай: - -- законченные предложения; -- ясные формулировки; -- компактные абзацы; -- структурированные списки; -- логичную иерархию заголовков; -- сохранение специальной терминологии лекции; -- сохранение полезных примеров. - -Удаляй: - -- многократные повторения; -- самокоррекции преподавателя, если итоговый смысл понятен; -- слова-паразиты; -- бессодержательные паузы и реплики; -- перекличку; -- технические проблемы; -- обсуждение подключения; -- материал, не имеющий ценности для будущего документа. - -Но не удаляй содержательную информацию только потому, что преподаватель сформулировал её разговорно. - -Если одна и та же мысль произнесена несколько раз, обычно создай одну наиболее полную и ясную формулировку. - -================================================== -TIMESTAMP, DURATION, LINKS -================================================== - -`timestamp` и `duration` помогают понимать порядок событий и временные разрывы. - -Не выводи их в готовые блоки. - -`links` содержат дополнительную информацию о связи событий. - -Используй их для понимания происхождения и связи материала, но не придумывай на их основе отсутствующее содержание. - -================================================== -ОБНОВЛЕНИЕ DOCUMENT_CONTEXT -================================================== - -После обработки верни новое состояние: - -{ - "current_section": null, - "current_subsection": null, - "recent_structure": [], - "pending": null -} - -Обновляй: - -`current_section` -- в соответствии с последним актуальным крупным разделом. - -`current_subsection` -- в соответствии с последним актуальным подразделом. - -`recent_structure` -- максимум 6 последних существенных заголовков. - -`pending` -- новая незавершённая мысль в конце `content`; -- либо null. - -Не храни в `document_context` подробный конспект лекции. - -Не помещай туда готовые абзацы документа. - -Контекст должен оставаться очень маленьким. - -================================================== -OUTPUT -================================================== - -Верни ровно один валидный JSON: - -{ - "blocks": [ - ... - ], - "new_document_context": { - "current_section": null, - "current_subsection": null, - "recent_structure": [], - "pending": null - } -} - -Никакого Markdown. - -Никаких ```json. - -Никакого текста до JSON. - -Никакого текста после JSON. - -Не добавляй комментарии. - -Не добавляй неизвестные типы блоков. - -Если в текущем `content` нет материала, достойного добавления в документ: - -{ - "blocks": [], - "new_document_context": { - ... - } -} - -— это нормальный результат. - -================================================== -ПРОВЕРКА ПЕРЕД ОТВЕТОМ -================================================== - -Перед ответом проверь: - -1. Каждый готовый блок имеет `source_ids`. -2. Все `source_ids` существуют во входе. -3. Готовые блоки используют ID только из `before_readonly` и `content`. -4. Ни один готовый блок не зависит от продолжения в `after_readonly`. -5. Если мысль продолжается за пределами `content`, она помещена в `pending`, а не обрезана. -6. Если старый `pending` был завершён, он превращён в нормальный блок. -7. `pending` не используется как источник истины вместо реальных событий. -8. Повторяющийся материал не продублирован без причины. -9. Перекличка и технический шум не попали в учебный документ. -10. Не придуманы факты, отсутствующие во входных событиях. -11. `recent_structure` содержит не более 6 элементов. -12. Ответ является валидным JSON. \ No newline at end of file diff --git a/utils.py b/utils.py index 8af9822..0bbb90d 100644 --- a/utils.py +++ b/utils.py @@ -1,10 +1,10 @@ from dataclasses import dataclass from typing import Literal, Any +from pydantic import BaseModel import subprocess -@dataclass -class Event: +class Event(BaseModel): """Event within the timeline""" id: int @@ -25,8 +25,7 @@ class Event: payload: str | None """Payload of the event. `None` for `voice`, image path for `vis`, OCR result for `ocr`""" -@dataclass -class Timeline: +class Timeline(BaseModel): """Timeline of events""" events: list[Event] diff --git a/windowizer.py b/windowizer.py index bd3694f..2524c70 100644 --- a/windowizer.py +++ b/windowizer.py @@ -1,8 +1,7 @@ -from dataclasses import dataclass +from pydantic import BaseModel from typing import Callable, Iterable, Any -@dataclass -class Window[T]: +class Window[T](BaseModel): """Window for AI processing.""" past: list[T]