azure-search-openai-demo 数据摄取实战指南:从本地 prepdocs 到 Azure Functions 云摄取流水线
2026/9/16 19:08:35 网站建设 项目流程

azure-search-openai-demo 数据摄取实战指南:从本地 prepdocs 到 Azure Functions 云摄取流水线

【免费下载链接】azure-search-openai-demoA sample app for the Retrieval-Augmented Generation pattern running in Azure, using Azure AI Search for retrieval and Azure OpenAI large language models to power ChatGPT-style and Q&A experiences.项目地址: https://gitcode.com/GitHub_Trending/az/azure-search-openai-demo

本文聚焦 azure-search-openai-demo 项目的数据摄取(Data Ingestion)组件,系统讲解两种摄取方式——本地摄取(prepdocs.py脚本)与云摄取(Azure Functions 作为 Azure AI Search 自定义技能)——以及贯穿两者的三阶段处理流水线:文档提取、图片处理、文本处理与索引写入。读完本文,你将掌握支持的文档格式与解析器选择逻辑、分块与向量化的底层实现、分类摄取与增量去重、文档删除,以及云摄取索引器的架构与启用步骤,并学会用索引查询快速验证摄取结果。

一、数据摄取概览:同一套处理逻辑,两种运行载体

azure-search-openai-demo的目标是在 Azure AI Search 与 Azure OpenAI 之上搭建一套完整的 RAG 聊天应用,让你能够基于自定义数据(企业内部文档、领域知识集等)进行对话。整个项目从部署到使用的完整指引见 主 README,而数据摄取是其中最关键的环节之一——没有它,检索与对话都无从谈起。

项目提供了两种摄取数据的方式:

  • 本地摄取(Manual / Local ingestion):在你自己的机器上运行,适用于一次性初始化索引或小批量数据;
  • 云摄取(Cloud ingestion):在 Azure Functions 中运行,以 Azure AI Search 索引器自定义技能的形式执行,适合大规模、持续更新的数据集。

关键设计:这两种方式复用完全相同的文档处理代码(app/backend/prepdocslib/下的解析器、切分器、图注处理器等),只是运行载体不同——本地跑在 Python 进程里,云端跑在函数应用里。这意味着两种方式的处理结果(分块、嵌入、元数据)是一致的,切换方式不会改变索引的数据质量。

app/backend/prepdocs.py的入口逻辑可以印证这一点:当环境变量USE_CLOUD_INGESTIONtrue时,prepdocs.py会直接提示改用setup_cloud_ingestion.py并退出(见 prepdocs.py),而scripts/prepdocs.sh也会在检测到云摄取开启时跳过本地流程(见 prepdocs.sh)。

二、支持的文档格式与解析器选择

要摄取某种格式,首先需要有一个能把它转成文本的工具。项目默认使用Azure Document Intelligence(简称 DI)做手动索引,同时为多种格式提供了本地解析器。本地解析器不如 Azure Document Intelligence 精细,但可以显著降低成本。

格式手动索引(Manual indexing)集成向量化(Integrated Vectorization)
PDF支持(DI 或本地 PyPDF)支持
HTML支持(DI 或本地 BeautifulSoup)支持
DOCX、PPTX、XLSX支持(DI)支持
图片(JPG、PNG、BMP、TIFF、HEIFF)支持(DI)支持
TXT支持(本地)支持
JSON支持(本地)支持
CSV支持(本地)支持

从源码看,解析器的选择逻辑集中在app/backend/prepdocslib/servicesetup.pybuild_file_processors()函数中,它按文件扩展名构建FileProcessor字典,FileStrategy.parse_file()再根据扩展名取出对应处理器(见 filestrategy.py)。两个开关环境变量控制本地解析器的启用:

  • USE_LOCAL_PDF_PARSER=true:使用基于 PyPDF 的本地 PDF 解析器(app/backend/prepdocslib/pdfparser.py);
  • USE_LOCAL_HTML_PARSER=true:使用基于 BeautifulSoup 的本地 HTML 解析器(app/backend/prepdocslib/htmlparser.py)。

此外,对超大的 CSV 文件还可以通过CSV_MAX_PAGE_CHARS环境变量设置按字符数将多行 CSV 聚合成一页,避免一次性加载导致内存溢出(见 prepdocs.py)。

三、摄取流水线的三个核心阶段

无论是本地摄取还是云摄取,原始文档都要经过三个主要阶段才能变成 Azure AI Search 中可检索的内容。这三个阶段在本地由prepdocslib各模块依次执行,在云端则映射为四个 Azure Functions 自定义技能(详见第五节)。

3.1 文档提取(Document Extraction)

第一阶段使用与每种文件格式匹配的解析器,从源文档中提取文本和结构化内容:

  • PDF、HTML、DOCX、PPTX、XLSX 与图片:默认调用 Azure Document Intelligence 提取带版式信息的文本、表格和图元。也可用 PyPDF、BeautifulSoup 等本地解析器降低成本;
  • TXT、JSON、CSV:直接用轻量级本地解析器提取内容,对应 textparser.py、jsonparser.py、csvparser.py。

提取过程中有两个值得注意的细节:

  1. 表格被转换为 HTML 标记,以保留其行列结构;
  2. 图元(开启多模态时)被识别并用边界框(bounding box)与占位符标注,例如<figure id="fig1"></figure>

本阶段的输出是一组页面(Page),每个页面包含嵌入表格 HTML 和图元占位符的文本。在云端,document_extractor函数输出的每个页面结构为{"page_num": 0, "text": "...", "figure_ids": ["fig1"]},图元则携带figure_idpage_numfilenamemime_typebytes_base64bboxtitleplaceholder等字段(见 document_extractor/function_app.py)。

3.2 图片处理(Figure Processing)

该阶段是可选的,仅当同时满足两个条件时才会执行:多模态功能已开启(USE_MULTIMODAL=true文档本身包含图元。完整的多模态功能说明见 多模态文档。

开启多模态后,上一阶段提取的图元会依次经历四个子步骤:

  1. 裁剪并保存:根据边界框坐标把图从 PDF 中裁剪出来,存为 PNG 文件;
  2. 生成描述:根据配置使用 Azure OpenAI 的 GPT-4 Vision 模型或 Azure AI Content Understanding 生成文本描述;
  3. 上传:把图上传到 Azure Blob Storage 并取得 URL;
  4. 向量化(可选):若开启了图片嵌入,使用 Azure AI Vision 计算图元的向量嵌入。

源码层面,app/backend/prepdocslib/figureprocessor.py中的FigureProcessor通过MediaDescriptionStrategy枚举(NONE/OPENAI/CONTENTUNDERSTANDING)选择描述生成机制,process_page_image()函数依次完成"生成描述 → 上传图片 → 计算图片嵌入"的完整流程,并且图片嵌入失败只会记录告警而不会中断整个流程(见 figureprocessor.py 与 figureprocessor.py)。

本阶段的输出是增强后的图元元数据:描述文本、存储 URL、以及可选的嵌入向量。

3.3 文本处理(Text Processing)

最后一个阶段把提取的文本与图元描述合并、切分成可检索的块,并计算嵌入向量。它包含两个子步骤。

图注合并(Figure Merging)

首先,页面文本中的图元占位符会被替换成包含图注和生成描述的完整 HTML 标记,形成一条将视觉内容融入其中的连贯文本叙事。对应的实现是 textprocessor.py 中的combine_text_with_figures(),它调用build_figure_markup()生成<figure><figcaption>...</figcaption></figure>这样的标记(见 figureprocessor.py)。

分块(Chunking)

合并后的文本会通过一个语义感知的切分器按语义边界切块。关键参数(来自 textsplitter.py):

  • 默认块大小约 1000 字符(英文约 400–500 token),对应DEFAULT_SECTION_LENGTH = 1000
  • 相邻块之间约 10% 重叠,对应DEFAULT_OVERLAP_PERCENT = 10
  • 单块 token 上限默认 500(max_tokens_per_section = 500)。

切分器采用滑动窗口思路:一个块结尾处的句子会同时出现在下一个块的开头附近,从而降低块边界处丢失关键上下文的风险。实现上,SentenceTextSplitter.split_pages()会:

  • 先把<figure>...</figure>块当作不可分割的原子单元提取出来,绝不把一个图元切碎或重复(含图元的块可突破 token 上限);
  • 文本部分按句子结束符(标准标点加 CJK 标点,见STANDARD_SENTENCE_ENDINGSCJK_SENTENCE_ENDINGS)拆分成 span,再按字符数与 token 数双阈值累积成块;
  • 支持跨页合并语义重叠:仅当上一块未以句子结束符收尾、下一块以小写字母开头且不是标题(_is_heading_like()可识别 Markdown 标题、全大写/标题化短行、编号列表等)时,才把下一块的前缀追加到上一块末尾(见 textsplitter.py)。

为什么要分块?虽然 Azure AI Search 能索引完整文档,但对 RAG 模式而言分块是必需的:OpenAI 上下文窗口有 token 上限,分块能限制发送给 OpenAI 的信息量。把内容切成聚焦的块后,系统可以只检索并把最相关的文本片段注入 LLM 提示词,既提升回答质量又降低成本。

如需调整切分算法,可修改 textsplitter.py;关于切分器如何工作(图元、递归、合并启发式、保证与示例)的更深入、图文并茂的解释,参见 文本切分器文档。

嵌入计算(Embedding)

最后,若启用了向量搜索(USE_VECTORS默认开启),为每个块用 Azure OpenAI 的嵌入模型(text-embedding-ada-002text-embedding-3-smalltext-embedding-3-large)计算文本嵌入。嵌入按批量生成以提高效率,并带重试逻辑以应对限流;prepdocs.py--disablebatchvectors参数可以关闭批量计算(见 prepdocs.py)。嵌入维度默认 1536,可通过AZURE_OPENAI_EMB_DIMENSIONS覆盖。

3.4 索引写入(Indexing)

最后一步是把块写入 Azure AI Search。每个块作为索引中的独立文档存储,其元数据记录了来源文件与页码。若开启了向量搜索,嵌入向量与文本一起存储,供查询时做相似度检索。从 searchmanager.py 可以看到,向量索引使用HNSW 算法、余弦相似度HnswAlgorithmConfiguration(metric="cosine")),并默认启用Binary Quantization 压缩truncation_dimension=1024)来降低存储成本。

一个最终索引块文档的示例:

{ "id": "file-Northwind_Health_Plus_Benefits_Details_pdf-4E6F72746877696E645F4865616C74685F506C75735F42656E65666974735F44657461696C732E706466-page-0", "content": "# Zava\n\nNorthwind Health Plus Plan\n...", "category": null, "sourcepage": "Northwind_Health_Plus_Benefits_Details.pdf#page=1", "sourcefile": "Northwind_Health_Plus_Benefits_Details.pdf", "storageUrl": "https://std4gfbajn3e3yu.blob.core.windows.net/content/Northwind_Health_Plus_Benefits_Details.pdf", "embedding": [0.0123, -0.0456, ...] }

若开启了多模态,该文档还会包含images字段,content字段中也会带有图元描述。从 listfilestrategy.py 可以看到id的生成规则:file-前缀 + 文件名 ASCII 化 + 文件名十六进制哈希,因此同一文件重新摄取不会产生重复文档

四、本地摄取:prepdocs.py的完整用法

prepdocs.py脚本同时负责上传与索引两类工作。典型用法是通过scripts/prepdocs.sh(Mac/Linux)或scripts/prepdocs.ps1(Windows)调用——这些脚本会自动创建 Python 虚拟环境,并根据当前azd环境传入所需参数。你也可以直接给脚本追加额外参数,例如scripts/prepdocs.ps1 --removeall。每当运行azd upazd provision时,该脚本都会被自动调用。

从 prepdocs.sh 可以看到,脚本实际执行的是./.venv/bin/python ./app/backend/prepdocs.py './data/*' --verbose $additionalArgs,即以data/目录下的所有文件为输入。prepdocs.py索引文档的步骤如下:

  1. 若索引尚不存在,在 Azure AI Search 中创建新索引;
  2. 把文档上传到 Azure Blob Storage;
  3. 将文档切分为文本块;
  4. 把块上传到 Azure AI Search;若使用向量(默认启用),同时计算嵌入并随文本一起上传。

这些步骤在FileStrategy.run()中对应为upload_blob()parse_file()update_content()(见 filestrategy.py)。prepdocs.py还支持**集成向量化(Integrated Vectorization)**模式:当USE_FEATURE_INT_VECTORIZATION=true时改用IntegratedVectorizerStrategy,把向量化交给 Azure AI Search 内置技能在云端完成(见 prepdocs.py)。

4.1 数据分类:用--category增强检索

为了增强检索功能,可以在摄取时用--category参数对数据分类,例如:

scripts/prepdocs.ps1 --category ExampleCategoryName

该参数会写入索引文档的category字段(对应前文示例 JSON 中的"category": null),从而允许你基于这些分类过滤搜索结果。

运行脚本写入期望的分类后,还需要把这些分类添加到"Include Category"(包含分类)下拉列表中——该下拉框位于开发者设置里,对应前端组件 Settings.tsx。下拉框默认选项为 "All"(全部)。把具体分类纳入其中后,你就可以更精准地缩小搜索范围。

4.2 增量摄取更多文档与 MD5 去重

要上传更多 PDF,把它们放进data/文件夹,然后运行:

./scripts/prepdocs.sh

或:

./scripts/prepdocs.ps1

prepdocs脚本会为每个上传的文件写一个.md5 文件,保存该文件的 MD5 哈希。每次重新运行时,脚本都会把当前哈希与已存哈希比对,若文件未发生变化则跳过上传。这一去重逻辑实现在LocalListFileStrategy.check_md5()中:命中缓存则跳过并记录Skipping '...', no changes detected.,否则写入新哈希(见 listfilestrategy.py)。这意味着重复执行脚本是安全的,不会重复索引未变更的文件。

4.3 删除文档

你可能需要从索引中移除文档——例如在使用示例数据时,先删掉索引中已有的文档,再加入自己的数据。

  • 删除全部文档
./scripts/prepdocs.sh --removeall

或:

./scripts/prepdocs.ps1 --removeall
  • 删除单个文档:使用--remove标志。打开scripts/prepdocs.shscripts/prepdocs.ps1,把data/*替换为data/YOUR-DOCUMENT-FILENAME-GOES-HERE.pdf,然后运行:
scripts/prepdocs.sh --remove

或:

scripts/prepdocs.ps1 --remove

prepdocs.py会把--removeall--remove分别映射为DocumentAction.RemoveAll/DocumentAction.Remove(见 prepdocs.py),最终由FileStrategy.run()执行 blob 与索引的双重删除(见 filestrategy.py)。另外该脚本还支持--searchkey--storagekey--documentintelligencekey等可选参数,用于以服务密钥代替当前用户身份登录各 Azure 服务。

五、云摄取:Azure Functions 自定义技能架构

项目包含一个可选的云摄取功能:用 Azure Functions 作为 Azure AI Search 索引器的自定义技能,在云端完成数据摄取。这种方式把摄取负载从本地机器卸载到云端,可以更弹性、高效地处理大数据集。

5.1 启用云摄取

前提:如果你之前已部署过,需要删除现有搜索索引或新建一个索引——该功能无法用于已有索引。因为新建索引的 schema 中会新增一个parent_id字段,索引器用它内部管理块的生命周期。

按以下步骤启用:

  1. 设置新的索引名:
azd env set AZURE_SEARCH_INDEX cloudindex
  1. 开启云摄取:
azd env set USE_CLOUD_INGESTION true
  1. (推荐)把嵌入模型的容量提升到所在区域/订阅允许的最大配额,避免 Azure Functions 生成嵌入时触发限流:
azd env set AZURE_OPENAI_EMB_DEPLOYMENT_CAPACITY 400
  1. 预置新的 Azure Functions 资源、部署函数应用并更新搜索索引器:
azd up
  1. 该命令会把data/文件夹中的文档上传到 Blob 存储容器,创建索引器(indexer)与技能集(skillset),并运行索引器摄取数据。之后可在门户中监控索引器状态。
  2. 当有新文档需要摄取时,把文档上传到 Blob 存储容器,然后从 Azure 门户运行索引器即可。

整个配置过程由 setup_cloud_ingestion.py 驱动:它从环境变量读取三个技能函数的端点(DOCUMENT_EXTRACTOR_SKILL_ENDPOINTFIGURE_PROCESSOR_SKILL_ENDPOINTTEXT_PROCESSOR_SKILL_ENDPOINT)及各自的托管标识资源 ID,构造CloudIngestionStrategy完成索引器、技能集和数据源的创建(见 setup_cloud_ingestion.py)。同时它也会检查AZURE_ENFORCE_ACCESS_CONTROLUSE_CLOUD_INGESTION_ACLS的配套关系,避免在未开启 ACL 提取的情况下索引无 ACL 文档导致访问控制失效(见 setup_cloud_ingestion.py)。

5.2 索引器架构:四个自定义技能

云摄取流水线在 Azure AI Search 索引器中使用4 个 Azure Functions 作为自定义技能,每个函数对应摄取过程的一个阶段:

  1. 用户上传文档到 Azure Blob Storage(content 容器);
  2. Azure AI Search 索引器监控 blob 容器并编排处理流程;
  3. 自定义技能分三阶段处理文档:
    • Document Extractor(技能 #1):从源文档提取文本与图元元数据;
    • Figure Processor(技能 #2):为图元补充描述与嵌入;
    • Shaper Skill(技能 #3):Azure AI Search 内置技能,整合富化数据;
    • Text Processor(技能 #4):把文本与富化图元合并、切块并生成嵌入;
  4. Azure AI Search 索引接收最终带嵌入的处理后块。

函数定义在app/functions/目录,自定义技能集在 setup_cloud_ingestion.py 中配置。

Document Extractor 函数(app/functions/document_extractor/)

  • 实现 文档提取 阶段;
  • 输出带<figure id="...">占位符的 Markdown 文本及图元元数据;
  • 其 HTTP 路由为extract,要求索引器把 batchSize 设为 1(每次请求一个 record,见 document_extractor/function_app.py);若存储启用了 ADLS Gen2 ACL,还会同步提取文件的 oids/groups 访问控制信息(get_file_acls())。

Figure Processor 函数(app/functions/figure_processor/)

  • 实现 图片处理 阶段;
  • 输出带描述、URL 和嵌入的富化图元元数据;
  • 其 HTTP 路由为process,通过ImageOnPage.from_skill_payload()还原图元,再复用本地相同的process_page_image()完成描述、上传与嵌入(见 figure_processor/function_app.py)。

Shaper Skill(Azure AI Search 内置技能)

  • 把 figure processor 产生的富化结果合并回主文档上下文;
  • 这是必需的,因为 Azure AI Search 的富化树按上下文隔离数据;
  • Shaper 显式合并三部分:document_extractor输出的原始pages数组、带描述/URL/嵌入的富化figures数组、文件元数据(file_namestorageUrl);
  • 生成consolidated_document对象供 text processor 消费。

Text Processor 函数(app/functions/text_processor/)

  • 实现 文本处理 阶段(图注合并、切块、嵌入);
  • 接收 Shaper 技能传来的带富化图元的合并文档;
  • 输出带图元引用与嵌入的可检索块;
  • 其配置中USE_VECTORS默认true、嵌入维度默认 3072、默认嵌入模型text-embedding-3-large(见 text_processor/function_app.py)。

5.3 增加与删除文档

增加文档:先把新文档上传到数据源(默认是 Blob 存储),然后到 Azure 门户运行索引器。索引器会识别新文档并摄取进索引。

删除文档:先从数据源(默认 Blob 存储)删除对应文档,再到 Azure 门户运行索引器。索引器会负责把这些文档从索引中移除。这正是parent_id字段发挥作用的场景——索引器通过它管理每个源文件对应的全部块的生命周期。

5.4 定时索引

如果希望索引器自动运行,可以按照 Azure AI Search 的索引器计划功能配置定时执行,实现周期性的数据同步。

六、排障技巧:用索引查询验证摄取结果

如果你不确定某个文件是否成功上传,可以从 Azure 门户或通过 REST API 查询索引。打开索引,把下面的查询粘贴到搜索栏。

查看索引中已上传的全部文件名

{ "search": "*", "count": true, "top": 1, "facets": ["sourcefile"] }

搜索特定文件名

{ "search": "*", "count": true, "top": 1, "filter": "sourcefile eq 'employee_handbook.pdf'", "facets": ["sourcefile"] }

第一个查询利用sourcefile字段的 facet 聚合列出所有已索引的文件名,第二个查询用filter精确匹配某个文件。如果期望的文件没有出现在 facet 列表或过滤结果中,说明它未被成功摄取,可以结合 blob 容器内容与索引器执行历史进一步排查。仓库中还有完整的端到端与单元测试(如 tests/test_app.py、tests/test_prepdocs.py、tests/test_prepdocslib_textsplitter.py)可以帮你验证摄取相关代码的正确性。

七、小结

数据摄取是 RAG 应用的地基。通过本文可以看到,azure-search-openai-demo将"文档提取 → 图片处理 → 文本处理 → 索引写入"这一套逻辑同时落地在本地脚本与云端函数两种载体上:

  • 本地摄取适合初始化与小批量场景,prepdocs.py配合--category、MD5 增量去重、--remove/--removeall提供了完整的数据生命周期管理;
  • 云摄取把同样的处理逻辑搬到 Azure Functions 自定义技能中,配合 Azure AI Search 索引器实现自动化、可扩展、可持续更新的大规模摄取,并支持定时索引;
  • 无论哪种方式,最终产出的都是带sourcepagesourcefilecategory与可选embedding的块文档,供对话检索直接使用。

掌握这些环节后,你就可以根据自己的数据规模、更新频率与成本预算,选择最合适的摄取方式,并利用索引查询快速验证结果、持续维护索引数据。

【免费下载链接】azure-search-openai-demoA sample app for the Retrieval-Augmented Generation pattern running in Azure, using Azure AI Search for retrieval and Azure OpenAI large language models to power ChatGPT-style and Q&A experiences.项目地址: https://gitcode.com/GitHub_Trending/az/azure-search-openai-demo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询