Array di strutture

Un campo Array of Structs, o campo StructArray, in un'entità memorizza un insieme ordinato di elementi Struct. Ogni struttura dell'array condivide lo stesso schema predefinito, che comprende vettori multipli e campi scalari.

Ecco un esempio di entità di una collezione che contiene un 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
}

Nell'esempio precedente, il campo chunks è un campo StructArray e ogni elemento Struct contiene i propri campi, ovvero text, text_vector e chapter.

Quando si usa

Le moderne applicazioni di intelligenza artificiale, dalla guida autonoma al recupero multimodale, si basano sempre più su dati eterogenei e annidati. I modelli di dati piatti tradizionali faticano a rappresentare relazioni complesse come"un documento con molti pezzi annotati" o"una scena di guida con più manovre osservate". È qui che il tipo di dati StructArray di Milvus si fa notare.

Per determinare rapidamente se il campo StructArray è adatto ai vostri scenari applicativi, considerate se:

  • I dati sono in una struttura gerarchica, come un documento con molti pezzi annotati.

  • Il risultato della ricerca dovrebbe essere il documento, piuttosto che i pezzi, come nell'esempio precedente.

  • I risultati della ricerca contengono molte entità duplicate e si fatica a recuperare i risultati finali utilizzando tecniche come il raggruppamento, la deduplicazione e il reranking.

Se le risposte alle domande precedenti sono affermative, si dovrebbe usare StructArray.

Limiti

  • Tipi di dati

    Quando si crea una collezione, è possibile utilizzare il tipo Struct come tipo di dati per gli elementi di un campo Array. Tuttavia, non è possibile aggiungere uno StructArray a una collezione esistente e Milvus non supporta l'uso del tipo Struct come tipo di dati per un campo della collezione.

    Le strutture di un campo Array condividono lo stesso schema, che deve essere definito quando si crea il campo Array.

    Uno schema Struct contiene sia vettori che campi scalari, come elencato di seguito:

    • Tipi di dati vettoriali applicabili: FLOAT_VECTOR, FLOAT16_VECTOR, BFLOAT16_VECTOR, INT8_VECTOR e BINARY_VECTOR.

    • Tipi di dati scalari applicabili: VARCHAR, INT8/16/32/64, FLOAT, DOUBLE, e BOOL.

    Mantenere il numero di campi vettoriali sia a livello di collezione che nelle Strutture combinate non superiore o uguale a 10.

  • Valori nulli e predefiniti

    Un campo StructArray non è nullable e non accetta alcun valore predefinito.

  • Funzione

    Non è possibile utilizzare una funzione per derivare un campo vettoriale da un campo scalare all'interno di una struttura.

  • Tipo di indice e tipo di metrica

    Tutti i campi vettoriali di un insieme devono essere indicizzati. Per indicizzare un campo vettoriale all'interno di un campo StructArray, Milvus utilizza un elenco di incorporazioni per organizzare le incorporazioni vettoriali in ogni elemento Struct e indicizza l'intero elenco di incorporazioni nel suo complesso.

    È possibile utilizzare AUTOINDEX o HNSW come tipo di indice e qualsiasi tipo di metrica elencata di seguito per costruire indici per gli elenchi di incorporazioni in un campo StructArray.

    Tipo di indice

    Tipo di metrica

    Osservazioni

    • AUTOINDEX

    • HNSW

    • IVF_FLAT

    • DISKANN

    • MAX_SIM_COSINE

    • MAX_SIM_IP

    • MAX_SIM_L2

    Per gli elenchi di incorporamento dei seguenti tipi:

    • FLOAT_VECTOR

    • FLOAT16_VECTOR

    • BFLOAT16_VECTOR

    • INT8_VECTOR

    • BINARY_VECTOR

    Per i dettagli su come Milvus calcola la somiglianza tra la query e un elenco di incorporazioni, fare riferimento a Somiglianza massima.

    I campi scalari del campo StructArray supportano i seguenti tipi di indice:

    • INVERTED

      Questo di solito si applica a filtri di tipo stringa o categorico, come structA[color] o structA[str_val]. Per i dettagli, fare riferimento a INVERTED.

    • STL_SORT

      Di solito si applica all'accelerazione di tipo range o order sui valori numerici, come strctA[num_val]. Per i dettagli, fare riferimento a STL_SORT.

  • Dati di tipo upsert

    Le strutture non supportano l'upsert in modalità merge. Tuttavia, è possibile eseguire l'upsert in modalità override per aggiornare i dati nelle strutture. Per maggiori dettagli sulle differenze tra l'upsert in modalità merge e quello in modalità override, fate riferimento a Upsert Entities.

  • Filtro scalare

    È possibile utilizzare i filtri degli elementi e gli operatori della famiglia match per eseguire un filtraggio scalare su un sottocampo scalare in un campo StructArray. Per ulteriori informazioni, consultare Filtraggio scalare in un campo StructArray.

Aggiungere una matrice di strutture

Per aggiungere un campo StructArray in Milvus, è necessario definire un campo array quando si crea una collezione e impostare il tipo di dati per i suoi elementi su Struct. La procedura è la seguente:

  1. Impostare il tipo di dati di un campo su DataType.ARRAY quando si aggiunge il campo come campo Array allo schema della collezione.

  2. Impostare l'attributo element_type del campo a DataType.STRUCT per rendere il campo una matrice Struct.

  3. Creare uno schema Struct e includere i campi richiesti. Quindi, fare riferimento allo schema Struct nell'attributo struct_schema del campo.

  4. Impostare l'attributo max_capacity del campo su un valore appropriato per specificare il numero massimo di strutture che ogni entità può contenere in questo campo.

  5. (Facoltativo) È possibile impostare mmap.enabled per qualsiasi campo all'interno dell'elemento Struct per bilanciare i dati caldi e freddi nella struttura.

Ecco come si può definire uno schema di raccolta che include un 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"
          }
        }
      ]
    }
  ]
}'

Le linee evidenziate nell'esempio di codice qui sopra illustrano come includere uno StructArray in uno schema di raccolta.

Impostare i parametri dell'indice

L'indicizzazione è obbligatoria per tutti i campi vettoriali, compresi quelli della collezione e quelli definiti nell'elemento Struct.

I parametri di indice applicabili variano a seconda del tipo di indice. Per i dettagli sui parametri di indice applicabili, consultare Indice spiegato e la documentazione del tipo di indice selezionato.

Indicizzare un elenco di incorporazioni

Per indicizzare un elenco di incorporazioni, è necessario impostare il suo tipo di indice su AUTOINDEX o su uno qualsiasi dei tipi di indice applicabili elencati sopra e utilizzare un tipo di metrica elencata per Milvus per misurare le somiglianze tra gli elenchi di incorporazioni.

# 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"
  }
]'

Indicizzare un sottocampo di una struttura scalare

Quando si creano indici su un sottocampo struct scalare, Milvus costruisce l'indice a livello di elemento e non di riga, per accelerare il filtraggio scalare.

Il seguente frammento di codice crea un indice su un sottocampo di struct scalare chiamato 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"
}'

Creare una collezione

Una volta che lo schema e l'indice sono pronti, è possibile creare una collezione che include un 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" \
  -H "Request-Timeout: 10" \
  -d "{
    \"collectionName\": \"my_collection\",
    \"description\": \"A collection for storing book information with struct array chunks\",
    \"schema\": $SCHEMA,
    \"indexParams\": $INDEX_PARAMS
  }"

Inserire i dati

Dopo aver creato la collezione, è possibile inserire i dati che includono array di strutture come segue.

# 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" \
  -H "Request-Timeout: 10" \
  -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"
          }
        ]
      }
    ]
  }'

Avete bisogno di altri dati?

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)

Ricerca vettoriale in un campo StructArray

È possibile eseguire ricerche vettoriali nei campi vettoriali di una collezione e in uno StructArray.

In particolare, è necessario concatenare il nome del campo StructArray e quelli dei campi vettoriali di destinazione all'interno degli elementi Struct come valore del parametro anns_field in una richiesta di ricerca e utilizzare EmbeddingList per organizzare ordinatamente i vettori della query.

Milvus mette a disposizione EmbeddingList per aiutare a organizzare in modo più ordinato i vettori di query per le ricerche su un elenco di incorporazioni in uno StructArray. Ogni EmbeddingList contiene almeno un embedding vettoriale e si aspetta in cambio un numero di entità topK.

Tuttavia, EmbeddingList può essere usato solo nelle richieste search() senza parametri di ricerca per intervallo o raggruppamento, per non parlare delle richieste 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" \
  -H "Request-Timeout: 10" \
  -d "{
    \"collectionName\": \"my_collection\",
    \"data\": [$embeddingList1],
    \"annsField\": \"chunks[text_vector]\",
    \"searchParams\": {\"metric_type\": \"MAX_SIM_COSINE\"},
    \"limit\": 3,
    \"outputFields\": [\"chunks[text]\"]
  }"

La richiesta di ricerca precedente utilizza chunks[text_vector] per fare riferimento al campo text_vector negli elementi Struct. Si può usare questa sintassi per impostare i parametri anns_field e output_fields.

L'output sarà un elenco delle tre entità più simili.

Output

# [
#     [
#         {
#             '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...'}
#                 ]
#             }
#         }
#     ]
# ]

È anche possibile includere più elenchi di incorporazioni nel parametro data per recuperare i risultati della ricerca per ciascuno di questi elenchi di incorporazioni.

# 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" \
  -H "Request-Timeout: 10" \
  -d "{
    \"collectionName\": \"my_collection\",
    \"data\": [$embeddingList1, $embeddingList2],
    \"annsField\": \"chunks[text_vector]\",
    \"searchParams\": {\"metric_type\": \"MAX_SIM_COSINE\"},
    \"limit\": 3,
    \"outputFields\": [\"chunks[text]\"]
  }"

L'output sarà un elenco delle tre entità più simili per ciascun elenco di incorporazioni.

L'output

# [
#   [
#     {
#       '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...'}
#         ]
#       }
#     }
#   ]
# ]

Nell'esempio di codice precedente, embeddingList1 è un elenco di incorporazioni di un vettore, mentre embeddingList2 contiene due vettori. Ciascuno di essi attiva una richiesta di ricerca separata e si aspetta un elenco di entità top-K simili.

Filtraggio scalare in un campo StructArray

È possibile utilizzare i filtri degli elementi e gli operatori della famiglia match per effettuare un filtraggio scalare su un sottocampo scalare di uno StructArray. Per maggiori dettagli ed esempi sui due tipi di operatori sopra citati, fare riferimento a Operatori di array di strutture.

Filtri di elementi

Si tratta di un filtro a livello di entità che verifica se almeno un elemento nel campo StructArray di un'entità soddisfa il predicato. Ad esempio, il seguente filtro elemento restituisce le entità che contengono almeno un chunk che inizia con "Red" nel sottocampo text.

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

È possibile utilizzare quasi tutti gli operatori di confronto, di intervallo e aritmetici nel predicato, che viene valutato per elemento, mentre gli operatori logici possono essere utilizzati per combinare più condizioni sullo stesso elemento. Per maggiori dettagli, vedere Operatori di base.

Se in una ricerca filtrata o in una richiesta di query sono presenti più espressioni di filtraggio scalare, posizionare l'espressione di filtro dell'elemento dopo tutte le espressioni di filtro a livello di entità, come mostrato di seguito.

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

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

Operatori della famiglia Match

Gli operatori della famiglia Match funzionano anche su un campo StructArray. Invece di verificare semplicemente se un elemento esiste, è possibile determinare quanti elementi (o quale proporzione) devono soddisfare un predicato di elemento.

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

    Restituisce le entità che contengono almeno un pezzo che inizia con "Red" nel sottocampo text; semanticamente, questo è equivalente a element_filter.

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

    Restituisce entità i cui sottocampi di testo in tutti i chunk iniziano con "Red".

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

    Restituisce le entità che contengono almeno k pezzi che iniziano con "Rosso" nel sottocampo text.

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

    Restituisce entità che contengono al massimo k pezzi che iniziano con "Rosso" nel sottocampo text.

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

    Restituisce le entità che contengono esattamente k pezzi che iniziano con "Rosso" nel sottocampo text.

Prossimi passi

Lo sviluppo di un tipo di dati nativo StructArray rappresenta un importante progresso nella capacità di Milvus di gestire strutture di dati complesse. Per comprendere meglio i casi d'uso e sfruttare al meglio questa nuova funzionalità, si consiglia di leggere Schema Design Using an Array of Structs.