# Build semantic search

Embed your notes through the exchange and search them by meaning.

In about 10 minutes, you embed six short notes, store the vectors in memory, and find the three notes that are closest in meaning to a question.

## Before you start

- A workspace with a balance and a `spend` key. See [API keys](https://inference.ai.exchange/docs/keys.md).
- Python 3.9 or newer.
- The key in your shell:

  ```bash
  export LIQUID_API_KEY=sk-liquid-...
  ```

## 1. Install the OpenAI SDK

The exchange has the shape of the OpenAI API, so the official client works without changes.

```bash
pip install openai
```

## 2. Write the search program

Save this as `search.py`. The program embeds the notes in one request. It then embeds the query and ranks the notes by cosine similarity.

```python
import math
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://router.inference.ai.exchange/v1",
    api_key=os.environ["LIQUID_API_KEY"],
)
MODEL = "text-embedding-3-small"

notes = [
    "Reset your password in Settings under Security.",
    "Invoices are sent by email on the first day of each month.",
    "The office closes at 6 pm on Fridays.",
    "To cancel a subscription, open Billing and choose Cancel plan.",
    "Our API returns a 429 error when you send requests too fast.",
    "New hires get a laptop on their first day.",
]

def embed(texts):
    result = client.embeddings.create(model=MODEL, input=texts)
    return [item.embedding for item in result.data]

def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)))

vectors = embed(notes)
query = "how do I change my password"
query_vector = embed([query])[0]

ranked = sorted(zip(notes, vectors), key=lambda p: cosine(query_vector, p[1]), reverse=True)
for note, vector in ranked[:3]:
    print(f"{cosine(query_vector, vector):.2f}  {note}")
```

## 3. Run it

```bash
python search.py
```

You see three lines, from the closest note to the third closest. The scores differ a little from run to run, because the exchange can buy from a different provider each time.

```text
0.62  Reset your password in Settings under Security.
0.21  To cancel a subscription, open Billing and choose Cancel plan.
0.17  Our API returns a 429 error when you send requests too fast.
```

## 4. Try your own query

Change `query` to `"when do I get a bill"` and run the program again. The invoice note now comes first, although the query has no word in common with it. This is the difference between search by meaning and search by keyword.

## Check it works

- The first line is the password note, with a score well above the other two.
- The program makes two embedding requests. Each request is one auction, with its own price cap.
- Your balance goes down by a very small amount. The **Credits** page lists the jobs.

## If something goes wrong

- `401`: the key is wrong, revoked or expired. Export it again.
- `KeyError: 'LIQUID_API_KEY'`: the shell does not hold the key. Run the `export` command in the same terminal.
- `model_not_found`: the model is not on the book. Run `curl 'https://router.inference.ai.exchange/v1/models?kind=embedding'` and use a name from the list.

## Next steps

- [Embeddings](https://inference.ai.exchange/docs/embeddings.md): the request fields, limits and price.
- [pgvector](https://inference.ai.exchange/docs/connect-pgvector.md), [Qdrant](https://inference.ai.exchange/docs/connect-qdrant.md) or [Pinecone](https://inference.ai.exchange/docs/connect-pinecone.md): store the vectors in a database, so you embed each note one time only.
- Keep one model for one index. If you change the model, embed all the notes again.
