学习

使用 LangChain(OpenAI)和 Redis 进行语义文本搜索

Prasan Kumar
作者
Prasan Kumar, Redis 技术解决方案开发人员
Will Johnston
作者
Will Johnston, Redis 开发者增长经理

本教程中您将学到的内容#

本教程探讨了使用 LangChain(OpenAI)和 Redis 在产品描述中实现语义文本搜索。重点领域包括

  • 电商情境:深入了解电商场景,其中语义文本搜索使用户能够通过详细的文本查询找到产品。
  • 数据库实现:了解如何在 Redis 中创建和存储产品描述的语义嵌入,以实现高效的搜索功能。
  • 搜索 API 开发:了解如何构建一个利用 OpenAI 进行文本语义分析和利用 Redis 进行数据管理的 API。

术语#

  • LangChain: 一个用于开发语言模型应用程序的多功能库,它将语言模型、存储系统和自定义逻辑结合在一起。
  • OpenAI: 提供 GPT-3 等尖端语言模型的提供商,对于语义搜索和对话式 AI 的应用程序至关重要。

电商应用程序的微服务架构#

GITHUB 代码

以下是对本教程中使用的应用程序的源代码进行克隆的命令

git clone --branch v9.2.0 https://github.com/redis-developer/redis-microservices-ecommerce-solutions

让我们看一下演示应用程序的架构

  1. 1.products service:处理从数据库查询产品并将其返回到前端
  2. 2.orders service:处理订单验证和创建
  3. 3.order history service:处理查询客户的订单历史记录
  4. 4.payments service:处理订单的付款处理
  5. 5.api gateway:将服务统一到一个端点下
  6. 6.mongodb/ postgresql:用作存储订单、订单历史记录、产品等的写入优化数据库
信息

您不需要在演示应用程序中使用 MongoDB/ Postgresql 作为您的写入优化数据库;您可以使用其他 prisma 支持的数据库 。这只是一个例子。

使用 Next.js 和 Tailwind 的电商应用程序前端#

电商微服务应用程序包含一个前端,使用 Next.js 和 TailwindCSS 构建。应用程序后端使用 Node.js。数据存储在 Redis 和 MongoDB 或 PostgreSQL 中,使用 Prisma。以下是展示电商应用程序前端的屏幕截图。

仪表板: 显示具有不同搜索功能的产品列表,可以在设置页面中配置。

设置: 可以通过单击仪表板右上角的齿轮图标访问。在此处控制搜索栏、聊天机器人可见性和其他功能。

仪表板(语义文本搜索): 配置为语义文本搜索,搜索栏允许使用自然语言查询。例如:“纯棉蓝色衬衫”。

仪表板(语义图像查询): 配置为语义图像摘要搜索,搜索栏允许使用基于图像的查询。例如:“左胸耐克标志”。

聊天机器人: 位于页面右下角,协助产品搜索和详细视图。

在聊天中选择产品会在仪表板上显示其详细信息。

购物车: 将产品添加到购物车,然后使用“立即购买”按钮结账。

订单历史记录: 购买后,顶部导航栏中的“订单”链接会显示订单状态和历史记录。

管理面板: 可以通过顶部导航栏中的“admin”链接访问。显示购买统计信息和趋势产品。

数据库设置#

信息

注册 OpenAI 帐户 以获取您将在演示中使用的 API 密钥(在 .env 文件中添加 OPEN_AI_API_KEY 变量)。您还可以参考 OpenAI API 文档 以获取更多信息。

GITHUB 代码

以下是对本教程中使用的应用程序的源代码进行克隆的命令

git clone --branch v9.2.0 https://github.com/redis-developer/redis-microservices-ecommerce-solutions

示例数据#

考虑一个简化的电商数据集,其中包含用于语义搜索的产品详细信息。

database/fashion-dataset/001/products/*.json
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 集成。

此过程涉及两个主要步骤

  1. 1.生成向量文档: 利用 convertToVectorDocuments 函数,我们将产品详细信息转换为向量文档。这种转换至关重要,因为它将产品详细信息转换为适合 Redis 存储的格式。
  2. 2.将嵌入数据注入 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 命令。

设置搜索 API#

API 终结点#

本节介绍 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 &eacute;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 实现#

此 API 的后端实现涉及以下步骤

  1. 1.getProductsByVSSText 函数处理 API 请求。
  2. 2.getSimilarProductsScoreByVSS 函数对产品详细信息执行语义搜索。它与 OpenAI 的语义分析功能集成,以解释 searchText 并从 Redis 向量存储中识别相关产品。
server/src/services/products/src/open-ai-prompt.ts
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;
};
server/src/services/products/src/service-impl.ts
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;
};

前端 UI#

  • 设置配置: 在设置页面启用 语义文本搜索 功能
  • 执行搜索: 在仪表板中使用文本查询。
  •  用户可以点击产品卡片中的产品描述以查看完整详细信息。

探索语义文本搜索在增强电子商务体验方面的强大功能。本教程指导您将 OpenAI 的语义功能与 Redis 集成,以构建动态的产品搜索引擎。

进一步阅读#