mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-04-25 08:26:21 +02:00
622 lines
36 KiB
Markdown
622 lines
36 KiB
Markdown
|
|
---
|
|||
|
|
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<list<text>>,
|
|||
|
|
data map<text, text>,
|
|||
|
|
source text,
|
|||
|
|
PRIMARY KEY ((collection, schema_name, index_name), index_value)
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Описание столбцов
|
|||
|
|
|
|||
|
|
| Столбец | Тип | Описание |
|
|||
|
|
|--------|------|-------------|
|
|||
|
|
| `collection` | `text` | Идентификатор сбора/импорта данных (из метаданных) |
|
|||
|
|
| `schema_name` | `text` | Название схемы, которой соответствует эта строка |
|
|||
|
|
| `index_name` | `text` | Название индексируемого поля (полей), объединенное запятыми для составных индексов |
|
|||
|
|
| `index_value` | `frozen<list<text>>` | Значение(я) индекса в виде списка |
|
|||
|
|
| `data` | `map<text, text>` | Данные строки в виде пар ключ-значение |
|
|||
|
|
| `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<list>`)
|
|||
|
|
|
|||
|
|
**Компромиссы:**
|
|||
|
|
Увеличение количества операций записи: каждая вставка строки = 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": "<uuid>",
|
|||
|
|
"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 = '<from payload>'
|
|||
|
|
AND index_value = <from payload>
|
|||
|
|
```
|
|||
|
|
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)
|