# Embeddings

Buy vectors for text. The endpoint has the shape of the OpenAI embeddings API, so an unchanged OpenAI client works. You pay for the input tokens only, because an embedding job has no output price.

```bash
curl https://router.inference.ai.exchange/v1/embeddings \
  -H "Authorization: Bearer sk-liquid-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "text-embedding-3-small",
    "input": ["a cat on a bicycle", "a dog in a car"]
  }'
```

The response has the OpenAI shape. The vectors come back in the order of the texts, and each vector has an `index`. The exchange adds an `exchange` block that shows what the job bought and what it cost.

```json
{
  "object": "list",
  "model": "text-embedding-3-small",
  "data": [
    { "object": "embedding", "index": 0, "embedding": [0.0123, -0.0456] },
    { "object": "embedding", "index": 1, "embedding": [0.0789, 0.0012] }
  ],
  "usage": { "prompt_tokens": 9, "total_tokens": 9 },
  "exchange": { "...": "..." }
}
```

Use a key with the `spend` scope. See [API keys](https://inference.ai.exchange/docs/keys.md).

## Examples

**Search your documents.** Embed each document once and store the vectors. To search, embed the query with the same model, then return the documents whose vectors are nearest.

```python
docs = ["Reset your password in Settings.", "Invoices are sent on the 1st."]
vectors = client.embeddings.create(model="text-embedding-3-small", input=docs)
query = client.embeddings.create(model="text-embedding-3-small", input=["how do I change my password"])
```

**Find duplicates.** Embed a batch of texts in one request. Two texts whose vectors are very close say nearly the same thing.

```python
batch = client.embeddings.create(model="text-embedding-3-small", input=tickets)
vectors = [item.embedding for item in batch.data]  # in the order of `tickets`
```

## Use the OpenAI SDK

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://router.inference.ai.exchange/v1",
    api_key="sk-liquid-...",
)

result = client.embeddings.create(
    model="text-embedding-3-small",
    input=["a cat on a bicycle", "a dog in a car"],
)
print(len(result.data), len(result.data[0].embedding))
```

```typescript
import OpenAI from 'openai'

const client = new OpenAI({
  baseURL: 'https://router.inference.ai.exchange/v1',
  apiKey: process.env.LIQUID_API_KEY,
})

const result = await client.embeddings.create({
  model: 'text-embedding-3-small',
  input: ['a cat on a bicycle', 'a dog in a car'],
})
console.log(result.data.length, result.data[0]?.embedding.length)
```

## Find a model and its price

The model list and the prices are public. You need no key to read them.

```bash
curl 'https://router.inference.ai.exchange/v1/models?kind=embedding'
```

Each entry has the model `id` and `min_input_usd_per_mtok`, the lowest input price among the offers now on the book, in USD for each million tokens. To keep the models whose cheapest offer is at most USD 0.05, add `max_input_price`: `?kind=embedding&max_input_price=0.05`. To see how many independent providers serve a model, read `providers` in `GET /v1/exchange/models`. See [Market data](https://inference.ai.exchange/docs/market.md).

You name the model to buy. The exchange does not choose an embedding model for you. The router models and [Auto Router](https://inference.ai.exchange/docs/auto.md) cannot choose one, because they rank by scores that were measured on text. A preset of the same kind also works.

## What a job costs

The exchange auctions each request among the providers that serve the model that you named. The charge is the input rate of the offer that won, not `min_input_usd_per_mtok`. The receipt records the rate. A job runs in three steps:

1. The exchange estimates the tokens of your texts and fixes that figure at the input rate of the offer as the price cap, the highest charge for the job.
2. The exchange returns the cap in `x-liquid-cap-usd` and the job id in `x-liquid-job-id`.
3. The charge is the tokens that the provider reports. It never goes above the cap.

The exchange fails the job, and you pay nothing, if the provider sends one of these:

- a different number of vectors than texts,
- vectors of different lengths, or
- output tokens.

`exchange.limits.price.input_usd_per_mtok` limits the input price. A rule that limits output price or speed in tokens returns a `400` error. See [Purchasing rules](https://inference.ai.exchange/docs/rules.md).

## Keep one model for one index

Vectors from different models cannot be compared. A search index must hold vectors from one model, at one vector length. If you change the model, embed the whole corpus again.

The provider can change from one request to the next. Offers of one model can differ in precision, so their vectors can differ by a small amount. To keep an index stable:

- Use `dimensions` only for a model that supports a shorter vector.
- Use the same `dimensions` value for every request that feeds the index.

## Use a framework or a vector database

These guides show the exchange as the embedding source:

- [LangChain](https://inference.ai.exchange/docs/connect-langchain.md): `OpenAIEmbeddings`. Set `check_embedding_ctx_length=False`.
- [LlamaIndex](https://inference.ai.exchange/docs/connect-llamaindex.md): `OpenAILikeEmbedding`.
- [pgvector](https://inference.ai.exchange/docs/connect-pgvector.md): store and search vectors in Postgres.
- [Qdrant](https://inference.ai.exchange/docs/connect-qdrant.md): store and search vectors in a Qdrant collection.
- [Pinecone](https://inference.ai.exchange/docs/connect-pinecone.md): store and search vectors in a Pinecone index.

## Limits

- There is no streaming form. The exchange sends the batch complete.
- Embeddings do not use [response caching](https://inference.ai.exchange/docs/response-caching.md) or [prompt caching](https://inference.ai.exchange/docs/caching.md).

## Reference

### Request fields

| Field             | Description                                                                                    |
| ----------------- | ---------------------------------------------------------------------------------------------- |
| `model`           | The deployment to buy from. Required. The exchange does not choose an embedding model for you. |
| `input`           | One string, or an array of up to 2,048 strings. Required.                                      |
| `dimensions`      | The length of each vector, from 1 to 4,096.                                                    |
| `encoding_format` | `float` (the default) or `base64`.                                                             |
| `input_type`      | What the vectors are for: `search_query`, `search_document`, `classification` or `clustering`. |
| `exchange`        | Your purchasing rules. See [Purchasing rules](https://inference.ai.exchange/docs/rules.md).                                     |

Each string can have up to 32,768 characters. All strings together can have up to 1,200,000 characters. The exchange does not support token arrays or multimodal objects.

With `base64`, each vector is a string of little-endian 32-bit floats, as the OpenAI SDKs expect.

### Input type

A model that embeds a query and a document in different ways reads `input_type`. Only an offer that declared `embedding.input_type` can win a request that has it. Without the field, any offer can win. The model card lists `input_type` in `parameters` when an offer declares it.

```json
{ "model": "<model>", "input": "how do I cancel a job?", "input_type": "search_query" }
```

A value outside the four gets `400 invalid_request`. If no offer declared `embedding.input_type`, the answer is `400 unsupported_parameter` and `param` is `embedding.input_type`.

Multimodal input items are not supported yet.

### Vector length

A deployment that shortens its vectors serves a request up to its own length. Any other deployment serves only its own length. A request for a length that the deployment cannot serve goes to a different offer. It does not fail after the choice.

### Job kinds

A chat deployment never wins an embedding job. An embedding deployment never wins a chat job. The exclusion reason is `job_kind_not_served`.

## Next steps

- [Modalities](https://inference.ai.exchange/docs/modalities.md): images, speech and video.
- [Embeddings API reference](https://inference.ai.exchange/docs/api-embeddings.md): the full request and response schema.
