> ## 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、余弦检索,以及一套完整的 Python RAG 流程

嵌入(embedding)就是把一段文本表示成一个向量(一串数字)。语义相近的文本,即使没有任何相同的词,得到的向量也相近。语义检索正是建立在这之上的:你匹配的是含义,而不是关键词。它也是 RAG(检索增强生成)的核心——让模型根据你自己的文档来回答。

RuAPI 的嵌入和聊天走同一个 OpenAI 兼容端点:**`https://www.ruapi.ai/v1`**,用的也是同一把 `sk-...` 密钥。代码里需要改的只有 `base_url`。

<Tip>
  还没有密钥?先看[快速开始](/zh/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. 把文本切成块(chunk)。** 块太长会稀释语义,太短又会丢失上下文。一般按段落,或者 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. 把检索到的块交给聊天模型。** 把最相关的几块放进系统提示词作为上下文,模型就会依据你的数据回答,而不是凭空猜测。

```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>

## 下一步

* [快速开始](/zh/quickstart) — 注册、拿密钥、发出第一个请求
* [GPT 模型](/zh/gpt-api) — 给 RAG 收尾作答的聊天模型怎么选
* [API 参考](/zh/api-reference) — 端点、协议与 `base_url` 的完整说明
* [LangChain](/zh/integrations/langchain) — 在 RuAPI 之上现成的链和 RAG,无需手写代码
