介绍
Thomas 于 2024 年 4 月加入 Vespa 担任高级软件工程师。在他之前作为 AI 顾问的最后一个任务中,他实际上构建了一个基于 Vespa 的大规模 PDF 集合的 RAG 应用。
PDF 在企业世界中无处不在,从中搜索和检索信息的能力是一个常见的用例。挑战在于许多 PDF 通常属于以下一种或多种类别:
- 它们是扫描文档,意味着文本无法轻松提取,因此必须使用 OCR,这增加了复杂性。
- 它们包含大量的图表、表格和示意图,即使可以提取文本,这些内容也不易被检索。
- 它们包含许多图像,有时其中包含有价值的信息。
请注意,术语 ColPali 有两个含义:
- 一个特定的 模型 ,以及一个相关的 论文 ,它在 VLM (PaliGemma) 之上训练一个 LoRa-adapter,以生成用于“后期交互”的文本和图像联合嵌入(图像中每个 patch 一个嵌入),基于 ColBERT 方法扩展视觉语言模型。
- 它还代表了一种视觉文档检索的 方向 ,结合 VLM 的能力与高效的后期交互机制。这种方向不限于原论文中的特定模型,还可以应用于其他 VLM,例如我们关于使用 ColQwen2 和 Vespa 的 notebook 。
在这篇博客文章中,我们将深入探讨如何使用 ColPali 嵌入在 Vespa 上构建一个展示视觉 RAG 的实时演示应用。我们将描述应用的架构、用户体验以及构建应用所使用的技术栈。
以下是演示应用的一些截图:
第一个示例并不是一个常见的查询,但它展示了视觉检索在某些类型查询中的强大功能。这很好地体现了“所见即所搜 (WYSIWYS)”范式。
相似性映射高亮了最相似的部分,使用户可以轻松看出页面的哪些部分与查询最为相关。
第二个示例是一个更常见的用户查询,展示了 ColPali 在语义相似性方面的强大能力。
亲身经历过让 PDF 可检索的困难后,Thomas 对视觉语言模型 (VLM) 领域的最新进展尤为感兴趣。
在阅读了之前关于 ColPali 的 Vespa 博客文章 以及与 Jo Bergum 进行了一系列深入讨论后,他受到了启发,提出了一个使用 Vespa 构建视觉 RAG 应用的项目。
在 Vespa,员工有机会在每个迭代周期中提出他们想要开展的工作计划。只要建议的工作与公司的目标一致,并且没有其他紧急优先事项,我们就可以开始实施。对于来自咨询行业的 Thomas 来说,这种自主性无疑是一股清新的空气。
TL;DR
我们构建了一个 实时演示应用程序,展示如何使用 Vespa 中的 ColPali 嵌入和 Python 仅借助 FastHTML 实现基于 PDF 的 Visual RAG。
我们还提供了复现代码:
- 一个可以运行的 notebook,用于设置您自己的 Vespa 应用程序以实现 Visual RAG。
- FastHTML 应用 的代码,您可以用来设置一个与 Vespa 应用程序交互的 Web 应用。
项目目标
该项目有两个主要目标:
1. 构建一个实时演示
虽然开发者可能会对以终端 JSON 输出作为 UI 的演示感到满意,但事实上,大多数人更倾向于一个网页界面。
这将使我们能够展示在 Vespa 中基于 ColPali 嵌入的 PDF Visual RAG。我们认为这在法律、金融、建筑、学术和医疗等众多领域和用例中都具有相关性。
我们有信心这将在未来非常重要,但目前尚未见到有任何实际应用能够展示这一点。
同时,这也为我们在效率、可扩展性和用户体验方面提供了许多宝贵见解。此外,我们也非常好奇(或者说有些紧张),想知道它是否足够快以提供良好的用户体验。
我们还希望突出一些 Vespa 的有用功能,例如:
- 分阶段排序
- 关键词联想建议
- 多向量 MaxSim 计算
2. 创建一个开源模板
我们希望提供一个模板,供他人构建自己的 Visual RAG 应用程序。
这个模板应该对他人来说足够简单,无需掌握大量特定的编程语言或框架。
创建数据集
在我们的演示中,我们希望使用一个 PDF 文档数据集,其中包含大量以图像、表格和图表形式呈现的重要信息。我们还需要一个规模足够大的数据集,以证明直接将所有图像上传到 VLM(跳过检索步骤)是不可行的。
使用 gemini-1.5-flash-8b
,当前的最大输入图像数为 3600。
由于没有符合我们需求的公共数据集,我们决定创建自己的数据集。
作为自豪的挪威人,我们很高兴发现挪威政府全球养老基金(GPFG,也称为石油基金)自 2000 年以来已在其网站上发布年度报告和治理文件。网站上未提及版权,并且其最近的声明表明其是世界上最透明的基金,因此我们确信可以使用这些数据进行演示。
数据集包括从 2000 年到 2024 年的 116 份不同 PDF 报告,共计 6992 页。
数据集包括图像、文本、URL、页码、生成的问题、查询以及 ColPali 嵌入,现已发布在 这里。
生成合成查询和问题
我们还为每页生成了合成查询和问题。这些可以用于以下两个目的:
- 在用户输入时,为搜索框提供关键词联想建议。
- 用于评估目的。
我们生成问题和查询所使用的提示来自 Daniel van Strien 的这篇精彩博文。
您是一名投资者、股票分析师和金融专家。接下来您将看到挪威政府全球养老基金(GPFG)发布的报告页面图像。该报告可能是年度或季度报告,或关于责任投资、风险等主题的政策报告。
您的任务是生成检索查询和问题,这些查询和问题可以用于在大型文档库中检索此文档(或基于该文档提出问题)。
请生成三种不同类型的检索查询和问题。
检索查询是基于关键词的查询,由 2-5 个单词组成,用于在搜索引擎中找到该文档。
问题是自然语言问题,文档中包含该问题的答案。
查询类型如下:
1. 广泛主题查询:覆盖文档的主要主题。
2. 具体细节查询:涵盖文档的某个具体细节或方面。
3. 可视元素查询:涵盖文档中的某个可视元素,例如图表、图形或图像。
重要指南:
- 确保查询与检索任务相关,而不仅仅是描述页面内容。
- 使用基于事实的自然语言风格来书写问题。
- 设计查询时,以有人在大型文档库中搜索此文档为前提。
- 查询应多样化,代表不同的搜索策略。
将您的回答格式化为如下结构的 JSON 对象:
{
"broad_topical_question": "2019 年的责任投资政策是什么?",
"broad_topical_query": "2019 责任投资政策",
"specific_detail_question": "可再生能源的投资比例是多少?",
"specific_detail_query": "可再生能源投资比例",
"visual_element_question": "总持有价值的时间趋势如何?",
"visual_element_query": "总持有价值趋势"
}
如果没有相关的可视元素,请在可视元素问题和查询中提供空字符串。
以下是需要分析的文档图像:
请基于此图像生成查询,并以指定的 JSON 格式提供响应。
只返回 JSON,不返回任何额外说明文本。
我们使用 gemini-1.5-flash-8b
生成问题和查询。
注意
在第一次运行时,我们发现生成了一些非常长的问题,因此我们在 generationconfig 中添加了 maxOutputTokens=500
,这非常有帮助。
我们还注意到生成的问题和查询中有一些奇怪的内容,例如“string”多次出现在问题中。我们确实希望对生成的问题和查询进行更深入的验证。
全程使用 Python
我们的目标用户是不断壮大的数据科学和 AI 社区。这一群体很可能是 Python 在 GitHub Octoverse 状态报告中被列为最受欢迎(且增长最快)的编程语言的主要原因之一。
我们需要在后端使用 Python 进行查询嵌入推理(使用 colpali-engine-库),直到 Vespa 原生支持 ColpaliEmbedder
(正在开发中,详见 github issue)。如果前端采用其他语言(及其框架),会增加项目复杂性,从而使他人更难复现该应用。
因此,我们决定用 Python 构建整个应用。
前端框架的选择
Streamlit 和 Gradio
我们承认,使用 Gradio 和 Streamlit 构建简单的 PoC(概念验证)非常容易,我们过去也出于这个目的使用过它们。但有两个主要原因让我们决定不选它们:
- 我们需要一个可以在生产环境中使用的专业外观 UI。
- 我们需要良好的性能。等待几秒钟或 UI 间歇性冻结,对我们想要展示的应用是不够的。
虽然我们喜欢锻炼身体,但我们不喜欢 Streamlit 屏幕右上角的“运行中”消息。
FastHTML 的救援
我们是 answer.ai 的忠实粉丝。因此,当他们在今年早些时候发布 FastHTML3时,我们很高兴尝试一下。
FastHTML 是一个使用纯 Python 构建现代 Web 应用的框架。根据其 愿景:
FastHTML 是一个通用的全栈 Web 编程系统,与 Django、NextJS 和 Ruby on Rails 属于同一类型。其愿景是成为创建快速原型的最简单方式,同时也是创建可扩展、强大、丰富应用的最简单方式。
FastHTML 在底层使用了 starlette 和 uvicorn。
它自带 Pico CSS 用于样式设置。由于团队中经验丰富的 Web 开发者 Leandro 希望尝试 Tailwind CSS,加上我们最近发现的 shad4fast,我们决定结合 FastHTML 和 shadcn/ui 中美观的 UI 组件。
Pyvespa
我们的 Vespa Python 客户端 pyvespa 以往主要用于 Vespa 应用的原型开发。然而,最近我们努力通过 pyvespa 提供更多的 Vespa 功能支持。目前已支持部署到生产环境,并添加了通过 pyvespa 高级配置 Vespa services.xml
文件的功能。详见 此 笔记本中的示例和详细信息。
因此,大多数不需要自定义 Java 组件的 Vespa 应用都可以通过 pyvespa 构建。
趣闻:
pyvespa 的高级配置功能实际上受到了 FastHTML 将 ft
-组件封装并转换为 HTML 标签方式的启发。在 pyvespa 中,我们对 vt
-组件执行了类似操作,将其转换为 Vespa services.xml
标签。对此感兴趣的读者可以查看 此 PR 了解详情。这种方法为我们节省了大量工作,相较于为所有支持的标签实现自定义类。
另外,使用 pyvespa 构建 Vespa 应用的过程也让我们进行了实践验证。
硬件
作为原生支持 Vespa 的 ColPali 嵌入器,目前仍处于 WIP 状态,我们知道需要 GPU 来完成推理。从在 Colab 中的实验中,我们得出结论:T4 实例就足够了。
为了在将数据集的 PDF 页面嵌入到 Vespa 之前生成嵌入,我们考虑使用无服务器的 GPU 提供商(Modal 是我们的最爱之一)。但是,由于数据集“仅有”6692页,我们使用了一台 Macbook M2 Pro 工作了 5-6 小时来创建这些嵌入。
托管
这里有很多选择。我们可以选择传统的云服务提供商,例如 AWS、GCP 或 Azure,但这需要我们花费更多精力来设置和管理基础设施,并且会让其他人更难复制这个应用程序。
我们了解到 Hugging Face Spaces 提供了一个可以根据需要添加 GPU 的托管服务。他们还提供了一个一键式的“克隆此空间”按钮,可以让其他人非常轻松地复制该应用程序。
我们发现 answer.ai 创建了一个 可重用的库,可以用于在 Hugging Face Spaces 上部署 FastHTML 应用程序。但在进一步研究后,我们发现他们的方法使用了 Docker SDK 来操作 Spaces,实际上还有更简单的方法。
通过利用 Custom Python Spaces。
虽然这不是官方工作流,但您可以通过选择 Gradio 作为 SDK 并在端口 7860 上提供前端界面,在 Spaces 中运行您自己的 Python + 界面栈。
趣闻 2: 文档中有一个错字,指出提供服务的端口是 7680
。幸运的是,我们没有花太多时间就发现正确的端口应该是 7860
,并提交了一个 PR,由 Hugging Face 的 CTO Julien Chaumond 合并,修复了这个错误。清单任务完成!
视觉语言模型
对于 Visual RAG 的“生成”部分,我们需要一个视觉语言模型(VLM)根据从 Vespa 获取的 top-k 排名文档生成响应。
Vespa 原生支持 LLM(大语言模型),无论是外部还是内部集成,但 VLM(视觉语言模型)尚未在 Vespa 中获得原生支持。
过去一年中,OpenAI、Anthropic 和 Google 都发布了优秀的视觉语言模型(VLM),这一领域发展迅速。出于性能考虑,我们希望选择一个较小的模型,鉴于 Google 的 Gemini API 最近改进了开发者体验,我们决定在这个演示中使用 gemini-1.5-flash-8b
。
当然,在生产环境中选择模型之前,建议对不同模型进行量化评估,但这超出了本项目的范围。
架构
有了技术栈后,我们可以开始构建应用程序了。应用程序的高层架构如下:
Vespa 应用程序
Vespa 应用程序的关键组件包括:
- 包含字段和类型的文档 schema definition。
- Rank profile 定义。
- 一个
services.xml
配置文件。
所有这些 可以 使用 pyvespa 在 Python 中定义,但我们建议同时检查生成的配置文件,可以通过调用 app.package.to_files()
来实现。详细信息请参见 pyvespa 文档。
排名配置
Vespa 的一个最被低估的功能是 分阶段排名 功能。它允许您定义多个排名配置文件,每个配置文件可以包含不同的(或继承的)排名阶段,这些阶段可以在内容节点(第一阶段和第二阶段)或容器节点上执行(全局阶段)。
这使我们能够分别处理许多不同的用例,并为每种情况找到延迟、成本和质量之间的理想平衡。
请阅读我们的 CEO Jon Bratseth 关于通过将计算移至数据端实现架构反转的 这篇博文。
对于这个应用程序,我们定义了 3 个不同的排名配置:
注意 检索阶段是在查询时通过 yql 指定的,而排名策略是在排名配置文件中指定的(是部署时提供的应用程序包的一部分)。
1. 纯 ColPali
在我们的应用程序中,用于这种排名模式的 yql 是:
select title, text from pdf_page where targetHits:{100}nearestNeighbor(embedding,rq{i}) OR targetHits:{100}nearestNeighbor(embedding,rq{i+1}) .. targetHits:{100}nearestNeighbor(embedding,rq{n}) OR userQuery();
我们还将 hnsw.exploreAdditionalHits
参数调整为 300,以确保在检索阶段不会错过任何相关的匹配项。请注意,这会带来性能成本。
其中 rq{i}
是查询中的第 i 个 Token (必须作为参数在 HTTP 请求中提供),n
是用于检索的最大查询 Token 数(我们在此应用程序中使用 64)。
此排名配置使用了 max_sim_binary
排名表达式,该表达式利用了 Vespa 中优化的汉明距离计算功能(详细信息见 Scaling ColPali to billions。在第一阶段排名中使用此方法,并对前 100 个匹配项使用 ColPali 嵌入的完整浮点表示重新排名。
2. 纯基于文本的排名(BM25)
在这种情况下,我们仅基于 weakAnd
检索文档。
select title, text from pdf_page where userQuery();
在排名阶段,我们使用 bm25 进行第一阶段排名(无第二阶段)。
请注意,为了获得最佳性能,我们很可能希望将基于文本和基于视觉的排名特征结合使用(例如使用 互惠排名融合),但在此演示中,我们希望展示它们之间的差异,而不是找到最优组合。
3. 混合 BM25 + ColPali
在检索阶段,我们使用与纯 ColPali 排名配置相同的 yql。
我们注意到,对于某些查询,尤其是较短的查询,纯 ColPali 匹配了许多没有文本的页面(仅有图像),而我们寻找的许多答案实际上出现在有文本的页面中。
为了解决这个问题,我们添加了一个结合 BM25 分数和 ColPali 分数的第二阶段排名表达式,使用两个分数的线性组合(max_sim + 2 * (bm25(title) + bm25(text))
)。
此方法基于简单的启发式方法,但通过进行排名实验找到不同特征的最优权重会更有益。
Vespa 中的片段生成
在搜索前端中,通常会包含一些来源文本的摘录,并将某些词以 粗体 (高亮)显示。
在上下文中显示匹配的查询词的片段,允许用户快速判断结果是否可能满足其信息需求。
在 Vespa 中,这种功能被称为“动态片段”,并且有多种参数可以调整,例如包括多少周围上下文以及用于突出显示匹配词的标签。
在此演示中,我们同时展示片段和页面的完整提取文本以作比较。
为了减少结果中的视觉噪声,我们从用户查询中移除了停用词(and、in、the 等),因此它们不会被高亮显示。
Vespa 中的查询建议
搜索中一个常见的功能是“搜索建议”,它会在用户输入时显示。
真实用户查询通常被用来提供预计算的结果,但在这里我们没有任何用户流量可供分析。
在本例中,我们使用简单的子字符串搜索,将用户输入的前缀与从 PDF 页面生成的相关问题匹配,以提供建议。
我们用于获取这些建议的 yql 查询是:
select questions from pdf_page where questions matches (".*{query}.*")
这种方法的一个优势是,任何出现在建议中的问题都可以确认在现有数据中有答案!
我们本可以确保生成建议问题的页面始终出现在前三个响应中(通过在排序配置中加入用户查询与文档生成问题之间的相似性指标),但从展示 ColPali 模型功能的角度来看,这种做法有点像“作弊”。
用户体验
我们很幸运地从首席科学家 Jo Bergum 那里得到了极好的 UX 反馈。他推动我们让 UX 变得“快速流畅”。人们习惯了 Google,因此毫无疑问,速度对于搜索(和 RAG)中的用户体验至关重要。这是目前 AI 社区仍然有些低估的一点,许多人似乎对等待 5-10 秒的响应感到满意。而我们希望实现以毫秒为单位的响应时间。
根据他的反馈,我们需要设置分阶段的请求流程,以避免在显示结果之前等待完整的图像和相似性映射张量从 Vespa 返回。
解决方案是首先从结果中仅提取最重要的数据。对我们来说,这意味着仅提取 title
、url
、text
、page_no
,以及缩小(模糊)版本的图像(32x32 像素),用于初步搜索结果展示。这使我们能够立即显示结果,并在后台继续加载完整图像和相似性映射。
完整的 UX 流程如下所示:
主要的延迟来源有:
- 生成 ColPali 嵌入的推理时间(在 GPU 上完成,取决于查询中的 Token 数量)
- 因此我们决定对该函数使用
@lru_cache
装饰器,以避免对相同查询多次重新计算嵌入。
- 因此我们决定对该函数使用
- Hugging Face Spaces 和 Vespa 之间的网络延迟(包括 TCP 握手)
- 完整图像的传输时间也很显著(每张约 0.5MB)。
- 相似性映射张量的大小更大(
n_query_tokens
xn_images
x 1030 patches x 128)。
- 创建相似性映射混合图像是一个 CPU 密集型任务,但这是通过
fastcore
的@threaded
装饰器以多线程后台任务完成的,每张图像轮询其对应的端点以检查相似性映射是否准备就绪。
压力测试
我们对应用程序在流量激增时的表现感到担忧,因此进行了一次简单的压力测试实验。实验方法是通过浏览器开发工具将请求 /fetch_results
的 cURL 命令复制下来(未启用缓存),并在 10 个并行终端中循环运行。 (这时我们禁用了 @lru_cache
装饰器。)
结果
尽管测试非常基础,但首次测试显示搜索吞吐量的瓶颈在于 Huggingface 空间的 GPU 上计算 ColPali 嵌入,而 Vespa 后端可以轻松处理每秒 20 多个查询,资源使用率很低。我们认为这对于演示来说已绰绰有余。如果需要扩展,我们的首要措施是为 Huggingface 空间启用更大的 GPU 实例。
如以下图表所示,Vespa 应用程序在负载下表现良好。
使用 FastHTML 的反思
使用 FastHTML 的主要收获是,它打破了前端和后端开发之间的障碍。代码紧密集成,让我们所有人都能理解并对应用程序的每个部分做出贡献。这一点不容低估。
我们非常享受能够使用浏览器的开发工具检查前端代码,并实际看到并理解其中的大部分内容。
与使用独立的前端框架相比,开发和部署过程显著简化。
它使我们能够用 uv 管理 所有 依赖项,这极大地改变了我们在 Python 中处理依赖项的方式。
Thomas 的观点:
作为一名数据科学和 AI 背景的开发者,更偏好 Python,但也曾使用过多个 JS 框架,我的体验非常积极。我感到自己能够更好地参与前端相关任务,而不会给项目增加太多复杂性。我非常喜欢能够理解应用程序的每个部分。
Andreas 的观点:
我在 Vespa 上工作了很长时间,但对 Python 或前端开发的涉足不多。一开始的一两天感觉有些不知所措,但能够在全栈中工作,并几乎实时看到自己更改的效果,实在是太令人兴奋了!有了大语言模型的帮助,进入一个不熟悉的环境比以往任何时候都更容易。我非常喜欢我们能够通过 Vespa 内的张量表达式计算图像补丁的相似性(向量已经存储在内存中),并将其与搜索结果一起返回,从而以更低的延迟和资源消耗创建相似性映射。
Leandro 的观点:
作为一名具有使用 React、JavaScript、TypeScript、HTML 和 CSS 进行 Web 开发的扎实基础的开发者,转向 FastHTML 相对简单。该框架的直接 HTML 元素映射与我之前的知识高度一致,这降低了学习曲线。主要的挑战是适应 FastHTML 的基于 Python 的语法,因为它不同于标准的 HTML/JS 结构。
视觉技术就是你所需要的一切吗?
我们已经看到,利用来自视觉语言模型(Vision Language Model,VLM)的 Token 级别晚期交互嵌入在某些类型的查询中非常强大,但我们并不认为它是万能的解决方案,而更像是工具箱中一个非常有价值的工具。
除了 ColPali,我们在过去一年中还看到了视觉检索领域的其他创新。两个特别有趣的方法是:
- 文档截图嵌入(Document Screenshot Embeddings,DSE)5 - 一种双编码器模型,用于为文档的截图生成密集嵌入,并利用这些嵌入进行检索。
- IBM 的 Docling - 一个库,用于将多种类型的文档(如 PDF、PPT、DOCX 等)解析为 Markdown,避免了 OCR,而是使用计算机视觉模型。
Vespa 支持将这些方法结合起来,并使开发者能够针对特定的用例,在延迟、成本和质量之间找到最具吸引力的平衡。
我们可以设想一个应用,它结合了 Docling 或类似工具的高质量文本提取,使用文档截图嵌入进行密集检索,并通过文本特征和类似 ColPali 模型的 MaxSim
分数进行排序。如果你真的想提升性能,你甚至可以将所有这些特征与诸如 XGBoost 或 LightGBM 的 GBDT 模型结合起来。
因此,尽管 ColPali 是一个强大的工具,可用于使文本中难以提取的信息变得可检索,但它并非万能,应该结合其他方法以实现最佳性能。
缺失的部分
模型是暂时的,而评估是永久的。
添加自动评估超出了此演示的范围,但我们强烈建议你为自己的用例创建一个评估数据集。你可以使用 LLM-as-a-judge 进行引导(请参阅这篇 博客文章,了解我们如何为 search.vespa.ai 实现这一点)。
Vespa 提供了许多可调参数,通过对不同实验进行定量反馈,你可以为自己的具体用例找到最吸引人的权衡。
结论
我们已构建了一个实时演示应用,展示如何在 Vespa 中使用 ColPali 嵌入对 PDF 执行视觉 RAG 检索。
如果你已经阅读到这里,可能对代码感兴趣。你可以在 此处 找到该应用的代码。
现在,去构建你自己的视觉 RAG 应用吧!
对于想了解更多关于视觉检索、ColPali 或 Vespa 的人,可以随时加入 Vespa 的 Slack 社区 提问、寻求社区的帮助或了解 Vespa 的最新发展。
常见问题
使用 ColPali 是否需要在推理时使用 GPU?
目前,为了在合理的时间内对查询进行推理,我们需要使用 GPU。
未来,我们预计类似 ColPali 模型的质量和效率(如更小的嵌入)将有所提升,并会有更多类似的模型出现,就像我们看到的 ColBERT 系列模型一样,例如 answer.ai 的 answerai-colbert-small-v1,其性能已超过原始 ColBERT 模型,尽管体积不到原模型的三分之一。
请参阅 Vespa 博客 ,了解如何在 Vespa 中使用 answerai-colbert-small-v1
。
能否在 Vespa 中将 ColPali 与查询过滤器结合使用?
可以。在这个应用中,我们为页面添加了 published_year
字段,但尚未在前端实现其作为过滤选项的功能。
Vespa 何时会原生支持 ColPali 嵌入?
请参阅 此 GitHub 问题。
这能扩展到数十亿文档吗?
可以。Vespa 支持水平扩展,并允许你根据特定用例调整延迟、成本和质量之间的权衡。
这个演示可以改编为支持 ColQwen2 吗?
可以,但在计算相似性图时存在一些差异。
请参阅 此 notebook 作为起点。
我可以用自己的数据运行此演示吗?
当然可以!通过调整提供的 notebook 指向你的数据,你可以为视觉 RAG 设置自己的 Vespa 应用。你还可以将提供的 Web 应用作为自己前端的起点。