رفتن به محتوای اصلی
هوشوانا

Embeddings

ساخت بردار embedding برای متن فارسی و انگلیسی در هوشوانا: درخواست، ارسال دسته‌ای، کاهش ابعاد، محاسبه‌ی شباهت کسینوسی و نرمال‌سازی متن فارسی.

آخرین به‌روزرسانی:

embedding یک بردار عددی است که معنای متن را نشان می‌دهد. متن‌هایی که معنای نزدیک دارند، بردارهای نزدیک‌تری دارند. از embedding برای جستجوی معنایی، پیدا کردن متن‌های مشابه، دسته‌بندی، خوشه‌بندی و ساخت سامانه‌های RAG استفاده می‌شود.

POST https://hooshvana.ir/api/v1/embeddings

اگر فقط می‌خواهید مدل بر اساس اسناد شما جواب بدهد، شاید لازم نباشد خودتان embedding بسازید و پایگاه داده‌ی برداری راه بیندازید. پایگاه دانش هوشوانا همه‌ی این مراحل را برایتان انجام می‌دهد.

پارامترها

پارامترتوضیح
modelمثلاً openai/text-embedding-3-small (۱۵۳۶ بُعد) یا openai/text-embedding-3-large
inputیک رشته یا آرایه‌ای از رشته‌ها
dimensionsاختیاری. کاهش تعداد ابعاد خروجی در مدل‌های سری text-embedding-3
encoding_formatfloat (پیش‌فرض) یا base64

درخواست ساده

Shell
curl https://hooshvana.ir/api/v1/embeddings \
  -H "Authorization: Bearer $HOOSHVANA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/text-embedding-3-small",
    "input": "هزینه‌ی ارسال سفارش به شهرستان چقدر است؟"
  }'

پاسخ:

JSON
{
  "object": "list",
  "model": "openai/text-embedding-3-small",
  "data": [
    { "object": "embedding", "index": 0, "embedding": [0.0123, -0.0456, 0.0789] }
  ],
  "usage": { "prompt_tokens": 14, "total_tokens": 14 }
}

(آرایه‌ی embedding در عمل ۱۵۳۶ عدد دارد.)

ارسال دسته‌ای

به جای یک درخواست برای هر متن، چند متن را در یک آرایه بفرستید. این کار تعداد درخواست‌ها را کم می‌کند، سریع‌تر است و کمتر به محدودیت نرخ می‌خورد. ترتیب خروجی با فیلد index مشخص می‌شود.

Python
import os
from openai import OpenAI

client = OpenAI(base_url="https://hooshvana.ir/api/v1", api_key=os.environ["HOOSHVANA_API_KEY"])

def embed_all(texts, batch_size=100):
    vectors = []
    for start in range(0, len(texts), batch_size):
        batch = texts[start:start + batch_size]
        resp = client.embeddings.create(
            model="openai/text-embedding-3-small",
            input=batch,
        )
        # Sort by index to be safe, then keep only the vectors
        vectors.extend(item.embedding for item in sorted(resp.data, key=lambda d: d.index))
    return vectors

docs = [
    "ارسال به تهران یک روزه انجام می‌شود.",
    "ارسال به شهرستان‌ها دو تا چهار روز کاری زمان می‌برد.",
    "امکان مرجوعی کالا تا هفت روز وجود دارد.",
]
doc_vectors = embed_all(docs)

در هر دسته هم تعداد متن‌ها و هم مجموع توکن‌ها محدودیت دارد. دسته‌های ۵۰ تا ۲۰۰ متن کوتاه انتخاب امنی است. متن‌های خیلی طولانی را قبل از embedding تکه‌تکه کنید.

کاهش ابعاد

مدل‌های text-embedding-3 اجازه می‌دهند بردار کوتاه‌تری بگیرید. بردار کوتاه‌تر فضای کمتری در پایگاه داده می‌گیرد و جستجو را سریع‌تر می‌کند، با کمی کاهش دقت:

Python
resp = client.embeddings.create(
    model="openai/text-embedding-3-large",
    input="جستجوی معنایی",
    dimensions=1024,
)
print(len(resp.data[0].embedding))  # 1024

همه‌ی بردارهایی که با هم مقایسه می‌کنید باید با یک مدل و یک تعداد بُعد ساخته شده باشند. اگر مدل را عوض کردید، کل داده را دوباره embed کنید.

شباهت کسینوسی

برای مقایسه‌ی دو بردار معمولاً از شباهت کسینوسی استفاده می‌شود. عدد نزدیک به ۱ یعنی معنای خیلی نزدیک. ادامه‌ی مثال بالا:

Python
import math

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

query = "چند روز طول می‌کشد سفارش به اصفهان برسد؟"
q_vec = client.embeddings.create(
    model="openai/text-embedding-3-small", input=query
).data[0].embedding

ranked = sorted(
    zip(docs, doc_vectors),
    key=lambda pair: cosine(q_vec, pair[1]),
    reverse=True,
)
for text, vec in ranked:
    print(round(cosine(q_vec, vec), 3), text)

بردارهای مدل‌های OpenAI نرمال‌شده‌اند، پس ضرب داخلی ساده هم همان نتیجه را می‌دهد. برای داده‌ی زیاد، به جای حلقه‌ی پایتون از numpy یا یک پایگاه داده‌ی برداری مثل pgvector، Qdrant یا Milvus استفاده کنید.

JavaScript
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://hooshvana.ir/api/v1", apiKey: process.env.HOOSHVANA_API_KEY });

const { data } = await client.embeddings.create({
  model: "openai/text-embedding-3-small",
  input: ["خرید اقساطی", "پرداخت قسطی"],
});

const [a, b] = data.map((d) => d.embedding);
const dot = a.reduce((sum, x, i) => sum + x * b[i], 0);
console.log("similarity:", dot.toFixed(3));

نکته‌ی مهم برای متن فارسی: نرمال‌سازی

متن فارسی اغلب نویسه‌های عربی و فارسی را با هم دارد. مثلاً «ي» عربی به جای «ی» فارسی، یا «ك» به جای «ک». از دید مدل این‌ها نویسه‌های متفاوتی‌اند و می‌توانند کیفیت جستجو را کم کنند. قبل از embedding، هم اسناد و هم پرسش کاربر را با یک روش یکسان نرمال کنید:

Python
import re

ARABIC_TO_PERSIAN = str.maketrans({
    "ي": "ی", "ى": "ی", "ك": "ک", "ة": "ه",
    "٠": "0", "١": "1", "٢": "2", "٣": "3", "٤": "4",
    "٥": "5", "٦": "6", "٧": "7", "٨": "8", "٩": "9",
    "۰": "0", "۱": "1", "۲": "2", "۳": "3", "۴": "4",
    "۵": "5", "۶": "6", "۷": "7", "۸": "8", "۹": "9",
})

def normalize_fa(text: str) -> str:
    text = text.translate(ARABIC_TO_PERSIAN)
    text = re.sub(r"[\u064B-\u0652]", "", text)  # remove Arabic diacritics
    text = re.sub(r"[ \t]+", " ", text)
    return text.strip()

نیم‌فاصله (ZWNJ) را حذف نکنید، چون بخشی از املای درست کلمه است (مثل «می‌شود»). پایگاه دانش هوشوانا همین نرمال‌سازی را به‌طور خودکار انجام می‌دهد.

هزینه

هزینه‌ی embedding فقط بر اساس توکن‌های ورودی است و معمولاً بسیار کمتر از مدل‌های چت است. قیمت هر مدل را در صفحه‌ی قیمت‌ها ببینید.