Skip to content

Note

This tutorial is valid for versions ≥0.2.0

Tutorial

In this tutorial you will create an index, train it with sample data, and perform searches — all using curl against the Aspected REST API.

Prerequisites

  • Aspected is running on http://localhost:8080 (see Getting Started).
  • If you want to follow the text-resolver section, make sure GGUF models are available (see Text Resolver).

Part 1 — Enum-only Index

We will build a simple product catalogue index using only enum resolvers. No model downloads are required for this part.

Step 1: Create the Index

Create an index called products with two enum aspects — category and colour:

curl -X PUT http://localhost:8080/indexes/products \
  -H "Content-Type: application/json" \
  -d '{
    "idSize": 36,
    "aspects": [
      {
        "name": "category",
        "type": "enum",
        "path": "$.category",
        "settings": {
          "values": ["electronics", "clothing", "food", "furniture", "toys"]
        }
      },
      {
        "name": "colour",
        "type": "enum",
        "path": "$.colour",
        "settings": {
          "values": ["red", "green", "blue", "black", "white", "yellow"]
        }
      }
    ]
  }'

Expected response:

{
  "data": { "created": true },
  "status": "ok"
}

During creation the HNSW parameters for an index can be configured (see HNSW Parameters). For this tutorial we will keep them to the default values.

Step 2: Inspect the Index

Retrieve the index metadata to see the computed dimensions and HNSW parameters:

curl http://localhost:8080/indexes/products

The response will include the full schema, the total number of dimensions (the sum of dimensions produced by each resolver), and the default HNSW configuration.

Step 3: Train the Index

Insert some sample products:

curl -X POST http://localhost:8080/indexes/products/docs \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      { "id": "prod-001", "doc": { "category": "electronics", "colour": "black" } },
      { "id": "prod-002", "doc": { "category": "electronics", "colour": "white" } },
      { "id": "prod-003", "doc": { "category": "clothing",    "colour": "red"   } },
      { "id": "prod-004", "doc": { "category": "clothing",    "colour": "blue"  } },
      { "id": "prod-005", "doc": { "category": "food",        "colour": "green" } },
      { "id": "prod-006", "doc": { "category": "furniture",   "colour": "white" } },
      { "id": "prod-007", "doc": { "category": "toys",        "colour": "red"   } },
      { "id": "prod-008", "doc": { "category": "toys",        "colour": "yellow"} },
      { "id": "prod-009", "doc": { "category": "electronics", "colour": "blue"  } },
      { "id": "prod-010", "doc": { "category": "furniture",   "colour": "black" } }
    ]
  }'

Expected response:

{
  "data": { "success": true },
  "status": "ok"
}

Each document is passed through the resolvers: the category field is extracted via $.category and mapped to a radial embedding by the enum resolver, and the same happens for colour. The two embeddings are concatenated to form the stored vector.

Step 4: Search — Single Aspect

Search for products in the electronics category. We only specify the category aspect, so the colour dimensions are treated as sparse (ignored):

curl -X POST http://localhost:8080/indexes/products/search \
  -H "Content-Type: application/json" \
  -d '{
    "k": 5,
    "query": {
      "category": "electronics"
    }
  }'

The response returns the nearest neighbours ranked by distance:

{
  "data": [
    { "id": "prod-001", "distance": 0.0 },
    { "id": "prod-002", "distance": 0.0 },
    { "id": "prod-009", "distance": 0.0 },
    { "id": "prod-003", "distance": 0.29 },
    { "id": "prod-004", "distance": 0.29 }
  ],
  "status": "ok"
}

All three electronics products appear first with a distance of 0 (exact match on that aspect).

Step 5: Search — Multiple Aspects

Now search for red toys — specifying both aspects:

curl -X POST http://localhost:8080/indexes/products/search \
  -H "Content-Type: application/json" \
  -d '{
    "k": 3,
    "query": {
      "category": "toys",
      "colour": "red"
    }
  }'

prod-007 (a red toy) should appear first with the smallest distance.

Step 6: Remove docs

Remove docs from the index

curl -X POST http://localhost:8080/indexes/products/docs \
  -H "Content-Type: application/json" \
  -d '{
    "data": [ { "id": "prod-001" }, { "id": "prod-009" } ]
   }'

Step 7: Update (upsert) docs

Define docs to update

curl -X POST http://localhost:8080/indexes/products/docs \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      { "id": "prod-003", "doc": { "category": "clothing",    "colour": "green"  } },
      { "id": "prod-004", "doc": { "category": "clothing",    "colour": "green"  } }
    ]
   }'

Step 8: Delete index

Delete the index when you are done:

curl -X DELETE http://localhost:8080/indexes/products

Part 2 — Adding a Text Resolver

This part extends the example by adding a text aspect for semantic search on product descriptions.

Model required

Make sure you have downloaded at least one GGUF embedding model and mounted it into the container. See Text Resolver — Downloading Models.

Step 1: Create the Index

Create a catalogue index with an enum aspect for category and a text aspect for description:

curl -X PUT http://localhost:8080/indexes/catalogue \
  -H "Content-Type: application/json" \
  -d '{
    "idSize": 36,
    "aspects": [
      {
        "name": "category",
        "type": "enum",
        "path": "$.category",
        "settings": {
          "values": ["electronics", "clothing", "outdoor", "kitchen"]
        },
        "multiplier": 5.0
      },
      {
        "name": "description",
        "type": "text",
        "path": "$.description",
        "settings": {
          "model": "nomic-embed-text-v1.5.f16.gguf"
        },
        "multiplier": 1.0
      }
    ]
  }'

Warning

The first time a model is used, Aspected loads it into memory. This may take a few seconds depending on the model size. Subsequent requests using the same model are instant.

Step 2: Train the Index

Feed some products with both a category and a free-text description:

curl -X POST http://localhost:8080/indexes/catalogue/docs \
  -H "Content-Type: application/json" \
  -d '{
    "data": [
      {
        "id": "item-001",
        "doc": {
          "category": "electronics",
          "description": "Wireless noise-cancelling headphones with 30-hour battery life"
        }
      },
      {
        "id": "item-002",
        "doc": {
          "category": "electronics",
          "description": "Portable Bluetooth speaker, waterproof and dustproof"
        }
      },
      {
        "id": "item-003",
        "doc": {
          "category": "clothing",
          "description": "Lightweight waterproof running jacket with reflective strips"
        }
      },
      {
        "id": "item-004",
        "doc": {
          "category": "outdoor",
          "description": "Compact camping tent for two people, easy to set up"
        }
      },
      {
        "id": "item-005",
        "doc": {
          "category": "outdoor",
          "description": "Insulated stainless steel water bottle, keeps drinks cold for 24 hours"
        }
      },
      {
        "id": "item-006",
        "doc": {
          "category": "kitchen",
          "description": "Non-stick frying pan with ceramic coating"
        }
      }
    ]
  }'

Search for products using only a text description — ignore the category entirely:

curl -X POST http://localhost:8080/indexes/catalogue/search \
  -H "Content-Type: application/json" \
  -d '{
    "k": 3,
    "query": {
      "description": "something to keep my drinks warm or cold"
    }
  }'

The text resolver converts the query into an embedding and finds the most semantically similar items. You should see item-005 (the insulated water bottle) near the top.

Search for outdoor items that are related to water:

curl -X POST http://localhost:8080/indexes/catalogue/search \
  -H "Content-Type: application/json" \
  -d '{
    "k": 3,
    "query": {
      "category": "outdoor",
      "description": "water"
    }
  }'

This combines the enum match on category with the semantic match on description, so results must be close on both aspects.

Step 5: Clean Up

curl -X DELETE http://localhost:8080/indexes/catalogue

Summary

Action Method Endpoint
List indexes GET /indexes
Create index PUT /indexes/{indexName}
Get index GET /indexes/{indexName}
Delete index DELETE /indexes/{indexName}
Search POST /indexes/{indexName}/search
List docs GET /indexes/{indexName}/docs
Create, update, delete docs POST /indexes/{indexName}/docs
Get server status GET /status

Info

When creating/updating/deleting docs, you may specify policies, e.g. policy: { duplicate: "Ignore", missing: "Fail" }, which change the behaviour when values are missing/duplicates. The allowable values are Ignore, Fail for both policies, and additionally Replace is allowed for the duplicate policy. The default duplicate policy is Replace, and the default missing policy is Ignore.

Note

To delete with the /indexes/{indexName}/docs endpoint, specify the doc(s) in data with IDs only.

You now know how to:

  1. Define an index schema using the available resolvers (enum, text, number, datetime, and raw).
  2. Train the index with structured documents.
  3. Perform sparse searches on any combination of aspects.
  4. Perform alterations to the index such as remove/update