Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
vectorIndexFromVectorStore.ts 4.71 KiB
import {
  getResponseSynthesizer,
  OpenAI,
  OpenAIEmbedding,
  RetrieverQueryEngine,
  Settings,
  TextNode,
  VectorIndexRetriever,
  VectorStore,
  VectorStoreIndex,
  VectorStoreQuery,
  VectorStoreQueryResult,
} from "llamaindex";

import { Index, Pinecone, RecordMetadata } from "@pinecone-database/pinecone";

// Update llm
Settings.llm = new OpenAI({
  model: "gpt-4",
  apiKey: process.env.OPENAI_API_KEY,
});

/**
 * Please do not use this class in production; it's only for demonstration purposes.
 */
class PineconeVectorStore<T extends RecordMetadata = RecordMetadata>
  implements VectorStore
{
  storesText = true;
  isEmbeddingQuery = false;
  embedModel = new OpenAIEmbedding();

  indexName!: string;
  pineconeClient!: Pinecone;
  index!: Index<T>;

  constructor({ indexName, client }: { indexName: string; client: Pinecone }) {
    this.indexName = indexName;
    this.pineconeClient = client;
    this.index = client.index<T>(indexName);
  }

  client() {
    return this.pineconeClient;
  }

  async query(
    query: VectorStoreQuery,
    kwargs?: any,
  ): Promise<VectorStoreQueryResult> {
    let queryEmbedding: number[] = [];
    if (query.queryEmbedding) {
      if (typeof query.alpha === "number") {
        const alpha = query.alpha;
        queryEmbedding = query.queryEmbedding.map((v) => v * alpha);
      } else {
        queryEmbedding = query.queryEmbedding;
      }
    }

    // Current LlamaIndexTS implementation only support exact match filter, so we use kwargs instead.
    const filter = kwargs?.filter || {};

    const response = await this.index.query({
      filter,
      vector: queryEmbedding,
      topK: query.similarityTopK,
      includeValues: true,
      includeMetadata: true,
    });

    console.log(
      `Numbers of vectors returned by Pinecone after preFilters are applied: ${
        response?.matches?.length || 0
      }.`,
    );

    const topKIds: string[] = [];
    const topKNodes: TextNode[] = [];
    const topKScores: number[] = [];

    const metadataToNode = (metadata?: T): Partial<TextNode> => {
      if (!metadata) {
        throw new Error("metadata is undefined.");
      }

      const nodeContent = metadata["_node_content"];
      if (!nodeContent) {
        throw new Error("nodeContent is undefined.");
      }

      if (typeof nodeContent !== "string") {
        throw new Error("nodeContent is not a string.");
      }

      return JSON.parse(nodeContent);
    };

    if (response.matches) {
      for (const match of response.matches) {
        const node = new TextNode({
          ...metadataToNode(match.metadata),
          embedding: match.values,
        });

        topKIds.push(match.id);
        topKNodes.push(node);
        topKScores.push(match.score ?? 0);
      }
    }

    const result = {
      ids: topKIds,
      nodes: topKNodes,
      similarities: topKScores,
    };

    return result;
  }

  add(): Promise<string[]> {
    return Promise.resolve([]);
  }

  delete(): Promise<void> {
    throw new Error("Method `delete` not implemented.");
  }

  persist(): Promise<void> {
    throw new Error("Method `persist` not implemented.");
  }
}

/**
 * The goal of this example is to show how to use Pinecone as a vector store
 * for LlamaIndexTS with(out) preFilters.
 *
 * It should not be used in production like that,
 * as you might want to find a proper PineconeVectorStore implementation.
 */
async function main() {
  process.env.PINECONE_API_KEY = "Your Pinecone API Key.";
  process.env.PINECONE_ENVIRONMENT = "Your Pinecone Environment.";
  process.env.PINECONE_PROJECT_ID = "Your Pinecone Project ID.";
  process.env.PINECONE_INDEX_NAME = "Your Pinecone Index Name.";
  process.env.OPENAI_API_KEY = "Your OpenAI API Key.";
  process.env.OPENAI_API_ORGANIZATION = "Your OpenAI API Organization.";

  const getPineconeVectorStore = async () => {
    return new PineconeVectorStore({
      indexName: process.env.PINECONE_INDEX_NAME || "index-name",
      client: new Pinecone(),
    });
  };

  const getQueryEngine = async (filter: unknown) => {
    const vectorStore = await getPineconeVectorStore();

    const vectorStoreIndex =
      await VectorStoreIndex.fromVectorStore(vectorStore);

    const retriever = new VectorIndexRetriever({
      index: vectorStoreIndex,
      similarityTopK: 500,
    });

    const responseSynthesizer = getResponseSynthesizer("tree_summarize");
    return new RetrieverQueryEngine(retriever, responseSynthesizer, {
      filter,
    });
  };

  // whatever is a key from your metadata
  const queryEngine = await getQueryEngine({
    whatever: {
      $gte: 1,
      $lte: 100,
    },
  });

  const response = await queryEngine.query({
    query: "How many results do you have?",
  });

  console.log(response.toString());
}

main().catch(console.error);