PRD
- TextFoundry PRD: Архитектура Text Engine
- Цель и назначение документа
- Ключевые ограничения (Design Constraints)
- Основные сущности и компоненты
- Расширенная структура Composition: Fragments
- Жизненный цикл сущностей
- Этапы обработки текста
- Поддержка языков и времени (i18n)
- Модель хранения (Storage Model)
- Схема хранения (ObjectBox)
- Публикация и синхронизация
- Интерфейсы: CLI и Terminal UI
- Сервер промптов (Prompt Server)
- UI (Qt/QML)
- Дорожная карта (Roadmap)
- Границы проекта (Non-goals)
- Критерии успешности
- Архитектура реализации (C++ Core)
- Пример использования (Library Usage)
- Критические ограничения реализации
TextFoundry PRD: Архитектура Text Engine
Цель и назначение документа
Этот документ описывает ограничения, архитектурные принципы, жизненные циклы сущностей и дорожную карту Text Engine — движка для конструирования текстов и промптов из переиспользуемых смысловых блоков [1].
Документ служит:
-
фиксацией инженерных решений
-
защитой от расползания требований
-
контрактом между Core, Storage, Server и UI
-
ориентиром для дальнейшей реализации и стабилизации
|
PRD в
Фактический срез текущего состояния проекта вынесен в |
Область применения
Text Engine предназначен для:
-
сборки текстов из логических блоков
-
переиспользования фрагментов между разными текстами
-
параметризации и вариативности формулировок
-
контролируемой нормализации стиля
-
явных AI-assisted workflow для генерации и rewrite
Text Engine не является:
-
LLM-фреймворком общего назначения
-
системой автоматического перевода "по умолчанию"
-
WYSIWYG-редактором
Ключевые ограничения (Design Constraints)
Принцип engine-first
Движок разрабатывается как самостоятельная библиотека [1], которая:
-
не зависит от UI, CLI или сетевого протокола
-
имеет единый публичный API
-
рассматривает любые интерфейсы как клиентов Core
Отсутствие "магии"
Запрещены операции, нарушающие детерминизм без явного запроса [1]:
-
неявное семантическое переписывание текста
-
автоматическое изменение языка или времени
-
попытки угадать намерения пользователя
Все обычные преобразования должны быть детерминированными, воспроизводимыми и тестируемыми [1].
Основные сущности и компоненты
Блок (Block)
Логический переиспользуемый фрагмент текста с характеристиками [1]:
-
уникальный идентификатор
-
тип (
role,system,mission,safety,constraint,style,domain,meta) -
шаблон содержания, параметры, теги
-
язык, версия и состояние жизненного цикла
-
описание и комментарий ревизии
Block не знает [1]:
-
где он используется
-
в каком порядке
-
в каком финальном тексте
Уточнение: Поле Type vs ID
Важно различать поле Type (категория блока) и поле ID (адрес блока):
| Атрибут | Тип данных | Назначение |
|---|---|---|
|
Строка ( |
Глобально уникальный идентификатор. Принято использовать точечную нотацию ( |
|
Перечисление ( |
Таксономическая категория: |
Type — это metadata, не behavior
Поле type — это тег для организации хранилища и UI, а не класс или
интерфейс, определяющий поведение:
-
НЕ влияет на Template Expansion — Renderer обрабатывает шаблоны всех типов одинаково
-
НЕ определяет структуру данных — у всех типов одинаковый базовый набор полей
-
Используется для:
-
фильтрации в CLI/API
-
валидации и доменной классификации
-
группировки и подсказок в UI
-
передачи более точного контекста в AI-assisted workflow
-
Соглашение об именовании ID
Хотя type и id — независимые поля, рекомендуется кодировать категорию в ID
для человекочитаемости:
// Хорошо: ID содержит namespace, совпадающий с type
BlockDraftBuilder("role.analyst")
BlockDraftBuilder("constraint.latency")
BlockDraftBuilder("footer.signature") // допустимо и без префикса type
Важно: Block greeting.basic может иметь type = role, даже если в ID нет
префикса role.. Type — это явное поле структуры, а не парсимая часть
строки ID.
|
Архитектурный принцип: Type — Enum, не Class. Использование enum вместо полиморфного класса гарантирует, что логика рендеринга остаётся единой и детерминированной, независимо от категории блока. |
Композиция (Composition)
Composition состоит из упорядоченного списка Fragment, где каждый фрагмент может быть [1]:
-
BlockRef — ссылка на опубликованный Block с локальными переопределениями параметров. Указание версии обязательно для воспроизводимости [1]
-
StaticText — произвольный текст без параметров, не имеет собственного id и не версионируется отдельно от Composition
-
Separator — типизированный разделитель (
newline,paragraph,hr)
StyleProfile
StyleProfile задаёт правила оформления текста и разделяется на два уровня:
StructuralStyle (структурный)
Детерминированные параметры, применяемые на этапе Template Expansion [1]:
-
block_wrapper— шаблон обёртки для каждого блока -
preamble/postamble— статический текст до и после всей Composition -
delimiter— разделитель между фрагментами
Текущее решение: различные output_format (json | xml | markdown | plain)
как отдельный тип рендеринга не реализованы.
|
Рендерер возвращает итоговый текст, а форматирование под конкретный выходной
формат должно достигаться через block_wrapper, preamble/postamble,
delimiter, StaticText и структуру самой Composition.
Применяется автоматически при рендеринге, не изменяет семантику текста [1].
SemanticStyle (семантический)
Параметры, требующие переписывания текста:
-
tone -
tense -
target_language -
person -
дополнительные поля semantic rewrite policy
Ограничение: не применяется автоматически. Требует явного вызова
Normalize(…), NormalizeComposition(…) или другого explicit AI workflow
с подключенным normalizer/rewriter [1].
Renderer
Сервис рендеринга, отвечающий за преобразование Composition в текст [1]:
-
Template Expansion: подстановка параметров в Block через иерархию
Defaults → Local Override → Runtime[1] -
StructuralStyle Application: применение обёрток и разделителей [1]
-
Output Generation: формирование финального текста без модификации семантики [1]
Renderer не должен содержать скрытой AI-логики и по архитектурному контракту остаётся deterministic layer [1].
Расширенная структура Composition: Fragments
Для повышения гибкости сборки текстов Composition поддерживает гибридную
структуру, содержащую не только ссылки на переиспользуемые блоки, но и
статические текстовые вставки, не требующие создания отдельных сущностей
Block [1].
Типы фрагментов
Composition состоит из упорядоченного списка Fragment, где каждый фрагмент
может быть:
-
BlockRef — ссылка на опубликованный
Blockс локальными переопределениями параметров [1] -
StaticText — произвольный текст без параметров
-
Separator — типизированный разделитель
newline,paragraph,hr
Принципы использования StaticText
-
не имеет
idи не версионируется отдельно — является частью структурыComposition[1] -
не требует
RenderContextдля разрешения — текст используется as-is -
не попадает в глобальное хранилище
Block— не засоряет репозиторий одноразовыми строками [1] -
редактируется и версионируется вместе с
Composition
API работы с фрагментами
class Composition {
public:
Fragment& AddBlockRef(const BlockId& blockId, Version version, Params localParams = {});
Fragment& AddBlockRefLatest(const BlockId& blockId, Params localParams = {});
Fragment& AddStaticText(std::string text);
Fragment& AddSeparator(SeparatorType type);
void InsertFragment(size_t index, Fragment fragment);
void RemoveFragment(size_t index);
void ClearFragments();
};
Мотивация
Использование StaticText позволяет избежать создания избыточных сущностей
Block для [1]:
-
markdown-разметки и заголовков
-
одноразовых переходных фраз
-
синтаксических конструкций JSON/XML/markup
-
языкоспецифичных шаблонов, не требующих параметризации
Это соответствует принципу разделения ответственности: Block отвечает за
переиспользуемую параметризованную логику, а Composition — за структуру и
оформление конкретного текста [1].
Жизненный цикл сущностей
Состояния
-
Draft — редактируемая версия [1]
-
Published — зафиксированная versioned версия [1]
-
Deprecated — устаревшая, но доступная для чтения [1]
Правила переходов
-
Draft можно изменять без ограничений [1]
-
Published версии считаются immutable для пользовательского workflow [1]
-
Любое изменение Published сущности создаёт новую версию [1]
-
Deprecated версии не удаляются автоматически и не должны использоваться по умолчанию [1]
|
В текущей реализации жизненный цикл уже применяется и к |
Этапы обработки текста
Composition
↓
Fragment traversal
↓
Template Expansion (Renderer)
↓
StructuralStyle
↓
Raw / Rendered Text
↓
(optional, explicit) Normalization / Rewrite
↓
Final Text
Разворачивание шаблонов (Template Expansion)
Детерминированная стадия [1], выполняемая Renderer:
-
подстановка параметров
-
сборка фрагментов
-
применение structural formatting
Normalization (опциональный, явный вызов)
Отдельный этап, который [1]:
-
вызывается явно через
Normalize(text, semantic_style)илиNormalizeComposition(…) -
может быть реализован через LLM
-
может быть полностью отключён
Запрещено: неявный вызов Normalization внутри Render() при наличии
SemanticStyle. Это нарушает детерминизм и контроль над текстом [1].
auto rendered = engine.Render(comp_id, ctx); // deterministic
auto normalized = engine.Normalize(text, style); // explicit AI step
Rewrite и generation workflows
Дополнительно допускаются explicit AI workflows:
-
генерация структурированных block draft из prompt-а
-
нормализация composition с сохранением структуры
-
block-preserving rewrite существующей composition через preview/apply
Эти сценарии не заменяют обычный render path и не должны маскироваться под него.
Поддержка языков и времени (i18n)
Стратегия многоязычности
Без LLM поддерживаются только [1]:
-
языкоспецифичные версии Block
-
параметризация терминов и значений
-
нейтральные формулировки
-
статические language-specific фрагменты в
Composition
Запрещено: автоматический перевод или адаптация текста под целевой язык
через SemanticStyle без явного AI вызова [1].
Модель хранения (Storage Model)
Storage является инфраструктурным слоем и полностью отделён от бизнес-логики движка [1].
Принципы хранения
-
движок не зависит от конкретной БД [1]
-
работа идёт только через репозитории [1]
-
формат хранения не является частью публичного API [1]
Проекты и пространства имён
Минимальная адресация сущностей [1]:
-
project_key -
entity_type -
entity_id -
version
Разделение по проектам является важным требованием для будущего Prompt Server и уже присутствует в текущей схеме хранения [1].
Схема хранения (ObjectBox)
Сущности
Текущая ObjectBox-схема включает:
-
ObxBlock— хранит версии блоков с их metadata, template, defaults, params и revision comment -
ObxComposition— хранит metadata композиции, versioning и style profile -
ObxFragment— хранит упорядоченные fragment records для composition
|
Ранее в PRD использовалась формулировка про Принятое текущее решение: композиция хранится через |
Публикация и синхронизация
Операция публикации
Publishing — явная операция [1]:
-
фиксирует текущую Draft-версию
-
создаёт Published-версию
-
делает её доступной для воспроизводимого использования
Интерфейсы: CLI и Terminal UI
CLI (обязательный компонент)
CLI является reference client для Text Engine и архитектурным якорем проекта [1].
Назначение CLI [1]:
-
проверка полноты Core API
-
инженерное использование без GUI
-
автоматизация и scripted usage
-
публикация, инспекция, render и validate сценарии
CLI не предназначен для визуального редактирования или замены GUI [1].
Terminal UI (TUI)
TUI — интерактивный клиент для удобства работы с Core API [1].
Архитектурные ограничения TUI [1]:
-
использует Core API напрямую, не через CLI subprocess
-
не содержит собственной логики рендеринга
-
не имеет собственного слоя хранения
TUI не является обёрткой над CLI, а отдельным клиентом Core API.
|
Первоначально TUI рассматривался как будущий этап. На текущий момент в проекте
уже существует terminal workflow на базе |
Сервер промптов (Prompt Server)
Назначение и ограничения
Prompt Server — planned сервис дистрибуции и разрешения промптов [1]:
-
не редактирует данные
-
не генерирует новые версии
-
не содержит своей доменной логики рендеринга
-
использует published entities в read-only режиме
|
|
Протокол взаимодействия (Draft v0.1)
Запрос клиента [1]:
-
project_key -
prompt_name -
version(optional, default: latest stable) -
params(optional) -
normalize(optional, boolean, default: false)
Ответ сервера (200 OK) [1]:
-
text— финальный текст -
meta.composition_version -
meta.blocks_used -
meta.style_applied
Ошибки [1]:
-
404— Project или Prompt не найдены -
422— отсутствует обязательный параметр или запрошена нормализация при отсутствии Normalizer -
409— запрошена Draft-версия
Внутренняя логика: разрешает Composition, загружает зафиксированные версии Block через Storage в read-only режиме, вызывает Renderer [1].
Обработка стилей
Prompt Server должен применять только StructuralStyle автоматически [1].
SemanticStyle должен обрабатываться только по стратегии explicit normalization:
-
сервер возвращает текст после Template Expansion + StructuralStyle
-
клиент может явно запросить нормализацию
-
при отсутствии normalizer запрос на нормализацию должен завершаться ошибкой
Это сохраняет принцип детерминизма [1].
Модель доступа Prompt Server
Prompt Server должен работать только с Published сущностями [1]:
-
запрос:
GET /prompts/blog.golang?v=1.0&audience=junior&normalize=false -
обработка: render published composition + optional explicit normalization
-
ответ: финальный текст + метаданные использованных блоков
Prompt Server не должен вызывать publish/save draft методы [1].
UI (Qt/QML)
UI является клиентом движка и не содержит бизнес-логики [1].
Назначение:
-
управление блоками и сборка Composition
-
публикация версий
-
предпросмотр результата
-
compare и rewrite workflows
Дорожная карта (Roadmap)
Этап 0 — Proof of Concept
-
Block, Composition
-
Renderer (базовый Template Expansion)
-
plain text output
Статус: в основном завершён.
Этап 1 — MVP
-
параметры и версии Block
-
сериализация и publishing
-
CLI (
create,publish,render,validate) -
строгая проверка версий в BlockRef [1]
Статус: завершён и расширен поверх исходного MVP.
Этап 2 — Stabilization
-
Normalizer API
-
diff версий и расширенный поиск
-
revision-aware editing workflow
Статус: частично реализован.
Этап 3 — AI-assisted authoring
-
block generation
-
composition normalization
-
block-preserving rewrite
-
reviewable preview/apply flows
Статус: реализуется, значительная часть уже есть в codebase и GUI.
Этап 4 — Interfaces
-
дальнейшее развитие UI
-
дополнительные bindings / integration surfaces
Статус: planned.
Границы проекта (Non-goals)
-
скрытое использование LLM [1]
-
UI как источник бизнес-логики [1]
-
Prompt Server как редактор [1]
-
автоматическое изменение текста без явного запроса [1]
-
универсальный формат данных как публичный контракт [1]
Критерии успешности
Проект считается успешным, если [1]:
-
движок используется как единый источник сборки reusable text assets
-
повторяющиеся тексты исчезают
-
изменение структуры не требует переписывания контента
-
AI-assisted workflow не ломают deterministic базовый контур
Архитектура реализации (C++ Core)
Структура слоёв
┌──────────────────────────────────────────────┐
│ CLI / TUI / Qt GUI / Prompt Server (future) │
├──────────────────────────────────────────────┤
│ TextFoundry Core │
│ - Engine │
│ - Renderer │
│ - Block / Composition / Fragment │
│ - versioning / validation / publish │
├──────────────────────────────────────────────┤
│ Repository Abstraction │
│ - IBlockRepository │
│ - ICompositionRepository │
├──────────────────────────────────────────────┤
│ ObjectBox Storage Layer │
│ - ObxBlock │
│ - ObxComposition │
│ - ObxFragment │
└──────────────────────────────────────────────┘
|
Ранее PRD использовал формулировку |
Стратегия разрешения параметров
Параметры разрешаются по иерархии (от менее приоритетных к более приоритетным) [1]:
-
Block Defaults — параметры, определённые в
Block -
Composition Local Override —
local_paramsвBlockRef -
Runtime Context — параметры, переданные в
RenderContext
Строгие vs опциональные параметры
-
Default: Block содержит
defaults. Если параметр не переопределён, используется значение по умолчанию [1] -
Strict: Block содержит
param_schemaс required semantics. Отсутствие значения вызывает ошибку [1]
Версионирование ссылок (BlockRef Strictness)
При создании Composition указание версии в BlockRef является
обязательным для воспроизводимости [1]:
-
API отклоняет попытку использовать published
BlockRefбез версии -
исключение:
use_latest/AddBlockRefLatest(…)только для Draft Composition
Пример использования (Library Usage)
Полный цикл: инициализация хранилища, создание Draft Block с параметрами по умолчанию, публикация, создание Composition с переопределением параметров, рендеринг с Runtime-контекстом [1].
|
Конкретные имена методов и сигнатуры следует синхронизировать по
|
Критические ограничения реализации
-
Immutable Published: published сущность считается read-only, изменение создаёт новую версию [1]
-
Composition BlockRef: обязательное указание версии
versionдля воспроизводимости [1] -
Rendering Failure: отсутствие параметра возвращает ошибку вместо магической подстановки [1]
-
No Hidden AI: LLM-операции не вызываются из обычного
Render()[1] -
Published/Read-only split: будущий Prompt Server должен работать только с published данными [1]