> For the complete documentation index, see [llms.txt](https://docs.telys.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.telys.ai/embeddings/recipe-openai.md).

# OpenAI recipe

Wrap OpenAI's `text-embedding-3-small` (1536 dimensions) in a [`CallableEmbedder`](/embeddings/callable-byo.md) so Telys embeds documents and queries through the OpenAI API, while the vectors, index, and search stay on your machine.

{% hint style="warning" %}
Unlike the on-device embedders, this path **calls OpenAI over the network** and is billed per token by OpenAI. Your query and document text is sent to OpenAI; only the returned vectors are stored locally. For a fully offline pipeline, use an on-device embedder (see [Choosing an embedder](/embeddings/choosing-an-embedder.md)).
{% endhint %}

## Prerequisites

{% tabs %}
{% tab title="pip" icon="python" %}

```bash
pip install telys openai numpy
telys login            # free telys_developer tier
telys runtime verify   # confirm the signed runtime is active
```

{% endtab %}

{% tab title="pipx" icon="terminal" %}

```bash
pipx install telys
pip install openai numpy
telys login
telys runtime verify
```

{% endtab %}
{% endtabs %}

Set your OpenAI key in the environment; the OpenAI SDK reads it automatically:

```bash
export OPENAI_API_KEY="sk-..."
```

## The full script

```python
import numpy as np
from openai import OpenAI
from telys import Telys
from telys.embedding import CallableEmbedder, EmbeddingProfile

client = OpenAI()                       # reads OPENAI_API_KEY from the environment
MODEL = "text-embedding-3-small"
DIM = 1536

def embed(texts):
    resp = client.embeddings.create(model=MODEL, input=list(texts))
    vecs = [item.embedding for item in resp.data]
    return np.asarray(vecs, dtype="float32")

profile = EmbeddingProfile(
    provider="openai",
    model_id=MODEL,
    model_version="1",
    dimension=DIM,
    normalization="l2",                 # text-embedding-3-* returns unit vectors
    distance="ip",                      # inner product == cosine on unit vectors
)
embedder = CallableEmbedder(embed, profile)

ame = Telys("./telys-openai")
col = ame.create_collection("docs", dim=DIM, partition_by="scope",
                            filter_columns=("source",), embedder=embedder)

col.add_texts(
    texts=[
        "Telys runs entirely on-device.",
        "Vectors are stored locally and never uploaded.",
        "The signed runtime is verified before it loads.",
    ],
    ids=["a", "b", "c"],
    metadata=[
        {"scope": "demo", "source": "docs"},
        {"scope": "demo", "source": "docs"},
        {"scope": "demo", "source": "docs"},
    ],
)
col.save()

hits = col.search_text("where do my vectors live?", top_k=2, with_metadata=True)
for doc_id, score, meta in zip(hits["ids"], hits["scores"], hits["metadata"]):
    print(f"{doc_id}  {score:.3f}  {meta}")
```

## What you will see

The two most relevant documents, ranked by inner-product score, with their metadata:

```
b  0.612  {'source': 'docs'}
a  0.559  {'source': 'docs'}
```

Exact scores vary with the model, but the ranking is stable: `search_text` embeds the query through `embed` and returns the nearest stored vectors. With `with_metadata=True`, each hit carries back only the columns you declared in `filter_columns` (here `source`) — the partition key `scope` is stored separately and is not echoed into `metadata`. `add_texts` inserts new rows only; it raises `KeyError` if an id already exists — use `upsert_texts` to replace.

{% hint style="info" %}
`text-embedding-3-small` returns L2-normalized vectors, so `distance="ip"` behaves as cosine similarity. If you request fewer dimensions via OpenAI's `dimensions` parameter, set `EmbeddingProfile(dimension=...)` and the collection `dim` to that same number — they must agree.
{% endhint %}

## Reopen later

The collection stores the profile, so a fresh process can reopen it — register a matching provider under the profile's `model_id` so Telys re-attaches the embedder on open:

```python
ame = Telys("./telys-openai")
ame.register_provider(embedder.profile.model_id, embedder)
col = ame.open_collection("docs")
print(len(col))
```

## Next steps

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Recipe: BGE / sentence-transformers</strong></td><td>The same pattern, fully offline with a local model.</td><td><a href="/pages/ygPCwb0JT95fwoRUVC1g">/pages/ygPCwb0JT95fwoRUVC1g</a></td></tr><tr><td><strong>Choosing an embedder</strong></td><td>Cloud dense vs on-device lexical — pick the right one.</td><td><a href="/pages/XuNwRcXB5hZbgTGaTAau">/pages/XuNwRcXB5hZbgTGaTAau</a></td></tr><tr><td><strong>CallableEmbedder</strong></td><td>The BYO wrapper this recipe is built on.</td><td><a href="/pages/Xzd0BXJbxW07haNz0M1J">/pages/Xzd0BXJbxW07haNz0M1J</a></td></tr></tbody></table>

Next: keep everything on your machine with the [BGE recipe](/embeddings/recipe-bge.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.telys.ai/embeddings/recipe-openai.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
