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_format | float (پیشفرض) یا base64 |
درخواست ساده
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": "هزینهی ارسال سفارش به شهرستان چقدر است؟"
}'پاسخ:
{
"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 مشخص میشود.
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 اجازه میدهند بردار کوتاهتری بگیرید. بردار کوتاهتر فضای کمتری در پایگاه داده میگیرد و جستجو را سریعتر میکند، با کمی کاهش دقت:
resp = client.embeddings.create(
model="openai/text-embedding-3-large",
input="جستجوی معنایی",
dimensions=1024,
)
print(len(resp.data[0].embedding)) # 1024همهی بردارهایی که با هم مقایسه میکنید باید با یک مدل و یک تعداد بُعد ساخته شده باشند. اگر مدل را عوض کردید، کل داده را دوباره embed کنید.
شباهت کسینوسی
برای مقایسهی دو بردار معمولاً از شباهت کسینوسی استفاده میشود. عدد نزدیک به ۱ یعنی معنای خیلی نزدیک. ادامهی مثال بالا:
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 استفاده کنید.
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، هم اسناد و هم پرسش کاربر را با یک روش یکسان نرمال کنید:
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 فقط بر اساس توکنهای ورودی است و معمولاً بسیار کمتر از مدلهای چت است. قیمت هر مدل را در صفحهی قیمتها ببینید.