构建自然语言与大语言模型(LLM)流水线——部署基于 Haystack 的应用

0 阅读34分钟

在上一章中,我们使用 Haystack 的 RAG 工具构建了一条稳健的问答(Q&A)Pipeline。我们重点关注的是如何打造一个可复现且可扩展的系统,并将评估指标与反馈回路整合进来,以持续提升模型性能。在这些基础已经具备之后,我们现在可以迈出下一步:将这条 Pipeline 部署到生产环境中

本章中,我们将深入探讨那些能够让我们的 NLP Pipeline 真正在现实应用中落地的部署策略。从理解部署的核心需求,到掌握 API 集成与容器化,我们将覆盖一系列技术,使得 Pipeline 在上线后仍然具备良好的可访问性、可扩展性与可管理性

我们将把 API 开发 作为提供推理结果服务的主要方式来展开讨论,使用户能够无缝地与 Pipeline 交互。此外,你还将学习如何为部署而组织项目结构,包括使用 Docker、CI/CD 自动化,以及 Haystack 提供的序列化 Pipeline 工具。

本章将涵盖以下主题:

  • Haystack Pipeline 的部署策略
  • 部署策略的实现
  • 将 Haystack Pipeline 暴露为 MCP Server
  • 从序列化开发过渡到生产环境

到本章结束时,你将能够完成从探索式开发到以部署为导向的工程化转变;你将学会如何借助 API 提供结果服务并构建面向用户的接口,如何根据应用的独特需求开发自定义 API 端点,以及如何使用 Docker 和 CI/CD 实践来组织、打包并自动化部署你的 Pipeline。

下面我们从审视部署中的关键考量开始,并比较不同部署策略,以判断哪一种最适合你的 NLP Pipeline。

技术要求

为了保证可复现性,本章代码被放置在两个文件夹中,每个文件夹分别对应两种部署方式:

使用 FastAPI 将 Haystack Pipeline 部署为 REST 端点

目录位置:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/tree/main/ch7

该目录中包含的是“从零开始(from-scratch)”实现的自定义 API。若要复现全部步骤,请参考其 README 文件:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7/README.md

该 README 允许你选择在本地运行 API,或者使用 Docker 容器运行。

使用 Hayhooks 将多个 Haystack Pipeline 部署为 REST 端点

目录位置:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/tree/main/ch7-hayhooks

该目录中包含的是“开箱即用(batteries-included)”的 Hayhooks 实现。若要复现全部步骤,请参考其 README 文件:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/README.md

这个 README 同样允许你选择在本地运行 Hayhooks,或者通过 Docker 容器运行。

我们将使用 Docker 来对最终完成的 NLP 应用进行容器化。更多信息可参见:
https://docs.docker.com/desktop/

Haystack Pipeline 的部署策略

要有效地从 Haystack 驱动的 NLP Pipeline 的开发阶段过渡到部署阶段,首先必须理解有哪些可用的部署策略。本节中,我们将探讨部署 NLP 应用——尤其是基于 Haystack 构建的应用——的基础问题。

一个成功的部署策略,需要仔细权衡诸如可扩展性、可访问性和资源管理等因素,以确保 Pipeline 在真实世界需求下依旧能够保持良好性能、良好用户体验以及足够的适应性。

本节将概述 NLP 应用最常见的部署需求。我们会讨论在选择部署方式时需要考虑的因素,例如:系统性能与基础设施成本之间的权衡、终端用户的访问便利性,以及如何管理资源以应对使用量波动。不同的部署策略各自具备不同优势:有的更适合快速扩展,有的更适合保持轻量化部署,还有的更适合管理复杂依赖。我们将比较这些方法,并概述它们在从本地服务器到云平台等不同环境中的优劣势。

到本节结束时,你将清楚理解哪些部署策略最契合你 Pipeline 的需求。这将为后续几节中更具体的技术实现方法打下基础,包括 API 开发、Docker 化以及自动化部署工作流。

NLP Pipeline 部署的关键需求概览

部署 NLP Pipeline,尤其是那些由 LLM 或复杂多阶段工作流驱动的 Pipeline,需要满足若干关键需求,才能确保它们在生产环境中实现平滑集成与稳健运行:

可扩展性(Scalability)

在 NLP Pipeline 中,可扩展性指的是系统在不牺牲性能的前提下处理不断增长的工作负载或支撑业务增长的能力。这保证了应用能够在数据量变大或并发用户增多时,依旧保持稳定响应。

对于聊天机器人、搜索引擎或实时文本分析工具这类 NLP 应用来说,可扩展性对于应对大规模数据与流量高峰、同时维持较快响应时间,是至关重要的。

而在可扩展性内部,另一个关键点是 弹性(elasticity) 。弹性通过根据需求动态增加或减少资源,进一步增强了可扩展性,从而优化资源利用率和成本。基于 Docker 与 Kubernetes 的容器化部署,尤其擅长支撑这种弹性扩缩容。

可扩展性,是我们必须从 Notebook 脚本走向真正部署系统的首要原因。我们的 RAG Pipeline 必须既能服务一个用户,也能同时服务一千个并发用户,而不发生故障。实现这一目标的路径,就是容器编排。但如果系统本身难以访问,那么再高的可扩展性也没有意义。

可访问性(Accessibility)

可访问性关注的是:用户或客户端应用与 NLP Pipeline 交互的便利程度。通常这意味着要通过 API 将 Pipeline 暴露出来,以便与其他服务或平台实现无缝集成。

REST API 可以让 Pipeline 与用户之间实现简洁交互,从而保证跨平台可访问性。对于地理位置分散的用户或服务而言,远程与分布式访问也同样关键;因此,经常会借助 AWS、Google Cloud 或 Azure 这类云部署选项,以降低延迟并确保全球可用性。

在我们的语境中,可访问性本质上就意味着:一个 API。把 Pipeline 暴露成 REST API,就意味着它变成了一个模块化的“积木块”,能够被任何其他服务消费——例如 Web 前端、移动端 App,或者另一个后端系统。而这也自然引出了下一个逻辑挑战。

资源管理(Resource management)

部署中的资源管理,是指对内存、算力和存储等计算资源进行高效分配,从而在保证高性能的同时,将成本控制在可接受范围内。

对于 NLP 模型,尤其是使用 LLM 的系统,这意味着要优化 GPU 和 CPU 的使用、有效管理内存,并通过 Serverless 计算与容器化等策略,在性能与成本之间取得平衡。

高效的资源管理,是部署可扩展 NLP 应用、同时又不让运营成本失控的关键。这一点对 NLP 尤其重要。LLM 和 embedding 模型都极度消耗内存和 GPU。一个成功的部署策略,必须高效管理这些昂贵资源,否则成本会迅速失控。

可靠性与韧性(Reliability and resilience)

可靠性与韧性对于保证系统持续稳定运行,以及在出现意外问题时快速恢复至关重要。健壮的错误处理机制与自动恢复流程——例如容错与自动重启——能够帮助系统保持不间断服务。全面的监控与日志记录,则可以通过及早发现并处理问题来增强可靠性,同时为调试与性能优化提供审计轨迹。

这是部署中“建立信任”的一面。在生产环境里,失败是不可接受的。用户一旦收到 500 Internal Server Error,就不会再信任你的应用。因此,我们需要自动健康检查、日志记录,以及自动恢复机制。

模块化与可维护性(Modularity and maintainability)

模块化与可维护性,依赖于一种结构化设计:把 Pipeline 各组件做成模块化单元,从而可以在不影响整个系统的情况下,对其进行更新或替换。这对包含数据预处理、模型推理和后处理等多个阶段的 Pipeline 尤其有价值。

而引入 CI/CD Pipeline,则能进一步简化变更集成、测试与部署流程,减少错误并加速迭代。

我们在第 6 章中构建的 Pipeline,本身就已经是模块化设计。我们的部署策略必须尊重这一点。我们应该能够在不让整个系统离线的情况下,更新 PromptBuilder 或替换 embedding 模型。这也是 CI/CD 实践不可或缺的原因所在。

安全与合规(Security and compliance)

安全与合规用于确保 NLP 部署中的数据保护与监管遵从。数据隐私措施,例如加密与访问控制,对于处理敏感信息并满足 GDPR、HIPAA 等合规要求至关重要。此外,通过身份认证与授权机制来保护 Pipeline 的外部 API 与用户交互,也是实现安全访问的重要手段。

把这些考虑放在一起,才能构成一个完整的框架,用于部署和管理可扩展、可访问且安全的 NLP 系统。这是最后一道不可妥协的门槛。我们的 Pipeline 将处理用户查询,甚至可能处理敏感文档,因此必须保证数据被加密,访问经过认证。

在本章中,把所有这些点都完整纳入 Pipeline 部署的讨论范围,超出了可承载的篇幅;我们的重点是围绕两种部署策略,搭起部署所需的初始基础设施。

现在,我们来看看在部署 NLP Pipeline 时可以采用的两种策略:

  • 通过 FastAPI 进行 API 部署(定制化、强控制路径)
  • 通过 Pipeline 序列化 + Hayhooks 集成(流线化、高效率路径)

两种主要部署策略的比较

为 NLP Pipeline 选择正确的部署策略,很大程度上取决于它运行的环境,以及项目自身的具体需求与约束。不同部署方法在可扩展性、集成难度、维护成本与资源效率方面,都会展现出不同优势。

本节中,我们将比较两种把 Haystack Pipeline 部署为容器化 REST 端点的方法:一种基于 FastAPI,另一种基于 Hayhooks。这两种方案都可以被 Docker 化,从而获得可移植性与可复现性,并通过 Kubernetes 等解决方案进一步获得可扩展性。

方法 1 —— FastAPI REST 端点

我们将使用 FastAPI 框架手工构建一个 REST API。这个方法能提供最大的控制力与定制能力。我们需要自行定义每一个端点、每一个数据校验模型,以及全部应用逻辑。这是一条“从零开始(from-scratch)”的路线,非常适合复杂、定制化程度高的应用。

方法 2 —— Pipeline 序列化与 Hayhooks 集成

我们将利用 Haystack 原生的序列化能力,把 Pipeline 保存为 YAML 文件,然后再使用 Hayhooks 自动将其暴露为 REST API。这个方法提供的是最高的开发效率与最紧密的 Haystack 原生集成。它是一条“开箱即用(batteries-included)”的路线,专门为 Haystack 生态而设计。

接下来,我们来看哪种方法更适合你的具体场景。

为你的环境选择合适的策略

本章的核心主题,是在 控制力(方法 1)开发效率(方法 2) 之间进行权衡。这两者并不是互斥关系;事实上,Hayhooks 本身就是构建在 FastAPI 之上的。这意味着,它们并不是彼此竞争的两种方案,而是位于不同抽象层级上的两种实现方式。

我们会先教你“手工搭建”的方式(FastAPI),这样你能够理解 Pydantic、端点创建等底层机制。然后,我们再引入 Hayhooks,作为一种抽象层,把你刚刚学会的那些样板代码全部自动化掉。

我们会先从构建 API 所需的基础模块讲起,再进一步理解 Hayhooks 的价值主张——也就是它如何替你接管那些重复性工作。

为更清晰地说明这一选择,我们把两种方式的权衡总结在表 7.1 中。

表 7.1 —— Haystack Pipeline 部署方式比较

FeatureMethod 1: Custom FastAPIMethod 2: Hayhooks
Primary goal完全控制、自定义逻辑、复杂端点速度、简洁性、原生 Haystack 集成
Boilerplate code高(需手动配置 app、端点与数据模型)极少甚至为零
How it works你需要在 Python API 代码中导入并运行 Haystack Pipeline 对象Hayhooks 读取序列化后的 pipeline.yml 文件,并自动生成 API 端点
Flexibility最高。可以添加任意端点、中间件或逻辑很高。基于 FastAPI,因此也可扩展,但主要面向 Pipeline 服务化进行了优化
Best for...复杂、多面向的应用场景,其中 Haystack Pipeline 只是整个系统的一部分快速部署 RAG API、MLOps 工作流,以及那些“Pipeline 本身就是系统”的服务
Learning curve中等。需要掌握 FastAPI、Pydantic 和 ASGI低。只需了解 Haystack 与 YAML

这两种方法都可以通过 Docker 与 Kubernetes 实现容器化与扩展。最终选择哪种方法,取决于项目整体的架构与目标。

接下来,我们会先从一个 FastAPI 的入门示例开始:把第 6 章中构建的 hybrid RAG Pipeline 暴露为一个端点,并假设索引 Pipeline 已经被独立执行。之后,我们再学习如何搭建一个更动态的系统:允许我们通过 REST 端点上传 PDF 与 URL 内容,执行索引 Pipeline,并再通过 hybrid RAG Pipeline 从已索引内容中取回信息。

实现部署策略

要有效部署 NLP Pipeline,需要借助现代技术:它们能够简化 API 开发、支持并行数据处理、监控系统性能,并推动容器化部署。本节中,我们将介绍在给定解决方案中使用到的关键工具(即 FastAPI),并探讨它们如何帮助我们构建健壮、可扩展且易维护的 NLP 应用。

方法 1:使用 FastAPI 提供 Haystack Pipeline 服务

FastAPI 是一个用于用 Python 构建 RESTful API 的高性能 Web 框架。它被设计得既快速、易用,又与现代 Python 特性(例如类型注解)完全兼容。

FastAPI 具有以下关键特性:

异步支持(Asynchronous support)
FastAPI 原生支持基于 Python asyncio 的异步请求处理,因此非常适合高并发应用。

自动文档生成(Automatic documentation)
它能够自动为 API 端点生成 OpenAPI 文档(Swagger UI),从而简化与客户端应用的集成。

基于 Pydantic 的校验(Validation with Pydantic)
FastAPI 使用 Pydantic 模型来做数据校验,以确保请求和响应符合预定义 schema。Pydantic 是一个流行的 Python 库,专门利用类型提示来校验数据,确保数据符合定义好的结构与类型,因此在 API 和数据 Pipeline 中都被广泛使用。

你可能会问:为什么是 FastAPI?为什么它已经成为现代 Python 与机器学习 API 的事实标准?
答案在于它的底层基础:

基于 Starlette,具备高性能

FastAPI 建立在 Starlette 的 ASGI 规范之上。ASGI 是 Python Web 应用的一种标准协议,这使得 FastAPI 从底层开始就是异步的。它通常由高性能的 ASGI 服务器 Uvicorn 来运行。

这一点对 NLP 推理尤为关键。当我们的 API 调用一个 LLM 时,这其实是一个 I/O 密集型操作——API 大部分时间都在等待结果返回。异步支持意味着,在等待的这段时间里,服务器还能同时处理其他数百个请求,而不是被阻塞住,从而显著提升吞吐量。

基于 Pydantic,具备高可靠性

Pydantic 会在运行时强制执行 Python 的类型提示。这意味着,我们可以为 /query 请求定义一个继承自 Pydantic 的模型,而 FastAPI 会自动校验传入的 JSON,对其进行解析,然后交给我们一个干净、通过验证的 Python 对象。它还会基于这些模型自动生成 OpenAPI(Swagger)文档。

这种组合——Starlette 带来的异步性能,加上 Pydantic 带来的数据校验能力——使 FastAPI 成为构建生产级服务时非常稳健的选择。

FastAPI 让我们能够方便地为 NLP Pipeline 构建 REST 端点,用于查询 Pipeline、管理工作流以及提供系统健康检查。它在速度与简洁性之间取得了很好的平衡,因此是构建生产级 API 的优秀选择。

一旦我们用 FastAPI 开发好了应用,就可以进一步用 Docker 对其进行打包。

Docker:为应用提供可移植的运行单元

Docker 是一个使用容器化技术将应用及其依赖打包成可移植单元的平台。这样一来,应用在开发、测试和生产环境中就能够表现一致。

Docker 具有以下关键特性:

隔离性(Isolation)
Docker 容器会隔离应用及其依赖,从而避免与系统层软件发生冲突。

可移植性(Portability)
容器可以在任何支持 Docker 的系统上运行,因此能够在不同环境中实现一致部署。

高资源利用率(Efficient resource usage)
相较于传统虚拟机,Docker 容器更加轻量,因此在资源利用上更高效。

在本方案中,Docker 使得 API 及其相关组件可以被打包进容器,从而确保部署一致性。再结合 Kubernetes 这样的编排工具,Docker 还能够借助负载均衡和自动故障转移等能力,实现可扩展性与高可用性。

通过把这些技术结合起来,我们就获得了一套强大的部署策略:FastAPI 作为 API 交互的骨干,为用户和客户端应用提供直观接口;而 Docker 则保证应用可移植、可扩展,并通过容器编排支撑弹性部署。

下面我们来看本章中的 FastAPI 示例代码。

构建 FastAPI 应用

现在,我们将演示一个简单的自定义 API:它使用的是第 6 章中的 hybrid RAG SuperComponent。这个应用会加载 Haystack Pipeline(其构建方式与第 6 章相同),并通过多个端点将其暴露出来,包括 /health/query。这可以看作是一个将 SuperComponent 打包成 FastAPI 应用的实际示例。

FastAPI 示例代码位于:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7/src/app.py

这个应用依赖两条被抽象成 SuperComponent 的 Pipeline:

  • 索引 Pipeline
    https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7/src/rag/indexing.py
  • 混合 RAG Pipeline
    https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7/src/rag/hybridrag.py

索引 Pipeline 会在应用之外单独运行(离线执行),以填充一个本地向量存储(Qdrant):

document_store = QdrantDocumentStore(
    path=qdrant_path,
    index=qdrant_index,
    embedding_dim=1536,
    recreate_index=False,
    use_sparse_embeddings=True
)

随后,混合 Pipeline 会利用与 Qdrant 兼容的稀疏检索器和稠密检索器来完成检索与答案生成。在 app.py 中,被抽象为 SuperComponent 的 hybrid Pipeline,正是我们用来创建查询端点、对 Qdrant 向量存储发起查询的核心。

下面我们来拆解它。app.py 是一个 FastAPI 应用的主入口,专门用于提供混合 RAG 系统服务。

关键功能包括:

初始化(Initialization)
在启动时,应用会连接到一个 Qdrant document store,并根据配置中的 embedding 模型与 LLM 模型设置,初始化被抽象为 SuperComponent 的 HybridRAGSuperComponent Pipeline。

端点(Endpoints)

  • /query(POST) :主要端点。它接收用户问题,将其送入 RAG Pipeline,并返回生成答案以及检索到的源文档。
  • /health(GET) :检查 Qdrant 连接状态,以及 RAG 组件是否已成功初始化。
  • /info(GET) :返回当前配置详情,例如所使用的模型和 Qdrant 相关设置。
  • /(GET) :根端点,用于显示 API 状态以及可用路由列表。

该应用使用 Pydantic 模型来校验传入的查询请求,并规范返回响应结构。

通过这个端点,我们就能直接从一个预先填充好的持久化 document store(例如 Qdrant)中进行检索。接下来我们看看这个应用的 Dockerfile 示例。

将你的 Haystack Pipeline Docker 化

本节中,我们将学习如何进一步增强并打包我们的 Haystack NLP Pipeline,也就是把刚刚构建的 API 封装进一个 Dockerfile 中。

Dockerfile 是一个文本文件,里面包含一系列指令,用于自动化构建 Docker 镜像。Docker 镜像是一种轻量级、独立且可执行的打包格式,其中包含运行应用所需的一切内容,包括应用代码、运行时、依赖以及配置文件。Dockerfile 就像是一张蓝图,规定了这个镜像应当如何一步步构建出来。

Dockerfile 不仅能通过定义环境、依赖和配置来自动创建镜像,还能支持诸如缓存、多阶段构建以及自定义运行时等高级能力。

Dockerfile 的关键功能包括:

定义环境(Defines the environment)
指定所基于的基础镜像,其中包括操作系统和运行时环境。

打包应用(Packages the application)
将运行应用所需的代码、依赖和配置一起纳入镜像中。

自动化构建与安装(Automates build and setup)
执行安装依赖、复制文件以及配置运行时环境所需的命令。

配置容器(Configures the container)
设置容器启动时需要暴露的端口、环境变量以及默认执行命令。

最终生成的容器,就是一个自包含环境,可以在不同系统上稳定运行我们的 FastAPI 应用。

把 NLP 应用封装到 Dockerfile 中,有很多好处,而且这些好处与现代部署的核心需求——可扩展性、可访问性、资源管理与可靠性——高度一致。Docker 允许应用进行横向复制与扩展,从而无缝应对不断增长的工作负载或用户流量。对于 NLP Pipeline 来说,这一点非常重要,因为它能够支撑实时文本分析或多用户并发查询等高需求场景。

同时,Docker 与 Kubernetes 这类编排工具的结合,又能支持动态扩缩容,从而优化资源利用率和成本。

Docker 容器还通过在不同环境之间保持应用行为一致,增强了可访问性与可维护性,无论目标环境是本地、云端还是本地机房(on-premises)。这使部署变得更简单,也让 NLP API 能够可靠地被分布式系统访问。模块化的容器化架构还能隔离 Pipeline 的不同组件,使得我们可以在不影响整个系统的前提下,更新或替换个别服务。

此外,Docker 的资源隔离与安全特性还能保护敏感数据、支持合规要求,并确保健壮的错误恢复能力,因此它是构建可扩展、安全且适应性强的 NLP Pipeline 时不可或缺的工具。

下面我们来创建 Dockerfile。这个文件就是我们应用“运输集装箱”的蓝图。一个结构良好的 Dockerfile,对安全性、性能,以及快速的构建—测试—部署循环都至关重要。

我们会使用 多阶段构建(multi-stage build) ,这是一种最佳实践。构建阶段(build stage)负责安装全部依赖,而最终运行阶段(runtime stage)只复制必要的应用代码和依赖,从而得到一个更精简、更安全、体积更小的生产镜像。

用于将示例 FastAPI 应用打包成 Docker 化应用的代码,可在以下位置找到:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7/Dockerfile

我们可以将其中的关键步骤概括如下:

基础(Foundation)

从轻量级的 Python 3.11 slim 基础镜像开始,安装必要工具(如 curl),并复制 uv 依赖管理器。

代码与依赖(Code and dependencies)

把工作目录设为 /app。然后,为了提高缓存利用效率,先复制依赖文件(pyproject.tomluv.lock),再复制应用源码(src/)、脚本以及用于索引的数据。

安装(Install)

使用 uv sync 安装依赖,并赋予启动脚本可执行权限。

安全(Security)

通过创建并切换到非 root 的 app 用户来增强安全性。

运行时配置(Runtime setup)

这一部分定义容器运行方式:

  • 暴露 8000 端口
  • 设置 HEALTHCHECK 以验证服务就绪状态
  • 创建 /app/start.sh 脚本,该脚本会先运行 RAG 索引 Pipeline(src.rag.indexing),再启动 Uvicorn API 服务器

执行(Execution)

将容器默认命令设置为运行自定义的 /app/start.sh 脚本。

本质上,这就是把我们前面每一步手工执行的过程都“生产化”了:设置 Python 版本、安装并管理依赖与版本、然后启动应用。也正是 Docker 容器的可移植性,使得我们能够把这些 REST 端点部署到云端,并在后续通过 Kubernetes 对其进行扩展。

下面我们转向另一个关键问题:如何保护我们的 /query 端点。

保护并校验我们的端点

为了防止端点被任意开放使用,我们可以利用内置能力,确保只有获得授权的客户端才能访问我们的 RAG 端点。

在我们的 FastAPI 示例应用中,/query 端点通过 API Key 认证 来进行保护,而这一认证是由 FastAPI 框架本身强制执行的。这保证了:只有在请求头中提供有效密钥的客户端,才能访问 RAG Pipeline。

认证逻辑通过 FastAPI 的依赖注入机制,在应用代码(app.pyconfig.py)中实现。在应用里,它是这样定义的:

api_key_header = APIKeyHeader(name="X-API-Key", auto_error=True)

async def get_api_key(api_key: str = Security(api_key_header)):
    """Validate API key from request header."""
    if api_key != settings.rag_api_key:
        raise HTTPException(
            status_code=401,
            detail="Invalid API Key"
        )
    return api_key

然后,这个 key 会被直接注入到 /query 端点中:

@app.post("/query", response_model=QueryResponse)
async def query_documents(
    request: QueryRequest,
    api_key: str = Depends(get_api_key)
):

config.py 中(文件位于:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7/src/config.py),我们可以看到一个基于 Pydantic 的配置校验类 Settings

config.py 通过定义一个继承自 BaseSettingsSettings 类来工作,其中每一个配置项都通过 Python 类型提示(如 strint)来定义。Pydantic 会在运行时自动强制执行这些类型。它也会自动执行类型转换,例如把环境变量中的字符串 "5" 转换成所要求的整数 5;如果值无效,则抛出错误。

Field 函数则负责约束和取值来源。例如,它可以把某个字段映射到对应的环境变量(如 env="OPENAI_API_KEY")。其中的省略号 ... 表示该字段是必需的;如果环境变量缺失,应用就会在启动时停止。

这一技术被专门用来强制要求提供 RAG_API_KEY 密钥,从而保护 /query 端点,不让其对所有人开放。

通过在启动时完成配置校验(settings = Settings()),应用就能确保贯穿整个系统的配置对象 settings 是干净、可靠且可直接使用的。

当我们启动与 API 对应的 Docker 容器时,也可以确保这些 key 不会泄露,做法如下。

首先,构建 Docker 镜像:

docker build -t hybrid-rag-api .

然后运行 Docker 容器,并传入环境变量:

docker run -d \
  --name hybrid-rag \
  -p 8000:8000 \
  -e OPENAI_API_KEY=your_actual_openai_key \
  -e RAG_API_KEY=your_secret_api_key \
  hybrid-rag-api

一旦 Docker 容器运行起来,我们就可以向 /query 端点发送 POST 请求,并通过 curl 命令传入 RAG 的 secret key:

curl -X POST http://localhost:8000/query \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-secret-key" \
  -d '{"query": "What is retrieval-augmented generation?"}'

或者通过 Python 来调用:

import requests

response = requests.post(
    "http://localhost:8000/query",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": "your-secret-key"
    },
    json={"query": "What is retrieval-augmented generation?"}
)

使用 CI/CD 自动化部署

CI/CD 是现代应用开发中的核心能力,它使得高质量软件能够被快速交付。对于 NLP Pipeline 而言,CI/CD 可以自动化部署流程,减少人为错误,保证一致性,并加快新功能与更新的集成速度。

CI(Continuous Integration,持续集成)

CI 会自动把代码变更集成进共享仓库,并确保所有变更都经过测试和验证。这能够最大程度地减少集成冲突,并保证代码库在更新后依然可用。

CD(Continuous Deployment / Delivery,持续部署 / 持续交付)

CD 是对 CI 的延伸,它进一步自动化地把已经通过测试的变更部署到生产环境中,从而保证新功能和修复能被快速、可靠地交付出去。

对于 NLP Pipeline 而言,CI/CD 可以保证对模型、配置或 API 端点的更新,能够无缝纳入生产工作流,尽量减少停机时间,同时保持系统可靠性。

CI/CD 的主要收益包括:

自动化测试(Automated testing)
CI/CD Pipeline 会自动执行针对 NLP Pipeline 的预处理、模型推理和后处理阶段的测试,确保更新不会破坏关键组件。

一致的构建(Consistent builds)
Docker 化的 NLP 应用能够在不同环境中被一致地构建和部署,从而减少开发环境与生产环境之间的不一致。

更快的迭代(Faster iterations)
新模型、新 embedding 或 Pipeline 增强可以更快地被测试与部署,从而加速创新。

回滚能力(Rollback capability)
CI/CD 工作流通常都会包含在失败时回退到先前稳定版本的机制,从而增强可靠性。

CI/CD 通过自动化测试、保证构建一致性、加速功能更新以及提供可靠的回滚机制,为 NLP 部署带来了重要价值。图 7.1 展示了一条包含 CI/CD 核心步骤的示例工作流。

图 7.1 —— 一个示例 CI/CD 工作流:完成 Docker 镜像的构建、清理、打标签和推送,以供部署

我们用于 Docker 容器的 CI/CD 工作流位于:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/.github/workflows/docker-test.yml

这个示例工作流以 YAML 文件形式定义,存放在 .github/workflows/ 目录中。它会在每次代码被推送到仓库时自动触发,构建 Docker 镜像并运行测试,从而确保任何会导致构建失败或测试失败的代码都无法被合并。

这条 Pipeline 包含镜像构建、测试执行以及 Docker 容器构建等步骤。其具体执行逻辑如下:

触发(Trigger)

它会在以下情况下自动运行:当有代码 push 到 main 分支,或有 pull request 指向 main 分支时,但前提是修改内容涉及 ch7/ 目录或工作流文件本身。

环境准备(Setup)

它会检出仓库代码,并主动清理 runner 上的磁盘空间,以防止构建因磁盘不足而失败。

配置(Configuration)

它会在 ch7/ 目录中创建一个本地 .env 文件,把必要配置(Qdrant 设置)和敏感 API key(OPENAI_API_KEYRAG_API_KEY)从 GitHub Secrets 注入进去。

构建(Build)

它会进入 ch7/ 目录,并使用应用的 Dockerfile 构建 Docker 镜像 hybrid-rag-api

测试与运行(Test and run)

它会以 detached mode 启动构建好的镜像,运行成一个名为 test-ch7-api 的容器,并暴露 8000 端口。所需 API key 会作为环境变量传入运行中的容器。

健康检查(Health check)

它会执行一个轮询循环,最多等待 120 秒,并利用 curl 反复访问 localhost:8000 上的 /health 端点。这一步用于验证容器是否成功启动,以及索引 Pipeline 是否已经在容器内部执行完成。

清理(Cleanup)

最后,它还会执行一步收尾操作:停止并删除测试容器,并对 Docker 系统进行 prune,以保证无论测试通过还是失败,下一次任务开始时都能获得一个干净环境。

如果想把这一 GitHub Action 扩展到云部署,那么在 docker-test-ch7 任务成功之后,只需要再增加一个最终的 CD 任务即可。

这个部署任务通常包括以下步骤:

Docker 登录(Docker login)
登录云端容器镜像仓库,例如 Docker Hub、Amazon ECR 或 Google Container Registry。这里需要通过 secrets 提供仓库凭证。

打标签(Tagging)
为成功构建的镜像打上生产可用标签,例如:
registry-url/repo-name/hybrid-rag-api:main-${{ github.sha }}

推送(Pushing)
将带有新标签的镜像推送到远程镜像仓库。

触发部署(Deployment trigger)
连接你的云端编排平台(例如 Kubernetes、Amazon ECS/EKS 或 Google Cloud Run),并更新部署定义,使其拉取并运行刚刚推送的新镜像。

这样一来,就形成了一条完整的 CI/CD Pipeline,把整个过程从代码变更自动推进到线上应用。

尽管手工构建一个自定义 FastAPI 应用能够给予你完全控制力,但它同时也需要编写相当多的样板代码。对于很多使用场景来说,你或许会更倾向于一种更加流线化、也更贴近 Haystack 原生体验的方法。这就引出了我们的第二种部署策略:Pipeline 序列化 + Hayhooks

与其自己编写一个定制化服务器,你可以直接把整个 Haystack Pipeline 序列化成一种可移植的 YAML 格式。随后,由一个专门的服务器应用 —— Hayhooks —— 来加载这些 YAML 定义,并自动将它们暴露为生产可用的 REST 端点。

这种方法通过牺牲一部分灵活性,来换取极大幅度的开发效率提升,也是一条非常强大的低代码部署路径。接下来,我们就来看看,如何利用这种方式。

方法 2:Pipeline 序列化与 Hayhooks 集成

Hayhooks 是 Haystack 提供的一套开箱即用(batteries-included)的部署方案。Hayhooks 并不是 FastAPI 的替代品;它本质上是一个构建在 FastAPI 之上的、经过专门优化的应用。它存在的唯一目的,就是充当从 Haystack Pipeline生产可用 API 之间的“桥梁”。它原生理解 YAML 序列化格式,并且能够零代码自动生成所有 FastAPI 的样板代码——包括应用本身、端点、Pydantic 模型,甚至 OpenAPI 文档。

Hayhooks 通过以下方式简化部署:

流线化设置(Streamlined setup)
运行 Hayhooks 之后,你就可以立刻把 Haystack Pipeline 暴露为 REST API,从而轻松与客户端应用交互。

可定制的 Pipeline(Customizable pipelines)
Hayhooks 支持以 YAML 定义的 Pipeline,因此序列化后的 Pipeline 可以被直接部署,而无需额外修改代码。

可扩展性(Scalability)
当与 Docker 或 Kubernetes 这样的容器化工具结合使用时,Hayhooks 支持可扩展部署,从而保证高可用性与负载均衡。

良好的集成能力(Integration-ready)
它可以通过预构建的连接器,与外部数据库、document store 和推理服务完成集成。

借助 Hayhooks,你可以用一种模块化且灵活的方式来部署 Haystack Pipeline,使其处理索引、查询以及其他 NLP 任务。要让一个 Haystack Pipeline 为 Hayhooks 部署做好准备,我们需要遵循两个步骤:Pipeline 序列化Pipeline 包装(wrapping) 。下面先来看序列化。

Pipeline 序列化

Pipeline 序列化,是把一个 NLP Pipeline 转换成一种可保存、可传输、并可在后续重新加载的格式的过程。在 Haystack 中,框架开箱即支持通过 YAML 进行序列化,这使得开发者能够以一种人类可读、可编辑的格式来保存和共享 Pipeline。序列化后的 Pipeline 在协作环境中尤其有用,也非常适合把工作流从开发阶段迁移到生产阶段。

例如,一条 Pipeline 可以先在 Python 代码中被创建并测试,然后导出为 YAML,再根据部署需要稍作调整,最后在生产环境中重新加载并使用,而不需要重新编程。序列化一个 Pipeline 的过程很简单:定义组件、初始化 Pipeline、连接各组件,然后通过 dump() 方法把 Pipeline 写入一个 YAML 文件。

我们对 Pipeline 的序列化方式如下:

  • 序列化索引 Pipeline 的脚本
    https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/pipelines/indexing_pipeline_serialization.py
  • 序列化 RAG Pipeline 的脚本
    https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/pipelines/rag_pipeline_serialization.py

把一个复杂的 Python Pipeline 对象转换成一个静态的、可读的 YAML 文件,本质上就是一种解耦(decoupling) 。这种“以 YAML 作为契约(YAML-as-contract)”的设计,形成了清晰的关注点分离:数据科学家可以在 Python Notebook 中迭代、调优 Pipeline,而最终产出物只是一个 pipeline.yml 文件;而 ML 工程师则可以接过这个 YAML 文件,利用 Hayhooks 将其部署到生产环境,而无需理解该 Pipeline 背后 Python 代码的内部细节。这样,开发生命周期与部署生命周期就被有效解耦了。

下面我们来看看序列化后的 Pipeline 是什么样子。下面这些示例片段定义了组件(例如 embedder)以及把它们连接起来的关系。这个 YAML 结构,实际上就表示了我们整条 Pipeline 的图结构:

  • 序列化后的索引 Pipeline
    https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/pipelines/indexing/indexing.yml
  • 序列化后的 RAG Pipeline
    https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/pipelines/hybrid_rag/rag.yml

序列化之所以灵活,是因为 Pipeline 可以被保存为文件、通过网络传输,或者存储到数据库中。一旦 Pipeline 被序列化并存储为 YAML 文件,就可以通过 Hayhooks 把它们部署成 REST 端点。序列化后的 Pipeline 还可以被加载进 PipelineWrapper。下面我们就来看这个部分。

为 Hayhooks 包装 Pipeline

ch7-hayhooks 示例中,我们要部署两条 Pipeline:一条索引 Pipeline,以及我们在第 6 章中构建的 hybrid RAG 查询 Pipeline。Hayhooks 很容易就能处理这种场景。

虽然 Hayhooks 可以仅靠 YAML 运行,但最佳实践仍然是使用一个 pipeline_wrapper.py 文件。这个轻量级 Python 文件会告诉 Hayhooks:应当如何加载 YAML,以及 API 的输入和输出应该是什么样子。它为我们预留了一个插入自定义逻辑的钩子。

序列化后 Pipeline 对应的 wrapper 文件位于:

  • 索引 Pipeline 的 wrapper
    https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/pipelines/indexing/pipeline_wrapper.py
  • RAG Pipeline 的 wrapper
    https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/pipelines/hybrid_rag/pipeline_wrapper.py

下面我们结合 ch7-hayhooksREADME.md 和 Hayhooks 文档,来看 hybrid RAG Pipeline 的 wrapper 是如何工作的。

BasePipelineWrapper

我们的 wrapper 会继承自这个类,而它正是 Hayhooks 所期望的契约。

setup()

和前面 FastAPI 示例中的做法一样,这个方法负责在启动时加载 Pipeline。但请注意,这里不再是通过 Python 手动构建 Pipeline,而只是简单调用 Pipeline.loads(),加载 pipeline.yml 文件。

run_api()

这部分就是我们的 API 逻辑。Hayhooks 会自动围绕这个函数搭建所有 FastAPI 机制。它会自动生成一个 /hybrid_rag/run 端点,把 JSON 请求反序列化为我们的 HybridRagInput 对象(其中包含 query),调用已经部署好的 RAG Pipeline,然后再把返回的 HybridRagOutput 对象序列化为 JSON 返回给客户端。

现在注意一下,这里省略了什么:没有 app = FastAPI(),没有 import uvicorn,也没有 @app.post 装饰器。方法 1 中那些样板代码全部消失了。Hayhooks 已经把这些统统抽象掉了,让我们只需要专注在 Pipeline 的输入 / 输出本身。

要运行这套系统,我们不再需要写一个 main.py。我们只需要安装依赖,然后在终端中运行 uv run hayhooks run 命令即可:

# Set environment variables
export OPENAI_API_KEY="sk-..."
export HAYHOOKS_PIPELINES_DIR="./pipelines"

# Run Hayhooks!
uv run hayhooks run

Hayhooks 会自动找到 hybrid_rag/pipeline_wrapper.pyindexing/pipeline_wrapper.py,加载它们,并立刻把它们作为两个独立端点提供服务。随后,我们就可以使用 FastAPI 的 Swagger API 页面(http://localhost:1416/docs)来与这些 Pipeline 交互。

例如,我们可以用下面这个命令调用 RAG Pipeline 的端点:

curl -X POST "http://localhost:1416/hybrid_rag/run" \
  -H "Content-Type: application/json" \
  -d '{"query": "What are the benefits of AI?"}'

这里的关键区别在于:索引 Pipeline 与检索 Pipeline 现在都可以在线运行(online fashion) 。也就是说,你可以像调用普通 REST 端点一样,动态执行索引 Pipeline;而在前一种方法中,索引 Pipeline 必须先离线执行好,之后 /query 端点才能使用。

和 FastAPI 应用一样,我们同样可以通过 Dockerfile 对 Hayhooks 应用进行打包。一个用于 Hayhooks 应用的 Dockerfile 示例可在以下位置找到:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/Dockerfile

而对应的 GitHub Actions 工作流示例可在以下位置找到:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/.github/workflows/docker-test-hayhooks.yml

重要说明:Qdrant 向量存储

一旦你迁移到 Hayhooks 版本,本地向量存储就需要升级为云端版本,这样索引 Pipeline 和 RAG Pipeline 才能共享同一个后端。当前脚本中仍然体现的是本地存储的使用方式;如果要将其修改为兼容云端的存储方案,可参考:
https://haystack.deepset.ai/integrations/qdrant-document-store

保护并校验我们的端点

一个示例部署方式,是使用 Nginx 反向代理 来通过 HTTP Basic Authentication 保护你的 Hayhooks Pipeline。Nginx 是一款流行的开源软件,可用于 Web 服务、反向代理、缓存、负载均衡与流媒体分发。

在这种架构中,Nginx 监听 8080 端口,并位于 Hayhooks 服务前方;而 Hayhooks 服务本身则运行在内部的 1416 端口上。一个示例 Nginx 配置文件位于:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/nginx.conf

这套安全方案确保了:所有 Hayhooks Pipeline 端点都受到保护,只有提供有效用户名与密码组合的请求才能访问;与此同时,健康检查相关端点仍保持公开可访问,以供系统监控使用。

为了进一步增强安全性与性能,这个实现还包含了一些生产级特性:

  • 将服务限流为 每个 IP 每秒 10 个请求
  • 支持最大 50 MB 的大文档上传
  • 配置了更长的超时时间,以便处理运行时间较长的 Pipeline 操作

从架构角度看,这种实现也让 Hayhooks 服务获得了更高安全性,因为它本身不会被直接暴露出来。整个模式,其实是将第 7 章 FastAPI 方案中的安全理念(通过 API key 保护)迁移到 Hayhooks 场景中,只不过这里采用的是行业标准的 NGINX 认证方式,从而更自然地融入 Hayhooks 的预构建服务器架构。而且,这套系统也很容易继续扩展到 HTTPS/SSL,从而满足更严格的生产部署要求。

为了执行这条工作流,我们会先使用一个辅助脚本来生成密码:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/scripts/generate_password.sh

执行方式如下:

./scripts/generate_password.sh

随后,终端会提示我们输入用户名与密码。这些凭据之后会被写入 .env 文件。

为了让 Nginx 及其前述配置更易于直接运行,我们还提供了一个额外的 docker-compose.yml 文件,位于:
https://github.com/PacktPublishing/Building-Natural-Language-and-LLM-Pipelines/blob/main/ch7-hayhooks/docker-compose.yml

这个 Docker Compose 文件包含两个服务:

  • Hayhooks service(应用后端)
  • Nginx service(安全与流量路由代理)

Hayhooks service

这是核心应用后端,Haystack 的索引 Pipeline 和 RAG Pipeline 都在这里运行。它会在本地构建所需镜像,并配置必要环境变量,其中包括 OPENAI_API_KEY,同时还会显式将处理设备设置为 cpu,以便控制资源使用。

更关键的是,它通过 volume mount 来确保 Qdrant 向量数据库文件能够持久保存在本地,即使容器重启也不会丢失。为了最大程度提升安全性,该服务会把 1416 端口限制在内部网络中;这保证了后端只能被 Nginx 服务访问,而不会直接暴露在公网。同时,它还包含了健康检查,用于监控自身运行状态。

Nginx service

Nginx 服务充当应用的安全网关与流量控制器。它使用官方 nginx:alpine 镜像,并且是整个系统中唯一对外暴露的部分:它会把宿主机的 8080 端口映射到自身内部的 80 端口。

它的主要职责是处理安全与流量控制。它会挂载两个本地文件:

  • nginx.conf(用于代理、限流与延长超时规则)
  • .htpasswd(用于 HTTP Basic Authentication 凭据)

它依赖 Hayhooks 服务成功启动,并通过共享的 Hayhooks 网络安全地与后端通信。这样的设置可以把 Hayhooks 应用隔离在后方,并在网络边缘统一实施安全控制。

随后,我们就可以非常方便地启动这些带密码保护的 Hayhooks 服务:

docker-compose up -d
./scripts/test_secured_api.sh
curl -u $username:$password http://localhost:8080/status

此时,我们就已经能够安全地访问端点了。然后可以访问 http://localhost:8080/docs,输入用户名和密码,并像之前一样测试这些端点。

Hayhooks 还提供了一项额外能力:对 Model Context Protocol(MCP) 的支持。这可以把你的 Haystack Pipeline 转换成标准化工具,使外部 Agent 可以发现并调用它们。

将 Haystack Pipeline 暴露为 MCP Server

MCP 提供了一种标准化方式,使外部 Agent 能够把你已部署的 Haystack Pipeline 识别为一组专门化工具。Hayhooks 原生支持这一能力,并可直接充当 MCP Server:

  • 功能(Function) :Hayhooks 会自动把你部署的所有 Pipeline 列为 MCP 工具
  • 执行(Execution) :MCP Server 通过 hayhooks mcp run 命令启动
  • 协议(Protocol) :它使用 Server-Sent Events(SSE) 作为通信传输方式

要把你的 Haystack Pipeline 暴露为一个可发现的 MCP 工具,其 wrapper 必须按照正确结构进行定义。Hayhooks 在这方面做了很大简化:它会自动把 PipelineWrapper 的名称用作 MCP 工具名,并把 run_api 方法的 docstring 用作该工具的描述。

如果你希望部署某条 Pipeline,但又不希望它被列为 MCP 工具(例如索引 Pipeline),那么可以在 PipelineWrapper 类中设置 skip_mcp = True。更多信息可参考:
https://docs.haystack.deepset.ai/docs/hayhooks

是否集成 MCP 支持,取决于你使用的是哪一种部署策略:

集成到自定义 FastAPI 中(偏控制)

自定义 FastAPI 路径提供了更高灵活性,因为它允许你把 Hayhooks 嵌入进去。你可以在主 Python 文件(app.py)中,通过调用 hayhooks.create_app() 以编程方式集成 MCP Server 功能。

这种方式让你在获得 MCP Server 能力的同时,仍然可以保留对现有 FastAPI 代码中自定义路由、认证中间件以及错误处理逻辑的完全控制。它最适合那些大型、复杂的应用,在这类系统中,Haystack Pipeline 只是整个系统中的一个组成部分。

集成到 Hayhooks 中(偏效率)

由于这种方式本身已经建立在 PipelineWrapper 所要求的结构之上,因此 MCP 集成就非常直接。你只需要把 Dockerfile 或本地启动脚本中的普通 hayhooks run 命令,替换为启用 MCP 的版本:hayhooks mcp run

由于 Hayhooks Server 本身就是基于 FastAPI 构建的,因此你原本已有的 Nginx 反向代理和 HTTP Basic Authentication 层都会无缝继续工作,从而继续用既有凭据保护新的 MCP 工具端点。

这条部署路径——无论是自定义 FastAPI,还是开箱即用的 Hayhooks Server——都成功满足了现代 RAG Pipeline 部署中的几个关键需求:可扩展性、可靠性与安全性

借助 Docker,这两种方法都获得了可移植性,并为后续通过 Kubernetes 进行扩展做好了准备。FastAPI 方法提供了对认证中间件和数据校验的细粒度控制,这对定制化系统尤为关键。相对地,Hayhooks 方法则通过抽象掉大量样板代码、利用 YAML 序列化构建出清晰的 MLOps 工件,实现了最高部署效率,并直接通向一条更快的路径:把系统部署成一个由 Nginx 保护的、安全的、API 驱动服务。

归根结底,这两条路径最终都能产出一个健壮、容器化且安全的解决方案,从而达成我们的目标:把一条复杂的 Haystack Pipeline 转变成一个生产级组件。

从序列化开发过渡到生产环境

把一条 NLP Pipeline 从开发环境迁移到生产环境,需要确保它是健壮的、可扩展的、且易于维护的。Haystack 借助 Pipeline 序列化与 Hayhooks 这样的工具,使这一过渡过程变得顺畅自然。

本章覆盖的 MLOps 工作流可以概括如下:

从开发到工件(Development to artifact)

Pipeline 首先在 Python 中完成原型设计,然后被序列化为 YAML。这会生成一个可复现的工件,并且可以轻松进行校验。更关键的是,YAML 文件使维护变得更简单:开发者可以直接微调组件或修改模型参数,而无需重新编程。

一致性部署(Consistent deployment)

序列化后的 YAML Pipeline 会借助 Hayhooks 和 Docker 化环境进行部署。通过在 Docker 中使用 Haystack 镜像,可以保证开发、测试与生产阶段的行为保持一致。

扩展与监控(Scaling and monitoring)

在生产环境中,可以通过 Kubernetes 或 Docker Compose 这样的编排工具实现扩展。若需要更高级的弹性能力,还可以利用无服务器架构或 Ray 之类的分布式处理工具。为了保持可靠性,还可以引入 Prometheus 之类的工具,对部署进行监控与故障排查。

将 YAML 序列化的易用性,与 Hayhooks 和 Docker 结合起来,代表了一种成熟的、以 MLOps 为导向的部署模式。它在以下几个维度之间达到了很好的平衡:

  • 灵活性:YAML 本身可编辑
  • 可维护性:Pipeline 作为版本可控的工件存在
  • 可扩展性:借助 Docker / Kubernetes 实现横向扩展

通过这些工具,整套迁移过程就变得结构清晰而且高效。

小结

本章是我们从一个本地 NLP 想法,走向一个可扩展、生产级应用的最后一步。我们掌握了部署稳健 Haystack Pipeline 所需的关键概念与实际步骤,确保它们具备可访问性、可管理性,并准备好应对现实世界需求。

我们首先探讨了两种主流部署策略,并围绕“定制化”与“速度”之间的权衡进行了比较。

首先,我们走的是自定义控制路径:使用 FastAPI 从零开始构建一个自定义 REST API。我们理解了为什么 FastAPI 会成为机器学习领域的行业标准:一方面,它借助 Starlette 实现异步高性能;另一方面,它通过 Pydantic 实现强大的数据校验。我们手工定义了应用的生命周期,编写了请求模型的 Pydantic 类,并通过依赖注入将 Pipeline 提供为服务。

随后,我们又使用 Docker 对这个自定义应用进行了打包,编写了一个多阶段 Dockerfile,以获得一个精简、安全且可移植的镜像。最后,我们通过 GitHub Actions 构建了一条 CI/CD 工作流,使得每次 push 都能自动触发容器构建与测试,从而完成了这一模式的闭环。

接着,我们又探索了高效率路径:使用 Haystack 原生工具。我们看到,Pipeline 序列化可以把一个复杂的 Python Pipeline 转换成一个简单、可移植的 YAML 文件——这就是一个可部署工件,同时也构成了清晰的 MLOps 交接边界。

然后,我们进一步考察了 Hayhooks:这是一个为部署而生的框架,它能够读取序列化后的 YAML,并立即生成一个生产可用的 FastAPI 服务,把我们在方法 1 中手工写下的大量样板代码统统抽象掉。

至此,你已经具备了构建自然语言 Pipeline 所需的完整端到端技能栈:从前几章中的创意构思与组件搭建,到第 6 章中的稳健评估与可观测性,再到本章中的可扩展生产部署。你已经掌握了从一个简单脚本到一个在线、容器化 API 的完整全栈路径。

在下一章中,我们将把这整套能力真正投入实战。理论与实践到这里已经全部就绪,你已经完全具备了处理三个令人兴奋的动手项目的能力。我们将把从 Pipeline 构建,到评估,再到部署的一切知识统统应用起来,去构建面向真实世界的解决方案:情感分析、命名实体识别(NER)以及文本分类。下面,开始真正动手吧。