WIP
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@ __pycache__/
|
||||
.venv/
|
||||
runtime/
|
||||
|
||||
debug/
|
||||
output.md
|
||||
*.json
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
31
main.py
31
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),
|
||||
|
||||
@@ -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": "Законченный осмысленный фрагмент речи."
|
||||
}
|
||||
}
|
||||
],
|
||||
"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.
|
||||
@@ -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": []` — это правильный и желательный результат.
|
||||
@@ -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.
|
||||
7
utils.py
7
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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user