
Haystack Builders 组件实战用 PromptBuilder、ChatPromptBuilder 与 AnswerBuilder 构建 RAG 提示与答案管线【免费下载链接】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 的组件体系中builders模块承担着“提示词构造”与“答案后处理”两大职责是任何 RAG检索增强生成或 Agent 流水线中都绕不开的拼图。本篇指南以 docs-website/reference/haystack-api/builders_api.md 为骨架完整讲解PromptBuilder文本提示渲染、ChatPromptBuilder对话提示渲染与AnswerBuilder答案与引用解析三个组件的参数、用法和 Pipeline 集成方式并结合 haystack/components/builders 目录下的源码实现与 test/components/builders 的测试用例做源码级佐证。读完本文你将能够把检索文档与用户查询渲染成符合 Generator 输入格式的提示词在运行期动态更换模板、覆盖变量以做提示词工程以及把 Generator 的原始输出解析为携带引用来源的GeneratedAnswer结构化答案。一、Builders 模块全景builders是 Haystack 中专门负责“提示构建”与“答案组装”的组件集合位于 haystack/components/builders/init.py对外暴露三个组件组件职责输出PromptBuilder用 Jinja2 模板渲染纯文本提示词供非 Chat 类 Generator 使用{prompt: str}ChatPromptBuilder用 Jinja2 模板渲染一组ChatMessage对话消息供 Chat 类 Generator 使用{prompt: list[ChatMessage]}AnswerBuilder用正则从 Generator 回复中提取答案并解析文档引用组装成GeneratedAnswer{answers: list[GeneratedAnswer]}三者天然串联PromptBuilder/ChatPromptBuilder负责“送进去”AnswerBuilder负责“取出来”。下文按“先取后送”的顺序从答案解析讲起再回到提示构造。二、AnswerBuilder把 Generator 回复解析成结构化答案2.1 组件定位与核心能力AnswerBuilder位于 haystack/components/builders/answer_builder.py用一句话概括把“查询 Generator 回复”转换为GeneratedAnswer对象。它的三个核心能力是正则提取答案通过pattern参数从 Generator 的原始回复中截取答案文本引用解析通过reference_pattern参数解析回复中的[n]形式引用标记把被引用的输入文档挂到答案上兼容两类 Generatorreplies既可以传list[str]非 Chat Generator 输出也可以传list[ChatMessage]Chat Generator 输出源码在 answer_builder.py 中通过isinstance(reply, ChatMessage)统一处理。GeneratedAnswer定义于 haystack/dataclasses/answer.py是一个包含data答案文本、query原始查询、documents引用文档列表、meta元数据四个字段的 dataclass支持to_dict/from_dict序列化。2.2 最简用法正则提取答案原文档给出的最小示例from haystack.components.builders import AnswerBuilder builder AnswerBuilder(patternAnswer: (.*)) builder.run(queryWhats the answer?, replies[This is an argument. Answer: This is the answer.])这里patternAnswer: (.*)会从回复中匹配到捕获组This is the answer.。pattern参数的取值规则见 answer_builder.py 与 answer_builder.py 的_extract_answer_string实现不传pattern时整个回复文本就是答案正则最多允许一个捕获组有捕获组时取match.group(1)无捕获组时取match.group(0)即整个匹配超过一个捕获组会在初始化或运行时抛出ValueError由_check_num_groups_in_regex校验见 answer_builder.py测试用例test_run_with_pattern_with_more_than_one_capturing_group验证了这一点未匹配到时返回空字符串。官方示例还给出另一个经典模式[^\n]$可在一串多行文本中取出最后一行的答案。2.3 引用文档解析带来源的答案这是AnswerBuilder最有价值的能力。原文档的核心示例from haystack import Document from haystack.components.builders import AnswerBuilder replies [The capital of France is Paris [2].] docs [ Document(contentBerlin is the capital of Germany.), Document(contentParis is the capital of France.), Document(contentRome is the capital of Italy.), ] builder AnswerBuilder(reference_patternr\[(\d)\], return_only_referenced_documentsFalse) result builder.run(queryWhat is the capital of France?, repliesreplies, documentsdocs)[answers][0] print(fAnswer: {result.data}) print(References:) for doc in result.documents: if doc.meta[referenced]: print(f[{doc.meta[source_index]}] {doc.content}) print(Other sources:) for doc in result.documents: if not doc.meta[referenced]: print(f[{doc.meta[source_index]}] {doc.content}) # Answer: The capital of France is Paris # References: # [2] Paris is the capital of France. # Other sources: # [1] Berlin is the capital of Germany. # [3] Rome is the capital of Italy.运行结果中每个返回文档的meta都会被打上两个键详见 answer_builder.pysource_index该文档在输入documents列表中的 1-based 位置即第 1 个文档为 1第 2 个为 2referenced布尔值表示该文档是否被回复中的引用标记命中。reference_pattern的语义要点引用从 [1] 开始计数对应输入文档列表的第一个元素回复中出现[2]即引用第二个文档不提供reference_pattern时不做引用解析全部文档原样返回也不会有referenced键输入文档不会被修改源码使用dataclasses.replace(doc, metadoc_meta)生成带新元数据的副本answer_builder.py测试用例test_run_does_not_mutate_input_documents_meta与test_run_does_not_mutate_document_with_empty_meta专门守护了这一行为越界引用会被跳过并记录 WARNING 日志如[0]、[3]但只有 2 个文档且对[0]做了显式边界检查避免 Python 负索引静默解析到最后一个文档见 answer_builder.py 及测试test_run_with_documents_with_zero_reference。2.4return_only_referenced_documents与引用范围展开__init__签名answer_builder.py__init__( pattern: str | None None, reference_pattern: str | None None, last_message_only: bool False, *, return_only_referenced_documents: bool True, expand_reference_ranges: bool False ) - None各参数行为return_only_referenced_documents默认True只返回被引用的文档设为False则返回全部文档但每个文档仍带referenced标记供上层区分“被引用”与“其他来源”。未提供reference_pattern时该参数无效果expand_reference_ranges默认False保持向后兼容。为True时会把[6-10]这类范围标记展开为第 6 到第 10 个文档。源码层面当它与默认引用模式\[(\d)\]配合时会自动切换为更宽的模式\[(\d(?:[,-]\d)*)\]见 answer_builder.py 与_resolve_reference_pattern支持逗号分隔的混合写法如[1-3,7-9]last_message_only默认False对replies中每条消息各生成一个GeneratedAnswer为True时只取最后一条消息生成答案answer_builder.py测试用例test_conversation_history_with_last_message_only_true展示了多轮对话场景。关于引用范围展开的边界处理测试用例给出了可验证的细节[3-1]这类 startend 的非法区间会被忽略test_run_ignores_invalid_reference_ranges[1-100]超出文档数时会把终点钳制到文档总数避免一次性物化巨大集合test_run_clamps_reference_range_to_number_of_documents对应 answer_builder.py 的 clamp 逻辑。2.5run方法签名与元数据合并run签名answer_builder.pyrun( query: str, replies: list[str] | list[ChatMessage], meta: list[dict[str, Any]] | None None, documents: list[Document] | None None, pattern: str | None None, reference_pattern: str | None None, expand_reference_ranges: bool | None None, ) - dict[str, Any]注意pattern、reference_pattern、expand_reference_ranges三个参数既可在__init__设置也可在run时覆盖run中的值优先见 answer_builder.py这为“同一个组件在不同 Pipeline 运行中采用不同解析规则”提供了灵活性。meta参数的行为值得留意不传时默认按replies数量填充空字典若传入则长度必须与replies一致否则抛ValueErroranswer_builder.py。对于ChatMessage类型的回复其自带meta会与传入meta合并{**extracted_metadata, **given_metadata}且答案的meta中始终会写入all_messages键保存完整对话历史answer_builder.py。2.6 源码佐证测试覆盖的行为契约test/components/builders/test_answer_builder.py 对上述行为做了系统验证可作为读者理解组件语义的“行为说明书”test_run_without_pattern/test_run_with_pattern_with_capturing_group验证无模式时整段回复即答案、有捕获组时取捕获组内容test_run_with_documents_with_reference_pattern验证[2]引用只返回第 2 个文档且meta[referenced]、meta[source_index]正确test_run_returns_referenced_documents_in_source_order引用结果按输入文档顺序升序返回test_run_with_chat_message_replies_with_pattern验证ChatMessage输入路径与元数据透传。三、PromptBuilderJinja2 渲染纯文本提示词3.1 组件定位PromptBuilderhaystack/components/builders/prompt_builder.py使用 Jinja2 语法渲染提示词模板输出可直接发送给 Generator 的字符串。模板中的变量默认全部必填可通过required_variables放行部分变量为可选缺失时渲染为空字符串。3.2 独立运行的最小示例from haystack.components.builders import PromptBuilder template Translate the following context to {{ target_language }}. Context: {{ snippet }}; Translation: builder PromptBuilder(templatetemplate) builder.run(target_languagespanish, snippetI cant speak spanish.)渲染结果为Translate the following context to Spanish. Context: I cant speak Spanish.; Translation:。3.3 在 RAG Pipeline 中使用原文档的经典 RAG 示例完整展示了检索结果与查询如何注入提示词并喂给 Chat Generatorfrom haystack import Pipeline, Document from haystack.utils import Secret from haystack.components.generators.chat import OpenAIChatGenerator from haystack.components.builders.prompt_builder import PromptBuilder # in a real world use case documents could come from a retriever, web, or any other source documents [Document(contentJoe lives in Berlin), Document(contentJoe is a software engineer)] prompt_template Given these documents, answer the question. Documents: {% for doc in documents %} {{ doc.content }} {% endfor %} Question: {{query}} Answer: p Pipeline() p.add_component(instancePromptBuilder(templateprompt_template), nameprompt_builder) p.add_component(instanceOpenAIChatGenerator(api_keySecret.from_env_var(OPENAI_API_KEY)), namellm) p.connect(prompt_builder, llm) question Where does Joe live? result p.run({prompt_builder: {documents: documents, query: question}}) print(result)这里的p.connect(prompt_builder, llm)把PromptBuilder的prompt输出接到 Chat Generator 的messages输入。由于模板使用了{% for doc in documents %}循环与doc.content属性访问说明 Jinja2 模板中可以直接遍历Document对象并访问其字段。3.4 运行时更换模板与覆盖变量提示词工程PromptBuilder一个重要的实战特性是无需重建 Pipeline即可在每次run时更换模板或覆盖变量。更换模板把新模板字符串通过template参数传入new_template You are a helpful assistant. Given these documents, answer the question. Documents: {% for doc in documents %} Document {{ loop.index }}: Document name: {{ doc.meta[name] }} {{ doc.content }} {% endfor %} Question: {{ query }} Answer: p.run({ prompt_builder: { documents: documents, query: question, template: new_template, }, })该模板展示了loop.index循环序号与doc.meta[name]文档元数据的访问方式。源码层面run在收到template参数时会用self._env.from_string(template)现场编译新模板再渲染prompt_builder.py。覆盖变量用template_variables覆盖 Pipeline 变量或引入模板中新出现但未绑定 Pipeline 输入的变量language_template ... Question: {{ query }} Please provide your answer in {{ answer_language | default(English) }} Answer: p.run({ prompt_builder: { documents: documents, query: question, template: language_template, template_variables: {answer_language: German}, }, })这里answer_language不在 Pipeline 输入中模板里用default(English)兜底运行时通过template_variables覆盖为German。run实现中template_variables与 kwargs 会合并{**kwargs, **template_variables}后者优先级更高prompt_builder.py。3.5__init__参数详解__init__( template: str, required_variables: list[str] | Literal[*] | None *, variables: list[str] | None None, ) - Nonetemplate必填Jinja2 模板字符串如Summarize this document: {{ documents[0].content }}\nSummary:。模板中出现的变量会被自动识别为组件输入required_variables默认*表示模板中所有变量必填传显式列表则只要求列出的变量必填其余变量缺失时渲染为空字符串传None表示全部可选。注意当存在模板变量且显式设置None时源码会打印警告日志prompt_builder.py提示在多分支 Pipeline 中“全可选”可能导致意外行为variables显式指定输入变量列表替代从模板自动推断的结果。典型场景是提示词工程期模板里暂时用不到的变量也想作为输入预留可以在此列出。源码通过_extract_template_variables_and_assignmentshaystack/utils/jinja2_extensions.py从模板提取“用到的变量”并减去“模板内已赋值的变量”。组件初始化时会对每个变量调用component.set_input_type必填变量类型为Any可选变量类型为Any且带默认值prompt_builder.py这解释了“可选变量缺失时渲染为空字符串”的机制来源。3.6run方法与校验run( template: str | None None, template_variables: dict[str, Any] | None None, **kwargs: Any ) - dict[str, Any]返回值恒为{prompt: str}。_validate_variablesprompt_builder.py在渲染前校验必填变量是否齐备缺失时抛出带完整信息的ValueError列出缺失变量、必填列表与已提供列表这正是run文档中Raises ValueError的来源。四、ChatPromptBuilder渲染多轮对话消息4.1 组件定位ChatPromptBuilderhaystack/components/builders/chat_prompt_builder.py是PromptBuilder的对话版模板可以是list[ChatMessage]也可以是带{% message %}块的特殊字符串渲染结果是一组ChatMessage直接对接 Chat Generator 的messages输入。4.2 静态 ChatMessage 模板from haystack.dataclasses import ChatMessage from haystack.components.builders import ChatPromptBuilder template [ChatMessage.from_user(Translate to {{ target_language }}. Context: {{ snippet }}; Translation:)] builder ChatPromptBuilder(templatetemplate) builder.run(target_languagespanish, snippetI cant speak spanish.)模板变量同样用{{ }}表示且user/system 角色的消息会被渲染其他角色的消息如历史 assistant 消息原样保留chat_prompt_builder.py。4.3 运行时覆盖静态模板msg Translate to {{ target_language }} and summarize. Context: {{ snippet }}; Summary: summary_template [ChatMessage.from_user(msg)] builder.run(target_languagespanish, snippetI cant speak spanish., templatesummary_template)与PromptBuilder一样run的template参数可以在每次运行覆盖默认模板。4.4 动态模板 Pipeline 集成更常见的做法是不在初始化时绑定模板而在每次run时传入原文档中的完整示例from haystack.components.builders import ChatPromptBuilder from haystack.components.generators.chat import OpenAIChatGenerator from haystack.dataclasses import ChatMessage from haystack import Pipeline # no parameter init, we dont use any runtime template variables prompt_builder ChatPromptBuilder() llm OpenAIChatGenerator(modelgpt-5-mini) pipe Pipeline() pipe.add_component(prompt_builder, prompt_builder) pipe.add_component(llm, llm) pipe.connect(prompt_builder.prompt, llm.messages) location Berlin language English system_message ChatMessage.from_system(You are an assistant giving information to tourists in {{language}}) messages [system_message, ChatMessage.from_user(Tell me about {{location}})] res pipe.run(data{prompt_builder: {template_variables: {location: location, language: language}, template: messages}})这里有两个值得注意的 Pipeline 用法ChatPromptBuilder()不传模板直接初始化模板与变量全部通过runPipeline 的data字典下发template_variables与template同时在 Pipeline 输入中提供——前者放变量、后者放消息模板。第二次运行时模板被替换为包含{{day_count}}的新消息template_variables提供day_count等变量同一个 Pipeline 就完成了两轮不同的对话构建。4.5 字符串模板与多模态内容ChatPromptBuilder还支持用特殊字符串模板一次性声明多个消息配合{% message role... %}块与templatize_part过滤器可在消息中嵌入图片等多模态内容from haystack.components.builders import ChatPromptBuilder from haystack.dataclasses.image_content import ImageContent template {% message rolesystem %} You are a helpful assistant. {% endmessage %} {% message roleuser %} Hello! I am {{user_name}}. Whats the difference between the following images? {% for image in images %} {{ image | templatize_part }} {% endfor %} {% endmessage %} images [ImageContent.from_file_path(test/test_files/images/apple.jpg), ImageContent.from_file_path(test/test_files/images/haystack-logo.png)] builder ChatPromptBuilder(templatetemplate) builder.run(user_nameJohn, imagesimages)实现上字符串模板由ChatMessageExtensionJinja2 扩展定义于 haystack/utils/jinja2_chat_extension.py处理渲染后按行切分、每行是一段 JSON再由ChatMessage.from_dict还原为消息对象chat_prompt_builder.py。templatize_part过滤器同一文件 jinja2_chat_extension.py负责把ImageContent等结构化内容安全地序列化为消息的一部分并且只能在字符串模板中使用——若在list[ChatMessage]模板中出现会抛出FILTER_NOT_ALLOWED_ERROR_MESSAGEchat_prompt_builder.py。注意示例中图片路径是测试用相对路径实际使用时请替换为你自己的图片路径。4.6__init__与run签名__init__( template: list[ChatMessage] | str | None None, required_variables: list[str] | Literal[*] | None *, variables: list[str] | None None, ) - None run( template: list[ChatMessage] | str | None None, template_variables: dict[str, Any] | None None, **kwargs: Any ) - dict[str, list[ChatMessage]]与PromptBuilder几乎一致的参数语义差异在于模板类型为list[ChatMessage] | str | None可不在初始化时提供变量提取只针对user 与 system 角色的消息chat_prompt_builder.pyrun返回{prompt: list[ChatMessage]}run中若模板为空、或列表中含有非ChatMessage元素会抛出ValueErrorchat_prompt_builder.py渲染后的消息通过dataclasses.replace生成新对象不会原地修改传入的模板消息chat_prompt_builder.py。4.7 序列化支持ChatPromptBuilder实现了to_dict/from_dictchat_prompt_builder.pyto_dict会把list[ChatMessage]模板转成字典列表from_dict反序列化时再还原为ChatMessage对象。这使组件可以被 Pipeline 的 YAML/JSON 序列化机制持久化。PromptBuilder同样提供to_dictprompt_builder.py保存的是原始模板字符串。五、渲染环境的底层实现细节两个 Builder 组件的模板渲染并不是裸用 Jinja2而是统一构建在一个沙箱环境之上了解这层实现有助于理解安全边界与能力边界沙箱环境PromptBuilder与ChatPromptBuilder都使用HaystackSandboxedEnvironment继承自 Jinja2 的SandboxedEnvironment见 haystack/utils/jinja2_sandbox.py对模板中可访问的对象与方法做了白名单式限制避免模板执行任意 Python 代码时间扩展若安装了可选依赖arrowpip install arrow1.3.0环境会额外注册Jinja2TimeExtension允许模板中使用当前时间相关能力PromptBuilder在导入失败时降级为纯沙箱环境prompt_builder.pyChatPromptBuilder则通过LazyImport延迟导入chat_prompt_builder.py变量推断_extract_template_variables_and_assignmentshaystack/utils/jinja2_extensions.py负责从模板文本中区分“被使用的变量”与“模板内部已赋值如{% set %}的变量”只把前者暴露为组件输入。六、三个组件的选型与串联建议场景推荐组件理由纯文本提示、非 Chat Generator如OpenAIGeneratorPromptBuilder输出str与生成器输入类型直接匹配多轮对话、Chat Generator如OpenAIChatGeneratorChatPromptBuilder输出list[ChatMessage]保留角色与多模态内容需要给答案带引用来源RAG 引用标注AnswerBuilder正则解析答案 [n]引用还原文档产出GeneratedAnswer一个典型的端到端 RAG 链路是Retriever → PromptBuilder/ChatPromptBuilder → Generator → AnswerBuilder。其中AnswerBuilder的documents输入直接接 Retriever 的documents输出replies接 Generator 的replies输出即可在最终答案中同时拿到答案文本、被引用的原文片段与元数据。完整的示例可在builders组件源码 docstringanswer_builder.py、prompt_builder.py与官方示例目录 examples 中继续探索。七、小结本文围绕 Haystackbuilders模块的三个组件展开了完整梳理PromptBuilderJinja2 纯文本提示渲染支持required_variables部分可选、variables显式声明输入、运行时template更换与template_variables变量覆盖ChatPromptBuilder对话消息渲染支持list[ChatMessage]与字符串两种模板形态可嵌入templatize_part多模态内容渲染结果直接对接 Chat GeneratorAnswerBuilder正则提取答案、解析[n]引用还原文档、合并元数据产出携带source_index与referenced标记的GeneratedAnswer。三者共同的底层支撑是 Jinja2 沙箱渲染环境haystack/utils/jinja2_sandbox.py与变量提取工具haystack/utils/jinja2_extensions.py。行为契约则由 test/components/builders 下的三个测试文件完整守护读者若想深挖边界行为越界引用、范围展开、消息不可变性等直接阅读对应测试是最快的路径。【免费下载链接】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),仅供参考