本教程侧重于构建视频内容的问答引擎。它将涵盖以下主题
OpenAI
, Google Gemini
和 LangChain
来总结视频内容并生成向量嵌入Redis
存储和搜索向量嵌入Redis
作为语义向量搜索缓存以下是在本教程中使用的应用程序的克隆源代码的命令
git clone https://github.com/redis-developer/video-qa-semantic-vector-caching
在我们深入探讨本教程的细节之前,让我们先回顾一下在构建生成式 AI 应用程序时需要理解的一些概念。
我们的应用程序利用这些技术来创建一个基于视频内容的独特问答平台。用户可以上传 YouTube 视频 URL 或 ID,应用程序利用生成式 AI 来总结这些视频,制定潜在问题并创建可搜索的数据库。然后,可以使用该数据库来查询以找到用户提交的问题的答案,直接从视频内容中获取信息。
以下是我们的应用程序如何使用 AI 和语义向量搜索来回答用户根据视频内容提出的问题
https://www.youtube.com/watch?v=LaiQFZ5bXaM
)或视频 ID(例如 LaiQFZ5bXaM
)上传 YouTube 视频。应用程序处理这些输入以检索必要的视频信息。在本教程中,该应用程序预先播种了来自 Redis YouTube 频道 的一组视频。但是,当您运行应用程序时,可以调整它以涵盖您自己的视频集。2. 视频处理和 AI 交互:使用 Youtube 数据 API,应用程序获取视频 标题、 描述和 缩略图。它还使用 SearchAPI.io 来检索视频转录。然后,将这些转录传递给大型语言模型 (LLM) - Google Gemini 或 OpenAI 的 ChatGPT - 以进行摘要和示例问题生成。LLM 还为这些摘要生成向量嵌入。
LLM 生成的示例摘要和示例问题如下所示
Summary:
The video provides a walkthrough of building a real-time stock tracking application
using Redis Stack, demonstrating its capability to handle multiple data models and
act as a message broker in a single integrated database. The application maintains
a watch list of stock symbols, along with real-time trading information and a chart
updated with live data from the Alpaca API. The presenter uses Redis Stack features
such as sets, JSON documents, time series, Pub/Sub, and Top-K filter to store and
manage different types of data. An architecture diagram is provided, explaining the
interconnection between the front end, API service, and streaming service within
the application. Code snippets highlight key aspects of the API and streaming
service written in Python, highlighting the use of Redis Bloom, Redis JSON, Redis
Time Series, and Redis Search for managing data. The video concludes with a
demonstration of how data structures are visualized and managed in RedisInsight,
emphasizing how Redis Stack can simplify the building of a complex real-time
application by replacing multiple traditional technologies with one solution.
Example Questions and Answers:
Q1: What is Redis Stack and what role does it play in the application?
Q2: How is the stock watch list stored and managed within the application?
Q3: What type of data does the application store using time series capabilities of
Redis Stack?
Q4: Can you explain the use of the Top-K filter in the application?
Q5: What methods are used to update the front end with real-time information in
the application?
Q6: How does the application sync the watch list with the streaming service?
Q7: What frontend technologies are mentioned for building the UI of the application?
Q8: How does Redis Insight help in managing the application data?
3. 使用 Redis 存储数据:所有生成的数据,包括视频摘要、潜在问题和向量嵌入,都存储在 Redis 中。该应用程序利用 Redis 的各种数据类型来高效地处理数据、缓存和快速检索。
4. 搜索和答案检索:使用 Next.js 构建的前端允许用户提问。然后,应用程序使用语义向量相似性搜索 Redis 数据库以查找相关视频内容。它还使用 LLM 来制定答案,优先考虑来自视频转录的信息。
5. 结果呈现:该应用程序显示最相关的视频以及 AI 生成的答案,提供全面的互动式用户体验。它还显示使用语义向量缓存从先前查询中缓存的结果,以实现更快的响应时间。
要开始使用我们的 AI 驱动的视频问答应用程序,您首先需要设置您的开发环境。我们将按照项目 README.md
文件中概述的说明进行操作。
Redis 用作我们的数据库,以高效地存储和检索数据。您可以通过在 redis.com/try-free 上注册来快速开始使用云托管的 Redis 实例。这非常适合开发和测试目的。您可以在 Redis 免费层的限制内轻松存储此应用程序的数据。
首先,克隆包含我们项目的存储库
git clone https://github.com/redis-developer/video-qa-semantic-vector-caching
在设置好 Node.js 环境后,您需要安装必要的包。导航到项目目录的根目录并运行以下命令
npm install
此命令将安装 package.json
文件中列出的所有依赖项,确保您拥有运行应用程序所需的一切。
在运行应用程序之前,请确保配置环境变量。有一个脚本可以自动为您生成 .env
文件。运行以下命令
npm run setup
这将生成以下文件
app/.env
- 此文件包含 Next.js 应用程序的环境变量。app/.env.docker
- 此文件包含在 Docker 中运行时环境变量的覆盖。services/video-search/.env
- 此文件包含视频搜索服务的环境变量。services/video-search/.env.docker
- 此文件包含在 Docker 中运行时环境变量的覆盖。默认情况下,您不应该触碰 app
中的环境文件。但是,您需要配置 services/video-search
目录中的环境文件。
The services/video-search/.env
看起来像这样
USE=<HF|OPENAI>
REDIS_URL=<redis[s]://[[username][:password]@][host][:port][/db-number]>
SEARCHAPI_API_KEY=<https://www.searchapi.io/>
YOUTUBE_TRANSCRIPT_PREFIX=<redis-transcript-prefix>
YOUTUBE_VIDEO_INFO_PREFIX=<redis-video-info-prefix>
GOOGLE_API_KEY=<https://console.cloud.google.com/apis/credentials>
GOOGLE_EMBEDDING_MODEL=<https://ai.google.dev/models/gemini#model_variations>
GOOGLE_SUMMARY_MODEL=<https://ai.google.dev/models/gemini#model_variations>
OPENAI_API_KEY=<https://platform.openai.com/api-keys>
OPENAI_ORGANIZATION=<https://platform.openai.com/account/organization>
OPENAI_EMBEDDING_MODEL=<https://platform.openai.com/account/limits>
OPENAI_SUMMARY_MODEL=<https://platform.openai.com/account/limits>
对于 Gemini 模型,如果您不确定该怎么做,可以使用以下内容
GOOGLE_EMBEDDING_MODEL=embedding-001
GOOGLE_SUMMARY_MODEL=gemini-pro
对于 OpenAI 模型,如果您不确定该怎么做,可以使用以下内容
OPENAI_EMBEDDING_MODEL=text-embedding-ada-002
OPENAI_SUMMARY_MODEL=gpt-4-1106-preview
注意:根据您的 OpenAI 层级,您可能需要使用不同的摘要模型。 gpt-3.5
模型就可以了。
The _PREFIX
环境变量用于为 Redis 中的键添加前缀。如果您想将同一个 Redis 实例用于多个应用程序,这将非常有用。它们具有以下默认值
YOUTUBE_TRANSCRIPT_PREFIX=transcripts:
YOUTUBE_VIDEO_INFO_PREFIX=yt-videos:
如果您对默认值满意,可以从 .env
文件中删除这些值。
最后, services/video-search/.env.docker
文件包含在 Docker 中使用时 Redis URL 的覆盖。默认情况下,此应用程序在 Docker 中设置本地 Redis 实例。如果您使用的是云实例,您只需将 URL 添加到您的 .env
中,并删除 .env.docker
文件中的覆盖。
在安装和配置应用程序后,运行以下命令来构建 Docker 镜像并运行容器
npm run dev
此命令构建应用程序和视频服务,并将它们部署到 Docker。它为热重载进行了所有设置,因此,如果您对代码进行更改,它将自动重新启动服务器。
容器启动并运行后,可以通过 Web 浏览器访问该应用程序
此设置允许您通过浏览器与客户端应用程序交互,并向在单独端口上托管的视频搜索服务发出请求。
视频搜索服务不发布客户端应用程序。相反,它公开了一个 REST API,可用于与该服务交互。您可以通过检查 Docker 或访问以下 URL 来验证它是否正在运行
您现在应该已启动并运行!本教程的其余部分重点介绍应用程序的工作原理以及如何使用它,以及代码示例。
后端已设置为处理 YouTube 视频链接或 ID。项目中相关的代码片段演示了如何处理这些输入。
export type VideoDocument = Document<{
id: string;
link: string;
title: string;
description: string;
thumbnail: string;
}>;
export async function load(videos: string[] = config.youtube.VIDEOS) {
// Parse the video URLs to get a list of video IDs
const videosToLoad: string[] = videos.map(parseVideoUrl).filter((video) => {
return typeof video === 'string';
}) as string[];
// Get video title, description, and thumbnail from YouTube API v3
const videoInfo = await getVideoInfo(videosToLoad);
// Get video transcripts from SearchAPI.io, join the video info
const transcripts = await mapAsyncInOrder(videosToLoad, async (video) => {
return await getTranscript(video, videoInfo[video]);
});
// Return the videos as documents with metadata, and pageContent being the transcript
return transcripts.filter(
(transcript) => typeof transcript !== 'undefined',
) as VideoDocument[];
}
在同一个文件中,您将看到两个缓存
const cache = cacheAside(config.youtube.TRANSCRIPT_PREFIX);
const videoCache = jsonCacheAside<VideoInfo>(config.youtube.VIDEO_INFO_PREFIX);
这些缓存用于在 Redis 中存储转录文本(作为 string
)和视频元数据(作为 JSON
)。 cache
函数是使用 Redis 来存储和检索数据的辅助函数。它们看起来像这样
export function cacheAside(prefix: string) {
return {
get: async (key: string) => {
return await client.get(`${prefix}${key}`);
},
set: async (key: string, value: string) => {
return await client.set(`${prefix}${key}`, value);
},
};
}
export function jsonCacheAside<T>(prefix: string) {
return {
get: async (key: string): Promise<T | undefined> => {
return client.json.get(`${prefix}${key}`) as T;
},
set: async (key: string, value: RedisJSON) => {
return await client.json.set(`${prefix}${key}`, '$', value);
},
};
}
您将看到这些函数在应用程序中的其他地方使用。它们用于防止不必要的 API 调用,在本例中,用于 SearchAPI.io 和 YouTube API。
在获取视频转录文本和元数据后,将使用 LangChain 和 LLM(Gemini 和 ChatGPT)对转录文本进行总结。这里有一些有趣的代码需要理解
prompt
refinement chain
vector embedding chain
LLM summary prompt
被分成两部分。这样做是为了允许分析转录文本长度超过 LLM 接受的上下文的视频。
import { PromptTemplate } from 'langchain/prompts';
const summaryTemplate = `
You are an expert in summarizing YouTube videos.
Your goal is to create a summary of a video.
Below you find the transcript of a video:
--------
{text}
--------
The transcript of the video will also be used as the basis for a question and answer bot.
Provide some examples questions and answers that could be asked about the video. Make these questions very specific.
Total output will be a summary of the video and a list of example questions the user could ask of the video.
SUMMARY AND QUESTIONS:
`;
export const SUMMARY_PROMPT = PromptTemplate.fromTemplate(summaryTemplate);
const summaryRefineTemplate = `
You are an expert in summarizing YouTube videos.
Your goal is to create a summary of a video.
We have provided an existing summary up to a certain point: {existing_answer}
Below you find the transcript of a video:
--------
{text}
--------
Given the new context, refine the summary and example questions.
The transcript of the video will also be used as the basis for a question and answer bot.
Provide some examples questions and answers that could be asked about the video. Make
these questions very specific.
If the context isn't useful, return the original summary and questions.
Total output will be a summary of the video and a list of example questions the user could ask of the video.
SUMMARY AND QUESTIONS:
`;
export const SUMMARY_REFINE_PROMPT = PromptTemplate.fromTemplate(
summaryRefineTemplate,
);
The summary prompts
用于使用 LangChain 创建 refinement chain
。LangChain 将自动处理拆分视频转录文本文档以及相应地调用 LLM。
const videoSummarizeChain = loadSummarizationChain(llm, {
type: 'refine',
questionPrompt: SUMMARY_PROMPT,
refinePrompt: SUMMARY_REFINE_PROMPT,
});
const summaryCache = cacheAside(`${prefix}-${config.redis.SUMMARY_PREFIX}`);
async function summarizeVideos(videos: VideoDocument[]) {
const summarizedDocs: VideoDocument[] = [];
for (const video of videos) {
log.debug(`Summarizing ${video.metadata.link}`, {
...video.metadata,
location: `${prefix}.summarize.docs`,
});
const existingSummary = await summaryCache.get(video.metadata.id);
if (typeof existingSummary === 'string') {
summarizedDocs.push(
new Document({
metadata: video.metadata,
pageContent: existingSummary,
}),
);
continue;
}
const splitter = new TokenTextSplitter({
chunkSize: 10000,
chunkOverlap: 250,
});
const docsSummary = await splitter.splitDocuments([video]);
const summary = await videoSummarizeChain.run(docsSummary);
log.debug(`Summarized ${video.metadata.link}:\n ${summary}`, {
summary,
location: `${prefix}.summarize.docs`,
});
await summaryCache.set(video.metadata.id, summary);
summarizedDocs.push(
new Document({
metadata: video.metadata,
pageContent: summary,
}),
);
}
return summarizedDocs;
}
注意, summaryCache
用于首先询问 Redis 该视频是否已总结。如果有,它将返回摘要并跳过 LLM。这是一个很好的例子,说明了如何使用 Redis 来缓存数据并避免不必要的 API 调用。以下是一个带有问题的视频摘要示例。
Summary:
The video provides a walkthrough of building a real-time stock tracking application
using Redis Stack, demonstrating its capability to handle multiple data models and
act as a message broker in a single integrated database. The application maintains
a watch list of stock symbols, along with real-time trading information and a chart
updated with live data from the Alpaca API. The presenter uses Redis Stack features
such as sets, JSON documents, time series, Pub/Sub, and Top-K filter to store and
manage different types of data. An architecture diagram is provided, explaining the
interconnection between the front end, API service, and streaming service within
the application. Code snippets highlight key aspects of the API and streaming
service written in Python, highlighting the use of Redis Bloom, Redis JSON, Redis
Time Series, and Redis Search for managing data. The video concludes with a
demonstration of how data structures are visualized and managed in RedisInsight,
emphasizing how Redis Stack can simplify the building of a complex real-time
application by replacing multiple traditional technologies with one solution.
Example Questions and Answers:
Q1: What is Redis Stack and what role does it play in the application?
Q2: How is the stock watch list stored and managed within the application?
Q3: What type of data does the application store using time series capabilities of
Redis Stack?
Q4: Can you explain the use of the Top-K filter in the application?
Q5: What methods are used to update the front end with real-time information in
the application?
Q6: How does the application sync the watch list with the streaming service?
Q7: What frontend technologies are mentioned for building the UI of the application?
Q8: How does Redis Insight help in managing the application data?
The vector embedding chain
用于为视频摘要生成向量嵌入。这是通过要求 LLM 为摘要生成文本嵌入来完成的。 vector embedding chain
的定义如下
const vectorStore = new RedisVectorStore(embeddings, {
redisClient: client,
indexName: `${prefix}-${config.redis.VIDEO_INDEX_NAME}`,
keyPrefix: `${prefix}-${config.redis.VIDEO_PREFIX}`,
indexOptions: {
ALGORITHM: VectorAlgorithms.HNSW,
DISTANCE_METRIC: 'IP',
},
});
向量存储使用 LangChain 中的 RedisVectorStore
类。此类是 Redis 的包装器,允许您存储和搜索向量嵌入。我们使用 HNSW
算法和 IP
距离度量。有关支持的算法和距离度量的更多信息,请参阅 Redis 向量存储文档。我们将 embeddings
对象传递给 RedisVectorStore
构造函数。此对象的定义如下
new GoogleGenerativeAIEmbeddings({
apiKey: config.google.API_KEY,
modelName: modelName ?? config.google.EMBEDDING_MODEL,
taskType: TaskType.SEMANTIC_SIMILARITY,
});
或对于 OpenAI
new OpenAIEmbeddings({
openAIApiKey: config.openai.API_KEY,
modelName: modelName ?? config.openai.EMBEDDING_MODEL,
configuration: {
organization: config.openai.ORGANIZATION,
},
});
The embeddings
对象用于为视频摘要生成向量嵌入。然后使用 vectorStore
将这些嵌入存储在 Redis 中。
注意,我们首先检查是否已经使用 Redis Set VECTOR_SET
生成了向量。如果有,我们将跳过 LLM 并使用现有的向量。这避免了不必要的 API 调用,可以加快速度。
我们的应用程序的关键功能之一是能够使用 AI 生成的查询搜索视频内容。本节将介绍后端如何处理搜索请求以及与 AI 模型的交互。
当用户通过前端提交问题时,后端将执行以下步骤来获取问题的答案以及支持视频
vectorStore
根据语义问题搜索最相关的视频。为了回答一个问题,我们首先会生成一个与所提问题语义相似的答案。这是通过使用下面定义的 QUESTION_PROMPT
来完成的
import { PromptTemplate } from 'langchain/prompts';
const questionTemplate = `
You are an expert in summarizing questions.
Your goal is to reduce a question down to its simplest form while still retaining the semantic meaning.
Below you find the question:
--------
{question}
--------
Total output will be a semantically similar question that will be used to search an existing dataset.
SEMANTIC QUESTION:
`;
export const QUESTION_PROMPT = PromptTemplate.fromTemplate(questionTemplate);
使用此提示,我们生成 语义问题
并使用它来搜索视频。如果我们没有使用 语义问题
找到任何视频,我们可能还需要使用原始 问题
进行搜索。这是通过使用下面定义的 ORIGINAL_QUESTION_PROMPT
来完成的
async function getVideos(question: string) {
log.debug(
`Performing similarity search for videos that answer: ${question}`,
{
question,
location: `${prefix}.search.search`,
},
);
const KNN = config.searches.KNN;
/* Simple standalone search in the vector DB */
return await (vectorStore.similaritySearch(question, KNN) as Promise<
VideoDocument[]
>);
}
async function searchVideos(question: string) {
log.debug(`Original question: ${question}`, {
location: `${prefix}.search.search`,
});
const semanticQuestion = await prompt.getSemanticQuestion(question);
log.debug(`Semantic question: ${semanticQuestion}`, {
location: `${prefix}.search.search`,
});
let videos = await getVideos(semanticQuestion);
if (videos.length === 0) {
log.debug(
'No videos found for semantic question, trying with original question',
{
location: `${prefix}.search.search`,
},
);
videos = await getVideos(question);
}
log.debug(`Found ${videos.length} videos`, {
location: `${prefix}.search.search`,
});
const answerDocument = await prompt.answerQuestion(question, videos);
return [
{
...answerDocument.metadata,
question: answerDocument.pageContent,
isOriginal: true,
},
];
}
上面的代码展示了从 LLM 获取答案并将其返回给用户的整个过程。一旦识别出相关的视频,后端就会使用 Google Gemini 或 OpenAI 的 ChatGPT 来生成答案。这些答案是根据存储在 Redis 中的视频字幕来制定的,确保它们与用户的查询相关。用于向 LLM 询问答案的 ANSWER_PROMPT
如下所示
import { PromptTemplate } from 'langchain/prompts';
const answerTemplate = `
You are an expert in answering questions about Redis and Redis Stack.
Your goal is to take a question and some relevant information extracted from videos and return the answer to the question.
- Try to mostly use the provided video info, but if you can't find the answer there you can use other resources.
- Make sure your answer is related to Redis. All questions are about Redis. For example, if a question is asking about strings, it is asking about Redis strings.
- The answer should be formatted as a reference document using markdown. Make all headings and links bold, and add new paragraphs around any code blocks.
- Your answer should include as much detail as possible and be no shorter than 500 words.
Here is some extracted video information relevant to the question: {data}
Below you find the question:
--------
{question}
--------
Total output will be the answer to the question.
ANSWER:
`;
export const ANSWER_PROMPT = PromptTemplate.fromTemplate(answerTemplate);
就是这样!后端现在将把答案和支持视频返回给用户。
我们在本教程中构建的应用程序是探索 AI 驱动的视频问答的可能性很好的起点。但是,有很多方法可以改进应用程序并使其更有效率。其中一项改进是使用 Redis 作为语义向量缓存。
请注意,在上一节中,我们讨论了对 LLM 发出调用以回答每个问题。在此步骤期间存在性能瓶颈,因为 LLM 响应时间会有所不同,但可能需要几秒钟。如果有一种方法可以防止不必要的 LLM 调用怎么办?这就是 语义向量缓存
发挥作用的地方。
语义向量缓存是指在将对 LLM 的调用的结果与提示的向量嵌入一起缓存时发生的情况。在我们的应用程序中,我们可以为问题生成向量嵌入,并将它们与来自 LLM 的答案一起存储在 Redis 中。这将使我们能够避免对已经回答过类似问题进行 LLM 调用。
你可能会问为什么要将问题存储为向量?为什么不直接将问题存储为字符串?答案是将问题存储为向量使我们能够执行语义向量相似性搜索。因此,我们不必依赖于有人提出完全相同的问题,而是可以确定一个可接受的相似性分数,并为类似的问题返回答案。
如果你已经熟悉将向量存储在 Redis 中,我们已经在本教程中介绍过,语义向量缓存是对它的扩展,并且基本上以相同的方式运行。唯一的区别是我们将问题存储为向量,而不是视频摘要。我们还使用 缓存旁路 模式。过程如下
为了存储问题向量,我们需要创建一个新的向量存储。这将创建一个专门针对问题和答案向量的索引。代码如下所示
const answerVectorStore = new RedisVectorStore(embeddings, {
redisClient: client,
indexName: `${prefix}-${config.redis.ANSWER_INDEX_NAME}`,
keyPrefix: `${prefix}-${config.redis.ANSWER_PREFIX}`,
indexOptions: {
ALGORITHM: VectorAlgorithms.FLAT,
DISTANCE_METRIC: 'L2',
},
});
我们之前定义的 answerVectorStore
与 vectorStore
非常相似,但它使用了不同的 算法和距离度量。此算法更适合为我们的问题进行相似性搜索。
以下代码演示了如何使用 answerVectorStore
来检查是否有类似问题已经得到解答。
async function checkAnswerCache(question: string) {
const haveAnswers = await answerVectorStore.checkIndexExists();
if (!(haveAnswers && config.searches.answerCache)) {
return;
}
log.debug(`Searching for closest answer to question: ${question}`, {
location: `${prefix}.search.getAnswer`,
question,
});
/**
* Scores will be between 0 and 1, where 0 is most accurate and 1 is least accurate
*/
let results = (await answerVectorStore.similaritySearchWithScore(
question,
config.searches.KNN,
)) as Array<[AnswerDocument, number]>;
if (Array.isArray(results) && results.length > 0) {
// Filter out results with too high similarity score
results = results.filter(
(result) => result[1] <= config.searches.maxSimilarityScore,
);
const inaccurateResults = results.filter(
(result) => result[1] > config.searches.maxSimilarityScore,
);
if (Array.isArray(inaccurateResults) && inaccurateResults.length > 0) {
log.debug(
`Rejected ${inaccurateResults.length} similar answers that have a score > ${config.searches.maxSimilarityScore}`,
{
location: `${prefix}.search.getAnswer`,
scores: inaccurateResults.map((result) => result[1]),
},
);
}
}
if (Array.isArray(results) && results.length > 0) {
log.debug(
`Accepted ${results.length} similar answers that have a score <= ${config.searches.maxSimilarityScore}`,
{
location: `${prefix}.search.getAnswer`,
scores: results.map((result) => result[1]),
},
);
return results.map((result) => {
return {
...result[0].metadata,
question: result[0].pageContent,
isOriginal: false,
};
});
}
}
The similaritySearchWithScore
将找到与所提问题类似的问题。它对它们进行排名,从 0
到 1
,其中 0
是最相似或“最接近”的。然后,我们会根据 maxSimilarityScore
环境变量过滤掉任何过于相似的结果。如果我们找到任何结果,我们会将其返回给用户。在这里使用最大分数至关重要,因为我们不想返回不准确的结果。
为了完成此过程,我们需要应用 缓存旁路 模式并将问题存储为 Redis 中的向量。这可以通过以下方式完成
async function searchVideos(
question: string,
{ useCache = config.searches.answerCache }: VideoSearchOptions = {},
) {
log.debug(`Original question: ${question}`, {
location: `${prefix}.search.search`,
});
if (useCache) {
const existingAnswer = await checkAnswerCache(question);
if (typeof existingAnswer !== 'undefined') {
return existingAnswer;
}
}
const semanticQuestion = await prompt.getSemanticQuestion(question);
log.debug(`Semantic question: ${semanticQuestion}`, {
location: `${prefix}.search.search`,
});
if (useCache) {
const existingAnswer = await checkAnswerCache(semanticQuestion);
if (typeof existingAnswer !== 'undefined') {
return existingAnswer;
}
}
let videos = await getVideos(semanticQuestion);
if (videos.length === 0) {
log.debug(
'No videos found for semantic question, trying with original question',
{
location: `${prefix}.search.search`,
},
);
videos = await getVideos(question);
}
log.debug(`Found ${videos.length} videos`, {
location: `${prefix}.search.search`,
});
const answerDocument = await prompt.answerQuestion(question, videos);
if (config.searches.answerCache) {
await answerVectorStore.addDocuments([answerDocument]);
}
return [
{
...answerDocument.metadata,
question: answerDocument.pageContent,
isOriginal: true,
},
];
}
当提出问题时,我们首先检查答案缓存。我们检查问题和生成的语义问题。如果我们找到答案,我们会将其返回给用户。如果我们没有找到答案,我们会调用 LLM 来生成答案。然后,我们将问题存储为 Redis 中的向量,以及来自 LLM 的答案。看起来我们在这里比没有缓存时做了更多工作,但请记住,LLM 是瓶颈。通过这样做,我们避免了不必要的 LLM 调用。
以下是应用程序的几个屏幕截图,展示了当您找到现有问题的答案时它是什么样的
在本教程中,我们探讨了如何使用 Redis、LangChain 和各种其他技术构建一个 AI 驱动的视频问答应用程序。我们涵盖了设置环境、处理视频上传以及实现搜索功能。您还了解了如何使用 Redis 作为 向量存储
和 语义向量缓存
。
注意:本教程中不包括对前端 Next.js
应用程序的概述。但是,您可以在 GitHub 存储库 中的 app
目录中找到代码。
请记住,Redis 允许您轻松使用云托管实例,您可以在 redis.com/try-free 上注册。这使得 AI 和 Redis 的实验比以往更容易。
我们希望本教程能够激励您探索将 AI 与像 Redis 这样的强大数据库结合使用以创建创新应用程序的激动人心的可能性。
async function storeVideoVectors(documents: VideoDocument[]) {
log.debug('Storing documents...', {
location: `${prefix}.store.store`,
});
const newDocuments: VideoDocument[] = [];
await Promise.all(
documents.map(async (doc) => {
const exists = await client.sIsMember(
`${prefix}-${config.redis.VECTOR_SET}`,
doc.metadata.id,
);
if (!exists) {
newDocuments.push(doc);
}
}),
);
log.debug(`Found ${newDocuments.length} new documents`, {
location: `${prefix}.store.store`,
});
if (newDocuments.length === 0) {
return;
}
await vectorStore.addDocuments(newDocuments);
await Promise.all(
newDocuments.map(async (doc) => {
await client.sAdd(
`${prefix}-${config.redis.VECTOR_SET}`,
doc.metadata.id,
);
}),
);
}