Files
content-atom/README.md
T

20 KiB
Raw Blame History

Content-atom: Implementation Plan

For Hermes: roadmap-план по этапам (MVP → V2 → V3). Реализация — по этапам, не все сразу.

Goal: Атомарный контент-конвейер — атом смысла как источник правды, каналы переупаковывают атомы вместо переписывания статей.

Architecture: Все стадии работают с atom_id вместо articleIds. extract разбирает статью на типизированные атомы, все 6 слоёв editor-pro привязываются к атому, atom_versions переупаковывают атом под каналы, compositions собирают выходы, panel показывает дашборд смысла.

Tech Stack: Python (editor-pro, neurogeo), TypeScript (content-pipeline, panel), Qdrant (эмбеддинги), Memgraph/AGE (граф, опционально), FastAPI (панель), Qwen 35B (локальный).


Контекст и предположения

Что уже есть

  • editor-pro — 6-слойный конвейер (typo → LLM → enrich → GEO → interlink → astro-seo), работает со статьёй. Стек: Python, spaCy, httpx, neo4j (Memgraph), PennyLane (QAOA). Модель: Qwen 35B.
  • NeuroGEO — GEO-скоринг целой статьи, FastAPI на :8009.
  • content-pipeline — TS-оркестратор (generate → optimize → publish) + CMS-слой для управления публикациями. Работает с объектом Article.
  • Qdrant — эмбеддинги уже используются (qdrant_save.py).
  • Memgraph — для интрелинки.

Что меняем

  • Article → atom. Новая стадия extract в начале конвейера.
  • Все стадии привязываются к atom_id вместо articleIds.
  • atom_versions — переупаковка одного атома под каналы.
  • compositions — сборка выходов (статья/TG/видео).
  • panel — дашборд смысла вместо списка статей.

Что НЕ тащим сразу

  • Postgres + pgvector — Qdrant для эмбеддингов, атомы в том, что уже стоит.
  • Graph (Memgraph/AGE) — только V4, когда атомы отработают.
  • Адаптеры TG/видео — V3, когда web-канал стабилен.

Дорожная карта

Этап 1 — MVP: атомы + переупаковка (web-канал)

Цель: extract разбирает статью на атомы, atom_versions переупаковывают под web.

  • atoms — типизированные атомы (claim/fact/stat/qa/comparison/hook/cta и др.), body по типам (JSONB).
  • atom_versions — версии под каналы (web_article | telegram | youtube_script | podcast | newsletter | service_card).
  • extract — стадия: статья → типизированные атомы.
  • jobs/runs — задачи пайплайна + стоимость/качество на каждый запуск.
  • Qdrant — эмбеддинги атомов для дедупликации.

KPI: 4 домена (orabota, ipoteka-ufa, ufa-kvartira, rabota-ufa), один канал (web).

Этап 2 — связи + композиции (второй канал: Telegram)

Цель: дедупликация между доменами, сборка выходов.

  • atom_relations — связи (supports/contradicts/expands/example_of/part_of/derived_from).
  • compositions — рецепты сборки (типы, темы, порядок), atom_ids.
  • prompts — промпты как данные (версионирование, A/B).
  • Дедупликация — embedding + atom_relations вместо дублирования статей.

KPI: 2 канала (web + Telegram), дедup между доменами.

Этап 3 — медиа + метрики (видео, подкаст, рассылка)

Цель: сквозная аналитика от смысла до метрики.

  • assets — медиа-активы (image | audio | video | subtitle), транскрипты.
  • atom_assets — привязка атомов к медиа.
  • publications — публикации (URL/channel_id/external_id) + метрики (views/clicks/conversions/revenue).
  • Адаптеры — видео-сценарий, подкаст, рассылка.

KPI: 4+ канала, сквозная аналитика.

Этап 4 — граф (Memgraph / Apache AGE)

Цель: обходы и анализ связей.

  • Проекция: Atom → Topic → Entity → Project → Asset.
  • graph_outbox — Postgres → воркер → Memgraph (только id + минимальные атрибуты, тело атома НЕ хранится).
  • Идемпотентные операции (MERGE по id), ребилд из Postgres за час.
  • Сборка через обходы графа (топологическая сортировка).

KPI: граф-проекция, сборка через обходы.

Этап 5 — панель (дашборд смысла)

Цель: управление атомами вместо статей.

  • Дашборд: атомы по доменам, статусы (draft → enriched → edited → approved → published), версии под каналы.
  • Стоимость на атом + runs — юнит-экономика по домену.
  • Дедупликация по embedding.
  • Composition builder — сборка выходов.

KPI: панель управляет атомами, юнит-экономика по домену.


Схемы данных

Атомы

CREATE TABLE atoms (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  project_id  UUID REFERENCES projects(id) ON DELETE CASCADE,
  type        TEXT NOT NULL,          -- claim | fact | stat | quote | definition | instruction | case | comparison | qa | opinion | hook | transition | cta | media_ref
  status      TEXT NOT NULL DEFAULT 'draft',  -- draft | enriched | edited | approved | published | archived
  title       TEXT,
  summary     TEXT,
  body        JSONB NOT NULL,         -- типизированное тело по типу атома
  plain_text  TEXT,
  language    TEXT DEFAULT 'ru',
  entities    JSONB DEFAULT '[]',
  topics      TEXT[] DEFAULT '{}',
  intent      TEXT,
  audience    TEXT,
  confidence  NUMERIC(3,2),
  quality_score NUMERIC(4,2),
  readability NUMERIC(5,2),
  word_count  INT,
  source_refs JSONB DEFAULT '[]',
  embedding   VECTOR(1024),
  generator   JSONB,
  cost        JSONB,
  pipeline_stage TEXT,
  parent_atom_id UUID,
  lineage_root_id UUID,
  created_at  TIMESTAMPTZ DEFAULT now(),
  updated_at  TIMESTAMPTZ DEFAULT now()
);

atom_versions

CREATE TABLE atom_versions (
  id        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  atom_id   UUID REFERENCES atoms(id) ON DELETE CASCADE,
  channel   TEXT NOT NULL,          -- web_article | telegram | youtube_script | podcast | newsletter | service_card
  format    TEXT NOT NULL,          -- markdown | html | plain | srt | ssml | json
  content   TEXT NOT NULL,
  meta      JSONB DEFAULT '{}',
  quality_score NUMERIC(4,2),
  created_at TIMESTAMPTZ DEFAULT now(),
  UNIQUE (atom_id, channel, format)
);

atom_relations

CREATE TABLE atom_relations (
  from_id   UUID REFERENCES atoms(id) ON DELETE CASCADE,
  to_id     UUID REFERENCES atoms(id) ON DELETE CASCADE,
  kind      TEXT NOT NULL,          -- supports | contradicts | expands | example_of | part_of | refutes | precedes | derived_from
  weight    NUMERIC(3,2) DEFAULT 1.0,
  meta      JSONB DEFAULT '{}',
  PRIMARY KEY (from_id, to_id, kind)
);

compositions

CREATE TABLE compositions (
  id        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
  channel   TEXT NOT NULL,
  slug      TEXT,
  title     TEXT,
  status    TEXT DEFAULT 'draft',   -- draft | building | ready | scheduled | published
  recipe    JSONB NOT NULL,         -- правила сборки: типы, темы, порядок
  atom_ids  UUID[] NOT NULL,        -- порядок атомов в композиции
  content   TEXT,
  meta      JSONB DEFAULT '{}',
  scheduled_at TIMESTAMPTZ,
  published_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT now(),
  UNIQUE (project_id, channel, slug)
);

publications

CREATE TABLE publications (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  composition_id UUID REFERENCES compositions(id) ON DELETE CASCADE,
  target      TEXT NOT NULL,        -- URL, channel_id, video_id
  external_id TEXT,
  published_at TIMESTAMPTZ DEFAULT now(),
  metrics     JSONB DEFAULT '{}'    -- views, clicks, conversions, revenue
);

jobs + runs

CREATE TABLE jobs (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  kind         TEXT NOT NULL,       -- generate | edit | adapt | publish | index
  atom_id      UUID REFERENCES atoms(id),
  composition_id UUID REFERENCES compositions(id),
  status       TEXT DEFAULT 'queued',-- queued | running | done | failed | dead
  attempts     INT DEFAULT 0,
  max_attempts INT DEFAULT 3,
  payload      JSONB DEFAULT '{}',
  error        TEXT,
  scheduled_at TIMESTAMPTZ DEFAULT now(),
  started_at   TIMESTAMPTZ,
  finished_at  TIMESTAMPTZ,
  created_at   TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE runs (
  id          BIGSERIAL PRIMARY KEY,
  job_id      UUID REFERENCES jobs(id) ON DELETE CASCADE,
  stage       TEXT NOT NULL,
  model       TEXT,
  prompt_id   UUID REFERENCES prompts(id),
  tokens_in   INT,
  tokens_out  INT,
  seconds     NUMERIC,
  cost_usd    NUMERIC DEFAULT 0,
  quality     NUMERIC(4,2),
  created_at  TIMESTAMPTZ DEFAULT now()
);

Интеграция с текущими сервисами

editor-pro (все 6 слоёв)

  • Привязать каждый слой к atom_id вместо обработки статьи целиком.
  • extract — новая стадия: python -m editor_pro.main --extract → атомы.
  • Все слои работают с кластером атомов, не с одной статьёй.

neurogeo

  • GEO-скоринг на кластере атомов, не на статье.
  • Цитируемость на атоме (fact/stat/claim).

content-pipeline (TS)

  • Новый шаг extract в DEFAULT_PIPELINE.
  • atomId вместо articleIds.
  • atom_versions — переупаковка под каналы.

Qdrant

  • Эмбеддинги атомов (уже используется).
  • Дедупликация по embedding + atom_relations.

Memgraph (V4)

  • Проекция: Atom → Topic → Entity → Project.
  • graph_outbox: Postgres → воркер → Memgraph.

Панель (дашборд смысла)

Стек: FastAPI + SQLite/Postgres + React/Vue (или Astro для единого со стэком).

Разделы:

  • Атомы по доменам (orabota, ipoteka-ufa, ufa-kvartira, rabota-ufa).
  • Статусы атомов (draft → enriched → edited → approved → published).
  • Версии под каналы (web/TG/видео).
  • Стоимость на атом + runs — юнит-экономика по домену.
  • Дедупликация по embedding.
  • Composition builder — сборка выходов.

KPI: панель управляет атомами, юнит-экономика по домену.


Правила

  • Каждая стадия не зависит от предыдущих напрямую — только через атом.
  • Промпты тянутся из prompts по slug + version.
  • Все запуски пишутся в runs — воспроизводимость и аналитика.
  • Идемпотентность через (atom_id, stage, prompt_id, input_hash).
  • Ошибки → jobs.error, ретраи с экспоненциальной задержкой.
  • После max_attempts — статус dead, требует ручного вмешательства.

Антипаттерны (из концепции)

  • Хранить готовый HTML в атоме.
  • Перезаписывать атом под канал.
  • Промпты в коде, а не в БД.
  • Один промпт на все проекты.
  • Масштабировать на 50 доменов до юнит-экономики.
  • Граф без ребилда из Postgres.
  • Публикация без compositions (нельзя пересобрать).
  • Метрики только на уровне сайта, не атома/композиции.

Что даёт такая архитектура

Возможность Как достигается
Мультиканальность без переписывания atom_versions + compositions
Дедупликация embedding + atom_relations
Полное покрытие темы граф связей + рецепты сборки
Сквозная аналитика publications.metrics + runs
Воспроизводимость prompts как данные + runs
Масштабирование на N доменов projects + общий движок
Избежание footprint разные шаблоны, ритмы, промпты
Стоимость и юнит-экономика cost на атом + runs
Защита от обновлений обновляешь атом → все производные пересобираются
Продажа частями композиции и проекты изолированы

Решения (зафиксированы 2026-09-14)

  1. Хранение атомов: Postgres (истина) + Qdrant (эмбеддинги). Postgres уже в Docker — один чистый инстанс atom-db с pgvector + pg_trgm. Атомы/версии/композиции/стоимость — в Postgres. Эмбеддинги + дедупликация — в Qdrant.
  2. Панель: отложить до V5.
  3. V4 граф: Apache AGE (расширение Postgres), не Memgraph. GraphOutbox/воркер не нужны.
  4. Эмбеддинги: bge-m3 — единственная модель для MVP и продакшена (установлен в Ollama 2026-09-14, dim=1024, 8192 контекст, мультязычный, гибридный dense+sparse). Не переключаться между nomic/bge-m3 — разный размер вектора (512 vs 1024) и разная система поиска = переиндексация и баги дедупа.
  5. Домены: 50 доменов, разделение по role (flagship/satellite/experiment/service). Технические домены (service) НЕ индексируются в семантику — дедуп только между SEO-доменами. Атомы привязаны к project_id, сборка — в рамках одного домена (защита footprint).
  6. Postgres: чистый инстанс atom-db (не использовать outline-postgres/coolify-db — они в продакшене). pgvector + pg_trgm + btree_gin.

Риски и как их избежать (проактивно, 2026-09-14)

Риск 1: Разный размер вектора → переиндексация Qdrant

Проблема: bge-m3 = dim 1024, nomic = dim 512. Если в MVP использовать nomic, а потом перейти на bge-m3 — коллекция Qdrant пересоздаётся, все эмбеддинги переиндексируются. Решение: сразу bge-m3 (dim=1024). Размер коллекции Qdrant фиксируется на старте. Проверить: curl http://127.0.0.1:6333/collection/<name> → vector_size должен быть 1024.

Риск 2: Postgres-контейнеры уже заняты

Проблема: outline-postgres-1 и postgres-zyo10oksv4l1i8s35zhrxijf — в продакшене (outline, twenty, coolify). Нельзя тащить atom-db в них. Решение: отдельный инстанс atom-db с pgvector+pg_trgm. Проверить свободный порт (сейчас 543x заняты — взять 5434). Тест: CREATE EXTENSION pgvector; на пустом БД.

Риск 3: pgvector — не в базовом образе

Проблема: postgres:16 не включает pgvector по умолчанию. Решение: образ pgvector/pgvector:pg16 (официальный, уже с расширением) или CREATE EXTENSION при старте контейнера. Проверить: SELECT name FROM pg_available_extensions WHERE name='pgvector'; → installed_version не NULL.

Риск 4: bge-m3 — latency под нагрузкой

Проблема: 1024-мерные эмбеддинги + гибридный поиск на 50 доменах → медленная индексация. Решение: эмбеддить батчами (по 10-20 атомов), асинхронно, не на критическом пути. Qdrant держит ivfflat/hnw-индекс. Проверить latency: curl /api/embeddings → ~200-500ms на запрос (нормально для локальной модели).

Риск 5: Дедуп между техническими и SEO-доменами

Проблема: 50 доменов, часть технические (wiki, docs, сервисы) — их текст «дублируется» с SEO-статьями по терминологии. Решение: role='service' исключается из семантической коллекции Qdrant. Дедуп только между flagship/satellite/experiment. Проверить: WHERE role IN (...) при эмбеддинге.

Риск 6: pg_trgm + gin — место на диске

Проблема: GIN-индекс по plain_text (trigram) на 50 доменах → раздувание. Решение: только для SEO-доменов, pg_trgm на plain_text атомов (не на всех полях). Monitor: SELECT pg_size_pretty(pg_relation_size('atoms'));.

Риск 7: bge-m3 в Ollama vs embed-сервер

Проблема: Ollama-эмбеддинги — медленнее, чем специализированный embed-сервер (e.g. Instructor, FastEmbed). Решение: MVP на Ollama bge-m3 (готово). Если latency станет проблемой — вынести embed в отдельный FastAPI-сервис (FastEmbed на CPU, dim=1024, ~10x быстрее). Не делать сразу.

Риск 8: Qdrant-коллекция для атомов

Проблема: Qdrant уже используется (knowledge_base и др.) — риск конфликта命名/конфигурации. Решение: новая коллекция content_atoms (dim=1024, distance=Cosine). Не трогать существующие коллекции. Проверить: curl /collections → content_atoms отсутствует → создать.


Версия 1.1 — roadmap по этапам + фиксация решений + проактивные риски. Детальные задачи (TDD) — на этапе реализации каждого уровня.