A Step-by-Step Guide to Semantic Search Using OpenSearch and OpenAI

In today’s world, building an intelligent and context-aware search system has become a priority for businesses across various industries. Traditional search solutions based on keywords often fail to capture the intent behind user queries. However, by combining OpenSearch with OpenAI embeddings, we can create a search system that understands the meaning of the query, enabling more relevant results. This guide will show you how to integrate OpenSearch with OpenAI embeddings in Java to build an efficient, semantic search system.

What is OpenSearch?

OpenSearch is an open-source, distributed search and analytics engine designed for large-scale, high-performance searches. It is a fork of Elasticsearch, with enhanced features and improved security, maintained by the community and Amazon Web Services (AWS). OpenSearch allows you to store, search, and analyze large volumes of data quickly and in real-time. It’s widely used for log and event data analysis, full-text search, and application monitoring.

Key Features of OpenSearch:

  • Distributed Search: Handles massive datasets with high scalability.
  • Full-Text Search: Supports powerful search capabilities, including keyword matching and phrase search.
  • Real-Time Analytics: Allows for real-time analysis of log files, metrics, and business data.
  • K-NN (k-Nearest Neighbors): Supports vector search, essential for semantic search applications.

What are OpenAI and Embeddings?

OpenAI is an artificial intelligence research and deployment company that provides advanced machine learning models, including language models like GPT (Generative Pretrained Transformer). OpenAI’s API offers access to powerful models capable of understanding and generating human-like text, as well as embeddings that represent text as high-dimensional vectors.

OpenAI Embeddings are vector representations of text that capture semantic meaning, making it easier to compare, search, and analyze text data. These embeddings allow for more relevant and accurate search results based on the meaning of words or sentences, not just keyword matching.

Key Features of OpenAI:

  • Language Models: Generates human-like text, summaries, and responses.
  • Embeddings: Converts text into numerical vectors that represent the semantic meaning of the text.
  • Advanced AI: Supports various natural language processing (NLP) tasks, such as translation, summarization, and sentiment analysis.

Why Use OpenAI with OpenSearch?

OpenAI embeddings combined with OpenSearch provide a powerful solution for semantic search, enabling systems to understand and return results based on meaning rather than keywords alone. Here’s why combining OpenAI with OpenSearch makes sense:

➡️Enhanced Search Relevance

Traditional search engines rely on keyword matching, but OpenAI embeddings understand the context and meaning of words, allowing for semantically relevant search results. For example, a query about “summer jackets” could return results for “lightweight coats” or “rain jackets” if they are contextually relevant.

➡️Efficient Handling of High-Dimensional Data

OpenSearch’s k-NN (k-nearest neighbor) plugin is optimized for vector searches, which is ideal for the high-dimensional data generated by OpenAI embeddings. This allows for fast and scalable similarity searches in large datasets.

➡️Real-Time Semantic Matching

By storing OpenAI embeddings in OpenSearch, you can perform real-time semantic searches. As users enter queries, their embeddings are compared to the indexed embeddings in OpenSearch, enabling quick retrieval of the most relevant documents.

➡️Scalable Solution

OpenSearch provides horizontal scalability, meaning it can efficiently handle an ever-growing number of documents with embeddings, while OpenAI offers a deep semantic understanding of text data. Together, they provide an ideal setup for scalable and intelligent search.

➡️Better User Experience

When combining OpenAI’s understanding of natural language with OpenSearch’s powerful search engine, you can build systems that provide users with highly relevant, accurate, and meaningful search results, leading to a better overall user experience.

Use Cases

Example 1: E-commerce Product Search

Imagine an e-commerce store where users search for products not by name but by intent. With OpenAI embeddings, a user query such as “I need a warm winter jacket” can be semantically matched with products like “Down jacket” or “Winter coat”, even if those exact words don’t appear in the product descriptions.

Example 2: Document Retrieval

For knowledge management or customer support systems, OpenAI embeddings can help find the most relevant documents or responses based on the content’s meaning, not just keyword matches.

Example 3: Customer Feedback Analysis

OpenAI embeddings can be used to analyze customer feedback by matching similar reviews or comments, helping businesses identify recurring issues or popular products.

How Does it Work?

➡️OpenAI Embeddings

OpenAI provides APIs to generate embeddings, which are high-dimensional vector representations of text. These embeddings capture the semantic meaning of text, enabling advanced tasks like semantic search. For example:

  • Input: “Find tech startups”
  • Output: A 1536-dimensional vector representing the semantic meaning of the text.

➡️OpenSearch Integration

OpenSearch’s k-NN (k-nearest neighbor) plugin allows the storage and retrieval of these vectors. When a query is received, its embedding is calculated, and OpenSearch performs a similarity search to return the closest matches.

➡️End-to-End Flow

  • Generate an embedding for both the query and document.
  • Store the document with its embedding in OpenSearch.
  • Perform a similarity search by comparing the query’s embedding with the document embeddings.

OpenAI Implementation

Since OpenAI does not offer a dedicated Java client, we will use an HTTP client to interact with the OpenAI API. Here’s how to generate embeddings for text using OpenAI’s API in Java.

@Service
public class OpenAIEmbeddingService {

    private static final String OPENAI_API_KEY = "your_openai_api_key";
    private static final String OPENAI_API_URL = "https://api.openai.com/v1/embeddings";

    public JSONArray getEmbedding(String text) throws IOException {
         CloseableHttpClient client = HttpClients.createDefault();
         HttpPost postRequest = new HttpPost(OPENAI_API_URL);

         JSONObject payload = new JSONObject();
         payload.put("input", text);
         payload.put("model", "text-embedding-ada-002");

         StringEntity entity = new StringEntity(payload.toString());
         postRequest.setEntity(entity);
         postRequest.setHeader("Authorization", "Bearer " + OPENAI_API_KEY);
         postRequest.setHeader("Content-Type", "application/json");

         try (CloseableHttpResponse response = client.execute(postRequest)) {
             String responseStr = EntityUtils.toString(response.getEntity());
             JSONObject jsonResponse = new JSONObject(responseStr);
             JSONArray embeddings = jsonResponse.getJSONArray("data");
             return embeddings.getJSONObject(0).getJSONArray("embedding");
         }
     }
}

While the above example uses the Apache HTTP client, there are multiple ways to call OpenAI APIs, depending on your requirements and preferred tools.

For more details on embedding generation, payload specifications, and other features of OpenAI APIs, refer to the official documentation.

OpenSearchService Implementation

Now let’s implement the service layer for interacting with OpenSearch. We will manage document creation, deletion, updating, and searching.

➡️OpenSearch Java Dependency

To integrate with OpenSearch, include the following dependency in your:

For Maven

<dependency>
    <groupId>org.opensearch.client</groupId>
    <artifactId>opensearch-java</artifactId>
    <version>2.10.4</version>
</dependency>

For Gradle

dependencies {
    implementation 'org.opensearch.client:opensearch-java:2.10.4'
}

Important Methods in OpenSearch for Handling Indexing

Below are some example methods that demonstrate common operations for handling indexing in OpenSearch. These methods can be implemented based on your specific requirements:

➡️Create Index Method

This method creates an index with the provided data document.

public void createIndex(Long entityId, String indexName, IndexData document) {
    IndexRequest<IndexData> indexRequest = new IndexRequest.Builder<IndexData>()
          .index(indexName)
          .document(document)
          .build();

    openSearchClient.index(indexRequest);
}

➡️Delete Index Method

This method deletes an index by its ID.

public void deleteIndex(String indexId, String indexName) {
    if (indexId != null) {
       openSearchClient.delete(d -> d.index(indexName).id(indexId));
    }
}

➡️Update Index Method

This method updates an existing index document by ID.

public void updateIndex(String indexId, String indexName, IndexData document) {
    UpdateRequest<IndexData, IndexData> updateRequest = new UpdateRequest.Builder<IndexData, IndexData>()
             .index(indexName)
             .id(indexId)
             .doc(document)
             .build();

    openSearchClient.update(updateRequest, IndexData.class);
}

➡️Search Index Method

This method searches for documents in an OpenSearch index using a query.

public <T> List<Hit<T>> searchIndexDocumentByIndexQueryAndIndexName(Query query, String indexName, Class<T> clazz) {
   SearchRequest searchRequest = new SearchRequest.Builder()
            .index(indexName)
            .query(query)
            .build();

   SearchResponse<T> searchResponse = openSearchClient.search(searchRequest, clazz);
   HitsMetadata<T> hits = searchResponse.hits();
   return hits.hits();
}

The above methods are provided as examples and can be modified or expanded to suit your specific use cases. OpenSearch offers a wide range of functionalities beyond these examples. For more detailed information and additional methods available in the OpenSearch Java client, refer to the official documentation.

Explore Our Advanced OpenSearch and AI Integration Services Now

Connecting to OpenSearch: Role of AWS

If your OpenSearch instance is hosted on AWS, you need to authenticate requests using AWS credentials. Here’s how to set up the connection:

@Bean
   public OpenSearchClient openSearchClient() {
       AwsBasicCredentials awsCreds = AwsBasicCredentials.create(accessKeyId, secretKey);
       StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(awsCreds);

       SdkHttpClient httpClient = ApacheHttpClient.builder().build();
       return new OpenSearchClient(
               new AwsSdk2Transport(
                       httpClient,
                       openSearchEndpoint,
                       Region.US_EAST_1,
                       AwsSdk2TransportOptions.builder()
                               .setCredentials(credentialsProvider)
                               .build()
               )
       );
}

Role of AWS in OpenSearch

  1. Authentication
    AWS-managed OpenSearch instances require authentication using AWS credentials. These credentials allow secure communication between your application and OpenSearch.
  2. Access Control
    AWS Identity and Access Management (IAM) policies are used to control which operations your application can perform (e.g., read, write, delete).
  3. High Availability
    AWS OpenSearch ensures that your search service is highly available, distributed, and fault-tolerant.
  4. Encryption
    AWS OpenSearch supports encryption at rest and in transit, ensuring data security.

What Do You Need to Connect?

  1. AWS Credentials
    Your Access Key ID and Secret Key to authenticate requests.
  2. Endpoint URL
    The endpoint URL of your OpenSearch instance.
  3. Region
    The AWS region where your OpenSearch cluster is hosted.
  4. IAM Policies
    Ensure the IAM user/role used by your application has the necessary permissions to perform OpenSearch operations (e.g., indexing, searching).

Let’s Understand with a Basic Application Example

Using embeddings in OpenSearch makes search queries faster and more meaningful by leveraging vector representations of text for semantic understanding. This example demonstrates a flow where we index and search for company data using OpenAI embeddings and OpenSearch.

Overview

Imagine you have a database of companies, each with a name and description. The goal is to allow users to search for companies based on a query, not just by matching keywords but by understanding the semantic meaning of their query. For example, if the user searches for “software startups,” the system can return companies that match this intent, even if the exact words “software startups” are not in their descriptions.

To achieve this, we will:

  1. Use OpenAI to generate embeddings for company descriptions and user queries.
  2. Store these embeddings in OpenSearch.
  3. Perform semantic searches by comparing query embeddings with stored embeddings.

Steps to Implement

Step 1: Generate Embeddings for Company Data

In this step, we take the descriptions of each company and convert them into vector embeddings using OpenAI’s API. These embeddings capture the semantic meaning of the text.

JSONArray embedding = openAIEmbeddingService.getEmbedding(companyDescription);
IndexData indexData = new IndexData(companyName, embedding.toList());

Here, getEmbedding is a method that sends the text to OpenAI’s API and retrieves the vector embedding. The IndexData object contains the company name and its embedding, ready for indexing.

Step 2: Index the Data in OpenSearch

The next step is to store the company data, including the generated embedding, into an OpenSearch index. This prepares the data for efficient vector-based searches.

openSearchService.createIndex(companyId, "company-index", indexData);

Step 3: Generate Embeddings for the User Query

When the user enters a search query, we convert it into an embedding using OpenAI. This embedding represents the meaning of the query and will be compared to the stored embeddings.

JSONArray queryEmbedding = openAIEmbeddingService.getEmbedding(userQuery);

Step 4: Perform a Semantic Search

Using the query embedding, we perform a k-NN (k-nearest neighbors) search in OpenSearch. This finds the stored embeddings closest to the query embedding, retrieving the most semantically relevant companies.

List<Hit<IndexData>> results = openSearchService.searchIndexDocumentByIndexQueryAndIndexName(
new Query.Builder().knn(q -> q.field("embedding").vector(queryEmbedding.toList()).k(5)).build(),"company-index", IndexData.class);

Putting it All Together

Below is the complete flow demonstrating how embeddings and OpenSearch work together:

1. Indexing Company Data

  • For each company, generate an embedding of its description using OpenAI.
  • Store the company name, description, and embedding in OpenSearch.

2. Processing User Query

  • Convert the user’s search query into an embedding using OpenAI.

3. Searching for Results

  • Use the query embedding to perform a k-NN search in OpenSearch.
  • Retrieve and display the top results based on semantic similarity.

Alternative Approaches

This example demonstrates one way of using embeddings with OpenSearch for semantic search. However, there are many other approaches you could follow, such as:

  1. Hybrid Search
    Combine traditional keyword search with embedding-based search for better results. For example, use keyword filters to narrow down results and embeddings for ranking relevance.
  2. Reranking Results
    Use embeddings to re-rank results retrieved through traditional search techniques, ensuring that the most semantically relevant results are displayed first.
  3. Clustering and Categorization
    Use embeddings to group similar documents or entities into clusters, making it easier to organize and browse large datasets.
  4. Pre-processed Embeddings
    Generate embeddings offline and periodically update the OpenSearch index, reducing latency for real-time embedding generation.
  5. Custom Similarity Metrics
    Instead of using cosine similarity (default in k-NN), experiment with other metrics to improve accuracy for your specific use case.

Advantages of OpenSearch with Embeddings

  • Semantic Understanding: Enables intent-based search by understanding the meaning behind user queries and text.
  • High Scalability: Handles large-scale datasets efficiently with its distributed architecture.
  • Efficient Similarity Searches: Optimized for high-dimensional vector searches using the k-NN plugin.
  • Real-Time Performance: Provides quick and accurate results for embedding-based queries.
  • Flexible Integration: Works well with AI tools like OpenAI for embedding generation, supporting diverse use cases.
  • Cost Efficiency: Open-source nature makes it cost-effective compared to proprietary search engines.
  • Security and Monitoring: Offers encryption, access control, and monitoring features, especially on AWS.

Limitations of OpenSearch with Embeddings

  • Storage Overhead: High-dimensional embeddings consume significant storage, especially for large datasets.
  • Computational Costs: Generating embeddings can be resource-intensive and expensive.
  • Query Latency: Vector searches may slow down for extremely large datasets or high-dimensional vectors.
  • Complexity in Setup: Requires expertise to configure and maintain embedding-based search solutions.
  • No Built-In Embedding Generation: Relies on external tools like OpenAI for embedding creation.
coma

Conclusion

Using OpenSearch with embeddings unlocks the potential for advanced, semantic search capabilities, offering intent-based, real-time, and scalable search solutions. While it excels in handling large datasets and diverse use cases, it requires careful consideration of storage, computational costs, and setup complexity. Proper integration and optimization make it a powerful choice for modern search applications.

Keep Reading

Keep Reading

  • Service
  • Career
  • Let's create something together!

  • We’re looking for the best. Are you in?