@langchain/community
Version:
Third-party integrations for LangChain.js
391 lines (389 loc) • 13.7 kB
JavaScript
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
const require_googlevertexai_connection = require('../utils/googlevertexai-connection.cjs');
const __langchain_core_utils_async_caller = require_rolldown_runtime.__toESM(require("@langchain/core/utils/async_caller"));
const __langchain_core_documents = require_rolldown_runtime.__toESM(require("@langchain/core/documents"));
const google_auth_library = require_rolldown_runtime.__toESM(require("google-auth-library"));
const uuid = require_rolldown_runtime.__toESM(require("uuid"));
const __langchain_core_vectorstores = require_rolldown_runtime.__toESM(require("@langchain/core/vectorstores"));
const flat = require_rolldown_runtime.__toESM(require("flat"));
//#region src/vectorstores/googlevertexai.ts
var googlevertexai_exports = {};
require_rolldown_runtime.__export(googlevertexai_exports, {
IdDocument: () => IdDocument,
MatchingEngine: () => MatchingEngine
});
/**
* A Document that optionally includes the ID of the document.
*/
var IdDocument = class extends __langchain_core_documents.Document {
id;
constructor(fields) {
super(fields);
this.id = fields.id;
}
};
var IndexEndpointConnection = class extends require_googlevertexai_connection.GoogleVertexAIConnection {
indexEndpoint;
constructor(fields, caller) {
super(fields, caller, new google_auth_library.GoogleAuth(fields.authOptions));
this.indexEndpoint = fields.indexEndpoint;
}
async buildUrl() {
const projectId = await this.client.getProjectId();
const url = `https://${this.endpoint}/${this.apiVersion}/projects/${projectId}/locations/${this.location}/indexEndpoints/${this.indexEndpoint}`;
return url;
}
buildMethod() {
return "GET";
}
async request(options) {
return this._request(void 0, options);
}
};
var RemoveDatapointConnection = class extends require_googlevertexai_connection.GoogleVertexAIConnection {
index;
constructor(fields, caller) {
super(fields, caller, new google_auth_library.GoogleAuth(fields.authOptions));
this.index = fields.index;
}
async buildUrl() {
const projectId = await this.client.getProjectId();
const url = `https://${this.endpoint}/${this.apiVersion}/projects/${projectId}/locations/${this.location}/indexes/${this.index}:removeDatapoints`;
return url;
}
buildMethod() {
return "POST";
}
async request(datapointIds, options) {
const data = { datapointIds };
return this._request(data, options);
}
};
var UpsertDatapointConnection = class extends require_googlevertexai_connection.GoogleVertexAIConnection {
index;
constructor(fields, caller) {
super(fields, caller, new google_auth_library.GoogleAuth(fields.authOptions));
this.index = fields.index;
}
async buildUrl() {
const projectId = await this.client.getProjectId();
const url = `https://${this.endpoint}/${this.apiVersion}/projects/${projectId}/locations/${this.location}/indexes/${this.index}:upsertDatapoints`;
return url;
}
buildMethod() {
return "POST";
}
async request(datapoints, options) {
const data = { datapoints };
return this._request(data, options);
}
};
var FindNeighborsConnection = class extends require_googlevertexai_connection.GoogleVertexAIConnection {
indexEndpoint;
deployedIndexId;
constructor(params, caller) {
super(params, caller, new google_auth_library.GoogleAuth(params.authOptions));
this.indexEndpoint = params.indexEndpoint;
this.deployedIndexId = params.deployedIndexId;
}
async buildUrl() {
const projectId = await this.client.getProjectId();
const url = `https://${this.endpoint}/${this.apiVersion}/projects/${projectId}/locations/${this.location}/indexEndpoints/${this.indexEndpoint}:findNeighbors`;
return url;
}
buildMethod() {
return "POST";
}
async request(request, options) {
return this._request(request, options);
}
};
/**
* A class that represents a connection to a Google Vertex AI Matching Engine
* instance.
*/
var MatchingEngine = class MatchingEngine extends __langchain_core_vectorstores.VectorStore {
/**
* Docstore that retains the document, stored by ID
*/
docstore;
/**
* The host to connect to for queries and upserts.
*/
apiEndpoint;
apiVersion = "v1";
endpoint = "us-central1-aiplatform.googleapis.com";
location = "us-central1";
/**
* The id for the index endpoint
*/
indexEndpoint;
/**
* The id for the index
*/
index;
/**
* Explicitly set Google Auth credentials if you cannot get them from google auth application-default login
* This is useful for serverless or autoscaling environments like Fargate
*/
authOptions;
/**
* The id for the "deployed index", which is an identifier in the
* index endpoint that references the index (but is not the index id)
*/
deployedIndexId;
callerParams;
callerOptions;
caller;
indexEndpointClient;
removeDatapointClient;
upsertDatapointClient;
constructor(embeddings, args) {
super(embeddings, args);
this.embeddings = embeddings;
this.docstore = args.docstore;
this.apiEndpoint = args.apiEndpoint ?? this.apiEndpoint;
this.deployedIndexId = args.deployedIndexId ?? this.deployedIndexId;
this.apiVersion = args.apiVersion ?? this.apiVersion;
this.endpoint = args.endpoint ?? this.endpoint;
this.location = args.location ?? this.location;
this.indexEndpoint = args.indexEndpoint ?? this.indexEndpoint;
this.index = args.index ?? this.index;
this.authOptions = args.authOptions ?? this.authOptions;
this.callerParams = args.callerParams ?? this.callerParams;
this.callerOptions = args.callerOptions ?? this.callerOptions;
this.caller = new __langchain_core_utils_async_caller.AsyncCaller(this.callerParams || {});
const indexClientParams = {
endpoint: this.endpoint,
location: this.location,
apiVersion: this.apiVersion,
indexEndpoint: this.indexEndpoint,
authOptions: this.authOptions
};
this.indexEndpointClient = new IndexEndpointConnection(indexClientParams, this.caller);
const removeClientParams = {
endpoint: this.endpoint,
location: this.location,
apiVersion: this.apiVersion,
index: this.index,
authOptions: this.authOptions
};
this.removeDatapointClient = new RemoveDatapointConnection(removeClientParams, this.caller);
const upsertClientParams = {
endpoint: this.endpoint,
location: this.location,
apiVersion: this.apiVersion,
index: this.index,
authOptions: this.authOptions
};
this.upsertDatapointClient = new UpsertDatapointConnection(upsertClientParams, this.caller);
}
_vectorstoreType() {
return "googlevertexai";
}
async addDocuments(documents) {
const texts = documents.map((doc) => doc.pageContent);
const vectors = await this.embeddings.embedDocuments(texts);
return this.addVectors(vectors, documents);
}
async addVectors(vectors, documents) {
if (vectors.length !== documents.length) throw new Error(`Vectors and metadata must have the same length`);
const datapoints = vectors.map((vector, idx) => this.buildDatapoint(vector, documents[idx]));
const options = {};
const response = await this.upsertDatapointClient.request(datapoints, options);
if (Object.keys(response?.data ?? {}).length === 0) {
const idDoc = documents;
const docsToStore = {};
idDoc.forEach((doc) => {
if (doc.id) docsToStore[doc.id] = doc;
});
await this.docstore.add(docsToStore);
}
}
cleanMetadata(documentMetadata) {
function getStringArrays(prefix, m) {
let ret = {};
Object.keys(m).forEach((key) => {
const newPrefix = prefix.length > 0 ? `${prefix}.${key}` : key;
const val = m[key];
if (!val) {} else if (Array.isArray(val)) ret[newPrefix] = val.map((v) => `${v}`);
else if (typeof val === "object") {
const subArrays = getStringArrays(newPrefix, val);
ret = {
...ret,
...subArrays
};
}
});
return ret;
}
const stringArrays = getStringArrays("", documentMetadata);
const flatMetadata = (0, flat.default)(documentMetadata);
Object.keys(flatMetadata).forEach((key) => {
Object.keys(stringArrays).forEach((arrayKey) => {
const matchKey = `${arrayKey}.`;
if (key.startsWith(matchKey)) delete flatMetadata[key];
});
});
const metadata = {
...flatMetadata,
...stringArrays
};
return metadata;
}
/**
* Given the metadata from a document, convert it to an array of Restriction
* objects that may be passed to the Matching Engine and stored.
* The default implementation flattens any metadata and includes it as
* an "allowList". Subclasses can choose to convert some of these to
* "denyList" items or to add additional restrictions (for example, to format
* dates into a different structure or to add additional restrictions
* based on the date).
* @param documentMetadata - The metadata from a document
* @returns a Restriction[] (or an array of a subclass, from the FilterType)
*/
metadataToRestrictions(documentMetadata) {
const metadata = this.cleanMetadata(documentMetadata);
const restrictions = [];
for (const key of Object.keys(metadata)) {
let valArray;
const val = metadata[key];
if (val === null) valArray = null;
else if (Array.isArray(val) && val.length > 0) valArray = val;
else valArray = [`${val}`];
if (valArray) {
const listType = "allowList";
const restriction = {
namespace: key,
[listType]: valArray
};
restrictions.push(restriction);
}
}
return restrictions;
}
/**
* Create an index datapoint for the vector and document id.
* If an id does not exist, create it and set the document to its value.
* @param vector
* @param document
*/
buildDatapoint(vector, document) {
if (!document.id) document.id = uuid.v4();
const ret = {
datapointId: document.id,
featureVector: vector
};
const restrictions = this.metadataToRestrictions(document.metadata);
if (restrictions?.length > 0) ret.restricts = restrictions;
return ret;
}
async delete(params) {
const options = {};
await this.removeDatapointClient.request(params.ids, options);
}
async similaritySearchVectorWithScore(query, k, filter) {
const deployedIndexId = await this.getDeployedIndexId();
const requestQuery = {
neighborCount: k,
datapoint: {
datapointId: `0`,
featureVector: query
}
};
if (filter) requestQuery.datapoint.restricts = filter;
const request = {
deployedIndexId,
queries: [requestQuery]
};
const apiEndpoint = await this.getPublicAPIEndpoint();
const findNeighborsParams = {
endpoint: apiEndpoint,
indexEndpoint: this.indexEndpoint,
apiVersion: this.apiVersion,
location: this.location,
deployedIndexId,
authOptions: this.authOptions
};
const connection = new FindNeighborsConnection(findNeighborsParams, this.caller);
const options = {};
const response = await connection.request(request, options);
const nearestNeighbors = response?.data?.nearestNeighbors ?? [];
const nearestNeighbor = nearestNeighbors[0];
const neighbors = nearestNeighbor?.neighbors ?? [];
const ret = await Promise.all(neighbors.map(async (neighbor) => {
const id = neighbor?.datapoint?.datapointId;
const distance = neighbor?.distance;
let doc;
try {
doc = await this.docstore.search(id);
} catch (xx) {
console.error(xx);
console.warn([
`Document with id "${id}" is missing from the backing docstore.`,
`This can occur if you clear the docstore without deleting from the corresponding Matching Engine index.`,
`To resolve this, you should call .delete() with this id as part of the "ids" parameter.`
].join("\n"));
doc = new __langchain_core_documents.Document({ pageContent: `Missing document ${id}` });
}
doc.id ??= id;
return [doc, distance];
}));
return ret;
}
/**
* For this index endpoint, figure out what API Endpoint URL and deployed
* index ID should be used to do upserts and queries.
* Also sets the `apiEndpoint` and `deployedIndexId` property for future use.
* @return The URL
*/
async determinePublicAPIEndpoint() {
const response = await this.indexEndpointClient.request(this.callerOptions);
const publicEndpointDomainName = response?.data?.publicEndpointDomainName;
this.apiEndpoint = publicEndpointDomainName;
const indexPathPattern = /projects\/.+\/locations\/.+\/indexes\/(.+)$/;
const deployedIndexes = response?.data?.deployedIndexes ?? [];
const deployedIndex = deployedIndexes.find((index) => {
const deployedIndexPath = index.index;
const match = deployedIndexPath.match(indexPathPattern);
if (match) {
const [, potentialIndexId] = match;
if (potentialIndexId === this.index) return true;
}
return false;
});
if (deployedIndex) this.deployedIndexId = deployedIndex.id;
return {
apiEndpoint: this.apiEndpoint,
deployedIndexId: this.deployedIndexId
};
}
async getPublicAPIEndpoint() {
return this.apiEndpoint ?? (await this.determinePublicAPIEndpoint()).apiEndpoint;
}
async getDeployedIndexId() {
return this.deployedIndexId ?? (await this.determinePublicAPIEndpoint()).deployedIndexId;
}
static async fromTexts(texts, metadatas, embeddings, dbConfig) {
const docs = texts.map((text, index) => ({
pageContent: text,
metadata: Array.isArray(metadatas) ? metadatas[index] : metadatas
}));
return this.fromDocuments(docs, embeddings, dbConfig);
}
static async fromDocuments(docs, embeddings, dbConfig) {
const ret = new MatchingEngine(embeddings, dbConfig);
await ret.addDocuments(docs);
return ret;
}
};
//#endregion
exports.IdDocument = IdDocument;
exports.MatchingEngine = MatchingEngine;
Object.defineProperty(exports, 'googlevertexai_exports', {
enumerable: true,
get: function () {
return googlevertexai_exports;
}
});
//# sourceMappingURL=googlevertexai.cjs.map