--- layout: default title: "Техническая спецификация структурированных данных (часть 2)" parent: "Russian (Beta)" --- # Техническая спецификация структурированных данных (часть 2) > **Beta Translation:** This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta. ## Обзор Эта спецификация рассматривает проблемы и недостатки, выявленные в процессе первоначальной реализации интеграции структурированных данных TrustGraph, как описано в `structured-data.md`. ## Описание проблем ### 1. Несоответствие в наименованиях: "Объект" против "Строка" В текущей реализации используется терминология "объект" во всем коде (например, `ExtractedObject`, извлечение объектов, векторные представления объектов). Это наименование слишком общее и вызывает путаницу: Термин "объект" является перегруженным в программном обеспечении (объекты Python, объекты JSON и т.д.) Данные, которые обрабатываются, по сути являются табличными - строки в таблицах с определенными схемами. "Строка" более точно описывает модель данных и соответствует терминологии баз данных. Это несоответствие проявляется в названиях модулей, названиях классов, типах сообщений и документации. ### 2. Ограничения запросов к хранилищу строк Текущая реализация хранилища строк имеет значительные ограничения по запросам: **Несоответствие с естественным языком**: Запросы испытывают трудности с вариациями реальных данных. Например: Сложно найти базу данных улиц, содержащую `"CHESTNUT ST"`, при запросе информации о `"Chestnut Street"`. Аббревиатуры, различия в регистре и вариации форматирования нарушают запросы точного соответствия. Пользователи ожидают семантического понимания, но хранилище обеспечивает только буквальное сопоставление. **Проблемы с эволюцией схемы**: Изменение схем вызывает проблемы: Существующие данные могут не соответствовать обновленным схемам. Изменения структуры таблицы могут нарушить запросы и целостность данных. Отсутствует четкий путь миграции для обновлений схемы. ### 3. Требуются векторные представления строк В связи с проблемой 2, системе требуются векторные представления данных строк для обеспечения: Семантического поиска по структурированным данным (поиск "Chestnut Street", когда данные содержат "CHESTNUT ST"). Сопоставления по сходству для нечетких запросов. Гибридного поиска, сочетающего структурированные фильтры с семантическим сходством. Лучшей поддержки запросов на естественном языке. Сервис создания векторных представлений был определен, но не реализован. ### 4. Неполная загрузка данных строк Конвейер загрузки структурированных данных не полностью функционирует: Существуют диагностические подсказки для классификации форматов входных данных (CSV, JSON и т.д.). Сервис загрузки, использующий эти подсказки, не подключен к системе. Отсутствует сквозной путь для загрузки предварительно структурированных данных в хранилище строк. ## Цели **Гибкость схемы**: Обеспечить эволюцию схемы без нарушения существующих данных или необходимости миграций. **Согласованное наименование**: Стандартизировать использование термина "строка" во всем коде. **Семантическая запросаемость**: Поддержка нечеткого/семантического сопоставления с помощью векторных представлений строк. **Полный конвейер загрузки**: Предоставить сквозной путь для загрузки структурированных данных. ## Технический дизайн ### Унифицированная схема хранения строк В предыдущей реализации для каждой схемы создавалась отдельная таблица Cassandra. Это вызывало проблемы при эволюции схем, поскольку изменения структуры таблицы требовали миграций. В новой разработке используется единая унифицированная таблица для всех данных строк: ```sql CREATE TABLE rows ( collection text, schema_name text, index_name text, index_value frozen>, data map, source text, PRIMARY KEY ((collection, schema_name, index_name), index_value) ) ``` #### Описание столбцов | Столбец | Тип | Описание | |--------|------|-------------| | `collection` | `text` | Идентификатор сбора/импорта данных (из метаданных) | | `schema_name` | `text` | Название схемы, которой соответствует эта строка | | `index_name` | `text` | Название индексируемого поля (полей), объединенное запятыми для составных индексов | | `index_value` | `frozen>` | Значение(я) индекса в виде списка | | `data` | `map` | Данные строки в виде пар ключ-значение | | `source` | `text` | Необязательный URI, ссылающийся на информацию об источнике в графе знаний. Пустая строка или NULL указывает на отсутствие источника. | #### Обработка индексов Каждая строка хранится несколько раз - один раз для каждого индексируемого поля, определенного в схеме. Первичные ключи рассматриваются как индекс без специального маркера, что обеспечивает гибкость в будущем. **Пример индекса для одного поля:** Схема определяет `email` как индексируемое `index_name = "email"` `index_value = ['foo@bar.com']` **Пример составного индекса:** Схема определяет составной индекс для `region` и `status` `index_name = "region,status"` (названия полей отсортированы и объединены запятыми) `index_value = ['US', 'active']` (значения в том же порядке, что и названия полей) **Пример первичного ключа:** Схема определяет `customer_id` как первичный ключ `index_name = "customer_id"` `index_value = ['CUST001']` #### Шаблоны запросов Все запросы следуют одной и той же схеме, независимо от того, какой индекс используется: ```sql SELECT * FROM rows WHERE collection = 'import_2024' AND schema_name = 'customers' AND index_name = 'email' AND index_value = ['foo@bar.com'] ``` #### Компромиссы в проектировании **Преимущества:** Изменения схемы не требуют изменений структуры таблицы Данные строк не видны Cassandra - добавление/удаление полей прозрачно Единый шаблон запросов для всех методов доступа Отсутствуют вторичные индексы Cassandra (которые могут быть медленными при больших масштабах) Использование нативных типов Cassandra (`map`, `frozen`) **Компромиссы:** Увеличение количества операций записи: каждая вставка строки = N вставок (по одной на каждое индексированное поле) Дополнительные затраты памяти из-за дублирования данных строк Информация о типах хранится в конфигурации схемы, преобразование происходит на уровне приложения #### Модель согласованности В проекте приняты определенные упрощения: 1. **Отсутствие обновлений строк**: Система работает только на добавление данных. Это устраняет проблемы согласованности, связанные с обновлением нескольких копий одной и той же строки. 2. **Толерантность к изменениям схемы**: При изменении схем (например, при добавлении/удалении индексов) существующие строки сохраняют свою первоначальную индексацию. Старые строки не будут доступны через новые индексы. Пользователи могут удалить и пересоздать схему для обеспечения согласованности, если это необходимо. ### Отслеживание и удаление разделов #### Проблема При использовании ключа раздела `(collection, schema_name, index_name)`, для эффективного удаления необходимо знать все ключи разделов, которые нужно удалить. Удаление только по `collection` или `collection + schema_name` требует знания всех значений `index_name`, которые содержат данные. #### Таблица отслеживания разделов Вторичная таблица поиска отслеживает, какие разделы существуют: ```sql CREATE TABLE row_partitions ( collection text, schema_name text, index_name text, PRIMARY KEY ((collection), schema_name, index_name) ) ``` Это обеспечивает эффективное обнаружение разделов для операций удаления. #### Поведение модуля записи строк Модуль записи строк поддерживает кэш в памяти, содержащий зарегистрированные пары `(collection, schema_name)`. При обработке строки: 1. Проверьте, есть ли `(collection, schema_name)` в кэше. 2. Если ⟦CODE_0⟧ не в кэше (первая строка для этой пары): Найдите конфигурацию схемы, чтобы получить все имена индексов. Вставьте записи в `row_partitions` для каждого `(collection, schema_name, index_name)`. Добавьте пару в кэш. 3. Перейдите к записи данных строки. Модуль записи строк также отслеживает события изменения конфигурации схемы. При изменении схемы соответствующие записи кэша очищаются, чтобы следующая строка вызвала повторную регистрацию с обновленными именами индексов. Этот подход обеспечивает: Записи в таблицу поиска происходят только один раз для каждой пары `(collection, schema_name)`, а не для каждой строки. Таблица поиска отражает индексы, которые были активны во время записи данных. Изменения схемы во время импорта обрабатываются правильно. #### Операции удаления **Удаление коллекции:** ```sql -- 1. Discover all partitions SELECT schema_name, index_name FROM row_partitions WHERE collection = 'X'; -- 2. Delete each partition from rows table DELETE FROM rows WHERE collection = 'X' AND schema_name = '...' AND index_name = '...'; -- (repeat for each discovered partition) -- 3. Clean up the lookup table DELETE FROM row_partitions WHERE collection = 'X'; ``` **Удалить коллекцию и схему:** ```sql -- 1. Discover partitions for this schema SELECT index_name FROM row_partitions WHERE collection = 'X' AND schema_name = 'Y'; -- 2. Delete each partition from rows table DELETE FROM rows WHERE collection = 'X' AND schema_name = 'Y' AND index_name = '...'; -- (repeat for each discovered partition) -- 3. Clean up the lookup table entries DELETE FROM row_partitions WHERE collection = 'X' AND schema_name = 'Y'; ``` ### Встраивания строк Встраивания строк обеспечивают семантическое/приближенное сопоставление проиндексированных значений, решая проблему несовместимости естественного языка (например, поиск "CHESTNUT ST", когда выполняется запрос "Chestnut Street"). #### Обзор архитектуры Каждое проиндексированное значение преобразуется во встраивание и хранится в векторной базе данных (Qdrant). Во время запроса запрос также преобразуется во встраивание, находятся похожие векторы, и связанная метаинформация используется для поиска фактических строк в Cassandra. #### Структура коллекции Qdrant Одна коллекция Qdrant для каждой кортежи `(user, collection, schema_name, dimension)`: **Именование коллекции:** `rows_{user}_{collection}_{schema_name}_{dimension}` Имена очищаются (небуквенно-цифровые символы заменяются на `_`, приводятся к нижнему регистру, числовые префиксы получают префикс `r_`) **Обоснование:** Позволяет чисто удалить экземпляр `(user, collection, schema_name)` путем удаления соответствующих коллекций Qdrant; суффикс измерения позволяет различным моделям встраивания сосуществовать. #### Что подвергается встраиванию Текстовое представление значений индекса: | Тип индекса | Пример `index_value` | Текст для встраивания | |------------|----------------------|---------------| | Одиночное поле | `['foo@bar.com']` | `"foo@bar.com"` | | Композитный | `['US', 'active']` | `"US active"` (объединенные пробелами) | #### Структура точки Каждая точка Qdrant содержит: ```json { "id": "", "vector": [0.1, 0.2, ...], "payload": { "index_name": "street_name", "index_value": ["CHESTNUT ST"], "text": "CHESTNUT ST" } } ``` | Поле полезной нагрузки | Описание | |---------------|-------------| | `index_name` | Индексированные поля, которые представляет эта вставка. | | `index_value` | Исходный список значений (для поиска в Cassandra). | | `text` | Текст, который был вставлен (для отладки/отображения). | Обратите внимание: `user`, `collection` и `schema_name` определяются неявно из имени коллекции Qdrant. #### Поток запросов 1. Пользователь запрашивает "Chestnut Street" для пользователя U, коллекции X, схемы Y. 2. Вставьте текст запроса. 3. Определите имя(и) коллекции Qdrant, соответствующие префиксу `rows_U_X_Y_`. 4. Выполните поиск в соответствующих коллекциях Qdrant для поиска ближайших векторов. 5. Получите соответствующие точки с полезными нагрузками, содержащими `index_name` и `index_value`. 6. Запрос к Cassandra: ```sql SELECT * FROM rows WHERE collection = 'X' AND schema_name = 'Y' AND index_name = '' AND index_value = ``` 7. Возврат соответствующих строк. #### Необязательно: Фильтрация по имени индекса. Запросы могут опционально фильтровать данные по `index_name` в Qdrant, чтобы искать только определенные поля: **"Найти любое поле, соответствующее 'Chestnut'"** → поиск по всем векторам в коллекции. **"Найти поле street_name, соответствующее 'Chestnut'"** → фильтрация по `payload.index_name = 'street_name'`. #### Архитектура. Векторные представления строк следуют **двухступенчатой схеме**, используемой в GraphRAG (graph-embeddings, document-embeddings): **Этап 1: Вычисление векторных представлений** (`trustgraph-flow/trustgraph/embeddings/row_embeddings/`) - Использует `ExtractedObject`, вычисляет векторные представления через сервис векторных представлений, выдает `RowEmbeddings`. **Этап 2: Хранение векторных представлений** (`trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/`) - Использует `RowEmbeddings`, записывает векторы в Qdrant. Модуль записи строк в Cassandra является отдельным параллельным компонентом: **Модуль записи строк в Cassandra** (`trustgraph-flow/trustgraph/storage/rows/cassandra`) - Использует `ExtractedObject`, записывает строки в Cassandra. Все три компонента используют один и тот же поток данных, что обеспечивает их независимость. Это позволяет: Независимое масштабирование записи в Cassandra по сравнению с генерацией векторных представлений и хранением векторов. Отключение сервисов генерации векторных представлений, если они не требуются. Отказы в одном компоненте не влияют на другие. Согласованная архитектура с конвейерами GraphRAG. #### Путь записи. **Этап 1 (процессор векторных представлений строк):** При получении `ExtractedObject`: 1. Получение схемы для поиска индексированных полей. 2. Для каждого индексированного поля: Создание текстового представления значения индекса. Вычисление векторного представления через сервис векторных представлений. 3. Вывод сообщения `RowEmbeddings`, содержащего все вычисленные векторы. **Этап 2 (запись векторных представлений в Qdrant):** При получении `RowEmbeddings`: 1. Для каждого векторного представления в сообщении: Определение коллекции Qdrant из `(user, collection, schema_name, dimension)`. Создание коллекции, если это необходимо (создание при первой записи). Добавление точки с вектором и полезной нагрузкой. #### Типы сообщений. ```python @dataclass class RowIndexEmbedding: index_name: str # The indexed field name(s) index_value: list[str] # The field value(s) text: str # Text that was embedded vectors: list[list[float]] # Computed embedding vectors @dataclass class RowEmbeddings: metadata: Metadata schema_name: str embeddings: list[RowIndexEmbedding] ``` #### Интеграция удаления Коллекции Qdrant обнаруживаются путем сопоставления префикса с шаблоном имени коллекции: **Удаление `(user, collection)`:** 1. Перечислить все коллекции Qdrant, соответствующие префиксу `rows_{user}_{collection}_` 2. Удалить каждую соответствующую коллекцию 3. Удалить разделы строк Cassandra (как описано выше) 4. Очистить записи `row_partitions` **Удаление `(user, collection, schema_name)`:** 1. Перечислить все коллекции Qdrant, соответствующие префиксу `rows_{user}_{collection}_{schema_name}_` 2. Удалить каждую соответствующую коллекцию (обрабатывает несколько измерений) 3. Удалить разделы строк Cassandra 4. Очистить `row_partitions` #### Расположение модулей | Этап | Модуль | Точка входа | |-------|--------|-------------| | Этап 1 | `trustgraph-flow/trustgraph/embeddings/row_embeddings/` | `row-embeddings` | | Этап 2 | `trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/` | `row-embeddings-write-qdrant` | ### API запросов для векторных представлений строк API запросов для векторных представлений строк является **отдельным API** от сервиса запросов строк GraphQL: | API | Назначение | Бэкенд | |-----|---------|---------| | Запрос строк (GraphQL) | Точное сопоставление с индексированными полями | Cassandra | | Запрос векторных представлений строк | Нечеткое/семантическое сопоставление | Qdrant | Это разделение позволяет четко разделить функциональность: Сервис GraphQL фокусируется на точных, структурированных запросах API векторных представлений обрабатывает семантическую схожесть Рабочий процесс пользователя: нечеткий поиск с помощью векторных представлений для поиска кандидатов, затем точный запрос для получения полных данных строки #### Схема запроса/ответа ```python @dataclass class RowEmbeddingsRequest: vectors: list[list[float]] # Query vectors (pre-computed embeddings) user: str = "" collection: str = "" schema_name: str = "" index_name: str = "" # Optional: filter to specific index limit: int = 10 # Max results per vector @dataclass class RowIndexMatch: index_name: str = "" # The matched index field(s) index_value: list[str] = [] # The matched value(s) text: str = "" # Original text that was embedded score: float = 0.0 # Similarity score @dataclass class RowEmbeddingsResponse: error: Error | None = None matches: list[RowIndexMatch] = [] ``` #### Обработчик запросов Модуль: `trustgraph-flow/trustgraph/query/row_embeddings/qdrant` Точка входа: `row-embeddings-query-qdrant` Обработчик: 1. Получает `RowEmbeddingsRequest` с векторами запросов 2. Находит соответствующую коллекцию Qdrant путем сопоставления префикса 3. Ищет ближайшие векторы с необязательным фильтром `index_name` 4. Возвращает `RowEmbeddingsResponse` с информацией об индексе, соответствующем запросу #### Интеграция с API-шлюзом Шлюз предоставляет запросы на получение векторных представлений строк через стандартный шаблон запроса/ответа: | Компонент | Местоположение | |-----------|----------| | Диспетчер | `trustgraph-flow/trustgraph/gateway/dispatch/row_embeddings_query.py` | | Регистрация | Добавьте `"row-embeddings"` в `request_response_dispatchers` в `manager.py` | Имя интерфейса потока: `row-embeddings` Определение интерфейса в шаблоне потока: ```json { "interfaces": { "row-embeddings": { "request": "non-persistent://tg/request/row-embeddings:{id}", "response": "non-persistent://tg/response/row-embeddings:{id}" } } } ``` #### Поддержка Python SDK SDK предоставляет методы для запросов векторных представлений строк: ```python # Flow-scoped query (preferred) api = Api(url) flow = api.flow().id("default") # Query with text (SDK computes embeddings) matches = flow.row_embeddings_query( text="Chestnut Street", collection="my_collection", schema_name="addresses", index_name="street_name", # Optional filter limit=10 ) # Query with pre-computed vectors matches = flow.row_embeddings_query( vectors=[[0.1, 0.2, ...]], collection="my_collection", schema_name="addresses" ) # Each match contains: for match in matches: print(match.index_name) # e.g., "street_name" print(match.index_value) # e.g., ["CHESTNUT ST"] print(match.text) # e.g., "CHESTNUT ST" print(match.score) # e.g., 0.95 ``` #### Утилита командной строки Команда: `tg-invoke-row-embeddings` ```bash # Query by text (computes embedding automatically) tg-invoke-row-embeddings \ --text "Chestnut Street" \ --collection my_collection \ --schema addresses \ --index street_name \ --limit 10 # Query by vector file tg-invoke-row-embeddings \ --vectors vectors.json \ --collection my_collection \ --schema addresses # Output formats tg-invoke-row-embeddings --text "..." --format json tg-invoke-row-embeddings --text "..." --format table ``` #### Типичная схема использования Запрос векторных представлений строк обычно используется как часть процесса поиска приблизительного соответствия для точного поиска: ```python # Step 1: Fuzzy search via embeddings matches = flow.row_embeddings_query( text="chestnut street", collection="geo", schema_name="streets" ) # Step 2: Exact lookup via GraphQL for full row data for match in matches: query = f''' query {{ streets(where: {{ {match.index_name}: {{ eq: "{match.index_value[0]}" }} }}) {{ street_name city zip_code }} }} ''' rows = flow.rows_query(query, collection="geo") ``` Эта двухэтапная схема позволяет: Находить "CHESTNUT ST", когда пользователь ищет "Chestnut Street" Получать полные данные строки со всеми полями Объединять семантическую схожесть с доступом к структурированным данным ### Импорт данных строк Отложено до последующей фазы. Будет разработано вместе с другими изменениями импорта. ## Влияние на реализацию ### Анализ текущего состояния Существующая реализация имеет два основных компонента: | Компонент | Местоположение | Строк | Описание | |-----------|----------|-------|-------------| | Сервис запросов | `trustgraph-flow/trustgraph/query/objects/cassandra/service.py` | ~740 | Монолит: генерация схемы GraphQL, разбор фильтров, запросы Cassandra, обработка запросов | | Записывающий модуль | `trustgraph-flow/trustgraph/storage/objects/cassandra/write.py` | ~540 | Создание таблиц для каждой схемы, вторичные индексы, вставка/удаление | **Текущая схема запросов:** ```sql SELECT * FROM {keyspace}.o_{schema_name} WHERE collection = 'X' AND email = 'foo@bar.com' ALLOW FILTERING ``` **Новая схема запроса:** ```sql SELECT * FROM {keyspace}.rows WHERE collection = 'X' AND schema_name = 'customers' AND index_name = 'email' AND index_value = ['foo@bar.com'] ``` ### Ключевые изменения 1. **Упрощение семантики запросов**: Новая схема поддерживает только точное соответствие для `index_value`. Текущие фильтры GraphQL (`gt`, `lt`, `contains` и т.д.) либо: Становятся постобработкой возвращаемых данных (если это все еще необходимо) Удаляются в пользу использования API для работы с эмбеддингами для нечеткого сопоставления. 2. **Код GraphQL тесно связан**: Текущие сборки `service.py` включают генерацию типов Strawberry, разбор фильтров и запросы, специфичные для Cassandra. Добавление еще одного бэкенда для хранения данных потребует дублирования примерно 400 строк кода GraphQL. ### Предлагаемая реструктуризация Реструктуризация состоит из двух частей: #### 1. Выделение кода GraphQL Извлеките многократно используемые компоненты GraphQL в общий модуль: ``` trustgraph-flow/trustgraph/query/graphql/ ├── __init__.py ├── types.py # Filter types (IntFilter, StringFilter, FloatFilter) ├── schema.py # Dynamic schema generation from RowSchema └── filters.py # Filter parsing utilities ``` Это позволяет: Повторное использование в различных системах хранения данных. Более четкое разделение ответственности. Более простое тестирование логики GraphQL независимо. #### 2. Реализация новой схемы таблицы Переработайте код, специфичный для Cassandra, для использования унифицированной таблицы: **Записывающий модуль** (`trustgraph-flow/trustgraph/storage/rows/cassandra/`): Одна `rows` таблица вместо таблиц для каждой схемы. Запись N копий на строку (по одной для каждого индекса). Регистрация в `row_partitions` таблице. Более простое создание таблицы (единоразовая настройка). **Сервис запросов** (`trustgraph-flow/trustgraph/query/rows/cassandra/`): Запрос унифицированной `rows` таблицы. Использование извлеченного модуля GraphQL для генерации схемы. Упрощенная обработка фильтров (только точное соответствие на уровне базы данных). ### Переименование модулей В рамках очистки наименований "object" → "row": | Текущее | Новое | |---------|-----| | `storage/objects/cassandra/` | `storage/rows/cassandra/` | | `query/objects/cassandra/` | `query/rows/cassandra/` | | `embeddings/object_embeddings/` | `embeddings/row_embeddings/` | ### Новые модули | Модуль | Назначение | |--------|---------| | `trustgraph-flow/trustgraph/query/graphql/` | Общие утилиты GraphQL | | `trustgraph-flow/trustgraph/query/row_embeddings/qdrant/` | API запросов для векторных представлений строк | | `trustgraph-flow/trustgraph/embeddings/row_embeddings/` | Вычисление векторных представлений строк (Этап 1) | | `trustgraph-flow/trustgraph/storage/row_embeddings/qdrant/` | Хранение векторных представлений строк (Этап 2) | ## Ссылки [Техническая спецификация структурированных данных](structured-data.md)