StructArray

Um campo Array of Structs, ou um campo StructArray, numa entidade armazena um conjunto ordenado de elementos Struct. Cada Struct no Array partilha o mesmo esquema pré-definido, compreendendo vários vectores e campos escalares.

Aqui está um exemplo de uma entidade de uma coleção que contém um campo StructArray.

{
    'id': 0,
    'title': 'Walden',
    'title_vector': [0.1, 0.2, 0.3, 0.4, 0.5],
    'author': 'Henry David Thoreau',
    'year_of_publication': 1845,
    'chunks': [
        {
            'text': 'When I wrote the following pages, or rather the bulk of them...',
            'text_vector': [0.3, 0.2, 0.3, 0.2, 0.5],
            'chapter': 'Economy',
        },
        {
            'text': 'I would fain say something, not so much concerning the Chinese and...',
            'text_vector': [0.7, 0.4, 0.2, 0.7, 0.8],
            'chapter': 'Economy'
        }
    ]
    // hightlight-end
}

No exemplo acima, o campo chunks é um campo StructArray, e cada elemento Struct contém os seus próprios campos, nomeadamente text, text_vector e chapter.

Quando utilizar

As aplicações modernas de IA, desde a condução autónoma à recuperação multimodal, dependem cada vez mais de dados aninhados e heterogéneos. Os modelos de dados planos tradicionais têm dificuldade em representar relações complexas, como"um documento com muitas partes anotadas" ou"uma cena de condução com várias manobras observadas". É aqui que o tipo de dados StructArray do Milvus se destaca.

Para determinar rapidamente se o campo StructArray se adequa aos cenários da sua aplicação, considere se:

  • Os seus dados estão numa estrutura hierárquica, como um documento com muitas partes anotadas.

  • O resultado da pesquisa deve ser o documento, e não os blocos, como no exemplo acima.

  • Os resultados da pesquisa contêm um grande número de entidades duplicadas e é difícil recuperar os resultados finais utilizando técnicas como o agrupamento, a desduplicação e o reranking.

Se as respostas às perguntas acima forem afirmativas, você deve usar o StructArray.

Limites

  • Tipos de dados

    Quando cria uma coleção, pode utilizar o tipo Struct como tipo de dados para os elementos de um campo Array. No entanto, não é possível adicionar um StructArray a uma coleção existente e o Milvus não suporta a utilização do tipo Struct como tipo de dados para um campo de coleção.

    Os Structs num campo Array partilham o mesmo esquema, que deve ser definido quando cria o campo Array.

    Um esquema Struct contém vectores e campos escalares, como indicado abaixo:

      <div>
    
          Applicable vector fields:
    
          - `FLOAT_VECTOR`
    
          - `FLOAT16_VECTOR`
    
          - `BFLOAT16_VECTOR`
    
          - `INT8_VECTOR`
    
          - `BINARY_VECTOR`
    
      </div>
    
      <div>
    
          Applicable scalar fields:
    
          - `VARCHAR`
    
          - `INT8/16/32/64`
    
          - `FLOAT`
    
          - `DOUBLE`
    
          - `BOOL`
    
      </div>
    

    Mantenha o número de campos vectoriais, tanto ao nível da coleção como das Structs combinadas, não superior ou igual a 10.

  • Valores anuláveis e por defeito

    Um campo StructArray não é anulável e não aceita qualquer valor por defeito.

  • Função

    Não é possível utilizar uma função para derivar um campo vetorial de um campo escalar dentro de um Struct.

  • Tipo de índice e tipo métrico

    Todos os campos vectoriais de uma coleção devem ser indexados. Para indexar um campo vetorial dentro de um campo StructArray, Milvus utiliza uma lista de incorporação para organizar as incorporações vectoriais em cada elemento Struct e indexa toda a lista de incorporação como um todo.

    É possível utilizar AUTOINDEX ou HNSW como tipo de índice e qualquer tipo de métrica listada abaixo para criar índices para as listas de incorporação num campo StructArray.

    Tipo de índice

    Tipo de métrica

    Observações

    • AUTOINDEX

    • HNSW

    • IVF_FLAT

    • DISKANN

    • MAX_SIM_COSINE

    • MAX_SIM_IP

    • MAX_SIM_L2

    Para listas de incorporação dos seguintes tipos:

    • FLOAT_VECTOR

    • FLOAT16_VECTOR

    • BFLOAT16_VECTOR

    • INT8_VECTOR

    • BINARY_VECTOR

    Para obter detalhes sobre como Milvus calcula a similaridade entre a consulta e uma lista de incorporação, consulte Similaridade máxima.

    Os campos escalares no campo StructArray suportam os seguintes tipos de índices:

    • INVERTED

      Isso geralmente se aplica a filtros do tipo string ou categóricos, como structA[color] ou structA[str_val]. Para obter detalhes, consulte INVERTED.

    • STL_SORT

      Isto aplica-se normalmente à aceleração do tipo intervalo ou ordem em valores numéricos, como strctA[num_val]. Para mais pormenores, consulte STL_SORT.

  • Dados upsert

    As structs não suportam upsert no modo de fusão. No entanto, você ainda pode executar upserts no modo de substituição para atualizar dados em Structs. Para obter detalhes sobre as diferenças entre a inserção ascendente no modo de mesclagem e no modo de substituição, consulte Entidades de inserção ascendente.

  • Filtragem escalar

    Você pode usar filtros de elemento e operadores na família de correspondência para realizar a filtragem escalar em um subcampo escalar em um campo StructArray. Para obter detalhes, consulte Filtragem escalar em um campo StructArray.

Adicionar um StructArray

Para adicionar um campo StructArray no Milvus, é necessário definir um campo de matriz ao criar uma coleção e definir o tipo de dados para os seus elementos como Struct. O processo é o seguinte:

  1. Defina o tipo de dados de um campo para DataType.ARRAY ao adicionar o campo como um campo Array ao esquema da coleção.

  2. Defina o atributo element_type do campo como DataType.STRUCT para tornar o campo uma Matriz Struct.

  3. Crie um esquema Struct e inclua os campos necessários. Em seguida, faça referência ao esquema Struct no atributo struct_schema do campo.

  4. Defina o atributo max_capacity do campo com um valor adequado para especificar o número máximo de Structs que cada entidade pode conter neste campo.

  5. (Opcional) Pode definir mmap.enabled para qualquer campo dentro do elemento Struct para equilibrar os dados quentes e frios no Struct.

Veja como você pode definir um esquema de coleção que inclui um campo StructArray:

from pymilvus import MilvusClient, DataType

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

schema = client.create_schema()

# add the primary field to the collection
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True)

# add some scalar fields to the collection
schema.add_field(field_name="title", datatype=DataType.VARCHAR, max_length=512)
schema.add_field(field_name="author", datatype=DataType.VARCHAR, max_length=512)
schema.add_field(field_name="year_of_publication", datatype=DataType.INT64)

# add a vector field to the collection
schema.add_field(field_name="title_vector", datatype=DataType.FLOAT_VECTOR, dim=5)

# Create a struct schema
struct_schema = client.create_struct_field_schema()

# add a scalar field to the struct
struct_schema.add_field("text", DataType.VARCHAR, max_length=65535)
struct_schema.add_field("chapter", DataType.VARCHAR, max_length=512)

# add a vector field to the struct with mmap enabled
struct_schema.add_field("text_vector", DataType.FLOAT_VECTOR, mmap_enabled=True, dim=5)

# reference the struct schema in an Array field with its 
# element type set to `DataType.STRUCT`
schema.add_field("chunks", datatype=DataType.ARRAY, element_type=DataType.STRUCT, 
                    struct_schema=struct_schema, max_capacity=1000)
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

CreateCollectionReq.CollectionSchema collectionSchema = CreateCollectionReq.CollectionSchema.builder()
        .build();
collectionSchema.addField(AddFieldReq.builder()
        .fieldName("id")
        .dataType(DataType.Int64)
        .isPrimaryKey(true)
        .autoID(true)
        .build());
collectionSchema.addField(AddFieldReq.builder()
        .fieldName("title")
        .dataType(DataType.VarChar)
        .maxLength(512)
        .build());
collectionSchema.addField(AddFieldReq.builder()
        .fieldName("author")
        .dataType(DataType.VarChar)
        .maxLength(512)
        .build());
collectionSchema.addField(AddFieldReq.builder()
        .fieldName("year_of_publication")
        .dataType(DataType.Int64)
        .build());
collectionSchema.addField(AddFieldReq.builder()
        .fieldName("title_vector")
        .dataType(DataType.FloatVector)
        .dimension(5)
        .build());

Map<String, String> params = new HashMap<>();
params.put("mmap_enabled", "true");
collectionSchema.addField(AddFieldReq.builder()
        .fieldName("chunks")
        .dataType(DataType.Array)
        .elementType(DataType.Struct)
        .maxCapacity(1000)
        .addStructField(AddFieldReq.builder()
                .fieldName("text")
                .dataType(DataType.VarChar)
                .maxLength(65535)
                .build())
        .addStructField(AddFieldReq.builder()
                .fieldName("chapter")
                .dataType(DataType.VarChar)
                .maxLength(512)
                .build())
        .addStructField(AddFieldReq.builder()
                .fieldName("text_vector")
                .dataType(DataType.FloatVector)
                .dimension(VECTOR_DIM)
                .typeParams(params)
                .build())
        .build());
// go
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";

const milvusClient = new MilvusClient("http://localhost:19530");

const schema = [
  {
    name: "id",
    data_type: DataType.INT64,
    is_primary_key: true,
    auto_id: true,
  },
  {
    name: "title",
    data_type: DataType.VARCHAR,
    max_length: 512,
  },
  {
    name: "author",
    data_type: DataType.VARCHAR,
    max_length: 512,
  },
  {
    name: "year_of_publication",
    data_type: DataType.INT64,
  },
  {
    name: "title_vector",
    data_type: DataType.FLOAT_VECTOR,
    dim: 5,
  },
  {
    name: "chunks",
    data_type: DataType.ARRAY,
    element_type: DataType.STRUCT,
    fields: [
      {
        name: "text",
        data_type: DataType.VARCHAR,
        max_length: 65535,
      },
      {
        name: "chapter",
        data_type: DataType.VARCHAR,
        max_length: 512,
      },
      {
        name: "text_vector",
        data_type: DataType.FLOAT_VECTOR,
        dim: 5,
        mmap_enabled: true,
      },
    ],
    max_capacity: 1000,
  },
];
# restful
SCHEMA='{
  "autoID": true,
  "fields": [
    {
      "fieldName": "id",
      "dataType": "Int64",
      "isPrimary": true
    },
    {
      "fieldName": "title",
      "dataType": "VarChar",
      "elementTypeParams": { "max_length": "512" }
    },
    {
      "fieldName": "author",
      "dataType": "VarChar",
      "elementTypeParams": { "max_length": "512" }
    },
    {
      "fieldName": "year_of_publication",
      "dataType": "Int64"
    },
    {
      "fieldName": "title_vector",
      "dataType": "FloatVector",
      "elementTypeParams": { "dim": "5" }
    }
  ],
  "structArrayFields": [
    {
      "name": "chunks",
      "description": "Array of document chunks with text and vectors",
      "elementTypeParams":{
         "max_capacity": 1000
      },
      "fields": [
        {
          "fieldName": "text",
          "dataType": "VarChar",
          "elementTypeParams": { "max_length": "65535" }
        },
        {
          "fieldName": "chapter",
          "dataType": "VarChar",
          "elementTypeParams": { "max_length": "512" }
        },
        {
          "fieldName": "text_vector",
          "dataType": "FloatVector",
          "elementTypeParams": {
            "dim": "5",
            "mmap_enabled": "true"
          }
        }
      ]
    }
  ]
}'

As linhas destacadas no exemplo de código acima ilustram como incluir um StructArray em um esquema de coleção.

Definir parâmetros de índice

A indexação é obrigatória para todos os campos de vetor, incluindo os campos de vetor na coleção e aqueles definidos no elemento Struct.

Os parâmetros de índice aplicáveis variam consoante o tipo de índice. Para obter detalhes sobre os parâmetros de índice aplicáveis, consulte Índice explicado e a documentação do tipo de índice selecionado.

Indexar uma lista de incorporação

Para indexar uma lista de incorporação, você precisa definir seu tipo de índice como AUTOINDEX ou qualquer um dos tipos de índice aplicáveis listados acima e usar um tipo de métrica listado para Milvus para medir as semelhanças entre as listas de incorporação.

# Create index parameters
index_params = client.prepare_index_params()

# Create an index for the vector field in the collection
index_params.add_index(
    field_name="title_vector",
    index_type="AUTOINDEX",
    metric_type="L2",
)

# Create an index for the vector field in the element Struct
index_params.add_index(
    field_name="chunks[text_vector]",
    index_type="AUTOINDEX",
    metric_type="MAX_SIM_COSINE",
)
import io.milvus.v2.common.IndexParam;

List<IndexParam> indexParams = new ArrayList<>();
indexParams.add(IndexParam.builder()
        .fieldName("title_vector")
        .indexType(IndexParam.IndexType.AUTOINDEX)
        .metricType(IndexParam.MetricType.L2)
        .build());
indexParams.add(IndexParam.builder()
        .fieldName("chunks[text_vector]")
        .indexType(IndexParam.IndexType.AUTOINDEX)
        .metricType(IndexParam.MetricType.MAX_SIM_COSINE)
        .build());
// go
await milvusClient.createCollection({
  collection_name: "books",
  fields: schema,
});

const indexParams = [
  {
    field_name: "title_vector",
    index_type: "AUTOINDEX",
    metric_type: "L2",
  },
  {
    field_name: "chunks[text_vector]",
    index_type: "AUTOINDEX",
    metric_type: "MAX_SIM_COSINE",
  },
];
# restful
INDEX_PARAMS='[
  {
    "fieldName": "title_vector",
    "indexName": "title_vector_index",
    "indexType": "AUTOINDEX",
    "metricType": "L2"
  },
  {
    "fieldName": "chunks[text_vector]",
    "indexName": "chunks_text_vector_index",
    "indexType": "AUTOINDEX",
    "metricType": "MAX_SIM_COSINE"
  }
]'

Indexar um subcampo de struct escalar

Quando cria índices num subcampo struct escalar, o Milvus constrói o índice ao nível do elemento, não ao nível da linha, para acelerar a filtragem escalar.

O seguinte trecho de código cria um índice em um subcampo de struct escalar chamado chunks[text].

index_params.add_index(
    field_name="chunks[text]",
    index_type="INVERTED"
)
indexParams.add(IndexParam.builder()
        .fieldName("chunks[text]")
        .indexType(IndexParam.IndexType.INVERTED)
        .build());
// go
indexParams.push({
    field_name: "chunks[text]",
    index_type: "INVERTED"
})
INDEX_PARAMS += '{
    "fieldName": "chunks[text]",
    "indexName": "chunks_text_vector_index",
    "indexType": "INVERTED"
}'

Criar uma coleção

Quando o esquema e o índice estiverem prontos, você pode criar uma coleção que inclua um campo StructArray.

client.create_collection(
    collection_name="my_collection",
    schema=schema,
    index_params=index_params
)
import io.milvus.v2.service.collection.request.CreateCollectionReq;

CreateCollectionReq requestCreate = CreateCollectionReq.builder()
        .collectionName("my_collection")
        .collectionSchema(collectionSchema)
        .indexParams(indexParams)
        .build();
client.createCollection(requestCreate);
// go
await milvusClient.createCollection({
  collection_name: "my_collection",
  fields: schema,
  indexes: indexParams,
});
# restful
curl -X POST "http://localhost:19530/v2/vectordb/collections/create" \
  -H "Content-Type: application/json" \
  -d "{
    \"collectionName\": \"my_collection\",
    \"description\": \"A collection for storing book information with struct array chunks\",
    \"schema\": $SCHEMA,
    \"indexParams\": $INDEX_PARAMS
  }"

Inserir dados

Depois de criar a coleção, você pode inserir dados que incluem matrizes de Structs da seguinte forma.

# Sample data
data = {
    'title': 'Walden',
    'title_vector': [0.1, 0.2, 0.3, 0.4, 0.5],
    'author': 'Henry David Thoreau',
    'year_of_publication': 1845,
    'chunks': [
        {
            'text': 'When I wrote the following pages, or rather the bulk of them...',
            'text_vector': [0.3, 0.2, 0.3, 0.2, 0.5],
            'chapter': 'Economy',
        },
        {
            'text': 'I would fain say something, not so much concerning the Chinese and...',
            'text_vector': [0.7, 0.4, 0.2, 0.7, 0.8],
            'chapter': 'Economy'
        }
    ]
}

# insert data
client.insert(
    collection_name="my_collection",
    data=[data]
)
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import io.milvus.v2.service.vector.request.InsertReq;
import io.milvus.v2.service.vector.response.InsertResp;

Gson gson = new Gson();
JsonObject row = new JsonObject();
row.addProperty("title", "Walden");
row.add("title_vector", gson.toJsonTree(Arrays.asList(0.1f, 0.2f, 0.3f, 0.4f, 0.5f)));
row.addProperty("author", "Henry David Thoreau");
row.addProperty("year_of_publication", 1845);

JsonArray structArr = new JsonArray();
JsonObject struct1 = new JsonObject();
struct1.addProperty("text", "When I wrote the following pages, or rather the bulk of them...");
struct1.add("text_vector", gson.toJsonTree(Arrays.asList(0.3f, 0.2f, 0.3f, 0.2f, 0.5f)));
struct1.addProperty("chapter", "Economy");
structArr.add(struct1);
JsonObject struct2 = new JsonObject();
struct2.addProperty("text", "I would fain say something, not so much concerning the Chinese and...");
struct2.add("text_vector", gson.toJsonTree(Arrays.asList(0.7f, 0.4f, 0.2f, 0.7f, 0.8f)));
struct2.addProperty("chapter", "Economy");
structArr.add(struct2);

row.add("chunks", structArr);

InsertResp insertResp = client.insert(InsertReq.builder()
        .collectionName("my_collection")
        .data(Collections.singletonList(row))
        .build());
// go
  {
    id: 0,
    title: "Walden",
    title_vector: [0.1, 0.2, 0.3, 0.4, 0.5],
    author: "Henry David Thoreau",
    "year-of-publication": 1845,
    chunks: [
      {
        text: "When I wrote the following pages, or rather the bulk of them...",
        text_vector: [0.3, 0.2, 0.3, 0.2, 0.5],
        chapter: "Economy",
      },
      {
        text: "I would fain say something, not so much concerning the Chinese and...",
        text_vector: [0.7, 0.4, 0.2, 0.7, 0.8],
        chapter: "Economy",
      },
    ],
  },
];

await milvusClient.insert({
  collection_name: "books",
  data: data,
});
# restful
curl -X POST "http://localhost:19530/v2/vectordb/entities/insert" \
  -H "Content-Type: application/json" \
  -d '{
    "collectionName": "my_collection",
    "data": [
      {
        "title": "Walden",
        "title_vector": [0.1, 0.2, 0.3, 0.4, 0.5],
        "author": "Henry David Thoreau",
        "year_of_publication": 1845,
        "chunks": [
          {
            "text": "When I wrote the following pages, or rather the bulk of them...",
            "text_vector": [0.3, 0.2, 0.3, 0.2, 0.5],
            "chapter": "Economy"
          },
          {
            "text": "I would fain say something, not so much concerning the Chinese and...",
            "text_vector": [0.7, 0.4, 0.2, 0.7, 0.8],
            "chapter": "Economy"
          }
        ]
      }
    ]
  }'

Precisa de mais dados?

import json
import random
from typing import List, Dict, Any

# Real classic books (title, author, year)
BOOKS = [
    ("Pride and Prejudice", "Jane Austen", 1813),
    ("Moby Dick", "Herman Melville", 1851),
    ("Frankenstein", "Mary Shelley", 1818),
    ("The Picture of Dorian Gray", "Oscar Wilde", 1890),
    ("Dracula", "Bram Stoker", 1897),
    ("The Adventures of Sherlock Holmes", "Arthur Conan Doyle", 1892),
    ("Alice's Adventures in Wonderland", "Lewis Carroll", 1865),
    ("The Time Machine", "H.G. Wells", 1895),
    ("The Scarlet Letter", "Nathaniel Hawthorne", 1850),
    ("Leaves of Grass", "Walt Whitman", 1855),
    ("The Brothers Karamazov", "Fyodor Dostoevsky", 1880),
    ("Crime and Punishment", "Fyodor Dostoevsky", 1866),
    ("Anna Karenina", "Leo Tolstoy", 1877),
    ("War and Peace", "Leo Tolstoy", 1869),
    ("Great Expectations", "Charles Dickens", 1861),
    ("Oliver Twist", "Charles Dickens", 1837),
    ("Wuthering Heights", "Emily Brontë", 1847),
    ("Jane Eyre", "Charlotte Brontë", 1847),
    ("The Call of the Wild", "Jack London", 1903),
    ("The Jungle Book", "Rudyard Kipling", 1894),
]

# Common chapter names for classics
CHAPTERS = [
    "Introduction", "Prologue", "Chapter I", "Chapter II", "Chapter III",
    "Chapter IV", "Chapter V", "Chapter VI", "Chapter VII", "Chapter VIII",
    "Chapter IX", "Chapter X", "Epilogue", "Conclusion", "Afterword",
    "Economy", "Where I Lived", "Reading", "Sounds", "Solitude",
    "Visitors", "The Bean-Field", "The Village", "The Ponds", "Baker Farm"
]

# Placeholder text snippets (mimicking 19th-century prose)
TEXT_SNIPPETS = [
    "When I wrote the following pages, or rather the bulk of them...",
    "I would fain say something, not so much concerning the Chinese and...",
    "It is a truth universally acknowledged, that a single man in possession...",
    "Call me Ishmael. Some years ago—never mind how long precisely...",
    "It was the best of times, it was the worst of times...",
    "All happy families are alike; each unhappy family is unhappy in its own way.",
    "Whether I shall turn out to be the hero of my own life, or whether that station...",
    "You will rejoice to hear that no disaster has accompanied the commencement...",
    "The world is too much with us; late and soon, getting and spending...",
    "He was an old man who fished alone in a skiff in the Gulf Stream..."
]

def random_vector() -> List[float]:
    return [round(random.random(), 1) for _ in range(5)]

def generate_chunk() -> Dict[str, Any]:
    return {
        "text": random.choice(TEXT_SNIPPETS),
        "text_vector": random_vector(),
        "chapter": random.choice(CHAPTERS)
    }

def generate_record(record_id: int) -> Dict[str, Any]:
    title, author, year = random.choice(BOOKS)
    num_chunks = random.randint(1, 5)  # 1 to 5 chunks per book
    chunks = [generate_chunk() for _ in range(num_chunks)]
    return {
        "title": title,
        "title_vector": random_vector(),
        "author": author,
        "year_of_publication": year,
        "chunks": chunks
    }

# Generate 1000 records
data = [generate_record(i) for i in range(1000)]

# Insert the generated data
client.insert(collection_name="my_collection", data=data)

Pesquisa de vetor em um campo StructArray

É possível realizar pesquisas vetoriais nos campos vetoriais de uma coleção e em um StructArray.

Especificamente, você deve concatenar o nome do campo StructArray e os dos campos vetoriais de destino dentro dos elementos Struct como o valor do parâmetro anns_field em uma solicitação de pesquisa e usar EmbeddingList para organizar os vetores de consulta de forma organizada.

O Milvus fornece EmbeddingList para o ajudar a organizar melhor os vectores de consulta para pesquisas numa lista de incorporação num StructArray. Cada EmbeddingList contém pelo menos um vetor de incorporação e espera um número de entidades topK em troca.

No entanto, EmbeddingList só pode ser usado em pedidos search() sem pesquisa de intervalo ou parâmetros de pesquisa de agrupamento, muito menos em pedidos search_iterator().

from pymilvus.client.embedding_list import EmbeddingList

# each query embedding list triggers a single search
embeddingList1 = EmbeddingList()
embeddingList1.add([0.2, 0.9, 0.4, -0.3, 0.2])

embeddingList2 = EmbeddingList()
embeddingList2.add([-0.2, -0.2, 0.5, 0.6, 0.9])
embeddingList2.add([-0.4, 0.3, 0.5, 0.8, 0.2])

# a search with a single embedding list
results = client.search(
    collection_name="my_collection",
    data=[ embeddingList1 ],
    anns_field="chunks[text_vector]",
    search_params={"metric_type": "MAX_SIM_COSINE"},
    limit=3,
    output_fields=["chunks[text]"]
)
import io.milvus.v2.service.vector.request.data.EmbeddingList;
import io.milvus.v2.service.vector.request.data.FloatVec;

EmbeddingList embeddingList1 = new EmbeddingList();
embeddingList1.add(new FloatVec(new float[]{0.2f, 0.9f, 0.4f, -0.3f, 0.2f}));

EmbeddingList embeddingList2 = new EmbeddingList();
embeddingList2.add(new FloatVec(new float[]{-0.2f, -0.2f, 0.5f, 0.6f, 0.9f}));
embeddingList2.add(new FloatVec(new float[]{-0.4f, 0.3f, 0.5f, 0.8f, 0.2f}));

Map<String, Object> params = new HashMap<>();
params.put("metric_type", "MAX_SIM_COSINE");
SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .annsField("chunks[text_vector]")
        .data(Collections.singletonList(embeddingList1))
        .searchParams(params)
        .limit(3)
        .outputFields(Collections.singletonList("chunks[text]"))
        .build());
// go
const embeddingList1 = [[0.2, 0.9, 0.4, -0.3, 0.2]];
const embeddingList2 = [
  [-0.2, -0.2, 0.5, 0.6, 0.9],
  [-0.4, 0.3, 0.5, 0.8, 0.2],
];
const results = await milvusClient.search({
  collection_name: "books",
  data: embeddingList1,
  anns_field: "chunks[text_vector]",
  search_params: { metric_type: "MAX_SIM_COSINE" },
  limit: 3,
  output_fields: ["chunks[text]"],
});

# restful
embeddingList1='[[0.2,0.9,0.4,-0.3,0.2]]'
embeddingList2='[[-0.2,-0.2,0.5,0.6,0.9],[-0.4,0.3,0.5,0.8,0.2]]'
curl -X POST "http://localhost:19530/v2/vectordb/entities/search" \
  -H "Content-Type: application/json" \
  -d "{
    \"collectionName\": \"my_collection\",
    \"data\": [$embeddingList1],
    \"annsField\": \"chunks[text_vector]\",
    \"searchParams\": {\"metric_type\": \"MAX_SIM_COSINE\"},
    \"limit\": 3,
    \"outputFields\": [\"chunks[text]\"]
  }"

O pedido de pesquisa acima usa chunks[text_vector] para se referir ao campo text_vector em elementos Struct. Você pode usar essa sintaxe para definir os parâmetros anns_field e output_fields.

A saída seria uma lista das três entidades mais semelhantes.

Saída

# [
#     [
#         {
#             'id': 461417939772144945,
#             'distance': 0.9675756096839905,
#             'entity': {
#                 'chunks': [
#                     {'text': 'The world is too much with us; late and soon, getting and spending...'},
#                     {'text': 'All happy families are alike; each unhappy family is unhappy in its own way.'}
#                 ]
#             }
#         },
#         {
#             'id': 461417939772144965,
#             'distance': 0.9555778503417969,
#             'entity': {
#                 'chunks': [
#                     {'text': 'Call me Ishmael. Some years ago—never mind how long precisely...'},
#                     {'text': 'He was an old man who fished alone in a skiff in the Gulf Stream...'},
#                     {'text': 'When I wrote the following pages, or rather the bulk of them...'},
#                     {'text': 'It was the best of times, it was the worst of times...'},
#                     {'text': 'The world is too much with us; late and soon, getting and spending...'}
#                 ]
#             }
#         },
#         {
#             'id': 461417939772144962,
#             'distance': 0.9469035863876343,
#             'entity': {
#                 'chunks': [
#                     {'text': 'Call me Ishmael. Some years ago—never mind how long precisely...'},
#                     {'text': 'The world is too much with us; late and soon, getting and spending...'},
#                     {'text': 'He was an old man who fished alone in a skiff in the Gulf Stream...'},
#                     {'text': 'Call me Ishmael. Some years ago—never mind how long precisely...'},
#                     {'text': 'The world is too much with us; late and soon, getting and spending...'}
#                 ]
#             }
#         }
#     ]
# ]

Você também pode incluir várias listas de incorporação no parâmetro data para recuperar resultados de pesquisa para cada uma dessas listas de incorporação.

# a search with multiple embedding lists
results = client.search(
    collection_name="my_collection",
    data=[ embeddingList1, embeddingList2 ],
    anns_field="chunks[text_vector]",
    search_params={"metric_type": "MAX_SIM_COSINE"},
    limit=3,
    output_fields=["chunks[text]"]
)

print(results)
Map<String, Object> params = new HashMap<>();
params.put("metric_type", "MAX_SIM_COSINE");
SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .annsField("chunks[text_vector]")
        .data(Arrays.asList(embeddingList1, embeddingList2))
        .searchParams(params)
        .limit(3)
        .outputFields(Collections.singletonList("chunks[text]"))
        .build());
        
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
for (int i = 0; i < searchResults.size(); i++) {
    System.out.println("Results of No." + i + " embedding list");
    List<SearchResp.SearchResult> results = searchResults.get(i);
    for (SearchResp.SearchResult result : results) {
        System.out.println(result);
    }
}
// go
const results2 = await milvusClient.search({
  collection_name: "books",
  data: [embeddingList1, embeddingList2],
  anns_field: "chunks[text_vector]",
  search_params: { metric_type: "MAX_SIM_COSINE" },
  limit: 3,
  output_fields: ["chunks[text]"],
});
# restful
curl -X POST "http://localhost:19530/v2/vectordb/entities/search" \
  -H "Content-Type: application/json" \
  -d "{
    \"collectionName\": \"my_collection\",
    \"data\": [$embeddingList1, $embeddingList2],
    \"annsField\": \"chunks[text_vector]\",
    \"searchParams\": {\"metric_type\": \"MAX_SIM_COSINE\"},
    \"limit\": 3,
    \"outputFields\": [\"chunks[text]\"]
  }"

A saída seria uma lista das três entidades mais semelhantes para cada lista de incorporação.

Saída

# [
#   [
#     {
#       'id': 461417939772144945,
#       'distance': 0.9675756096839905,
#       'entity': {
#         'chunks': [
#           {'text': 'The world is too much with us; late and soon, getting and spending...'},
#           {'text': 'All happy families are alike; each unhappy family is unhappy in its own way.'}
#         ]
#       }
#     },
#     {
#       'id': 461417939772144965,
#       'distance': 0.9555778503417969,
#       'entity': {
#         'chunks': [
#           {'text': 'Call me Ishmael. Some years ago—never mind how long precisely...'},
#           {'text': 'He was an old man who fished alone in a skiff in the Gulf Stream...'},
#           {'text': 'When I wrote the following pages, or rather the bulk of them...'},
#           {'text': 'It was the best of times, it was the worst of times...'},
#           {'text': 'The world is too much with us; late and soon, getting and spending...'}
#         ]
#       }
#     },
#     {
#       'id': 461417939772144962,
#       'distance': 0.9469035863876343,
#       'entity': {
#         'chunks': [
#           {'text': 'Call me Ishmael. Some years ago—never mind how long precisely...'},
#           {'text': 'The world is too much with us; late and soon, getting and spending...'},
#           {'text': 'He was an old man who fished alone in a skiff in the Gulf Stream...'},
#           {'text': 'Call me Ishmael. Some years ago—never mind how long precisely...'},
#           {'text': 'The world is too much with us; late and soon, getting and spending...'}
#         ]
#       }
#     }
#   ],
#   [
#     {
#       'id': 461417939772144663,
#       'distance': 1.9761409759521484,
#       'entity': {
#         'chunks': [
#           {'text': 'It was the best of times, it was the worst of times...'},
#           {'text': 'It is a truth universally acknowledged, that a single man in possession...'},
#           {'text': 'Whether I shall turn out to be the hero of my own life, or whether that station...'},
#           {'text': 'He was an old man who fished alone in a skiff in the Gulf Stream...'}
#         ]
#       }
#     },
#     {
#       'id': 461417939772144692,
#       'distance': 1.974656581878662,
#       'entity': {
#         'chunks': [
#           {'text': 'It is a truth universally acknowledged, that a single man in possession...'},
#           {'text': 'Call me Ishmael. Some years ago—never mind how long precisely...'}
#         ]
#       }
#     },
#     {
#       'id': 461417939772144662,
#       'distance': 1.9406685829162598,
#       'entity': {
#         'chunks': [
#           {'text': 'It is a truth universally acknowledged, that a single man in possession...'}
#         ]
#       }
#     }
#   ]
# ]

No exemplo de código acima, embeddingList1 é uma lista de incorporação de um vetor, enquanto embeddingList2 contém dois vetores. Cada um aciona uma solicitação de pesquisa separada e espera uma lista das principais K entidades semelhantes.

Filtragem escalar num campo StructArray

Pode utilizar filtros de elementos e operadores na família de correspondências para efetuar a filtragem escalar num subcampo escalar de um StructArray. Para obter mais detalhes e exemplos sobre os dois tipos de operadores acima, consulte Operadores de matriz de Structs.

Filtros de elemento

Este é um filtro a nível de entidade que verifica se pelo menos um elemento no campo StructArray de uma entidade satisfaz o predicado. Por exemplo, o seguinte filtro de elemento retorna entidades que contêm pelo menos um pedaço que começa com "Red" no subcampo text.

element_filter(chunks, $[text] LIKE "Red%")

Você pode usar quase todos os operadores de comparação, intervalo e aritmética no predicado, que é avaliado por elemento, e os operadores lógicos podem ser usados para combinar várias condições no mesmo elemento. Para obter detalhes, consulte Operadores básicos.

Se várias expressões de filtragem escalar estiverem presentes em uma pesquisa filtrada ou em uma solicitação de consulta, coloque a expressão de filtro de elemento após todas as expressões de filtro em nível de entidade, conforme mostrado abaixo.

# correct
id > 0 && element_filter(chunks, $[x] > 1)

# incorrect, resulting errors
element_filter(chunks, $[x] > 1) && id > 0

Operadores de família de correspondência

Os operadores de família de correspondência também funcionam num campo StructArray. Em vez de simplesmente verificar se um elemento existe, é possível determinar quantos elementos (ou que proporção) devem satisfazer um predicado de elemento.

  • MATCH_ANY(chunks, $[text] LIKE "Red%")

    Isso retorna entidades que contêm pelo menos um pedaço que começa com "Red" no subcampo text; semanticamente, isso é equivalente a element_filter.

  • MATCH_ALL(chunks, $[text] LIKE "Red%")

    Isto devolve entidades cujos subcampos de texto em todos os pedaços começam com "Red".

  • MATCH_LEAST(chunks, $[text] LIKE "Red%", k)

    Isso retorna entidades que contêm pelo menos k pedaços que começam com "Red" no subcampo text.

  • MATCH_MOST(chunks, $[text] LIKE "Red%", k)

    Devolve entidades que contêm, no máximo, k pedaços que começam com "Red" no subcampo text.

  • MATCH_EXACT(chunks, $[text] LIKE "Red%", k)

    Isto devolve entidades que contêm exatamente k pedaços que começam com "Red" no subcampo text.

Próximas etapas

O desenvolvimento de um tipo de dados StructArray nativo representa um grande avanço na capacidade do Milvus de lidar com estruturas de dados complexas. Para entender melhor seus casos de uso e maximizar esse novo recurso, recomendamos a leitura de Schema Design Using an Array of Structs.