Phase 1 — Extraction Core

Digest System · feed-gathering
Рабочий map-reduce pipeline с pointer extraction и 4-слойной валидацией.
Ручной trigger через GraphQL. Оценка качества на реальных каналах.
Нажми на карточку чтобы раскрыть детали
1

1.1 LLM Client

OpenAI SDK → CLIProxy. Tool-use вызовы для map и reduce шагов

Функции:

  • extract_chunk(chunk_xml, system_prompt, known_items)ChunkSummary — map step через tool-use emit_chunk_summary
  • synthesize_digest(chunk_summaries, top_messages, system_prompt)Digest — reduce step через tool-use emit_digest

Реализация:

  • openai.AsyncOpenAI(base_url=DIGEST_LLM_BASE_URL, api_key=DIGEST_LLM_API_KEY)
  • Tool-use через tools параметр (OpenAI формат, CLIProxy транслирует)
  • Prompt caching: extra_headers: {"anthropic-beta": "prompt-caching-2024-07-31"}
  • Retry: на 429/500/timeout, exponential backoff, макс 3 попытки
  • Модель из конфига: DIGEST_MAP_MODEL / DIGEST_REDUCE_MODEL
  • Парсинг tool call → Pydantic модель, возврат типизированного объекта
app/digest/llm.py
2

1.2 Tool Schemas

JSON schemas для emit_chunk_summary и emit_digest

emit_chunk_summary — выход map step:

  • chunk_summary (string, max 1200), signal_quality (enum + reason)
  • topics (max 6), decisions (max 5), action_items (max 8), open_questions (max 6)
  • links (max 15), artifacts (max 10), knowledge_items (max 15)
  • notable_message_ids (max 10)

knowledge_items — ключевые поля:

  • content_pointer: {message_id, char_start, char_end} — для verbatim (промпты, конфиги, код)
  • content: string — для synthesized (рекомендации, troubleshoot)
  • content_pointer и content взаимоисключающие
  • context_version — какая модель/версия, entity_refs — канонические сущности
  • supersedes_id, conflicts_with_id — связи между items
  • Нет полей авторства — автор берётся из message_ids

emit_digest — всё из chunk_summary плюс:

  • title (max 120), overview (max 2400, markdown)
  • highlights (max 5), coverage, search_keywords (max 10)

Синхронизация:

  • Pydantic модели из models.py — source of truth
  • Тест проверяет что JSON schemas и Pydantic модели совпадают
app/digest/schemas.py
3

1.3 Pointer Extraction

Resolve {message_id, char_start, char_end} → substring из исходного сообщения

Принцип:

  • LLM решает что извлечь и как назвать
  • Код детерминированно достаёт substring — побайтовая точность
  • LLM не копирует текст — он указывает на него

Валидация pointer:

  • message_id существует в окне
  • char_start < char_end, оба в пределах len(normalized_text)
  • Извлечённый substring непустой
  • Plausibility: "prompt" pointer извлекающий "lol nice" — подозрительно

Важно:

  • Pointer ссылается на normalized_text (что видит LLM), не на raw_content
  • Это решает проблему LLM corruption: пробелы, кавычки, порядок параметров — всё сохраняется
app/digest/pointers.py
4

1.4 Prompt Templates

Map/reduce system и user промпты по RFC §10

Map system prompt (кэшируемый):

  • Роль и задача (извлечение, не суммаризация)
  • Что считать сигналом / что шумом
  • Domain supplement из profile module
  • Hard rules: citations, null-over-guess, injection defense, version awareness
  • Few-shot примеры (2-3 из gold set)

Map user prompt (динамический):

  • Сервер/канал/окно metadata
  • Language hint (с поддержкой mixed RU/EN)
  • Field rubrics (confidence, status определения)
  • Known items placeholder (для Phase 2 cross-run dedup)
  • XML блок нормализованных сообщений

Reduce промпты:

  • Merge/dedup/conflict rules
  • Positional bias: важное в начало и конец (Lost in the Middle)
  • Chunk outputs JSON + top source messages
app/digest/prompts.py
5

1.5 Validation

4-слойная валидация: schema → Pydantic → semantic → self-refine retry

Layer 1 — Schema:

  • Tool-use обеспечивает JSON schema. Malformed → 1 retry с ошибкой

Layer 2 — Pydantic:

  • Парсинг в typed models. Unknown keys, bad enums, missing fields → reject. Валидация pointer bounds

Layer 3 — Semantic:

  • Citation check: каждый message_id должен существовать в input. + ≥50% n-gram overlap между cited message и claim
  • Pointer validation: bounds корректны, substring непустой, plausible для kind
  • Coverage: top-10 по importance должны быть в output. Flag если >50% отсутствуют
  • Density: много сигнала на входе + маленький выход → flag
  • Language: язык вывода ≈ язык ввода. Mixed RU/EN — tolerance, не retry
  • Signal consistency: signal_quality == "empty" → все массивы пустые
  • Version check: knowledge items без context_version для version-sensitive kinds → flag

Layer 4 — Retry:

  • Self-refine: retry с ошибкой валидации как feedback
  • Второй fail → extraction_failed, partial output с flags
Валидация НЕ ловит: сарказм, collapsed versions, false causality, lost negations. Это задача human review.
app/digest/validation.py
6

1.6 Pipeline Orchestrator

run_digest() — полный flow от загрузки до сохранения

Flow:

  • 1. Загрузка сообщений из MongoDB по окну
  • 2. Preprocess (filter → normalize → feature flags)
  • 3. Thread reconstruction
  • 4. Importance scoring
  • 5. Chunking
  • 6. Map: для каждого чанка → LLM extract → validate
  • 7. Collect chunk summaries
  • 8. Reduce: synthesize digest → validate
  • 9. Resolve pointers → достать verbatim substrings
  • 10. Сохранить в discord_digest
  • 11. Обновить channel.last_digested_id

Error handling:

  • Chunk failure → skip, продолжить с остальными
  • Reduce failure → mark job failed, сохранить partial chunks
  • DigestJob создаётся в начале, статус обновляется по ходу
app/digest/pipeline.py
7

1.7 GraphQL Types

Strawberry types для дайджестов, knowledge items, профилей

Типы:

  • FeedGatheringDigest — полный дайджест со всеми полями
  • FeedGatheringDigestSummary — лёгкий для списка (title, excerpt, signal quality, counts)
  • FeedGatheringKnowledgeItem — с content или resolved pointer content
  • FeedGatheringDecision, ActionItem, OpenQuestion
  • FeedGatheringDigestJob — статус и статистика
  • FeedGatheringTopicProfile, FeedGatheringEntity
  • Filter/input types для queries
app/graphql/types.py
8

1.8 GraphQL Mutation + Queries

Manual trigger + CRUD для дайджестов

Endpoints:

  • triggerFeedGatheringDigest(channelId) → создаёт job, будит runner, возвращает job для polling
  • feedGatheringDiscordDigests(filter) → keyset pagination, фильтры: server/channel/date/signal_quality
  • feedGatheringDiscordDigest(id) → single digest с полной детализацией
  • feedGatheringDigestJobs(channelId) → недавние jobs для канала
app/graphql/digest.py
9

1.9 Gold Set Bootstrap

Разметка 2-3 каналов для baseline метрик P/R/F1

Процесс:

  • Запустить extraction на 2-3 реальных каналах (high/medium/low activity)
  • Экспорт сырых результатов
  • Human-correct: пометить items как correct / incorrect / missing
  • Сохранить как версионированный gold set в docs/gold-set/
  • Посчитать baseline Precision / Recall / F1 per item type

Зачем:

  • Без gold set любые изменения промптов — вслепую
  • Это regression test для будущих prompt/model changes
  • Первый gold set может быть LLM-assisted (прогнать → поправить руками)
docs/gold-set/
10

1.10 Тесты

Integration тесты с mock LLM, validation edge cases, pointer resolution

Тест-файлы:

  • test_digest_pipeline.py — полный flow с mock openai.AsyncOpenAI: preprocess → chunk → mock extract → validate → mock reduce → store
  • test_digest_validation.py — fabricated message IDs, pointer out of bounds, empty citations, signal consistency, language mismatch
  • test_digest_pointers.py — valid bounds, edge cases (0-start, end-of-message), unicode, code blocks
  • test_digest_schemas.py — Pydantic models и JSON tool schemas совпадают
tests/test_digest_pipeline.py tests/test_digest_validation.py tests/test_digest_pointers.py tests/test_digest_schemas.py
LLM интеграция
Схемы и данные
Пайплайн
API
Оценка качества

✅ Exit Criteria

Вызвать triggerFeedGatheringDigest(channelId) через GraphiQL → получить полный дайджест реального канала.
Pointer extraction работает побайтово. Валидация ловит fabricated IDs и невалидные pointers.
Baseline P/R/F1 измерен на gold set.

← Назад к общему плану · GitHub Issue #5