SPB Git

Repository Public

embeddings.md 41.2 KB 1,358 lines markdown

1# Vector embeddings23> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.45## What are embeddings?67OpenAI’s text embeddings measure the relatedness of text strings. Embeddings are commonly used for:89- **Search** (where results are ranked by relevance to a query string)10- **Clustering** (where text strings are grouped by similarity)11- **Recommendations** (where items with related text strings are recommended)12- **Anomaly detection** (where outliers with little relatedness are identified)13- **Diversity measurement** (where similarity distributions are analyzed)14- **Classification** (where text strings are classified by their most similar label)1516An embedding is a vector (list) of floating point numbers. The [distance](#which-distance-function-should-i-use) between two vectors measures their relatedness. Small distances suggest high relatedness and large distances suggest low relatedness.1718Visit our [pricing page](https://openai.com/api/pricing/) to learn about embeddings pricing. Requests are billed based on the number of [tokens](https://platform.openai.com/tokenizer) in the [input](https://developers.openai.com/api/reference/resources/embeddings/methods/create#embeddings/create-input).1920## How to get embeddings2122To get an embedding, send your text string to the [embeddings API endpoint](https://developers.openai.com/api/reference/resources/embeddings) along with the embedding model name (e.g., `text-embedding-3-small`):2324Example: Getting embeddings2526```javascript27import OpenAI from "openai";28const openai = new OpenAI();2930const embedding = await openai.embeddings.create({31  model: "text-embedding-3-small",32  input: "Your text string goes here",33  encoding_format: "float",34});3536console.log(embedding);37```3839```python40from openai import OpenAI4142client = OpenAI()4344response = client.embeddings.create(45    input="Your text string goes here", model="text-embedding-3-small"46)4748print(response.data[0].embedding)49```5051```go52package main5354import (55	"context"56	"fmt"5758	"github.com/openai/openai-go/v3"59)6061func main() {62	client := openai.NewClient()6364	embedding, err := client.Embeddings.New(context.Background(), openai.EmbeddingNewParams{65		Model: openai.EmbeddingModelTextEmbedding3Small,66		Input: openai.EmbeddingNewParamsInputUnion{67			OfString: openai.String("Your text string goes here."),68		},69	})70	if err != nil {71		panic(err)72	}7374	fmt.Println(len(embedding.Data[0].Embedding))75}76```7778```java79import com.openai.client.OpenAIClient;80import com.openai.client.okhttp.OpenAIOkHttpClient;81import com.openai.models.embeddings.EmbeddingCreateParams;8283var embedding =84    client85        .embeddings()86        .create(87            EmbeddingCreateParams.builder()88                .model("text-embedding-3-small")89                .input("The food was delicious and the waiter...")90                .build());9192System.out.println(embedding.data().get(0).embedding());93```9495```csharp96using OpenAI.Embeddings;9798string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;99string model = "text-embedding-3-small";100EmbeddingClient client = new(model, key);101102OpenAIEmbedding embedding = await client.GenerateEmbeddingAsync(103    "The food was delicious and the waiter was friendly."104);105106Console.WriteLine($"Dimensions: {embedding.ToFloats().Length}");107```108109```ruby110require "openai"111112client = OpenAI::Client.new113114response = client.embeddings.create(115  model: "text-embedding-3-small",116  input: "The food was delicious and the waiter..."117)118119puts(response.data.fetch(0).embedding)120```121122```bash123curl https://api.openai.com/v1/embeddings \124  -H "Content-Type: application/json" \125  -H "Authorization: Bearer $OPENAI_API_KEY" \126  -d '{127    "input": "Your text string goes here",128    "model": "text-embedding-3-small"129  }'130```131132133The response contains the embedding vector (list of floating point numbers) along with some additional metadata. You can extract the embedding vector, save it in a vector database, and use for many different use cases.134135```json136{137  "object": "list",138  "data": [139    {140      "object": "embedding",141      "index": 0,142      "embedding": [143        -0.006929283495992422, -0.005336422007530928, -4.547132266452536e-5,144        -0.024047505110502243145      ]146    }147  ],148  "model": "text-embedding-3-small",149  "usage": {150    "prompt_tokens": 5,151    "total_tokens": 5152  }153}154```155156By default, the length of the embedding vector is `1536` for `text-embedding-3-small` or `3072` for `text-embedding-3-large`. To reduce the embedding's dimensions without losing its concept-representing properties, pass in the [dimensions parameter](https://developers.openai.com/api/reference/resources/embeddings/methods/create#embeddings-create-dimensions). Find more detail on embedding dimensions in the [embedding use case section](#use-cases).157158## Embedding models159160OpenAI offers two powerful third-generation embedding model (denoted by `-3` in the model ID). Read the embedding v3 [announcement blog post](https://openai.com/blog/new-embedding-models-and-api-updates) for more details.161162Usage is priced per input token. Below is an example of pricing pages of text per US dollar (assuming ~800 tokens per page):163164| Model                  | ~ Pages per dollar | Performance on [MTEB](https://github.com/embeddings-benchmark/mteb) eval | Max input |165| ---------------------- | ------------------ | ------------------------------------------------------------------------ | --------- |166| text-embedding-3-small | 62,500             | 62.3%                                                                    | 8192      |167| text-embedding-3-large | 9,615              | 64.6%                                                                    | 8192      |168| text-embedding-ada-002 | 12,500             | 61.0%                                                                    | 8192      |169170## Use cases171172Here we show some representative use cases, using the [Amazon fine-food reviews dataset](https://www.kaggle.com/snap/amazon-fine-food-reviews).173174### Obtaining the embeddings175176The dataset contains a total of 568,454 food reviews left by Amazon users up to October 2012. We use a subset of the 1000 most recent reviews for illustration purposes. The reviews are in English and tend to be positive or negative. Each review has a `ProductId`, `UserId`, `Score`, review title (`Summary`) and review body (`Text`). For example:177178179180181| Product Id | User Id        | Score | Summary               | Text                                              |182| ---------- | -------------- | ----- | --------------------- | ------------------------------------------------- |183| B001E4KFG0 | A3SGXH7AUHU8GW | 5     | Good Quality Dog Food | I have bought several of the Vitality canned...   |184| B00813GRG4 | A1D87F6ZCVE5NK | 1     | Not as Advertised     | Product arrived labeled as Jumbo Salted Peanut... |185186187188189Below, we combine the review summary and review text into a single combined text. The model encodes this combined text and output a single vector embedding.190191192193Get_embeddings_from_dataset.ipynb194```javascript195import { mkdir, writeFile } from "node:fs/promises";196import OpenAI from "openai";197198const client = new OpenAI();199const reviews = ["A rich cup of coffee.", "A bright herbal tea."];200201const response = await client.embeddings.create({202  model: "text-embedding-3-small",203  input: reviews.map((review) => review.replaceAll("\n", " ")),204});205206const csvField = (value) => `"${value.replaceAll('"', '""')}"`;207const rows = response.data.map(({ embedding }, index) =>208  [csvField(reviews[index]), csvField(JSON.stringify(embedding))].join(",")209);210211await mkdir("output", { recursive: true });212await writeFile(213  "output/embedded_1k_reviews.csv",214  ["combined,ada_embedding", ...rows].join("\n") + "\n"215);216```217218```python219from openai import OpenAI220221client = OpenAI()222223224def get_embedding(text, model="text-embedding-3-small"):225    text = text.replace("\n", " ")226    return client.embeddings.create(input=[text], model=model).data[0].embedding227228229df["ada_embedding"] = df.combined.apply(230    lambda x: get_embedding(x, model="text-embedding-3-small")231)232df.to_csv("output/embedded_1k_reviews.csv", index=False)233```234235```java236import com.openai.client.OpenAIClient;237import com.openai.client.okhttp.OpenAIOkHttpClient;238import com.openai.models.embeddings.EmbeddingCreateParams;239import java.io.IOException;240import java.nio.file.Files;241import java.nio.file.Path;242import java.util.List;243244static String csvField(String value) {245  return "\"" + value.replace("\"", "\"\"") + "\"";246}247248List<String> reviews = List.of("A rich cup of coffee.", "A bright herbal tea.");249Path output = Path.of("output", "embedded_1k_reviews.csv");250Files.createDirectories(output.getParent());251try (var writer = Files.newBufferedWriter(output)) {252  writer.write("combined,ada_embedding\n");253  for (String review : reviews) {254    var embedding =255        client256            .embeddings()257            .create(258                EmbeddingCreateParams.builder()259                    .model("text-embedding-3-small")260                    .inputOfArrayOfStrings(List.of(review.replace("\n", " ")))261                    .build())262            .data()263            .get(0)264            .embedding();265    writer.write(csvField(review) + "," + csvField(embedding.toString()) + "\n");266  }267}268System.out.println(output);269```270271```ruby272require "csv"273require "fileutils"274require "json"275require "openai"276277client = OpenAI::Client.new278reviews = ["A rich cup of coffee.", "A bright herbal tea."]279280response = client.embeddings.create(281  model: "text-embedding-3-small",282  input: reviews.map { |review| review.tr("\n", " ") }283)284285FileUtils.mkdir_p("output")286CSV.open("output/embedded_1k_reviews.csv", "w") do |csv|287  csv << ["combined", "ada_embedding"]288  response.data.each do |embedding|289    csv << [reviews.fetch(embedding.index), JSON.generate(embedding.embedding)]290  end291end292```293294295To load the data from a saved file, you can run the following:296297```python298import pandas as pd299300df = pd.read_csv("output/embedded_1k_reviews.csv")301df["ada_embedding"] = df.ada_embedding.apply(eval).apply(np.array)302```303304305306307#### Reducing embedding dimensions308309310311Using larger embeddings, for example storing them in a vector store for retrieval, generally costs more and consumes more compute, memory and storage than using smaller embeddings.312313Both of our new embedding models were trained [with a technique](https://arxiv.org/abs/2205.13147) that allows developers to trade-off performance and cost of using embeddings. Specifically, developers can shorten embeddings (i.e. remove some numbers from the end of the sequence) without the embedding losing its concept-representing properties by passing in the [`dimensions` API parameter](https://developers.openai.com/api/reference/resources/embeddings/methods/create#embeddings-create-dimensions). For example, on the MTEB benchmark, a `text-embedding-3-large` embedding can be shortened to a size of 256 while still outperforming an unshortened `text-embedding-ada-002` embedding with a size of 1536. You can read more about how changing the dimensions impacts performance in our [embeddings v3 launch blog post](https://openai.com/blog/new-embedding-models-and-api-updates#:~:text=Native%20support%20for%20shortening%20embeddings).314315In general, using the `dimensions` parameter when creating the embedding is the suggested approach. In certain cases, you may need to change the embedding dimension after you generate it. When you change the dimension manually, you need to be sure to normalize the dimensions of the embedding as is shown below.316317```javascript318import OpenAI from "openai";319320const client = new OpenAI();321322const response = await client.embeddings.create({323  model: "text-embedding-3-small",324  input: "Testing 123",325  encoding_format: "float",326});327328const shortened = response.data[0].embedding.slice(0, 256);329const magnitude = Math.hypot(...shortened);330const normalized = shortened.map((value) =>331  magnitude === 0 ? 0 : value / magnitude332);333334console.log(normalized);335```336337```python338from openai import OpenAI339import numpy as np340341client = OpenAI()342343344def normalize_l2(x):345    x = np.array(x)346    if x.ndim == 1:347        norm = np.linalg.norm(x)348        if norm == 0:349            return x350        return x / norm351    else:352        norm = np.linalg.norm(x, 2, axis=1, keepdims=True)353        return np.where(norm == 0, x, x / norm)354355356response = client.embeddings.create(357    model="text-embedding-3-small", input="Testing 123", encoding_format="float"358)359360cut_dim = response.data[0].embedding[:256]361norm_dim = normalize_l2(cut_dim)362363print(norm_dim)364```365366```java367import com.openai.client.OpenAIClient;368import com.openai.client.okhttp.OpenAIOkHttpClient;369import com.openai.models.embeddings.EmbeddingCreateParams;370import java.util.List;371372private static List<Double> normalizeL2(List<Float> embedding) {373  double norm = Math.sqrt(embedding.stream().mapToDouble(value -> value * value).sum());374  return embedding.stream().map(value -> norm == 0 ? 0.0 : value / norm).toList();375}376377var embedding =378    client379        .embeddings()380        .create(381            EmbeddingCreateParams.builder()382                .model("text-embedding-3-small")383                .input("Testing 123")384                .encodingFormat(EmbeddingCreateParams.EncodingFormat.FLOAT)385                .build());386387List<Float> shortened = embedding.data().get(0).embedding().subList(0, 256);388System.out.println(normalizeL2(shortened));389```390391```csharp392using OpenAI.Embeddings;393394string key = Environment.GetEnvironmentVariable("OPENAI_API_KEY")!;395string model = "text-embedding-3-small";396EmbeddingClient client = new(model, key);397398OpenAIEmbedding embedding = await client.GenerateEmbeddingAsync("Testing 123");399400float[] shortened = embedding.ToFloats().Span[..256].ToArray();401double magnitude = Math.Sqrt(shortened.Sum(value => value * value));402float[] normalized =403    magnitude == 0404        ? shortened405        : shortened.Select(value => (float)(value / magnitude)).ToArray();406407Console.WriteLine($"Dimensions: {normalized.Length}");408Console.WriteLine($"First value: {normalized[0]:F6}");409Console.WriteLine(410    $"L2 norm: {Math.Sqrt(normalized.Sum(value => value * value)):F3}"411);412```413414```ruby415require "openai"416417client = OpenAI::Client.new418419response = client.embeddings.create(420  model: "text-embedding-3-small",421  input: "Testing 123",422  encoding_format: :float423)424425shortened = response.data.fetch(0).embedding.first(256)426magnitude = Math.sqrt(shortened.sum { |value| value**2 })427normalized = shortened.map { |value| magnitude.zero? ? 0 : value / magnitude }428429puts(normalized)430```431432433Dynamically changing the dimensions enables very flexible usage. For example, when using a vector data store that only supports embeddings up to 1024 dimensions long, developers can now still use our best embedding model `text-embedding-3-large` and specify a value of 1024 for the `dimensions` API parameter, which will shorten the embedding down from 3072 dimensions, trading off some accuracy in exchange for the smaller vector size.434435436437438439440441#### Question answering using embeddings-based search442443444445446447  448449Question_answering_using_embeddings.ipynb450 There are many common cases where the model is not trained on data which contains key facts and information you want to make accessible when generating responses to a user query. One way of solving this, as shown below, is to put additional information into the context window of the model. This is effective in many use cases but leads to higher token costs. In this notebook, we explore the tradeoff between this approach and embeddings bases search.451452```javascript453import OpenAI from "openai";454455const client = new OpenAI();456const article =457  "At the 2022 Winter Olympics, Great Britain won women's curling and Sweden won men's curling.";458const question = `Use the article below to answer the question. If the answer cannot be found, say "I don't know."459460Article:461${article}462463Question: Which athletes won the gold medal in curling at the 2022 Winter Olympics?`;464465const response = await client.chat.completions.create({466  model: "gpt-4.1-mini",467  messages: [468    {469      role: "system",470      content: "You answer questions about the 2022 Winter Olympics.",471    },472    { role: "user", content: question },473  ],474  temperature: 0,475});476477console.log(response.choices[0].message.content);478```479480```python481query = f"""Use the below article on the 2022 Winter Olympics to answer the subsequent question. If the answer cannot be found, write "I don't know."482483Article:484\"\"\"485{wikipedia_article_on_curling}486\"\"\"487488Question: Which athletes won the gold medal in curling at the 2022 Winter Olympics?"""489490response = client.chat.completions.create(491    messages=[492        {493            "role": "system",494            "content": "You answer questions about the 2022 Winter Olympics.",495        },496        {"role": "user", "content": query},497    ],498    model=GPT_MODEL,499    temperature=0,500)501502print(response.choices[0].message.content)503```504505```java506import com.openai.client.OpenAIClient;507import com.openai.client.okhttp.OpenAIOkHttpClient;508import com.openai.models.chat.completions.ChatCompletionCreateParams;509510String article =511    "At the 2022 Winter Olympics, Great Britain won women's curling and Sweden won men's curling.";512String question =513    "Use the below article on the 2022 Winter Olympics to answer the subsequent question. "514        + "If the answer cannot be found, write \"I don't know.\"\n\n"515        + "Article:\n"516        + article517        + "\n\nQuestion: Which athletes won the gold medal in curling at the 2022 Winter Olympics?";518519ChatCompletionCreateParams params =520    ChatCompletionCreateParams.builder()521        .model("gpt-4.1-mini")522        .addSystemMessage("You answer questions about the 2022 Winter Olympics.")523        .addUserMessage(question)524        .temperature(0)525        .build();526527client.chat().completions().create(params).choices().stream()528    .flatMap(choice -> choice.message().content().stream())529    .forEach(System.out::println);530```531532```ruby533require "openai"534535client = OpenAI::Client.new536article = "At the 2022 Winter Olympics, Great Britain won women's curling and Sweden won men's curling."537question = <<~QUESTION538  Use the article below to answer the question. If the answer cannot be found, say "I don't know."539540  Article:541  #{article}542543  Question: Which athletes won the gold medal in curling at the 2022 Winter Olympics?544QUESTION545546response = client.chat.completions.create(547  model: "gpt-4.1-mini",548  messages: [549    {550      role: :system,551      content: "You answer questions about the 2022 Winter Olympics."552    },553    {554      role: :user,555      content: question556    }557  ],558  temperature: 0559)560561puts(response.choices.fetch(0).message.content)562```563564565566567568569570571#### Text search using embeddings572573574575576577  578579Semantic_text_search_using_embeddings.ipynb580 To retrieve the most relevant documents we use the cosine similarity between the embedding vectors of the query and each document, and return the highest scored documents.581582```javascript583import OpenAI from "openai";584585const client = new OpenAI();586const reviews = [587  "A rich cup of coffee.",588  "Smooth beans in tomato sauce.",589  "Dark chocolate with orange.",590];591592const { data } = await client.embeddings.create({593  model: "text-embedding-3-small",594  input: [...reviews, "delicious beans"],595});596597const query = data.at(-1).embedding;598const similarity = (embedding) => {599  const dotProduct = embedding.reduce(600    (total, value, index) => total + value * query[index],601    0602  );603  return dotProduct / (Math.hypot(...embedding) * Math.hypot(...query));604};605606const results = reviews607  .map((review, index) => ({608    review,609    score: similarity(data[index].embedding),610  }))611  .sort((left, right) => right.score - left.score)612  .slice(0, 3);613614console.log(results);615```616617```python618def search_reviews(df, product_description, n=3, pprint=True):619    embedding = get_embedding(product_description, model="text-embedding-3-small")620    df["similarities"] = df.ada_embedding.apply(621        lambda x: cosine_similarity(x, embedding)622    )623    res = df.sort_values("similarities", ascending=False).head(n)624    return res625626627res = search_reviews(df, "delicious beans", n=3)628```629630```java631import com.openai.client.OpenAIClient;632import com.openai.client.okhttp.OpenAIOkHttpClient;633import com.openai.models.embeddings.EmbeddingCreateParams;634import java.util.Comparator;635import java.util.List;636import java.util.stream.IntStream;637638List<String> reviews =639    List.of(640        "A rich cup of coffee.",641        "Smooth beans in tomato sauce.",642        "Dark chocolate with orange.");643var reviewEmbeddings =644    client645        .embeddings()646        .create(647            EmbeddingCreateParams.builder()648                .model("text-embedding-3-small")649                .inputOfArrayOfStrings(reviews)650                .build())651        .data();652List<Float> query =653    client654        .embeddings()655        .create(656            EmbeddingCreateParams.builder()657                .model("text-embedding-3-small")658                .inputOfArrayOfStrings(List.of("delicious beans"))659                .build())660        .data()661        .get(0)662        .embedding();663664IntStream.range(0, reviews.size())665    .boxed()666    .sorted(667        Comparator.comparingDouble(668                (Integer index) ->669                    cosineSimilarity(query, reviewEmbeddings.get(index).embedding()))670            .reversed())671    .limit(3)672    .map(reviews::get)673    .forEach(System.out::println);674```675676```ruby677require "openai"678679client = OpenAI::Client.new680reviews = [681  "A rich cup of coffee.",682  "Smooth beans in tomato sauce.",683  "Dark chocolate with orange."684]685686response = client.embeddings.create(687  model: "text-embedding-3-small",688  input: reviews + ["delicious beans"]689)690691query = response.data.fetch(-1).embedding692similarity = lambda do |embedding|693  dot_product = embedding.zip(query).sum { |value, query_value| value * query_value }694  magnitude = Math.sqrt(embedding.sum { |value| value**2 })695  query_magnitude = Math.sqrt(query.sum { |value| value**2 })696  dot_product / (magnitude * query_magnitude)697end698699results = reviews.map.with_index do |review, index|700  {701    review: review,702    score: similarity.call(response.data.fetch(index).embedding)703  }704end.sort_by { |result| -result.fetch(:score) }.first(3)705706puts(results)707```708709710711712713714715716#### Code search using embeddings717718719720721722  723724Code_search.ipynb725 Code search works similarly to embedding-based text search. We provide a method to extract Python functions from all the Python files in a given repository. Each function is then indexed by the `text-embedding-3-small` model.726727To perform a code search, we embed the query in natural language using the same model. Then we calculate cosine similarity between the resulting query embedding and each of the function embeddings. The highest cosine similarity results are most relevant.728729```javascript730import OpenAI from "openai";731732const client = new OpenAI();733const functions = [734  "function add(a, b) { return a + b; }",735  "function complete(prompt) { return prompt; }",736];737738const { data } = await client.embeddings.create({739  model: "text-embedding-3-small",740  input: [...functions, "Completions API tests"],741});742743const query = data.at(-1).embedding;744const similarity = (embedding) => {745  const dotProduct = embedding.reduce(746    (total, value, index) => total + value * query[index],747    0748  );749  return dotProduct / (Math.hypot(...embedding) * Math.hypot(...query));750};751752const results = functions753  .map((source, index) => ({754    source,755    score: similarity(data[index].embedding),756  }))757  .sort((left, right) => right.score - left.score);758759console.log(results);760```761762```python763df["code_embedding"] = df["code"].apply(764    lambda x: get_embedding(x, model="text-embedding-3-small")765)766767768def search_functions(df, code_query, n=3, pprint=True, n_lines=7):769    embedding = get_embedding(code_query, model="text-embedding-3-small")770    df["similarities"] = df.code_embedding.apply(771        lambda x: cosine_similarity(x, embedding)772    )773774    res = df.sort_values("similarities", ascending=False).head(n)775    return res776777778res = search_functions(df, "Completions API tests", n=3)779```780781```java782import com.openai.client.OpenAIClient;783import com.openai.client.okhttp.OpenAIOkHttpClient;784import com.openai.models.embeddings.EmbeddingCreateParams;785import java.util.Comparator;786import java.util.List;787import java.util.stream.IntStream;788789List<String> functions =790    List.of("def add(a, b): return a + b", "def complete(prompt): return prompt");791var functionEmbeddings =792    client793        .embeddings()794        .create(795            EmbeddingCreateParams.builder()796                .model("text-embedding-3-small")797                .inputOfArrayOfStrings(functions)798                .build())799        .data();800List<Float> query =801    client802        .embeddings()803        .create(804            EmbeddingCreateParams.builder()805                .model("text-embedding-3-small")806                .input("Completions API tests")807                .build())808        .data()809        .get(0)810        .embedding();811IntStream.range(0, functions.size())812    .boxed()813    .sorted(814        Comparator.comparingDouble(815                (Integer index) ->816                    cosineSimilarity(query, functionEmbeddings.get(index).embedding()))817            .reversed())818    .map(functions::get)819    .forEach(System.out::println);820```821822```ruby823require "openai"824825client = OpenAI::Client.new826functions = [827  "function add(a, b) { return a + b; }",828  "function complete(prompt) { return prompt; }"829]830831response = client.embeddings.create(832  model: "text-embedding-3-small",833  input: functions + ["Completions API tests"]834)835836query = response.data.fetch(-1).embedding837similarity = lambda do |embedding|838  dot_product = embedding.zip(query).sum { |value, query_value| value * query_value }839  magnitude = Math.sqrt(embedding.sum { |value| value**2 })840  query_magnitude = Math.sqrt(query.sum { |value| value**2 })841  dot_product / (magnitude * query_magnitude)842end843844results = functions.map.with_index do |source, index|845  {846    source: source,847    score: similarity.call(response.data.fetch(index).embedding)848  }849end.sort_by { |result| -result.fetch(:score) }850851puts(results)852```853854855856857858859860861#### Recommendations using embeddings862863864865866867  868869Recommendation_using_embeddings.ipynb870 Because shorter distances between embedding vectors represent greater similarity, embeddings can be useful for recommendation.871872Below, we illustrate a basic recommender. It takes in a list of strings and one 'source' string, computes their embeddings, and then returns a ranking of the strings, ranked from most similar to least similar. As a concrete example, the linked notebook below applies a version of this function to the [AG news dataset](http://groups.di.unipi.it/~gulli/AG_corpus_of_news_articles.html) (sampled down to 2,000 news article descriptions) to return the top 5 most similar articles to any given source article.873874```javascript875import OpenAI from "openai";876877const client = new OpenAI();878const strings = [879  "A cheetah is a fast land animal.",880  "A peregrine falcon is a fast bird.",881  "A tortoise moves slowly.",882];883884const { data } = await client.embeddings.create({885  model: "text-embedding-3-small",886  input: strings,887});888889const query = data[0].embedding;890const recommendations = data891  .map(({ embedding }, index) => {892    const dotProduct = embedding.reduce(893      (total, value, dimension) => total + value * query[dimension],894      0895    );896    const similarity =897      dotProduct / (Math.hypot(...embedding) * Math.hypot(...query));898    return { index, text: strings[index], similarity };899  })900  .sort((left, right) => right.similarity - left.similarity);901902console.log(recommendations);903```904905```python906def recommendations_from_strings(907    strings: list[str],908    index_of_source_string: int,909    model="text-embedding-3-small",910) -> list[int]:911    """Return nearest neighbors of a given string."""912913    # get embeddings for all strings914    embeddings = [embedding_from_string(string, model=model) for string in strings]915916    # get the embedding of the source string917    query_embedding = embeddings[index_of_source_string]918919    # get distances between the source embedding and other embeddings (function from embeddings_utils.py)920    distances = distances_from_embeddings(921        query_embedding, embeddings, distance_metric="cosine"922    )923924    # get indices of nearest neighbors (function from embeddings_utils.py)925    indices_of_nearest_neighbors = indices_of_nearest_neighbors_from_distances(926        distances927    )928    return indices_of_nearest_neighbors929```930931```java932import com.openai.client.OpenAIClient;933import com.openai.client.okhttp.OpenAIOkHttpClient;934import com.openai.models.embeddings.EmbeddingCreateParams;935import java.util.Comparator;936import java.util.List;937import java.util.stream.IntStream;938939List<String> strings =940    List.of(941        "A cheetah is a fast land animal.",942        "A peregrine falcon is a fast bird.",943        "A tortoise moves slowly.");944945var embeddings =946    client947        .embeddings()948        .create(949            EmbeddingCreateParams.builder()950                .model("text-embedding-3-small")951                .inputOfArrayOfStrings(strings)952                .build())953        .data();954955List<Float> query = embeddings.get(0).embedding();956var nearestNeighbors =957    IntStream.range(0, embeddings.size())958        .boxed()959        .sorted(960            Comparator.comparingDouble(961                (Integer index) -> {962                  List<Float> candidate = embeddings.get(index).embedding();963                  double dotProduct = 0;964                  double queryMagnitude = 0;965                  double candidateMagnitude = 0;966                  for (int dimension = 0; dimension < query.size(); dimension++) {967                    dotProduct += query.get(dimension) * candidate.get(dimension);968                    queryMagnitude += query.get(dimension) * query.get(dimension);969                    candidateMagnitude += candidate.get(dimension) * candidate.get(dimension);970                  }971                  return 1 - dotProduct / Math.sqrt(queryMagnitude * candidateMagnitude);972                }))973        .toList();974975System.out.println(nearestNeighbors);976```977978```ruby979require "openai"980981client = OpenAI::Client.new982strings = [983  "A cheetah is a fast land animal.",984  "A peregrine falcon is a fast bird.",985  "A tortoise moves slowly."986]987988response = client.embeddings.create(989  model: "text-embedding-3-small",990  input: strings991)992993query = response.data.fetch(0).embedding994similarity = lambda do |embedding|995  dot_product = embedding.zip(query).sum { |value, query_value| value * query_value }996  magnitude = Math.sqrt(embedding.sum { |value| value**2 })997  query_magnitude = Math.sqrt(query.sum { |value| value**2 })998  dot_product / (magnitude * query_magnitude)999end10001001recommendations = response.data.map.with_index do |embedding, index|1002  {1003    index: index,1004    text: strings.fetch(index),1005    similarity: similarity.call(embedding.embedding)1006  }1007end.sort_by { |recommendation| -recommendation.fetch(:similarity) }10081009puts(recommendations)1010```101110121013101410151016101710181019#### Data visualization in 2D102010211022102310241025  10261027Visualizing_embeddings_in_2D.ipynb1028 The size of the embeddings varies with the complexity of the underlying model. In order to visualize this high dimensional data we use the t-SNE algorithm to transform the data into two dimensions.10291030We color the individual reviews based on the star rating which the reviewer has given:10311032- 1-star: red1033- 2-star: dark orange1034- 3-star: gold1035- 4-star: turquoise1036- 5-star: dark green10371038The visualization seems to have produced roughly 3 clusters, one of which has mostly negative reviews.10391040```python1041import numpy as np1042import pandas as pd1043from sklearn.manifold import TSNE1044import matplotlib.pyplot as plt1045import matplotlib10461047df = pd.read_csv("output/embedded_1k_reviews.csv")1048matrix = np.array(df.ada_embedding.apply(eval).to_list())10491050# Create a t-SNE model and transform the data1051tsne = TSNE(1052    n_components=2, perplexity=15, random_state=42, init="random", learning_rate=2001053)1054vis_dims = tsne.fit_transform(matrix)10551056colors = ["red", "darkorange", "gold", "turquoise", "darkgreen"]1057x = [x for x, y in vis_dims]1058y = [y for x, y in vis_dims]1059color_indices = df.Score.values - 110601061colormap = matplotlib.colors.ListedColormap(colors)1062plt.scatter(x, y, c=color_indices, cmap=colormap, alpha=0.3)1063plt.title("Amazon ratings visualized in language using t-SNE")1064```106510661067106810691070107110721073#### Embedding as a text feature encoder for ML algorithms107410751076107710781079  10801081Regression_using_embeddings.ipynb1082 An embedding can be used as a general free-text feature encoder within a machine learning model. Incorporating embeddings will improve the performance of any machine learning model, if some of the relevant inputs are free text. An embedding can also be used as a categorical feature encoder within a ML model. This adds most value if the names of categorical variables are meaningful and numerous, such as job titles. Similarity embeddings generally perform better than search embeddings for this task.10831084We observed that generally the embedding representation is very rich and information dense. For example, reducing the dimensionality of the inputs using SVD or PCA, even by 10%, generally results in worse downstream performance on specific tasks.10851086This code splits the data into a training set and a testing set, which will be used by the following two use cases, namely regression and classification.10871088```python1089from sklearn.model_selection import train_test_split10901091X_train, X_test, y_train, y_test = train_test_split(1092    list(df.ada_embedding.values), df.Score, test_size=0.2, random_state=421093)1094```109510961097#### Regression using the embedding features10981099Embeddings present an elegant way of predicting a numerical value. In this example we predict the reviewer’s star rating, based on the text of their review. Because the semantic information contained within embeddings is high, the prediction is decent even with very few reviews.11001101We assume the score is a continuous variable between 1 and 5, and allow the algorithm to predict any floating point value. The ML algorithm minimizes the distance of the predicted value to the true score, and achieves a mean absolute error of 0.39, which means that on average the prediction is off by less than half a star.11021103```python1104from sklearn.ensemble import RandomForestRegressor11051106rfr = RandomForestRegressor(n_estimators=100)1107rfr.fit(X_train, y_train)1108preds = rfr.predict(X_test)1109```111011111112111311141115111611171118#### Classification using the embedding features111911201121112211231124  11251126Classification_using_embeddings.ipynb1127 This time, instead of having the algorithm predict a value anywhere between 1 and 5, we will attempt to classify the exact number of stars for a review into 5 buckets, ranging from 1 to 5 stars.11281129After the training, the model learns to predict 1 and 5-star reviews much better than the more nuanced reviews (2-4 stars), likely due to more extreme sentiment expression.11301131```python1132from sklearn.ensemble import RandomForestClassifier1133from sklearn.metrics import classification_report, accuracy_score11341135clf = RandomForestClassifier(n_estimators=100)1136clf.fit(X_train, y_train)1137preds = clf.predict(X_test)1138```113911401141114211431144114511461147#### Zero-shot classification114811491150115111521153  11541155Zero-shot_classification_with_embeddings.ipynb1156 We can use embeddings for zero shot classification without any labeled training data. For each class, we embed the class name or a short description of the class. To classify some new text in a zero-shot manner, we compare its embedding to all class embeddings and predict the class with the highest similarity.11571158```javascript1159import OpenAI from "openai";11601161const client = new OpenAI();1162const labels = ["negative", "positive"];11631164const { data } = await client.embeddings.create({1165  model: "text-embedding-3-small",1166  input: [...labels, "The coffee arrived quickly and tastes great."],1167});11681169const review = data.at(-1).embedding;1170const similarity = (embedding) => {1171  const dotProduct = embedding.reduce(1172    (total, value, index) => total + value * review[index],1173    01174  );1175  return dotProduct / (Math.hypot(...embedding) * Math.hypot(...review));1176};11771178const [negative, positive] = data.map(({ embedding }) => similarity(embedding));1179console.log(positive > negative ? "positive" : "negative");1180```11811182```python1183df = df[df.Score != 3]1184df["sentiment"] = df.Score.replace(1185    {1: "negative", 2: "negative", 4: "positive", 5: "positive"}1186)11871188labels = ["negative", "positive"]1189label_embeddings = [get_embedding(label, model=model) for label in labels]119011911192def label_score(review_embedding, label_embeddings):1193    return cosine_similarity(review_embedding, label_embeddings[1]) - cosine_similarity(1194        review_embedding, label_embeddings[0]1195    )119611971198prediction = (1199    "positive" if label_score(get_embedding("Sample Review", model=model), label_embeddings) > 0 else "negative"1200)1201```12021203```java1204import com.openai.client.OpenAIClient;1205import com.openai.client.okhttp.OpenAIOkHttpClient;1206import com.openai.models.embeddings.EmbeddingCreateParams;1207import java.util.List;12081209var embeddings =1210    client1211        .embeddings()1212        .create(1213            EmbeddingCreateParams.builder()1214                .model("text-embedding-3-small")1215                .inputOfArrayOfStrings(List.of("negative", "positive", "Sample Review"))1216                .build())1217        .data();12181219List<Float> review = embeddings.get(2).embedding();1220double negative = cosineSimilarity(review, embeddings.get(0).embedding());1221double positive = cosineSimilarity(review, embeddings.get(1).embedding());1222System.out.println(positive > negative ? "positive" : "negative");1223```12241225```ruby1226require "openai"12271228client = OpenAI::Client.new1229labels = ["negative", "positive"]12301231response = client.embeddings.create(1232  model: "text-embedding-3-small",1233  input: labels + ["The coffee arrived quickly and tastes great."]1234)12351236review = response.data.fetch(-1).embedding1237similarity = lambda do |embedding|1238  dot_product = embedding.zip(review).sum { |value, review_value| value * review_value }1239  magnitude = Math.sqrt(embedding.sum { |value| value**2 })1240  review_magnitude = Math.sqrt(review.sum { |value| value**2 })1241  dot_product / (magnitude * review_magnitude)1242end12431244negative, positive = response.data.first(2).map do |embedding|1245  similarity.call(embedding.embedding)1246end1247puts((positive > negative) ? "positive" : "negative")1248```124912501251125212531254125512561257#### Obtaining user and product embeddings for cold-start recommendation125812591260126112621263  12641265User_and_product_embeddings.ipynb1266 We can obtain a user embedding by averaging over all of their reviews. Similarly, we can obtain a product embedding by averaging over all the reviews about that product. In order to showcase the usefulness of this approach we use a subset of 50k reviews to cover more reviews per user and per product.12671268We evaluate the usefulness of these embeddings on a separate test set, where we plot similarity of the user and product embedding as a function of the rating. Interestingly, based on this approach, even before the user receives the product we can predict better than random whether they would like the product.12691270```python1271user_embeddings = df.groupby("UserId").ada_embedding.apply(np.mean)1272prod_embeddings = df.groupby("ProductId").ada_embedding.apply(np.mean)1273```127412751276127712781279128012811282#### Clustering128312841285128612871288  12891290Clustering.ipynb1291 Clustering is one way of making sense of a large volume of textual data. Embeddings are useful for this task, as they provide semantically meaningful vector representations of each text. Thus, in an unsupervised way, clustering will uncover hidden groupings in our dataset.12921293In this example, we discover four distinct clusters: one focusing on dog food, one on negative reviews, and two on positive reviews.12941295```python1296import numpy as np1297from sklearn.cluster import KMeans12981299matrix = np.vstack(df.ada_embedding.values)1300n_clusters = 413011302kmeans = KMeans(n_clusters=n_clusters, init="k-means++", random_state=42)1303kmeans.fit(matrix)1304df["Cluster"] = kmeans.labels_1305```1306130713081309131013111312## FAQ13131314### How can I tell how many tokens a string has before I embed it?13151316In Python, you can split a string into tokens with OpenAI's tokenizer [`tiktoken`](https://github.com/openai/tiktoken).13171318Example code:13191320```python1321import tiktoken132213231324def num_tokens_from_string(string: str, encoding_name: str) -> int:1325    """Returns the number of tokens in a text string."""1326    encoding = tiktoken.get_encoding(encoding_name)1327    num_tokens = len(encoding.encode(string))1328    return num_tokens132913301331num_tokens_from_string("tiktoken is great!", "cl100k_base")1332```133313341335For third-generation embedding models like `text-embedding-3-small`, use the `cl100k_base` encoding.13361337More details and example code are in the OpenAI Cookbook guide [how to count tokens with tiktoken](https://developers.openai.com/cookbook/examples/how_to_count_tokens_with_tiktoken).13381339### How can I retrieve K nearest embedding vectors quickly?13401341For searching over many vectors quickly, we recommend using a vector database. You can find examples of working with vector databases and the OpenAI API [in our Cookbook](https://developers.openai.com/cookbook/examples/vector_databases/readme) on GitHub.13421343### Which distance function should I use?13441345We recommend [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity). The choice of distance function typically doesn't matter much.13461347OpenAI embeddings are normalized to length 1, which means that:13481349- Cosine similarity can be computed slightly faster using just a dot product1350- Cosine similarity and Euclidean distance will result in the identical rankings13511352### Can I share my embeddings online?13531354Yes, customers own their input and output from our models, including in the case of embeddings. You are responsible for ensuring that the content you input to our API does not violate any applicable law or our [Terms of Use](https://openai.com/policies/terms-of-use).13551356### Do V3 embedding models know about recent events?13571358No, the `text-embedding-3-large` and `text-embedding-3-small` models lack knowledge of events that occurred after September 2021. This is generally not as much of a limitation as it would be for text generation models but in certain edge cases it can reduce performance.
Files main
Menu

Theme