Skip to content

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"
}

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/train \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      { "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: Clean Up

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/train \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      {
        "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
Create index PUT /indexes/{id}
Get index GET /indexes/{id}
List indexes GET /indexes
Delete index DELETE /indexes/{id}
Train POST /indexes/{id}/train
Search POST /indexes/{id}/search
Check exists GET /indexes/{id}/exists

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.