
Haystack Builders 组件全解析AnswerBuilder、PromptBuilder 与 ChatPromptBuilder 的答案提取与提示词构建实战【免费下载链接】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 官方 API 参考文档 builders_api.md 展开结合仓库源码与测试用例深入讲解builders模块中三个核心组件用于把 Generator 输出整理为结构化答案的AnswerBuilder以及分别面向文本生成与对话生成场景的PromptBuilder与ChatPromptBuilder。读完本文你将掌握这三个组件的全部初始化参数、run()调用方式、正则表达式用法、Jinja2 模板渲染机制并能直接把它们接入 Haystack Pipeline搭建带引用溯源的可信 RAG 应用。一、Builders 组件在 Haystack 中的定位在 Haystack 的 Pipeline 编排体系中builders模块承担着输入构造与输出整理两类职责PromptBuilder / ChatPromptBuilder位于 Pipeline 的前半段负责把检索结果、用户查询、系统指令等数据渲染成 Generator 真正消费的提示词PromptAnswerBuilder位于 Pipeline 的后半段负责把 Generator 返回的原始文本回复通过正则表达式清洗、抽取并和检索文档关联最终封装为统一的GeneratedAnswer对象。源码目录 haystack/components/builders 下仅有三个文件分别对应这三个组件结构非常清晰haystack/components/builders/ ├── __init__.py ├── answer_builder.py ├── chat_prompt_builder.py └── prompt_builder.py二、AnswerBuilder把 Generator 回复加工成结构化答案2.1 组件职责AnswerBuilder将查询query和Generator 的回复replies转换为GeneratedAnswer对象。它的核心能力包括使用自定义正则表达式从 Generator 回复中解析出答案文本可选地将 Generator 输入侧的检索文档与元数据一并封装进答案同时兼容非聊天型 Generator返回字符串与聊天型 Generator返回ChatMessage对象。GeneratedAnswer定义于 haystack/dataclasses/answer.py是一个 dataclass包含四个字段data答案文本、query原查询、documents关联文档列表、meta元数据字典并实现了to_dict()/from_dict()序列化方法便于在 Pipeline 中流转与持久化。2.2 初始化参数详解AnswerBuilder.__init__的签名如下见 answer_builder.pydef __init__(pattern: str | None None, reference_pattern: str | None None, last_message_only: bool False, *, return_only_referenced_documents: bool True)参数类型默认值说明patternstr \| NoneNone用于从 Generator 回复中抽取答案文本的正则表达式。不指定时整段回复作为答案。正则最多允许一个捕获组有捕获组时用捕获组文本无捕获组时用整个匹配文本reference_patternstr \| NoneNone用于解析文档引用的正则。不指定时不做解析所有文档都会返回。引用以输入文档的从 1 开始的索引表示例如\[(\d)\]可以在字符串this is an answer[1]中匹配出1。指定后文档元数据中会新增referenced布尔键last_message_onlyboolFalse为False时所有消息都作为答案为True时只取最后一条消息作为答案return_only_referenced_documentsboolTrue与reference_pattern配合使用。为True时只返回回复中实际被引用的文档为False时返回全部文档。未提供reference_pattern时该参数不生效关于pattern的典型示例[^\n]$在字符串this is an argument.\nthis is an answer中匹配出this is an answerAnswer: (.*)在字符串this is an argument. Answer: this is an answer中匹配出this is an answer。版本差异提示在当前仓库源码 answer_builder.py 中__init__还新增了一个expand_reference_ranges: bool False参数用于把[6-10]这类区间引用展开为第 610 篇文档。它默认关闭以保持向后兼容启用后会从默认引用模式\[(\d)\]自动切换到更宽泛的模式\[(\d(?:[,-]\d)*)\]见源码中的DEFAULT_REFERENCE_PATTERN与EXPANDED_REFERENCE_PATTERN常量。2.3 基本用法示例最简单的用法只做答案文本抽取from haystack.components.builders import AnswerBuilder builder AnswerBuilder(patternAnswer: (.*)) builder.run(queryWhats the answer?, replies[This is an argument. Answer: This is the answer.])2.4 带文档与引用模式的用法示例下面的示例展示了reference_pattern的完整工作流Generator 回复The capital of France is Paris [2].中的[2]表示引用了输入文档列表中的第 2 篇文档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_pattern\[(\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.注意两点答案文本本身仍保留原始回复未用pattern抽取时而[2]这样的引用标记会通过reference_pattern解析出来并在每个文档副本的meta中写入referenced与source_index两个键。2.5 run() 方法参数与返回值component.output_types(answerslist[GeneratedAnswer]) def run(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)参数说明query输入查询即发送给 Generator 的提示词会原样写入GeneratedAnswer.queryrepliesGenerator 的输出可以是字符串列表也可以是ChatMessage对象列表metaGenerator 返回的元数据列表与replies一一对应。不指定时答案不携带元数据documents作为 Generator 输入的文档。指定后会被加入GeneratedAnswer每份文档副本的meta中含source_index键1 起始位置。当提供reference_pattern时还会额外写入referenced键pattern/reference_pattern与初始化同名参数含义一致可在运行时覆盖初始化时的配置返回值是字典{answers: [...]}其中answers是GeneratedAnswer对象列表。2.6 源码级实现细节印证文档行为阅读 answer_builder.py 可以确认以下实现事实答案抽取逻辑_extract_answer_string先re.search(pattern, reply)无捕获组时取match.group(0)整体匹配有捕获组时取match.group(1)完全没匹配到则返回空字符串。初始化或运行时传入的 pattern 若含多个捕获组_check_num_groups_in_regex会直接抛出ValueError。元数据合并若meta为空则用[{}] * len(replies)补齐若len(replies) ! len(meta)则抛ValueError。对于ChatMessage类型的回复会取其.text作为答案文本、.meta与传入的meta合并并统一在元数据中加入all_messages键保存完整回复历史方便下游追溯。文档引用解析_extract_reference_idxs用re.findall收集所有引用索引并转为集合去重。注意引用是 1 起始的——源码对越界索引做了显式检查并记录 WARNING注释明确说明这是为了防止[0]产生idx -1时 Python 静默解析到最后一个文档的坑。不修改输入文档返回的文档副本通过dataclasses.replace(doc, metadoc_meta)生成原始输入文档的meta不会被改动。对应测试 test_answer_builder.py 中的test_run_does_not_mutate_input_documents_meta系列用例对此有专门断言。区间引用展开当前源码新增expand_reference_rangesTrue时支持[1-3,7-9]这类写法并将超出文档数量的范围钳制到文档总数防止类似[1-999999999]的异常引用造成内存爆炸[3-1]这类倒序区间会被忽略。相关行为均有测试覆盖见 test_answer_builder.py 中test_run_expands_reference_ranges_when_enabled、test_run_clamps_reference_range_to_number_of_documents等用例。聊天场景支持replies为ChatMessage列表时last_message_only控制是否只处理最后一条消息测试test_conversation_history_with_last_message_only_true/false验证了两种模式下答案数量与all_messages元数据的正确性。三、PromptBuilder面向文本 Generator 的提示词渲染3.1 组件职责PromptBuilder使用Jinja2 模板语法渲染提示词填充模板中的变量后交给 Generator 使用。模板中的变量默认即组件的输入未提供时会在渲染结果中以空字符串填充版本 2.23 文档行为以便你可以随时在 Pipeline 运行时替换模板做提示词工程。3.2 初始化参数详解def __init__(template: str, required_variables: list[str] | Literal[*] | None None, variables: list[str] | None None)参数类型说明templatestr使用 Jinja2 语法的提示词模板例如Summarize this document: {{ documents[0].content }}\nSummary:。模板中的变量是 PromptBuilder 的输入默认均为可选v2.23required_variableslist[str] \| * \| None必须作为输入提供的变量列表未提供则抛异常。设为*表示模板中所有变量都必须提供。可选variableslist[str] \| None显式声明模板输入变量替代从template自动推断的结果。例如提示词工程中你想在默认模板之外预留更多变量可以在这里声明3.3 独立使用示例下面的示例渲染后得到的提示词为Translate the following context to Spanish. Context: I cant speak Spanish.; Translation: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.)3.4 在 Pipeline 中使用RAG 场景官方文档给出了一个典型的 RAG PipelinePromptBuilder把检索文档与查询渲染进提示词再交给OpenAIGeneratorfrom haystack import Pipeline, Document from haystack.utils import Secret from haystack.components.generators import OpenAIGenerator 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(instanceOpenAIGenerator(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)3.5 运行时更换模板提示词工程无需重建 Pipeline直接在run时传入新模板即可documents [ Document(contentJoe lives in Berlin, meta{name: doc1}), Document(contentJoe is a software engineer, meta{name: doc1}), ] 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, }, })这里使用了 Jinja2 的loop.index与doc.meta[name]语法演示了模板中访问文档元数据的能力。官方文档提示在测试提示词时如果要在默认模板之外引入更多变量可以把新变量通过variables参数传给初始化。3.6 运行时覆盖变量template_variablestemplate_variables参数可以在run时覆盖 Pipeline 传入的变量包括documents等例如把回答语言从模板默认值改为德语language_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 }} 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}, }, })注意language_template引入了模板中未绑定任何 Pipeline 变量的answer_language借助 Jinja2 的default过滤器未覆盖时默认是English这里被template_variables覆盖为German。3.7 run() 方法与源码实现component.output_types(promptstr) def run(template: str | None None, template_variables: dict[str, Any] | None None, **kwargs)template可选覆盖初始化时的默认模板为None时使用默认模板template_variables可选字典覆盖 Pipeline 变量kwargs用于渲染提示词的 Pipeline 变量返回{prompt: 渲染后的文本}缺少必需变量时抛ValueError。源码层面见 prompt_builder.py模板在初始化时通过HaystackSandboxedEnvironment编译该沙箱环境定义于 haystack/utils/jinja2_sandbox.py并尝试加载可选的Jinja2TimeExtension未安装arrow依赖时静默降级变量通过_extract_template_variables_and_assignments从模板中自动推断排除{% set %}赋值产生的变量每个变量调用component.set_input_type注册为组件输入可选变量会带上默认值这正是未提供的可选变量渲染为空字符串的实现机制run时先合并kwargs与template_variables后者优先再经_validate_variables校验必需变量缺失则抛出带变量清单的ValueError最后compiled_template.render(...)输出结果to_dict()通过default_to_dict序列化模板与参数保证组件可以存入 YAML/JSON 配置并在反序列化后恢复。四、ChatPromptBuilder面向聊天 Generator 的提示词渲染4.1 组件职责ChatPromptBuilder使用 Jinja2 语法把聊天提示词模板渲染为一组ChatMessage消息。模板可以是ChatMessage对象列表静态模板特殊格式的字符串模板支持{% message %}标签与图片等富内容。它支持静态/动态模板并且可以在每次 Pipeline 运行时更新模板。模板变量默认可选v2.23 文档未提供时以空字符串填充可通过variables与required_variables声明输入类型与必需变量。4.2 静态 ChatMessage 模板示例template [ChatMessage.from_user(Translate to {{ target_language }}. Context: {{ snippet }}; Translation:)] builder ChatPromptBuilder(templatetemplate) builder.run(target_languagespanish, snippetI cant speak spanish.)4.3 运行时覆盖静态模板初始化时给定模板运行时传入新的template覆盖它template [ChatMessage.from_user(Translate to {{ target_language }}. Context: {{ snippet }}; Translation:)] builder ChatPromptBuilder(templatetemplate) builder.run(target_languagespanish, snippetI cant speak spanish.) 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)4.4 动态模板在 Pipeline 中按需传入模板与变量官方文档给出了一个不初始化模板参数、每次运行都传入不同模板的动态示例。ChatPromptBuilder()不传模板模板与变量全部在pipe.run时通过template_variables和template传入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}}) print(res) # {llm: {replies: [ChatMessage(...)]}}第二次运行换成天气模板并引入新变量day_countmessages [system_message, ChatMessage.from_user(Whats the weather forecast for {{location}} in the next {{day_count}} days?)] res pipe.run(data{prompt_builder: {template_variables: {location: location, day_count: 5}, template: messages}}) print(res) # {llm: {replies: [ChatMessage(...)]}}这个例子展示了聊天场景的核心优势系统消息与用户消息可以在同一次运行中携带不同的模板变量且模板整体可按需更换非常适合多轮对话应用。4.5 字符串模板支持图片等多模态内容字符串模板配合{% message %}标签可以构造多角色消息配合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){% message rolesystem %}...{% endmessage %}定义了消息角色边界templatize_part过滤器把ImageContent序列化为消息内容的一部分使多模态提示词图片 文本得以构造。需要说明的是示例中的图片路径指向 Haystack 仓库内的测试资源目录test/test_files/images/实际使用时应替换为你自己的图片路径。4.6 初始化与 run 方法def __init__(template: list[ChatMessage] | str | None None, required_variables: list[str] | Literal[*] | None None, variables: list[str] | None None)参数说明templateChatMessage列表或字符串模板。组件会查找 Jinja2 模板语法并用变量渲染模板可以在init或run时提供required_variables必须提供的变量列表缺失则抛异常。设为*表示模板中所有变量都必需。可选variables显式声明模板输入变量替代从模板自动推断的结果component.output_types(promptlist[ChatMessage]) def run(template: list[ChatMessage] | str | None None, template_variables: dict[str, Any] | None None, **kwargs)template覆盖默认模板None时使用初始化模板template_variables字典覆盖 Pipeline 变量kwargs渲染提示词所用的 Pipeline 变量返回{prompt: 渲染后的 ChatMessage 列表}异常当chat_messages为空或包含非ChatMessage元素时抛ValueError。4.7 源码级实现细节阅读 chat_prompt_builder.py 可以印证沙箱渲染环境组件使用HaystackSandboxedEnvironment并注册ChatMessageExtension定义于 haystack/utils/jinja2_chat_extension.py该扩展负责解析{% message %}标签与templatize_part过滤器若已安装arrow还会附加Jinja2TimeExtension对应源码顶部的LazyImport延迟导入。变量推断仅从USER与SYSTEM角色的消息文本中推断模板变量若这类消息缺少文本会抛ValueError若在 ChatMessage 列表模板中使用templatize_part过滤器会抛出专门的FILTER_NOT_ALLOWED_ERROR_MESSAGE该过滤器只允许出现在字符串模板中。字符串模板渲染_render_chat_messages_from_str_template字符串模板渲染后会按行解析——每一行是一个 JSON 序列化的ChatMessage由ChatMessageExtension生成最终通过ChatMessage.from_dict还原为消息对象。不修改原始消息对USER/SYSTEM消息渲染时使用dataclasses.replace(message, _content[TextContent(textrendered_text)])生成副本避免原地修改传入的ChatMessage。序列化to_dict()会把ChatMessage列表转为字典列表后交给default_to_dictfrom_dict()则把字典还原为ChatMessage对象保证组件可被 YAML/JSON 配置系统完整存取。五、三个组件的选型对比维度PromptBuilderChatPromptBuilderAnswerBuilder输入模板字符串Jinja2ChatMessage列表或字符串无需模板输出str提示词list[ChatMessage]list[GeneratedAnswer]典型对接非聊天 Generator如OpenAIGenerator聊天 Generator如OpenAIChatGenerator输出端口messagesGenerator 输出后处理核心能力变量填充、运行时换模板、必需变量校验多角色消息渲染、多模态内容图片、运行时换模板正则抽取答案、引用文档溯源、元数据装配所在位置Pipeline 前半段Pipeline 前半段Pipeline 后半段一条非常自然的组合链路是Retriever → PromptBuilder渲染检索上下文 → Generator生成带引用的回复 → AnswerBuilder抽取答案并绑定引用文档。PromptBuilder/ChatPromptBuilder 负责喂进去AnswerBuilder 负责取出来三者配合即可构建一个带引用溯源的端到端 RAG 应用。六、实战组合出一个带引用的 RAG Pipeline结合官方示例与源码行为下面给出一个整合三个组件的完整思路关键代码可直接运行from haystack import Pipeline, Document from haystack.utils import Secret from haystack.components.builders import PromptBuilder, AnswerBuilder from haystack.components.generators import OpenAIGenerator documents [ Document(contentBerlin is the capital of Germany.), Document(contentParis is the capital of France.), Document(contentRome is the capital of Italy.), ] prompt_template Given these documents, answer the question. Cite sources as [1], [2], ... Documents: {% for doc in documents %} [{{ loop.index }}] {{ doc.content }} {% endfor %} Question: {{ query }} Answer: pipeline Pipeline() pipeline.add_component(prompt_builder, PromptBuilder(templateprompt_template)) pipeline.add_component( llm, OpenAIGenerator(api_keySecret.from_env_var(OPENAI_API_KEY), generation_kwargs{temperature: 0}), ) pipeline.add_component( answer_builder, AnswerBuilder(patternrAnswer: (.*), reference_patternr\[(\d)\]), ) pipeline.connect(prompt_builder.prompt, llm.prompt) pipeline.connect(llm.replies, answer_builder.replies) pipeline.connect(prompt_builder.documents, answer_builder.documents) result pipeline.run({ prompt_builder: {documents: documents, query: What is the capital of France?}, answer_builder: {query: What is the capital of France?}, }) answer result[answer_builder][answers][0] print(answer.data) # 答案文本已按 pattern 抽取 for doc in answer.documents: print(doc.meta[source_index], doc.meta[referenced], doc.content)要点说明提示词模板中把文档编号[{{ loop.index }}]渲染进上下文引导模型在回答中引用[1]/[2]这类编号AnswerBuilder的reference_patternr\[(\d)\]负责把这些编号还原为输入文档索引通过pipeline.connect(prompt_builder.documents, answer_builder.documents)把同一份文档列表同时喂给渲染与答案装配两端source_index与referenced即能正确对应。七、常见错误与规避建议结合源码中的校验逻辑与测试用例test_answer_builder.py、test_prompt_builder.py、test_chat_prompt_builder.py以下是高频踩坑点pattern 含多个捕获组AnswerBuilder(patternrAnswer: (.*), (.*))会直接抛ValueErrorcontains multiple capture groups。正则最多保留一个捕获组。meta 与 replies 长度不一致run时meta列表长度必须等于replies长度否则抛ValueError。引用越界或 0 引用引用是 1 起始的[0]或超出文档数量的引用会被跳过并打印 WARNING而非报错或静默取错文档。必需变量缺失配置了required_variables或当前源码默认*后若run时未提供对应变量PromptBuilder/ChatPromptBuilder会抛出包含缺失变量清单的ValueError。ChatPromptBuilder 空模板或非法元素模板为空或列表内含非ChatMessage元素时抛ValueError。USER/SYSTEM 消息缺文本列表模板中这类角色消息必须含文本否则抛ValueErrortemplatize_part过滤器只能用于字符串模板。八、小结builders模块是 Haystack 提示词链路中承上启下的关键组件PromptBuilder用 Jinja2 模板为文本 Generator 渲染提示词支持运行时换模板与变量覆盖ChatPromptBuilder在聊天场景下渲染多角色消息并支持字符串模板、图片等多模态内容AnswerBuilder用正则从 Generator 回复中抽取答案通过引用模式把检索文档绑定进GeneratedAnswer实现可追溯、可展示引用来源的答案输出。三个组件的实现都遵循 Haystack 组件协议component装饰器、output_types、to_dict/from_dict序列化因此可以无缝嵌入 Pipeline、SuperComponent 与 YAML 配置系统。想进一步深入源码可阅读 answer_builder.py、prompt_builder.py、chat_prompt_builder.py 三个实现文件以及对应的三份测试文件GeneratedAnswer数据结构定义于 haystack/dataclasses/answer.py模板渲染的沙箱与扩展机制位于 haystack/utils/jinja2_sandbox.py 与 haystack/utils/jinja2_chat_extension.py。【免费下载链接】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),仅供参考