> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ruapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Эмбеддинги и поиск (RAG)

> Как превратить текст в векторы через RuAPI: text-embedding-3-small, косинусный поиск и сквозной RAG на Python

Эмбеддинг — это представление текста в виде вектора (списка чисел). Близкие по смыслу фразы дают близкие векторы, даже если в них нет общих слов. На этом строится семантический поиск: вы ищете не по совпадению слов, а по смыслу. И именно это лежит в основе RAG (retrieval-augmented generation) — когда модель отвечает, опираясь на ваши документы.

Эмбеддинги в RuAPI работают через тот же OpenAI-совместимый эндпоинт, что и чат: **`https://www.ruapi.ai/v1`**, тот же ключ `sk-...`. Менять в коде нужно только `base_url`.

<Tip>
  Если вы ещё не получили ключ — начните с [Быстрого старта](/ru/quickstart). Дальше предполагается, что ключ `sk-...` у вас уже есть.
</Tip>

## Первый вектор

```python Python (OpenAI SDK) theme={null}
from openai import OpenAI

client = OpenAI(
    api_key="sk-ВАШ_КЛЮЧ",
    base_url="https://www.ruapi.ai/v1",
)

resp = client.embeddings.create(
    model="text-embedding-3-small",
    input="Москва — столица России",
)

vector = resp.data[0].embedding
print(len(vector))   # размерность вектора
print(vector[:5])    # первые пять чисел
```

`resp.data` — это список: на один входной текст приходится один элемент с полем `.embedding`. У `text-embedding-3-small` вектор содержит 1536 чисел.

Нужно качество повыше — возьмите **`text-embedding-3-large`** (3072 числа на вектор). Он точнее на сложных текстах, но дороже и медленнее. Для большинства задач `-small` достаточно.

## Сразу несколько текстов

Передавайте список в `input` — за один запрос вернётся вектор для каждого элемента. Это и быстрее, и дешевле, чем слать тексты по одному.

```python theme={null}
resp = client.embeddings.create(
    model="text-embedding-3-small",
    input=[
        "Как пополнить баланс?",
        "Минимальная сумма пополнения — 5 USDT.",
        "Кошка спит на подоконнике.",
    ],
)

vectors = [item.embedding for item in resp.data]
```

Порядок ответов совпадает с порядком входных текстов.

## Сквозной RAG за пять шагов

Идея простая: заранее разбиваем документы на куски и считаем для них векторы, а на запрос пользователя находим самые близкие куски и отдаём их чат-модели как контекст.

**1. Разбиваем текст на куски (chunks).** Слишком длинные куски «размывают» смысл, слишком короткие теряют контекст. Обычно берут абзацы или окна по 200–500 слов.

```python theme={null}
docs = [
    "RuAPI принимает оплату в USDT. Минимум — 5 USDT.",
    "Эмбеддинги и чат используют один ключ и один base_url.",
    "Базовый адрес для OpenAI-совместимых клиентов — https://www.ruapi.ai/v1",
]
```

**2. Считаем векторы и складываем их в хранилище.** На небольшом объёме хватит обычного списка в памяти. Для продакшена возьмите векторную базу — FAISS, pgvector или Chroma; они умеют быстро искать по миллионам векторов.

```python theme={null}
import numpy as np

emb = client.embeddings.create(model="text-embedding-3-small", input=docs)
index = np.array([item.embedding for item in emb.data])
```

**3. Считаем вектор запроса** — тем же эмбеддингом, что и документы (это важно, см. ниже).

```python theme={null}
question = "Сколько минимум нужно для пополнения?"
q = np.array(
    client.embeddings.create(
        model="text-embedding-3-small", input=question
    ).data[0].embedding
)
```

**4. Ищем ближайшие куски по косинусной близости.** Косинус измеряет угол между векторами: 1 — почти одинаковы, 0 — не связаны.

```python theme={null}
def cosine(a, b):
    return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))

scores = [cosine(q, v) for v in index]
top = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:2]
context = "\n".join(docs[i] for i in top)
```

**5. Отдаём найденное чат-модели.** Лучшие куски кладём в системный промпт как контекст — модель отвечает по делу, а не наугад. Под ответ подойдёт любая чат-модель тем же ключом — например, из [линейки GPT](/ru/gpt-api).

```python theme={null}
answer = client.chat.completions.create(
    model="claude-opus-4-8",
    messages=[
        {"role": "system", "content": f"Отвечай только по контексту:\n{context}"},
        {"role": "user", "content": question},
    ],
)

print(answer.choices[0].message.content)
```

Векторную базу из шага 2 считают один раз и переиспользуют — пересчитывать эмбеддинги на каждый запрос не нужно.

## Подводные камни

<AccordionGroup>
  <Accordion title="Одна и та же модель для индекса и для запроса">
    Векторы из `text-embedding-3-small` и `text-embedding-3-large` несравнимы между собой — у них разная размерность и разное пространство смыслов. Каким эмбеддингом построили индекс, тем же считайте и вектор запроса. Сменили модель — пересоберите весь индекс заново.
  </Accordion>

  <Accordion title="Считайте пачками через список в input">
    Отправлять тексты по одному — медленно и дороже по числу запросов. Передавайте список в `input` (обычно до нескольких сотен строк за раз) — ответы вернутся в том же порядке.
  </Accordion>

  <Accordion title="-small против -large: размерность и цена">
    `text-embedding-3-small` — 1536 чисел на вектор, дешевле и быстрее, подходит для большинства задач. `text-embedding-3-large` — 3072 числа, точнее на сложных и длинных текстах, но дороже и занимает вдвое больше места в хранилище. Начните с `-small` и переходите на `-large`, только если качества поиска не хватает.
  </Accordion>
</AccordionGroup>

## Что дальше

* [Быстрый старт](/ru/quickstart) — регистрация, ключ и первый запрос
* [Модели GPT](/ru/gpt-api) — выбор чат-модели, которая отвечает по найденному контексту
* [Справочник API](/ru/api-reference) — эндпоинты, протоколы и базовые адреса
* [LangChain](/ru/integrations/langchain) — готовые цепочки и RAG поверх RuAPI без ручного кода
