本教程探讨了使用 LangChain(OpenAI)和 Redis 在产品描述中实现语义文本搜索。重点领域包括
以下是对本教程中使用的应用程序的源代码进行克隆的命令
git clone --branch v9.2.0 https://github.com/redis-developer/redis-microservices-ecommerce-solutions
让我们看一下演示应用程序的架构
products service
:处理从数据库查询产品并将其返回到前端orders service
:处理订单验证和创建order history service
:处理查询客户的订单历史记录payments service
:处理订单的付款处理api gateway
:将服务统一到一个端点下mongodb/ postgresql
:用作存储订单、订单历史记录、产品等的写入优化数据库您不需要在演示应用程序中使用 MongoDB/ Postgresql 作为您的写入优化数据库;您可以使用其他 prisma 支持的数据库 。这只是一个例子。
电商微服务应用程序包含一个前端,使用 Next.js 和 TailwindCSS 构建。应用程序后端使用 Node.js。数据存储在 Redis 和 MongoDB 或 PostgreSQL 中,使用 Prisma。以下是展示电商应用程序前端的屏幕截图。
仪表板: 显示具有不同搜索功能的产品列表,可以在设置页面中配置。
设置: 可以通过单击仪表板右上角的齿轮图标访问。在此处控制搜索栏、聊天机器人可见性和其他功能。
仪表板(语义文本搜索): 配置为语义文本搜索,搜索栏允许使用自然语言查询。例如:“纯棉蓝色衬衫”。
仪表板(语义图像查询): 配置为语义图像摘要搜索,搜索栏允许使用基于图像的查询。例如:“左胸耐克标志”。
聊天机器人: 位于页面右下角,协助产品搜索和详细视图。
在聊天中选择产品会在仪表板上显示其详细信息。
购物车: 将产品添加到购物车,然后使用“立即购买”按钮结账。
订单历史记录: 购买后,顶部导航栏中的“订单”链接会显示订单状态和历史记录。
管理面板: 可以通过顶部导航栏中的“admin”链接访问。显示购买统计信息和趋势产品。
注册 OpenAI 帐户 以获取您将在演示中使用的 API 密钥(在 .env 文件中添加 OPEN_AI_API_KEY 变量)。您还可以参考 OpenAI API 文档 以获取更多信息。
以下是对本教程中使用的应用程序的源代码进行克隆的命令
git clone --branch v9.2.0 https://github.com/redis-developer/redis-microservices-ecommerce-solutions
考虑一个简化的电商数据集,其中包含用于语义搜索的产品详细信息。
const products = [
{
productId: '11000',
price: 3995,
productDisplayName: 'Puma Men Slick 3HD Yellow Black Watches',
variantName: 'Slick 3HD Yellow',
brandName: 'Puma',
ageGroup: 'Adults-Men',
gender: 'Men',
displayCategories: 'Accessories',
masterCategory_typeName: 'Accessories',
subCategory_typeName: 'Watches',
styleImages_default_imageURL:
'https://host.docker.internal:8080/images/11000.jpg',
productDescriptors_description_value: 'Stylish and comfortable, ...',
stockQty: 25,
},
//...
];
实现 addEmbeddingsToRedis
函数,将 AI 生成的产品描述嵌入与 Redis 集成。
此过程涉及两个主要步骤
convertToVectorDocuments
函数,我们将产品详细信息转换为向量文档。这种转换至关重要,因为它将产品详细信息转换为适合 Redis 存储的格式。seedOpenAIEmbeddings
函数将这些向量文档存储到 Redis 中。此步骤对于在 Redis 数据库中启用高效的检索和搜索功能至关重要。import { Document } from 'langchain/document';
import { OpenAIEmbeddings } from 'langchain/embeddings/openai';
import { RedisVectorStore } from 'langchain/vectorstores/redis';
const convertToVectorDocuments = async (
_products: Prisma.ProductCreateInput[],
) => {
const vectorDocs: Document[] = [];
if (_products?.length > 0) {
for (let product of _products) {
let doc = new Document({
metadata: {
productId: product.productId,
},
pageContent: ` Product details are as follows:
productId: ${product.productId}.
productDisplayName: ${product.productDisplayName}.
price: ${product.price}.
variantName: ${product.variantName}.
brandName: ${product.brandName}.
ageGroup: ${product.ageGroup}.
gender: ${product.gender}.
productColors: ${product.productColors}
Category: ${product.displayCategories}, ${product.masterCategory_typeName} - ${product.subCategory_typeName}
productDescription: ${product.productDescriptors_description_value}`,
});
vectorDocs.push(doc);
}
}
return vectorDocs;
};
const seedOpenAIEmbeddings = async (
vectorDocs: Document[],
_redisClient: NodeRedisClientType,
_openAIApiKey: string,
) => {
if (vectorDocs?.length && _redisClient && _openAIApiKey) {
console.log('openAIEmbeddings started !');
const embeddings = new OpenAIEmbeddings({
openAIApiKey: _openAIApiKey,
});
const vectorStore = await RedisVectorStore.fromDocuments(
vectorDocs,
embeddings,
{
redisClient: _redisClient,
indexName: 'openAIProductsIdx',
keyPrefix: 'openAIProducts:',
},
);
console.log('OpenAIEmbeddings completed');
}
};
const addEmbeddingsToRedis = async (
_products: Prisma.ProductCreateInput[],
_redisClient: NodeRedisClientType,
_openAIApiKey: string,
_huggingFaceApiKey?: string,
) => {
if (_products?.length > 0 && _redisClient && _openAIApiKey) {
const vectorDocs = await convertToVectorDocuments(_products);
await seedOpenAIEmbeddings(vectorDocs, _redisClient, _openAIApiKey);
}
};
使用 RedisInsight 检查 Redis 中结构化的 openAI 产品详细信息
。
下载 RedisInsight 以可视化方式探索您的 Redis 数据或在工作台中使用原始 Redis 命令。
本节介绍 getProductsByVSSText
的 API 请求和响应结构,这对于基于语义文本搜索检索产品至关重要。
请求格式
API 的示例请求格式如下
POST https://localhost:3000/products/getProductsByVSSText
{
"searchText":"pure cotton blue shirts",
//optional
"maxProductCount": 4, // 2 (default)
"similarityScoreLimit":0.2, // 0.2 (default)
}
响应结构
API 的响应是一个 JSON 对象,包含与语义搜索条件匹配的产品详细信息数组
{
"data": [
{
"productId": "11031",
"price": 1099,
"productDisplayName": "Jealous 21 Women Check Blue Tops",
"productDescriptors_description_value": "Composition : Green and navy blue checked round neck blouson tunic top made of 100% cotton, has a full buttoned placket, three fourth sleeves with buttoned cuffs and a belt below the waist<br /><br /><strong>Fitting</strong><br />Regular<br /><br /><strong>Wash care</strong><br />Machine/hand wash separately in mild detergent<br />Do not bleach or wring<br />Dry in shade<br />Medium iron<br /><br />If you're in the mood to have some checked fun, this blouson tunic top from jealous 21 will fulfil your heart's desire with élan. The cotton fabric promises comfort, while the smart checks guarantee unparalleled attention. Pair this top with leggings and ballerinas for a cute, neat look.<br /><br /><em>Model statistics</em><br />The model wears size M in tops<br />Height: 5'7\"; Chest: 33\"; Waist: 25\"</p>",
"stockQty": 25,
"productColors": "Blue,Green",
"similarityScore": 0.168704152107
//...
}
],
"error": null,
"auth": "SES_fd57d7f4-3deb-418f-9a95-6749cd06e348"
}
此 API 的后端实现涉及以下步骤
getProductsByVSSText
函数处理 API 请求。getSimilarProductsScoreByVSS
函数对产品详细信息执行语义搜索。它与 OpenAI
的语义分析功能集成,以解释 searchText 并从 Redis
向量存储中识别相关产品。const getSimilarProductsScoreByVSS = async (
_params: IParamsGetProductsByVSS,
) => {
let {
standAloneQuestion,
openAIApiKey,
//optional
KNN,
scoreLimit,
} = _params;
let vectorDocs: Document[] = [];
const client = getNodeRedisClient();
KNN = KNN || 2;
scoreLimit = scoreLimit || 1;
let embeddings = new OpenAIEmbeddings({
openAIApiKey: openAIApiKey,
});
let indexName = 'openAIProductsIdx';
let keyPrefix = 'openAIProducts:';
if (embeddings) {
// create vector store
const vectorStore = new RedisVectorStore(embeddings, {
redisClient: client,
indexName: indexName,
keyPrefix: keyPrefix,
});
// search for similar products
const vectorDocsWithScore = await vectorStore.similaritySearchWithScore(
standAloneQuestion,
KNN,
);
// filter by scoreLimit
for (let [doc, score] of vectorDocsWithScore) {
if (score <= scoreLimit) {
doc['similarityScore'] = score;
vectorDocs.push(doc);
}
}
}
return vectorDocs;
};
const getProductsByVSSText = async (
productsVSSFilter: IProductsVSSBodyFilter,
) => {
let { searchText, maxProductCount, similarityScoreLimit } = productsVSSFilter;
let products: IProduct[] = [];
const openAIApiKey = process.env.OPEN_AI_API_KEY || '';
maxProductCount = maxProductCount || 2;
similarityScoreLimit = similarityScoreLimit || 0.2;
if (!openAIApiKey) {
throw new Error('Please provide openAI API key in .env file');
}
if (!searchText) {
throw new Error('Please provide search text');
}
//VSS search
const vectorDocs = await getSimilarProductsScoreByVSS({
standAloneQuestion: searchText,
openAIApiKey: openAIApiKey,
KNN: maxProductCount,
scoreLimit: similarityScoreLimit,
});
if (vectorDocs?.length) {
const productIds = vectorDocs.map((doc) => doc?.metadata?.productId);
//get product with details
products = await getProductByIds(productIds, true);
}
//...
return products;
};
语义文本搜索
功能探索语义文本搜索在增强电子商务体验方面的强大功能。本教程指导您将 OpenAI 的语义功能与 Redis 集成,以构建动态的产品搜索引擎。