Wikipedia Semantic Search

repository·main·Indexed 19 days ago

https://github.com/upstash/wikipedia-semantic-search

A demonstration project implementing a multilingual semantic search engine and RAG chatbot using Wikipedia data. It is powered by Upstash Vector (using the BGE-M3 embedding model), Upstash Redis for session management, the Upstash RAG Chat SDK, and Meta-Llama-3-8B-Instruct via QStash LLM APIs.

Tokens
3.6K
Snippets
13
Records
16
Agent score
67%

What's inside upstash-wikipedia-semantic-search

  1. Overview of the Wikipedia Semantic Search architecture

    main

    This project demonstrates a full RAG (Retrieval-Augmented Generation) pipeline using Wikipedia data. The architecture consists of:

    • Embedding Generation: Uses SentenceTransformers (specifically the BGE-M3 model) to create multilingual embeddings.
    • Vector Storage & Search: Uses Upstash Vector to store and perform semantic searches across millions of articles.
    • Session Management: Uses Upstash Redis to persist chat sessions.
    • RAG Application: Built using the Upstash RAG Chat SDK.
    • LLM Inference: Uses Meta-Llama-3-8B-Instruct via QStash LLM APIs to generate responses based on retrieved Wikipedia context.
  2. Set up the Wikipedia Semantic Search project locally

    main

    To run this project locally, you need to configure Upstash databases and environment variables.

    1. Provision Upstash Resources:

      • Create an Upstash Vector database. For multilingual support, select the BGE-M3 embedding model.
      • Create an Upstash Redis database to manage chat sessions.
      • Obtain QStash credentials to access hosted LLM models.
    2. Configure Environment Variables: Create a .env file in the project root and populate it with your credentials:

    3. Install and Run: Use pnpm to install dependencies and start the development server.

    # .env file template
    UPSTASH_VECTOR_REST_URL=
    UPSTASH_VECTOR_REST_TOKEN=
    
    UPSTASH_REDIS_REST_TOKEN=
    UPSTASH_REDIS_REST_URL=
    
    QSTASH_TOKEN=
    pnpm install
    pnpm dev
  3. Populate the Upstash Vector index with Wikipedia articles

    main
    When indexing Wikipedia articles into Upstash Vector, this project utilizes namespaces to organize articles by language. To ensure correct retrieval, you must upsert vectors into the namespace corresponding to the article's language. For example, English articles must be upserted into the en namespace.
  4. Understand the structure of Wiki and WikiMetadata

    main

    The project uses WikiMetadata to store descriptive information about a Wikipedia article and Wiki to represent the search result object containing the vector similarity score and the actual content.

    • WikiMetadata contains the id, url, and title of the article.
    • Wiki contains the id (number or string), the similarity score, optional metadata of type WikiMetadata, and optional data (the article content as a string).
    export type WikiMetadata = {
      id: string;
      url: string;
      title: string;
    };
    
    export type Wiki = {
      id: number | string;
      score: number;
      metadata?: WikiMetadata | undefined;
      data?: string;
    };
  5. Implement a streaming RAG chat endpoint with aiUseChatAdapter

    main

    To create a streaming chat endpoint compatible with the Vercel AI SDK, use the aiUseChatAdapter from @upstash/rag-chat/nextjs. This adapter takes a response from ragChat.chat and a metadata object, allowing the client-side useChat hook to consume both the stream and additional context data (like retrieved Wikipedia articles).

    When implementing the POST handler:

    1. Extract messages and an optional namespace from the request body.
    2. Retrieve a sessionId from the request cookies to maintain conversation state.
    3. Call ragChat.chat with streaming: true.
    4. Use the onContextFetched callback to filter or transform the retrieved context (e.g., limiting to the top 5 results).
    5. Construct a metadata object containing the prompt, history, and context used.
    6. Return the result of aiUseChatAdapter(response, meta).
    import { aiUseChatAdapter } from "@upstash/rag-chat/nextjs";
    import { ragChat } from "@/lib/rag-chat";
    
    export async function POST(request: NextRequest) {
      const { messages, namespace } = await request.json();
      const sessionId = request.cookies.get("sessionId")?.value;
      const question = (messages as Message[]).at(-1)?.content;
    
      const response = await ragChat.chat(question, {
        streaming: true,
        sessionId: sessionId,
        namespace: namespace,
        onContextFetched(context) {
          return context.slice(0, 5);
        },
        topK: 50,
      });
    
      const meta = {
        usedPrompt: "...",
        usedHistory: response.history.map(({ role, content }) => ({ role, content })),
        usedContext: response.context.map(({ metadata, data }) => ({
          url: (metadata as { url?: string }).url ?? "<NO_URL>",
          data,
        })),
      };
    
      return aiUseChatAdapter(response, meta);
    }
  6. Initialize RAGChat with a custom prompt function

    main

    To customize how the LLM receives information, provide a promptFn in the RAGChat constructor. The promptFn receives an object containing chatHistory, context, and question. You must return a string that incorporates these values into your prompt template.

    Commonly, developers use string replacement to inject these variables into a predefined prompt string. The available variables are:

    • chatHistory: The previous turns in the conversation.
    • context: The retrieved semantic search results from the vector store.
    • question: The current user query.
    import { RAGChat, openai } from "@upstash/rag-chat";
    import { index, redis } from "./dbs";
    
    export const ragChat = new RAGChat({
      model: openai("gpt-4-turbo", {
        apiKey: process.env.OPENAI_API_KEY!,
      }),
      vector: index,
      redis: redis,
      debug: false,
      promptFn: ({ chatHistory, context, question }) => {
        return PROMPT.replace("{chatHistory}", chatHistory ?? "<NO_CHAT_HISTORY>")
          .replace("{context}", context)
          .replace("{question}", question);
      },
    });
  7. Query the Wikipedia semantic index with serverQueryIndex

    main

    Perform a semantic search against the Upstash Vector index. This function automatically enhances the user's query by using OpenAI to extract relevant keywords, then queries the index using those keywords to improve retrieval accuracy. It uses the user's locale as a namespace for the query.

    Returns: An object containing:

    • code: ResultCode.Success if successful, or error codes like ResultCode.MinLengthError or ResultCode.UnknownError.
    • data: The retrieved WikiMetadata results.
    • ms: The time taken for the query in milliseconds.
    import { serverQueryIndex } from '@/lib/actions';
    
    const result = await serverQueryIndex("Who was Gandhi?");
    
    if (result.code === 'Success') {
      console.log("Results:", result.data);
      console.log("Query time:", result.ms, "ms");
    } else {
      console.error("Query failed with code:", result.code);
    }
  8. Manage user language settings with getUserLocale and setUserLocale

    main

    The service provides utilities to manage the user's preferred language (locale) using Next.js cookies. It supports a specific set of language codes and defaults to English if no locale is set.

    Supported Locales: de, en, es, fa, fr, it, ja, pt, ru, tr, zh

    Key Functions:

    • getUserLocale(): An asynchronous function that retrieves the current locale from the NEXT_LOCALE cookie. If the cookie is missing, it returns the default locale (en).
    • setUserLocale(locale: Locale): An asynchronous function that sets the NEXT_LOCALE cookie to the provided locale value.
    import { getUserLocale, setUserLocale } from './service';
    
    // Get the current user locale
    const locale = await getUserLocale();
    
    // Set a new locale for the user
    await setUserLocale('fr');
  9. Initialize Upstash Vector and Redis clients

    main

    The project provides pre-initialized instances of the Upstash Vector Index and Upstash Redis clients.

    • index: An instance of Index<WikiMetadata> used for semantic search operations. It requires the environment to be configured with Upstash Vector credentials.
    • redis: An instance of the Upstash Redis client initialized via Redis.fromEnv(). This expects UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN environment variables to be set.
    import { index, redis } from './lib/dbs';
    
    // Use the vector index for semantic search
    // const results = await index.query(...);
    
    // Use the redis client for key-value operations
    // await redis.set('key', 'value');
  10. Configure the RAGChat constructor options

    main

    The RAGChat class is initialized with a configuration object. Based on the implementation, the following keys are used:

    • model: An LLM provider instance (e.g., using openai()). Requires an apiKey.
    • vector: The Upstash Vector index instance used for semantic search.
    • redis: The Redis instance used for managing chat history/state.
    • debug: A boolean flag to enable/disable debug logging.
    • promptFn: A function ({ chatHistory, context, question }) => string used to construct the final prompt sent to the model.