本教程探讨了如何使用 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:
'http://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 product details
。
下载 RedisInsight 以可视化探索您的 Redis 数据或在工作台中使用原始 Redis 命令。
本节介绍 getProductsByVSSText
的 API 请求和响应结构,这对于基于语义文本搜索检索产品至关重要。
请求格式
API 的请求格式示例如下
POST http://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;
};
Semantic text search
。探索语义文本搜索的强大功能,提升电子商务体验。本教程将指导您将 OpenAI 的语义能力与 Redis 集成,构建动态的产品搜索引擎。