Совпадение текста

Функция «Совпадение текста» в Milvus позволяет точно находить документы по конкретным терминам. Эта функция в основном используется для фильтрованного поиска по определенным условиям и может включать скалярную фильтрацию для уточнения результатов запроса, что позволяет выполнять поиск по схожести среди векторов, отвечающих скалярным критериям.

TEXT_MATCH Находит точные проанализированные термины, тогда как поиск по фразам ( TEXT_MATCH_FUZZY ) допускает небольшое расстояние редактирования между токенами запроса и индексированными токенами. Обе операции являются булевыми фильтрами и не оценивают релевантность найденных документов. Если вы хотите найти наиболее релевантные документы на основе семантического значения и важности терминов запроса, мы рекомендуем использовать полнотекстовый поиск.

Обзор

Milvus интегрирует Tantivy для обеспечения работы своего базового инвертированного индекса и текстового поиска по терминам. Каждый текстовый фрагмент Milvus индексирует следующим образом:

  1. Анализатор: Анализатор обрабатывает входной текст, разбивая его на отдельные слова (токены), а затем применяя необходимые фильтры. Это позволяет Milvus создавать индекс на основе этих токенов.

  2. Индексирование: после анализа текста Milvus создает инвертированный индекс, который сопоставляет каждый уникальный токен с документами, в которых он встречается.

Когда пользователь выполняет поиск по тексту, инвертированный индекс используется для быстрого извлечения всех документов, содержащих эти термины. Это происходит гораздо быстрее, чем сканирование каждого документа по отдельности.

Keyword Match Поиск по ключевым словам

Включение поиска по тексту

Поиск по тексту работает с полями строк, для которых включена функция поиска. В примерах на этой странице используется VARCHAR, который поддерживается во всех клиентских SDK. В Milvus 3.0.x TEXT поля также поддерживают текстовое сопоставление, если включен Storage V3. Для любого из этих типов полей установите значения как для параметра ` enable_analyzer `, так и для ` enable_match ` равными ` True`, а затем, при необходимости, настройте анализатор при определении схемы коллекции.

Установите значения для параметров enable_analyzer и enable_match

Чтобы включить текстовое сопоставление для конкретного поля типа « VARCHAR », при определении схемы поля установите для параметров « enable_analyzer » и « enable_match » значение « True ». Это указывает Milvus на необходимость токенизации текста и создания инвертированного индекса для указанного поля, что обеспечивает быстрое и эффективное текстовое сопоставление.

from pymilvus import MilvusClient, DataType

schema = MilvusClient.create_schema(enable_dynamic_field=False)
schema.add_field(
    field_name="id",
    datatype=DataType.INT64,
    is_primary=True,
    auto_id=True
)
schema.add_field(
    field_name='text', 
    datatype=DataType.VARCHAR, 
    max_length=1000, 
    enable_analyzer=True, # Whether to enable text analysis for this field
    enable_match=True # Whether to enable text match
)
schema.add_field(
    field_name="embeddings",
    datatype=DataType.FLOAT_VECTOR,
    dim=5
)
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
        .enableDynamicField(false)
        .build();
schema.addField(AddFieldReq.builder()
        .fieldName("id")
        .dataType(DataType.Int64)
        .isPrimaryKey(true)
        .autoID(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("text")
        .dataType(DataType.VarChar)
        .maxLength(1000)
        .enableAnalyzer(true)
        .enableMatch(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("embeddings")
        .dataType(DataType.FloatVector)
        .dimension(5)
        .build());
import "github.com/milvus-io/milvus/client/v2/entity"

schema := entity.NewSchema().WithDynamicFieldEnabled(false)
schema.WithField(entity.NewField().
    WithName("id").
    WithDataType(entity.FieldTypeInt64).
    WithIsPrimaryKey(true).
    WithIsAutoID(true),
).WithField(entity.NewField().
    WithName("text").
    WithDataType(entity.FieldTypeVarChar).
    WithEnableAnalyzer(true).
    WithEnableMatch(true).
    WithMaxLength(1000),
).WithField(entity.NewField().
    WithName("embeddings").
    WithDataType(entity.FieldTypeFloatVector).
    WithDim(5),
)
const schema = [
  {
    name: "id",
    data_type: DataType.Int64,
    is_primary_key: true,
  },
  {
    name: "text",
    data_type: "VarChar",
    enable_analyzer: true,
    enable_match: true,
    max_length: 1000,
  },
  {
    name: "embeddings",
    data_type: DataType.FloatVector,
    dim: 5,
  },
];
export schema='{
        "autoId": true,
        "enabledDynamicField": false,
        "fields": [
            {
                "fieldName": "id",
                "dataType": "Int64",
                "isPrimary": true
            },
            {
                "fieldName": "text",
                "dataType": "VarChar",
                "elementTypeParams": {
                    "max_length": 1000,
                    "enable_analyzer": true,
                    "enable_match": true
                }
            },
            {
                "fieldName": "embeddings",
                "dataType": "FloatVector",
                "elementTypeParams": {
                    "dim": "5"
                }
            }
        ]
    }'

Необязательно: настройте анализатор

Производительность и точность сопоставления по ключевым словам зависят от выбранного анализатора. Различные анализаторы адаптированы к разным языкам и структурам текста, поэтому выбор подходящего анализатора может существенно повлиять на результаты поиска в вашем конкретном случае использования.

По умолчанию Milvus использует анализатор « standard », который разбивает текст на токены на основе пробелов и знаков препинания, удаляет токены длиной более 40 символов и преобразует текст в нижний регистр. Для применения этой настройки по умолчанию не требуется никаких дополнительных параметров. Дополнительную информацию см. в разделе «Стандарт».

Если требуется другой анализатор, его можно настроить с помощью параметра ` analyzer_params `. Например, чтобы применить анализатор ` english ` для обработки текста на английском языке:

analyzer_params = {
    "type": "english"
}
schema.add_field(
    field_name='text',
    datatype=DataType.VARCHAR,
    max_length=200,
    enable_analyzer=True,
    analyzer_params = analyzer_params,
    enable_match = True,
)
Map<String, Object> analyzerParams = new HashMap<>();
analyzerParams.put("type", "english");
schema.addField(AddFieldReq.builder()
        .fieldName("text")
        .dataType(DataType.VarChar)
        .maxLength(200)
        .enableAnalyzer(true)
        .analyzerParams(analyzerParams)
        .enableMatch(true)
        .build());
analyzerParams := map[string]any{"type": "english"}
schema.WithField(entity.NewField().
    WithName("text").
    WithDataType(entity.FieldTypeVarChar).
    WithEnableAnalyzer(true).
    WithEnableMatch(true).
    WithAnalyzerParams(analyzerParams).
    WithMaxLength(200),
)
const schema = [
  {
    name: "id",
    data_type: DataType.Int64,
    is_primary_key: true,
  },
  {
    name: "text",
    data_type: "VarChar",
    enable_analyzer: true,
    enable_match: true,
    max_length: 1000,
    analyzer_params: { type: 'english' },
  },
  {
    name: "embeddings",
    data_type: DataType.FloatVector,
    dim: 5,
  },
];
export schema='{
        "autoId": true,
        "enabledDynamicField": false,
        "fields": [
            {
                "fieldName": "id",
                "dataType": "Int64",
                "isPrimary": true
            },
            {
                "fieldName": "text",
                "dataType": "VarChar",
                "elementTypeParams": {
                    "max_length": 200,
                    "enable_analyzer": true,
                    "enable_match": true,
                    "analyzer_params": {"type": "english"}
                }
            },
            {
                "fieldName": "embeddings",
                "dataType": "FloatVector",
                "elementTypeParams": {
                    "dim": "5"
                }
            }
        ]
    }'

Milvus также предоставляет различные другие анализаторы, подходящие для разных языков и сценариев. Подробнее см. в разделе «Обзор анализаторов».

Использование сопоставления текста

После включения текстового сопоставления для поля VARCHAR или TEXT в схеме коллекции можно выполнять текстовое сопоставление с помощью выражения TEXT_MATCH.

Синтаксис выражения TEXT_MATCH

Выражение TEXT_MATCH используется для указания поля и терминов, по которым будет выполняться поиск. Его синтаксис следующий:

TEXT_MATCH(field_name, text)
  • field_name: Имя поля VARCHAR или TEXT с поддержкой сопоставления, в котором будет выполняться поиск.

  • text: Термины для поиска. Несколько терминов можно разделять пробелами или другими подходящими разделителями в зависимости от языка и настроенного анализатора.

По умолчанию поле « TEXT_MATCH » использует логику сопоставления «ИЛИ», то есть будет возвращать документы, содержащие любой из указанных терминов. Например, чтобы найти документы, содержащие термин « machine » или « deep » в поле « text », используйте следующее выражение:

filter = "TEXT_MATCH(text, 'machine deep')"
String filter = "TEXT_MATCH(text, 'machine deep')";
filter := "TEXT_MATCH(text, 'machine deep')"
const filter = "TEXT_MATCH(text, 'machine deep')";
export filter="\"TEXT_MATCH(text, 'machine deep')\""

Вы также можете комбинировать несколько выражений TEXT_MATCH с помощью логических операторов для выполнения сопоставления по оператору AND.

  • Чтобы найти документы, содержащие как machine, так и deep в поле text, используйте следующее выражение:

    filter = "TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')"
    
    String filter = "TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')";
    
    filter := "TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')"
    
    const filter = "TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')"
    
    export filter="\"TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'deep')\""
    
  • Чтобы найти документы, содержащие одновременно machine и learning, но не содержащие deep в поле text, используйте следующие выражения:

    filter = "not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')"
    
    String filter = "not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')";
    
    filter := "not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')"
    
    const filter = "not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')";
    
    export filter="\"not TEXT_MATCH(text, 'deep') and TEXT_MATCH(text, 'machine') and TEXT_MATCH(text, 'learning')\""
    

Синтаксис выражения TEXT_MATCH_FUZZYCompatible with Milvus 3.0.0+

Используйте TEXT_MATCH_FUZZY, чтобы учитывать различия в написании между токенами запроса и индексированными токенами. Milvus анализирует текст запроса с помощью анализатора поля и применяет неточное сопоставление к каждому полученному токену. Если запрос генерирует несколько токенов, выражение сопоставляется с сущностью, когда любой токен удовлетворяет настроенному расстоянию редактирования.

Синтаксис следующий:

TEXT_MATCH_FUZZY(field_name, text, max_edit_distance = 1)
  • field_name: Название поля с поддержкой сопоставления ( VARCHAR или TEXT ), в котором будет выполняться поиск.

  • text: Текст запроса, который необходимо проанализировать и сопоставить с индексированными токенами.

  • max_edit_distance: Максимальное допустимое расстояние редактирования для каждого токена запроса. Имя параметра должно быть именно max_edit_distance, а его значением должно быть 0, 1 или 2. Значение 0 выполняет точное сопоставление токенов, что эквивалентно TEXT_MATCH.

Например, следующее выражение находит токены, отличающиеся не более чем на одну символьную замену от machne, включая machine:

filter = "TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)"
String filter = "TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)";
filter := "TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)"
const filter = "TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)";
export filter="\"TEXT_MATCH_FUZZY(text, 'machne', max_edit_distance = 1)\""

TEXT_MATCH_FUZZY является частью синтаксиса фильтрующих выражений, поэтому клиентским SDK не требуется отдельный метод неточного сопоставления. Передайте выражение через тот же параметр filter, который используется для TEXT_MATCH в операциях поиска или запроса.

Поиск с текстовым сопоставлением

Совпадение по тексту можно использовать в сочетании с векторным поиском по схожести, чтобы сузить область поиска и повысить производительность поиска. Фильтруя коллекцию с помощью совпадения по тексту перед векторным поиском по схожести, вы можете уменьшить количество документов, которые необходимо просматривать, что приведет к сокращению времени выполнения запроса.

В этом примере выражение filter фильтрует результаты поиска, оставляя только те документы, которые содержат указанные термины keyword1 или keyword2. Затем поиск по векторному сходству выполняется по этому отфильтрованному подмножеству документов.

Вы можете выделять совпадающие термины в результатах поиска, настроив подсветку текста. Подробности см. в разделе «Подсветка текста ».

# Match entities with `keyword1` or `keyword2`
filter = "TEXT_MATCH(text, 'keyword1 keyword2')"

# Assuming 'embeddings' is the vector field and 'text' is the VARCHAR field
result = client.search(
    collection_name="my_collection", # Your collection name
    anns_field="embeddings", # Vector field name
    data=[query_vector], # Query vector
    filter=filter,
    search_params={"params": {"nprobe": 10}},
    limit=10, # Max. number of results to return
    output_fields=["id", "text"] # Fields to return
)
String filter = "TEXT_MATCH(text, 'keyword1 keyword2')";

SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .annsField("embeddings")
        .data(Collections.singletonList(queryVector)))
        .filter(filter)
        .topK(10)
        .outputFields(Arrays.asList("id", "text"))
        .build());
filter := "TEXT_MATCH(text, 'keyword1 keyword2')"

resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
    "my_collection", // collectionName
    10,               // limit
    []entity.Vector{entity.FloatVector(queryVector)},
).WithANNSField("embeddings").
    WithFilter(filter).
    WithOutputFields("id", "text"))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
// Match entities with `keyword1` or `keyword2`
const filter = "TEXT_MATCH(text, 'keyword1 keyword2')";

// Assuming 'embeddings' is the vector field and 'text' is the VARCHAR field
const result = await client.search(
    collection_name: "my_collection", // Your collection name
    anns_field: "embeddings", // Vector field name
    data: [query_vector], // Query vector
    filter: filter,
    params: {"nprobe": 10},
    limit: 10, // Max. number of results to return
    output_fields: ["id", "text"] //Fields to return
);
export filter="\"TEXT_MATCH(text, 'keyword1 keyword2')\""

export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
    "collectionName": "my_collection",
    "annsField": "embeddings",
    "data": [[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]],
    "filter": '"$filter"',
    "searchParams": {
        "params": {
            "nprobe": 10
        }
    },
    "limit": 10,
    "outputFields": ["text","id"]
}'

Запрос с текстовым сопоставлением

Совпадение текста также можно использовать для скалярной фильтрации в операциях запроса. Указав выражение TEXT_MATCH в параметре expr метода query(), вы можете получить документы, соответствующие заданным терминам.

В приведенном ниже примере извлекаются документы, в поле text которых содержатся оба термина: keyword1 и keyword2.

# Match entities with both `keyword1` and `keyword2`
filter = "TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')"

result = client.query(
    collection_name="my_collection",
    filter=filter, 
    output_fields=["id", "text"]
)
String filter = "TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')";

QueryResp queryResp = client.query(QueryReq.builder()
        .collectionName("my_collection")
        .filter(filter)
        .outputFields(Arrays.asList("id", "text"))
        .build()
);
filter = "TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')"
resultSet, err := client.Query(ctx, milvusclient.NewQueryOption("my_collection").
    WithFilter(filter).
    WithOutputFields("id", "text"))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}

// Match entities with both `keyword1` and `keyword2`
const filter = "TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')";

const result = await client.query(
    collection_name: "my_collection",
    filter: filter, 
    output_fields: ["id", "text"]
)
export filter="\"TEXT_MATCH(text, 'keyword1') and TEXT_MATCH(text, 'keyword2')\""

export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/query" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
    "collectionName": "my_collection",
    "filter": '"$filter"',
    "outputFields": ["id", "text"]
}'

Рекомендации

  • Включение сопоставления терминов для поля приводит к созданию инвертированного индекса, что требует ресурсов хранения. При принятии решения о включении этой функции учитывайте влияние на хранилище, так как оно зависит от размера текста, количества уникальных токенов и используемого анализатора.

  • После определения анализатора в схеме его настройки становятся постоянными для данной коллекции. Если вы решите, что другой анализатор лучше соответствует вашим потребностям, можно удалить существующую коллекцию и создать новую с нужной конфигурацией анализатора.

  • Правила экранирования в выражениях filter:

    • Символы, заключённые в двойные или одинарные кавычки внутри выражений, интерпретируются как строковые константы. Если строковая константа содержит символы экранирования, эти символы должны быть представлены с помощью экранирующих последовательностей. Например, используйте \\ для представления \, \\t для представления табуляции \t и \\n для представления символа новой строки.

    • Если строковая константа заключена в одинарные кавычки, одинарная кавычка внутри константы должна представляться как \\', тогда как двойная кавычка может представляться либо как ", либо как \\". Пример: 'It\\'s milvus'.

    • Если строковая константа заключена в двойные кавычки, двойная кавычка внутри константы должна быть представлена как \\", а одинарная кавычка может быть представлена либо как ', либо как \\'. Пример: "He said \\"Hi\\"".