Haystack 文本提取器深度指南:LLMDocumentContentExtractor、LLMMetadataExtractor 与 RegexTextExtractor
【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack
Haystack 是开源、面向生产环境的 LLM 应用编排框架,其 extractors 组件族 专门负责从文档中抽取结构化信息。本文以 extractors_api.md 为骨架,结合 源码 与 测试用例,系统讲解LLMDocumentContentExtractor(图像文档内容抽取)、LLMMetadataExtractor(LLM 元数据抽取)与RegexTextExtractor(正则文本抽取)三大组件的设计原理、参数语义、运行流程与实战用法,读完即可在索引、RAG 与 Agent 流程中落地使用。
一、组件总览:三种抽取器各司其职
haystack/components/extractors包由三个模块组成,模块入口在 extractors/init.py,并通过 pydoc/extractors_api.yml 生成 API 文档:
| 组件 | 模块路径 | 输入 | 输出能力 |
|---|---|---|---|
LLMDocumentContentExtractor | image/llm_document_content_extractor.py | 图像/PDF 文件路径(存于文档 meta) | 将图像内容抽取为文本(content),并可附带元数据 |
LLMMetadataExtractor | llm_metadata_extractor.py | 文本 Documents | 为每个文档生成结构化元数据(如 NER 实体) |
RegexTextExtractor | regex_text_extractor.py | 字符串或ChatMessage列表 | 用正则捕获组提取指定文本片段 |
从源码结构看,三者都通过@component装饰器注册为标准 Haystack 组件,均提供run/run_async(或其中一种)与to_dict/from_dict序列化能力,可以无缝嵌入 Pipeline。其中LLMDocumentContentExtractor与LLMMetadataExtractor属于"LLM 驱动型"抽取器,RegexTextExtractor则是零依赖的"规则型"抽取器。
二、LLMDocumentContentExtractor:用视觉 LLM 抽取图像文档
2.1 核心思想与工作流
LLMDocumentContentExtractor面向图像型文档(扫描件、截图、PDF 页面渲染图),借助支持视觉输入的 ChatGenerator 完成内容抽取。其内部流程如下:
- 每个文档一次 prompt、一次 LLM 调用;
- 通过
DocumentToImageContent(见 converters/image/document_to_image.py)将文档转换为ImageContent——它支持直接图像文件,也支持从 PDF 按page_number渲染指定页; - 将 prompt 与图像组装成多模态
ChatMessage(TextContent+ImageContent,见 llm_document_content_extractor.py)发给 ChatGenerator; - 按约定的响应格式解析结果,回填文档的
content与meta。
组件内建默认 prompt 模板(DEFAULT_PROMPT_TEMPLATE,源码 L33-L61),要求模型"精确抽取、按 markdown 排版、保持阅读顺序",对图表用[img-caption][/img-caption]添加说明、表格用[table-caption][/table-caption]加注,表单勾选以 markdown 还原,最终返回含document_content键的 JSON 对象。
2.2 响应处理规则(重点)
组件对 LLM 返回文本采用统一解析逻辑_process_response(源码 L256-L274):
- 纯字符串(非 JSON 或非 JSON 对象):整个字符串直接写入文档
content; - 仅含
document_content键的 JSON 对象:该键的值写入content; - 含多个键的 JSON 对象:
document_content的值写入content,其余键值对合并进文档 meta(例如 author、date、title 等); - 合法 JSON 但不是对象(数组或原始值):报告错误,文档进入
failed_documents。
因此,为了让模型稳定输出结构化结果,推荐在generation_kwargs中配置response_format={"type": "json_object"}或更严格的json_schema。
2.3 参数说明
__init__签名(源码 L136-L177):
__init__( *, chat_generator: ChatGenerator, prompt: str = DEFAULT_PROMPT_TEMPLATE, file_path_meta_field: str = "file_path", root_path: str | None = None, detail: Literal["auto", "high", "low"] | None = None, size: tuple[int, int] | None = None, raise_on_failure: bool = False, max_workers: int = 3 ) -> None| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
chat_generator | ChatGenerator | 必填 | 支持视觉输入的 ChatGenerator,可选配置 JSON 输出 |
prompt | str | 内建模板 | 抽取指令,严禁包含 Jinja 变量(构造时用沙箱环境解析并校验,见 L245-L254) |
file_path_meta_field | str | "file_path" | 文档 meta 中保存图像/PDF 路径的字段名 |
root_path | str \| None | None | 文档文件所在根目录;设置后路径按相对根目录解析并强制限定在该目录内,用于防御路径穿越 |
detail | "auto"\|"high"\|"low" | None | 图像细节级别(仅 OpenAI 支持) |
size | tuple[int,int] | None | 等比缩放到 (宽, 高) 范围内,降低传输与处理开销 |
raise_on_failure | bool | False | True 时 LLM 异常直接抛出;False 时失败文档进入failed_documents |
max_workers | int | 3 | 并行 LLM 调用的最大线程数 |
安全提示(务必阅读):该组件会读取
file_path_meta_field指向的宿主机文件。若文档 meta 可能受不可信输入影响,必须设置root_path为专用数据目录,使绝对路径或../等路径穿越载荷被拒绝而非读取。这一约束同样内置于DocumentToImageContent(document_to_image.py L81-L86)。
2.4 完整用法示例
以下示例取自 API 文档,使用OpenAIChatGenerator并配置json_schema强制输出document_content与可选元数据字段:
from haystack import Document from haystack.components.generators.chat import OpenAIChatGenerator from haystack.components.extractors.image import LLMDocumentContentExtractor prompt = """ Extract the content from the provided image. Format everything as markdown. Return only the extracted content as a JSON object with the key 'document_content'. No markdown, no code fence, only raw JSON. Extract metadata about the image like source of the image, date of creation, etc. if you can. Return this metadata as additional key-value pairs in the same JSON object. """ chat_generator = OpenAIChatGenerator( generation_kwargs={ "response_format": { "type": "json_schema", "json_schema": { "name": "entity_extraction", "schema": { "type": "object", "properties": { "document_content": {"type": "string"}, "author": {"type": "string"}, "date": {"type": "string"}, "document_type": {"type": "string"}, "title": {"type": "string"}, }, "additionalProperties": False, }, }, } } ) extractor = LLMDocumentContentExtractor( chat_generator=chat_generator, file_path_meta_field="file_path", raise_on_failure=False, ) documents = [ Document(content="", meta={"file_path": "test/test_files/images/image_metadata.png"}), Document(content="", meta={"file_path": "test/test_files/images/apple.jpg", "page_number": 1}), ] result = extractor.run(documents=documents) updated_documents = result["documents"]注意page_number用于指示 PDF 文档要渲染的页码;若传入的是图片文件,则无需该字段。失败文档会进入result["failed_documents"],其 meta 中带有content_extraction_error键(测试见 test_llm_document_content_extractor.py 中test_run_with_llm_failure_raise_on_failure_false等用例)。
2.5 生命周期方法与异步支持
warm_up()/warm_up_async():预热底层 ChatGenerator(异步版本优先调用warm_up_async,否则回退同步warm_up);close()/close_async():释放底层生成器资源;run_async():异步版本,LLM 调用并发执行但受max_workers信号量约束;若生成器仅实现同步run,会在线程中执行以避免阻塞事件循环;图片文件读取与 PDF 渲染属于阻塞操作,会在线程中执行(源码 L407-L451)。测试test_run_async_falls_back_to_sync_run、test_run_async_converts_images_off_the_event_loop、test_run_async_respects_max_workers对此均有覆盖。
三、LLMMetadataExtractor:LLM 驱动的结构化元数据抽取
3.1 设计原理
LLMMetadataExtractor(llm_metadata_extractor.py)接收 Documents 列表与一个 prompt,对每个文档分别运行一次 LLM,将抽取结果合并进文档 meta。典型场景是 NER 实体识别、文档标签、摘要字段等。
其 prompt 约束是关键设计:
- prompt 中必须有且仅有一个变量
document,指向列表中的单个文档,例如{{ document.content }}; - 组件在
__init__中通过SandboxedEnvironment().parse(prompt)解析模板变量,若变量集合不等于["document"]会直接抛出ValueError(源码 L187-L194); - 组件内部用
PromptBuilder(prompt, required_variables=variables)渲染 prompt,再包装为ChatMessage.from_user(...)调用生成器(源码 L285-L312)。
3.2 参数说明
__init__( prompt: str, chat_generator: ChatGenerator, expected_keys: list[str] | None = None, page_range: list[str | int] | None = None, raise_on_failure: bool = False, max_workers: int = 3, ) -> None| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
prompt | str | 必填 | 模板 prompt,必须恰好含一个变量document |
chat_generator | ChatGenerator | 必填 | LLM 实例,建议配置generation_kwargs={"response_format": {"type": "json_object"}}强制 JSON 输出 |
expected_keys | list[str] | None | 期望 LLM JSON 输出中包含的键;解析时校验缺失键(见_extract_metadata,L271-L283) |
page_range | list[str\|int] | None | 按页抽取范围,可被run方法覆盖 |
raise_on_failure | bool | False | LLM 执行或 JSON 校验失败时是否抛出异常 |
max_workers | int | 3 | 线程池最大并发数,run_async中用作并发上限 |
3.3 page_range:按页抽取
page_range支持单页与可打印范围字符串,由expand_page_range展开(haystack/utils/misc.py):
['1', '3']→ 抽取第 1、3 页;['1-3', '5', '8', '10-12']→ 展开为 1,2,3,5,8,10,11,12;- 传入整数时若含
-会被拒绝(要求范围必须是'start-end'字符串);空结果会抛出ValueError。
实现上,组件内部创建DocumentSplitter(split_by="page", split_length=1)(源码 L198),在_prepare_prompts中按展开后的页码拼接目标页文本,再填充进 prompt;不传page_range时对整篇文档抽取。run与run_async均支持运行期覆盖page_range。
3.4 失败处理与重跑机制
这是该组件最具实战价值的设计:
- 失败文档进入
failed_documents,meta 中写入metadata_extraction_error(错误信息)与metadata_extraction_response(LLM 原始回复,供后续重试参考); - 成功重跑后,组件会清除之前遗留的
metadata_extraction_error/metadata_extraction_response键(源码 L378-L382); - 可将
metadata_extraction_response与metadata_extraction_error重新注入 prompt,用另一个抽取器对失败文档二次抽取。
3.5 NER 实战示例
以下为 API 文档的完整 NER 示例(使用OpenAIChatGenerator+json_schema强制输出entities数组):
from haystack import Document from haystack.components.extractors.llm_metadata_extractor import LLMMetadataExtractor from haystack.components.generators.chat import OpenAIChatGenerator NER_PROMPT = ''' -Goal- Given text and a list of entity types, identify all entities of those types from the text. -Steps- 1. Identify all entities. For each identified entity, extract the following information: - entity: Name of the entity - entity_type: One of the following types: [organization, product, service, industry] Format each entity as a JSON like: {"entity": <entity_name>, "entity_type": <entity_type>} 2. Return output in a single list with all the entities identified in steps 1. -Examples- ###################### Example 1: entity_types: [organization, person, partnership, financial metric, product, service, industry, investment strategy, market trend] text: Another area of strength is our co-brand issuance. Visa is the primary network partner for eight of the top 10 co-brand partnerships in the US today and we are pleased that Visa has finalized a multi-year extension of our successful credit co-branded partnership with Alaska Airlines, a portfolio that benefits from a loyal customer base and high cross-border usage. ... output: {"entities": [{"entity": "Visa", "entity_type": "company"}, {"entity": "Alaska Airlines", "entity_type": "company"}, ...]} ############################# -Real Data- ###################### entity_types: [company, organization, person, country, product, service] text: {{ document.content }} ###################### output: ''' docs = [ Document(content="deepset was founded in 2018 in Berlin, and is known for its Haystack framework"), Document(content="Hugging Face is a company that was founded in New York, USA and is known for its Transformers library") ] chat_generator = OpenAIChatGenerator( generation_kwargs={ "max_completion_tokens": 500, "temperature": 0.0, "seed": 0, "response_format": { "type": "json_schema", "json_schema": { "name": "entity_extraction", "schema": { "type": "object", "properties": { "entities": { "type": "array", "items": { "type": "object", "properties": { "entity": {"type": "string"}, "entity_type": {"type": "string"} }, "required": ["entity", "entity_type"], "additionalProperties": False } } }, "required": ["entities"], "additionalProperties": False } } }, }, max_retries=1, timeout=60.0, ) extractor = LLMMetadataExtractor( prompt=NER_PROMPT, chat_generator=chat_generator, expected_keys=["entities"], raise_on_failure=False, ) result = extractor.run(documents=docs)输出示意(来自 API 文档):
{'documents': [ Document(id=.., content: 'deepset was founded in 2018 in Berlin, ...', meta: {'entities': [{'entity': 'deepset', 'entity_type': 'company'}, {'entity': 'Berlin', 'entity_type': 'city'}, {'entity': 'Haystack', 'entity_type': 'product'}]}), Document(id=.., content: 'Hugging Face is a company ...', meta: {'entities': [{'entity': 'Hugging Face', 'entity_type': 'company'}, {'entity': 'New York', 'entity_type': 'city'}, {'entity': 'USA', 'entity_type': 'country'}, {'entity': 'Transformers', 'entity_type': 'product'}]}) ], 'failed_documents': []}要点提示:
expected_keys=["entities"]会在_extract_metadata中校验输出 JSON 是否含该键;- 推荐
temperature=0.0、seed=0提升确定性;json_schema比json_object约束更严格,能显著降低解析失败率; - 若 LLM 输出非合法 JSON 或缺失键,且
raise_on_failure=False,文档会进入failed_documents,可通过metadata_extraction_response人工检查或重试。
四、RegexTextExtractor:零依赖的正则文本抽取
4.1 功能与用法
RegexTextExtractor(regex_text_extractor.py)是三者中最轻量的组件:给定一个含捕获组的正则,从字符串或ChatMessage列表中提取匹配文本。适用于从 LLM 输出或结构化文本中提取 URL、ID、命令等确定模式,无需调用任何模型。
from haystack.components.extractors import RegexTextExtractor from haystack.dataclasses import ChatMessage # 传入字符串 parser = RegexTextExtractor(regex_pattern='<issue url="(.+)">') result = parser.run(text_or_messages='<issue url="github.com/hahahaha">hahahah</issue>') # result: {"captured_text": "github.com/hahahaha"} # 传入 ChatMessages(仅处理最后一条消息) messages = [ChatMessage.from_user('<issue url="github.com/hahahaha">hahahah</issue>')] result = parser.run(text_or_messages=messages) # result: {"captured_text": "github.com/hahahaha"}4.2 行为细节(从源码与测试印证)
__init__(regex_pattern: str):构造时用re.compile(...).groups检查捕获组数量,若无捕获组会打印警告"整个匹配将被返回"(源码 L52-L59),此时返回match.group(0)即完整匹配;run(text_or_messages: str | list[ChatMessage]) -> dict[str, str]:- 输入为字符串:直接
re.search提取; - 输入为
ChatMessage列表:仅处理最后一条消息(_process_last_message),若末元素不是ChatMessage实例抛出TypeError;空列表返回{"captured_text": ""}; - 有捕获组时返回第 1 个捕获组
match.group(1);无匹配返回{"captured_text": ""};
- 输入为字符串:直接
- 序列化:
to_dict/from_dict仅保留regex_pattern;from_dict会兼容清理旧版本遗留的return_empty_on_no_match参数(源码 L80-L84),保证老 Pipeline 反序列化不中断。
4.3 与 LLM 抽取器的分工
| 维度 | RegexTextExtractor | LLMMetadataExtractor | LLMDocumentContentExtractor |
|---|---|---|---|
| 依赖 | 无 | 需 LLM(ChatGenerator) | 需视觉 LLM(ChatGenerator) |
| 适用输入 | 字符串 / ChatMessage | 文本 Documents | 图像 / PDF Documents |
| 输出 | captured_text | 合并进meta | 写入content+meta |
| 失败处理 | 返回空字符串 | failed_documents+ 重跑机制 | failed_documents+content_extraction_error |
| 异步 | 仅同步run | run/run_async | run/run_async |
实际 Pipeline 中常见组合:先用RegexTextExtractor做确定性的轻量抽取(如抓取工具调用返回中的 URL),再对需要语义理解的部分交给LLMMetadataExtractor或LLMDocumentContentExtractor;两类组件共享ChatGenerator接口,便于替换后端模型。
五、序列化与 Pipeline 集成
三个组件都实现了标准的to_dict/from_dict:
LLMDocumentContentExtractor.to_dict会序列化chat_generator(通过component_to_dict)、prompt、file_path_meta_field、root_path、detail、size、raise_on_failure、max_workers;from_dict通过deserialize_chatgenerator_inplace原地还原生成器(源码 L230-L243);LLMMetadataExtractor额外序列化expected_keys与展开后的page_range(源码 L239-L255);RegexTextExtractor仅序列化regex_pattern。
这意味着三者都可以:
- 直接嵌入 Haystack Pipeline 用 YAML 声明(
type: haystack.components.extractors.llm_metadata_extractor.LLMMetadataExtractor等); - 与 DocumentSplitter 组合做按页/按块批量抽取;
- 配合
DocumentWriter将更新后的文档(含新 meta)写入 DocumentStore。
六、总结
本文以 extractors_api.md 为主线,完整覆盖了 Haystack 三大文本抽取组件的设计、参数、运行流程与实战用法:
- LLMDocumentContentExtractor:视觉 LLM +
DocumentToImageContent完成图像/PDF 文档内容抽取,支持内容回填与元数据合并,务必关注root_path路径安全; - LLMMetadataExtractor:单变量 prompt 约束 + JSON 校验 +
page_range按页抽取 + 失败重跑机制,是 RAG 元数据增强与 NER 的通用方案; - RegexTextExtractor:轻量确定性抽取,与 LLM 抽取器形成互补。
三者共享 Haystack 的组件协议(生命周期、序列化、同步/异步),可无缝组合进生产级 Pipeline。深入阅读可继续查看源码实现 haystack/components/extractors 与测试用例 test/components/extractors。
【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考