0Pricing
AI Engineering Academy · Lekcja

Generowanie embeddingów za pomocą OpenAI

Uczestnicy użyją modeli text-embedding-3-small i text-embedding-3-large do tworzenia embeddingów zdań, akapitów i dokumentów oraz porównają kompromisy między jakością a kosztem.

Generowanie embeddingów za pomocą OpenAI to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Przegląd modeli embeddingów OpenAI

OpenAI oferuje dwa produkcyjne modele embeddingów: text-embedding-3-small i text-embedding-3-large. Mały model generuje wektory o 1536 wymiarach i jest 5 razy tańszy w przeliczeniu na token, natomiast duży model generuje wektory o 3072 wymiarach i zapewnia wyższą dokładność w testach porównawczych. W większości zastosowań RAG mały model jest właściwym punktem wyjścia.

Pierwsze wywołanie embeddingu

Metoda client.embeddings.create() przyjmuje nazwę model oraz ciąg znaków lub listę w parametrze input. Zwraca obiekt odpowiedzi z listą data, w której każdy element ma atrybut embedding zawierający wektor jako pythonową listę liczb zmiennoprzecinkowych.

Klucz API należy zawsze przechowywać w zmiennej środowiskowej — nigdy nie należy wpisywać go bezpośrednio do plików źródłowych.

import os
from openai import OpenAI

client = OpenAI(api_key=os.environ['OPENAI_API_KEY'])

response = client.embeddings.create(
    model='text-embedding-3-small',
    input='What is retrieval-augmented generation?'
)

vector = response.data[0].embedding
print(f'Vector length: {len(vector)}')      # 1536
print(f'Type: {type(vector[0])}')           # float
print(f'Sample values: {vector[:3]}')       # [-0.02, 0.01, ...]

Wydajne przetwarzanie partii embeddingów

Przekazanie listy ciągów znaków do input tworzy embeddingi dla wszystkich elementów w ramach jednej podróży w obie strony przez sieć. Jest to zalecane podejście podczas indeksowania korpusu dokumentów. Lista data w odpowiedzi zachowuje pierwotną kolejność, dzięki czemu można łatwo zbudować słownik wyszukiwania.

Jedno żądanie może zawierać maksymalnie 2048 elementów, a łączna liczba tokenów we wszystkich danych wejściowych musi mieścić się w limicie tokenów modelu.

from openai import OpenAI

client = OpenAI()

documents = [
    'RAG stands for Retrieval-Augmented Generation.',
    'Embeddings convert text to numerical vectors.',
    'Pinecone is a managed vector database service.'
]

response = client.embeddings.create(
    model='text-embedding-3-small',
    input=documents
)

embeddings = [item.embedding for item in response.data]
print(f'Got {len(embeddings)} embeddings')
print(f'Each has {len(embeddings[0])} dimensions')

Zmniejszanie wymiarów embeddingów

Oba modele text-embedding-3 obsługują parametr dimensions, który skraca wyjściowy wektor. Na przykład ustawienie dimensions=256 zwraca wektor zawierający 256 elementów zamiast 1536. Krótsze wektory wymagają mniej miejsca i mniejszej mocy obliczeniowej, kosztem niewielkiego spadku dokładności.

Jest to przydatne podczas szybkich eksperymentów lub wtedy, gdy koszt przechowywania jest ważniejszy niż najwyższa jakość wyszukiwania.

from openai import OpenAI

client = OpenAI()

response = client.embeddings.create(
    model='text-embedding-3-small',
    input='Shorter vectors save storage and query time.',
    dimensions=256   # truncate from 1536 to 256
)

print(len(response.data[0].embedding))  # 256

Format kodowania: Base64 a lista liczb zmiennoprzecinkowych

Domyślnie API zwraca embeddingi jako tablicę liczb zmiennoprzecinkowych w formacie JSON (encoding_format='float'). Można zażądać wartości encoding_format='base64', aby otrzymać kompaktowy, zakodowany w formacie base64 binarny blok danych, który szybciej przesyła się przez sieć w przypadku dużych partii.

W przypadku base64 należy zdekodować dane za pomocą NumPy: np.frombuffer(base64.b64decode(b64_str), dtype='float32').

import base64
import numpy as np
from openai import OpenAI

client = OpenAI()

response = client.embeddings.create(
    model='text-embedding-3-small',
    input='Base64 encoding transfers faster.',
    encoding_format='base64'
)

b64 = response.data[0].embedding
vector = np.frombuffer(base64.b64decode(b64), dtype='float32')
print(f'Decoded {len(vector)} floats')

Śledzenie użycia tokenów i kosztów

Każda odpowiedź dotycząca embeddingu zawiera obiekt usage z polem prompt_tokens. Opłata jest naliczana za token: text-embedding-3-small kosztuje 0,02 USD za milion tokenów, a text-embedding-3-large — 0,13 USD za milion tokenów (według stanu na połowę 2024 roku).

Śledzenie użycia pozwala oszacować koszt zindeksowania całego korpusu przed rozpoczęciem przetwarzania produkcyjnego.

from openai import OpenAI

client = OpenAI()

texts = ['Document one content here.', 'Document two content here.']

response = client.embeddings.create(
    model='text-embedding-3-small',
    input=texts
)

tokens_used = response.usage.prompt_tokens
cost_usd = tokens_used * 0.00000002  # $0.02 per 1M tokens
print(f'Tokens: {tokens_used}, Cost: ${cost_usd:.6f}')

Porównanie text-embedding-3-small i large

Wybór między małym a dużym modelem zależy od wymagań dotyczących dokładności i dostępnego budżetu:

  • text-embedding-3-small: 1536D, szybszy, 5 razy tańszy, doskonały w większości obciążeń RAG
  • text-embedding-3-large: 3072D, wyższe wyniki w teście MTEB, lepszy w przypadku treści wielojęzycznych i złożonych zadań semantycznych

Często najpierw tworzy się prototyp i weryfikuje jakość wyszukiwania za pomocą małego modelu, a następnie przechodzi na duży model tylko wtedy, gdy metryki ewaluacji są niższe od założonego celu.

Normalizowanie embeddingów na potrzeby wyszukiwania za pomocą iloczynu skalarnego

OpenAI zwraca embeddingi znormalizowane L2, co oznacza, że każdy wektor ma już długość 1. Dzięki temu można używać iloczynu skalarnego jako szybkiego przybliżenia podobieństwa cosinusowego bez dodatkowego etapu normalizacji. Ma to znaczenie podczas przeszukiwania milionów wektorów, gdy drobne optymalizacje zaczynają się sumować.

import numpy as np
from openai import OpenAI

client = OpenAI()

response = client.embeddings.create(
    model='text-embedding-3-small',
    input='Are OpenAI embeddings normalized?'
)

vec = np.array(response.data[0].embedding)
magnitude = np.linalg.norm(vec)
print(f'Vector magnitude: {magnitude:.6f}')  # very close to 1.0

Obsługa dużych dokumentów

Model text-embedding-3-small przyjmuje maksymalnie 8191 tokenów na jedno wejście. Jeśli dokument przekracza ten limit, API zwróci błąd. Standardowym rozwiązaniem jest wcześniejsze podzielenie dokumentu na fragmenty (zwykle po 200–500 tokenów) i wygenerowanie osobnego embeddingu dla każdego fragmentu.

Dzielenie na fragmenty nie jest wyłącznie wymaganiem technicznym — poprawia także wyszukiwanie, ponieważ embeddingi mniejszych, skoncentrowanych fragmentów zwracają dokładniejsze wyniki niż embeddingi całych stron.

import tiktoken

enc = tiktoken.encoding_for_model('text-embedding-3-small')

def count_tokens(text):
    return len(enc.encode(text))

max_tokens = 8191
text = 'Very long document content...'  # pretend this is huge

if count_tokens(text) > max_tokens:
    print('Document too long — chunk before embedding')
else:
    print(f'Safe to embed: {count_tokens(text)} tokens')

Asynchroniczne generowanie embeddingów dla dużej przepustowości

Podczas indeksowania tysięcy dokumentów należy używać asynchronicznego klienta OpenAI wraz z asyncio.gather, aby wysyłać wiele żądań generowania embeddingów jednocześnie. Jest to znacznie szybsze niż wywołania sekwencyjne, ponieważ wąskim gardłem jest opóźnienie sieci, a nie procesor.

Podczas wykonywania żądań współbieżnych należy zawsze dodać obsługę limitu częstotliwości z wykładniczym zwiększaniem opóźnienia, aby uniknąć przekroczenia limitu tokenów na minutę.

import asyncio
from openai import AsyncOpenAI

async def embed_batch(texts):
    client = AsyncOpenAI()
    response = await client.embeddings.create(
        model='text-embedding-3-small',
        input=texts
    )
    return [item.embedding for item in response.data]

async def main():
    batches = [['doc1', 'doc2'], ['doc3', 'doc4']]
    results = await asyncio.gather(*[embed_batch(b) for b in batches])
    all_embeddings = [emb for batch in results for emb in batch]
    print(f'Embedded {len(all_embeddings)} documents')

asyncio.run(main())

Buforowanie embeddingów w celu obniżenia kosztów

Wielokrotne generowanie embeddingów dla tego samego tekstu powoduje niepotrzebne koszty i stratę czasu. Należy buforować embeddingi w słowniku, używając ciągu tekstowego (lub jego skrótu) jako klucza, a następnie zapisywać ten bufor na dysku między sesjami.

W systemach produkcyjnych należy przechowywać embeddingi w bazie wektorowej, która eliminuje duplikaty na podstawie identyfikatora dokumentu. Dokument należy ponownie przetwarzać tylko wtedy, gdy jego treść rzeczywiście się zmieniła, a nie przy każdym uruchomieniu indeksowania.

import json, hashlib, os
from openai import OpenAI

CACHE_FILE = '/tmp/embedding_cache.json'
client = OpenAI()

try:
    cache = json.load(open(CACHE_FILE))
except FileNotFoundError:
    cache = {}

def get_embedding(text):
    key = hashlib.md5(text.encode()).hexdigest()
    if key not in cache:
        r = client.embeddings.create(model='text-embedding-3-small', input=text)
        cache[key] = r.data[0].embedding
        json.dump(cache, open(CACHE_FILE, 'w'))
    return cache[key]

vec = get_embedding('Caching saves API costs.')
print(f'Retrieved {len(vec)}-dim embedding')

Szybki test

Sprawdź swoją znajomość zagadnień inżynierii AI z tej lekcji.

Podsumowanie lekcji

W tej lekcji poznali Państwo następujące zagadnienia: text-embedding-3-small to opłacalny wybór dla większości obciążeń RAG, generowanie embeddingów dla wielu tekstów w jednym wywołaniu API jest wydajniejsze niż wywołania sekwencyjne, a parametr dimensions może zmniejszyć rozmiar wektora, stanowiąc kompromis między dokładnością a oszczędnością miejsca. Następnie zbudujemy system wyszukiwania semantycznego wykorzystujący te embeddingi i NumPy.

Często zadawane pytania

Czy lekcja „Generowanie embeddingów za pomocą OpenAI” jest bezpłatna?

Tak — pełny tekst „Generowanie embeddingów za pomocą OpenAI” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Generowanie embeddingów za pomocą OpenAI”?

Uczestnicy użyją modeli text-embedding-3-small i text-embedding-3-large do tworzenia embeddingów zdań, akapitów i dokumentów oraz porównają kompromisy między jakością a kosztem. Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Generowanie embeddingów za pomocą OpenAI”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Czym są embeddingi wektorowe?
  2. Generowanie embeddingów za pomocą OpenAI
  3. Wyszukiwanie semantyczne z NumPy
  4. Grupowanie i wizualizacja embeddingów
← Powrót do AI Engineering Academy