Guides

Search Integration

Keyword search fails for food. A user searching "butter chicken" won't find "Murgh Makhani". Someone searching "something spicy" gets no results at all. Semantic search solves both.

Two approaches

Simple: send corpus each time

Best for small menus (under 100 items) or when you don't want to manage embeddings. The API embeds everything on each request.

import requests

API_KEY = "YOUR_KEY"
BASE = "https://embed.statode.com"
headers = {"X-API-Key": API_KEY, "Content-Type": "application/json"}

menu_items = ["Butter Chicken", "Dal Tadka", "Paneer Tikka", "Masala Dosa", "Chicken Biryani"]

resp = requests.post(f"{BASE}/search", headers=headers,
    json={"query": "something creamy and rich", "corpus": menu_items, "top_k": 5})

for result in resp.json()["results"]:
    print(f"{result['item']} (score: {result['score']:.3f})")

Pre-computed: embed once, search many

Best for large or static menus. Embed your menu once, store the vectors, and pass them with each search request.

# Step 1: Embed your menu once
embed_resp = requests.post(f"{BASE}/embed", headers=headers,
    json={"items": menu_items, "dimension": 384})
corpus_embeddings = embed_resp.json()["embeddings"]
# Store corpus_embeddings in your database

# Step 2: Search with pre-computed embeddings
search_resp = requests.post(f"{BASE}/search", headers=headers,
    json={
        "query": "spicy chicken",
        "corpus": menu_items,
        "corpus_embeddings": corpus_embeddings,
        "top_k": 10
    })

for result in search_resp.json()["results"]:
    print(f"{result['item']} (score: {result['score']:.3f})")

This skips the corpus embedding step on each search, making responses faster and cheaper.

Abstract queries

dish-embed handles abstract food queries that keyword search can never solve:

  • "something spicy" - returns dishes with chili, pepper, hot sauce
  • "lunch options" - returns mains, rice dishes, meal combos
  • "healthy food" - returns salads, grilled items, soups
  • "comfort food" - returns mac and cheese, biryani, ramen
  • "something sweet" - returns desserts, sweet drinks

These work because the model understands food concepts, not just word matching.

Updating embeddings when menu changes

You only need to re-embed items that changed. Keep a mapping of item text to embedding:

def update_embeddings(current_items, stored_items, stored_embeddings):
    """Re-embed only new or changed items."""
    new_items = [item for item in current_items if item not in stored_items]
    if not new_items:
        return stored_items, stored_embeddings

    resp = requests.post(f"{BASE}/embed", headers=headers,
        json={"items": new_items, "dimension": 384})
    new_embeddings = resp.json()["embeddings"]

    # Merge
    all_items = stored_items + new_items
    all_embeddings = stored_embeddings + new_embeddings
    return all_items, all_embeddings

Response fields

Each search result includes:

  • item - the corpus item text
  • score - relevance score (higher is better)
  • reranker_score - cross-encoder precision score (when available)
  • is_fallback - true if the result came from category-aware fallback

Results are sorted by reranker score when the search reranker is active, otherwise by cosine similarity.