跳至主要内容

如何使用 Docker 部署 LLM 应用:分步指南

本教程将带您使用 Docker 在 Hugging Face Cloud 上构建并部署一个文档问答应用。
· 10分钟 阅读

使用 AI 探索

Docker 可用于创建一致、可移植且隔离的环境,是 LLMOps(大型语言模型运维)的关键工具。通过将各种 LLM 应用及其依赖封装进容器,Docker 简化了部署、确保跨系统兼容,并加速测试流程。

本教程将带您构建一个“文档问答”聊天机器人应用,并使用 Docker 将其部署到云端。我们将使用 Gradio 构建用户界面,使用 LlamaIndex 进行编排,用 LlamaParse 解析文档,Mixedbread AI 生成向量嵌入,通过 Groq 访问大型语言模型,并用 Docker 打包应用及其依赖,最终通过 Hugging Face Spaces 将应用部署到云端。

本教程力求简明,即便您对 AI 应用原理了解不多,也能免费动手搭建。

项目说明

开发与部署 AI 应用主要有两种思路:

  • 完全开源:强调隐私和数据保护。
  • 完全闭源:集成多个 API 和云服务。

两种方式各有优劣。本文选择第二种,集成多种 AI 服务。这使我们可以在几秒内构建并部署一个响应快速的 AI 应用。我们的核心目标是尽可能减小 Docker 镜像体积,而通过整合多项 AI 服务可以有效实现这一点。

如需使用开源工具和框架构建 LLM 应用并强化隐私保护,请查看 使用 Docker、n8n、Qdrant 和 Ollama 的本地 AI 教程。

我们将构建一个通用问答聊天机器人,允许用户上传文档并与之实时对话。这与 Google 的 NotebookLM 十分类似。

Doc Q&A LLM Application diagram

项目示意图。作者制图

本项目将使用以下工具:

  1. Gradio Blocks:用于创建用户界面,便于用户上传任意文本文档并轻松与文档对话。
  2. LlamaCloud:用于解析文件并以 Markdown 形式提取文本数据。
  3. MixedBread AI:将已加载文档和聊天消息转换为向量嵌入,以便进行上下文检索。
  4. Groq Cloud:用于获取高速 LLM 响应。本项目将使用 llama-3.1-70b 模型。
  5. LlamaIndex:用于创建编排所有 AI 服务的 RAG(检索增强生成)流水线。该流水线将结合上传文件和用户消息生成具备语境的答案。
  6. Docker:用于封装应用、依赖、环境与配置。
  7. Hugging Face Cloud:我们将把所有文件推送到 Spaces 仓库,Hugging Face 会基于 Dockerfile 自动构建镜像并部署到服务器。

如果您是 LLM 新手,建议先学习 Master Large Language Models (LLMs) Concepts 课程,了解基础术语、方法论、伦理考量与最新研究。

在构建 LLM 应用之前,请先从官网下载并安装 Docker。 

  • 在本地系统使用默认选项安装 Docker。
  • 接着,使用您喜欢的 IDE 创建一个项目目录,并添加 .env 文件。我们将用该文件存储 LlamaCloud、MixedBread AI 和 Groq Cloud 的 API 密钥。
  • 随后注册 LlamaCloud 并生成 API 密钥。我们将用 LlamaCloud 解析多种文本格式,包括 Excel、txt、Word 和 PDF。

LlamaCloud dashbord

在 LlamaCloud 生成新密钥。图片来源:LlamaCloud

  • 接着注册 MixedBread AI 并生成 API 密钥。我们将使用该密钥免费访问其顶级嵌入模型。

MixedBread AI  dashboard

在 MixedBread 创建 API 密钥。图片来源:MixedBread

  • 前往 GroqCloud 注册并免费生成 API 密钥。我们将通过 GroqCloud 访问高速 LLM。

Groq Cloud Dashboard

在 GroqCloud 创建 API 密钥。图片来源:GroqCloud

通过阅读Groq LPU 推理引擎一文,全面了解 GroqCloud。您将结合代码示例学习 Groq API 及其特性,并了解如何借助 Groq API 与 LlamaIndex 构建具备语境感知的 AI 应用。

您的 .env 文件应如下所示:

LLAMA_CLOUD_API_KEY=llx-XXXXXX
GROQ_API_KEY=gsk_XXXXXXX
MXBAI_API_KEY=emb_XXXXXX

请记得将 .env 文件加入 .gitignore,以避免不慎将 API 密钥暴露给公众。

现在我们将创建名为 app.py 的 Python 脚本,添加用户界面组件,并通过 LlamaParser 集成所有 AI 服务,使用 LlamaIndex 构建 RAG(检索增强生成)流水线。

app.py 脚本将执行以下操作:

  1. 加载所有必要的 Python 包。
  2. 从 .env 文件安全读取所有 API 密钥。
  3. 使用 LlamaCloud API 密钥初始化 LlamaParser。
  4. 定义可处理多种常见文件扩展名的文件解析器。
  5. 使用 MixedBread AI 的 API 密钥初始化嵌入模型,具体模型为 mixedbread-ai/mxbai-embed-large-v1。
  6. 使用 Groq Cloud 的 API 密钥初始化大语言模型,具体模型为 llama-3.1-70b-versatile。

接下来会实现以下 Python 函数:

  • load_files():加载文件,使用 LlamaParser 进行解析,将其转换为嵌入,并存入向量库。包含异常处理,用于应对上传的非文件或不受支持的文件格式。
  • respond():接收用户输入,从向量库中检索内容,并使用 Groq 模型生成响应。响应以流式输出形式返回,并在未上传任何文件的情况下进行异常处理。

最后,将构建 UI 组件,包括文件上传器、按钮、聊天框以及整体对话界面。

想进一步了解 LlamaIndex 框架,请参阅更为简明的 LlamaIndex 教程。

以下是 app.py 脚本:

import os

import gradio as gr
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.embeddings.mixedbreadai import MixedbreadAIEmbedding
from llama_index.llms.groq import Groq
from llama_parse import LlamaParse

# API keys
llama_cloud_key = os.environ.get("LLAMA_CLOUD_API_KEY")
groq_key = os.environ.get("GROQ_API_KEY")
mxbai_key = os.environ.get("MXBAI_API_KEY")
if not (llama_cloud_key and groq_key and mxbai_key):
    raise ValueError(
        "API Keys not found! Ensure they are passed to the Docker container."
    )

# models name
llm_model_name = "llama-3.1-70b-versatile"
embed_model_name = "mixedbread-ai/mxbai-embed-large-v1"

# Initialize the parser
parser = LlamaParse(api_key=llama_cloud_key, result_type="markdown")

# Define file extractor with various common extensions
file_extractor = {
    ".pdf": parser,
    ".docx": parser,
    ".doc": parser,
    ".txt": parser,
    ".csv": parser,
    ".xlsx": parser,
    ".pptx": parser,
    ".html": parser,
    ".jpg": parser,
    ".jpeg": parser,
    ".png": parser,
    ".webp": parser,
    ".svg": parser,
}

# Initialize the embedding model
embed_model = MixedbreadAIEmbedding(api_key=mxbai_key, model_name=embed_model_name)

# Initialize the LLM
llm = Groq(model="llama-3.1-70b-versatile", api_key=groq_key)


# File processing function
def load_files(file_path: str):
    global vector_index
    if not file_path:
        return "No file path provided. Please upload a file."
   
    valid_extensions = ', '.join(file_extractor.keys())
    if not any(file_path.endswith(ext) for ext in file_extractor):
        return f"The parser can only parse the following file types: {valid_extensions}"

    document = SimpleDirectoryReader(input_files=[file_path], file_extractor=file_extractor).load_data()
    vector_index = VectorStoreIndex.from_documents(document, embed_model=embed_model)
    print(f"Parsing completed for: {file_path}")
    filename = os.path.basename(file_path)
    return f"Ready to provide responses based on: {filename}"


# Respond function
def respond(message, history):
    try:
        # Use the preloaded LLM
        query_engine = vector_index.as_query_engine(streaming=True, llm=llm)
        streaming_response = query_engine.query(message)
        partial_text = ""
        for new_text in streaming_response.response_gen:
            partial_text += new_text
            # Yield an empty string to cleanup the message textbox and the updated conversation history
            yield partial_text
    except (AttributeError, NameError):
        print("An error occurred while processing your request.")
        yield "Please upload the file to begin chat."


# Clear function
def clear_state():
    global vector_index
    vector_index = None
    return [None, None, None]


# UI Setup
with gr.Blocks(
    theme=gr.themes.Default(
        primary_hue="green",
        secondary_hue="blue",
        font=[gr.themes.GoogleFont("Poppins")],
    ),
    css="footer {visibility: hidden}",
) as demo:
    gr.Markdown("# DataCamp Doc Q&A 🤖📃")
    with gr.Row():
        with gr.Column(scale=1):
            file_input = gr.File(
                file_count="single", type="filepath", label="Upload Document"
            )
            with gr.Row():
                btn = gr.Button("Submit", variant="primary")
                clear = gr.Button("Clear")
            output = gr.Textbox(label="Status")
        with gr.Column(scale=3):
            chatbot = gr.ChatInterface(
                fn=respond,
                chatbot=gr.Chatbot(height=300),
                theme="soft",
                show_progress="full",
                textbox=gr.Textbox(
                    placeholder="Ask questions about the uploaded document!",
                    container=False,
                ),
            )

    # Set up Gradio interactions
    btn.click(fn=load_files, inputs=file_input, outputs=output)
    clear.click(
        fn=clear_state,  # Use the clear_state function
        outputs=[file_input, output],
    )

# Launch the demo
if __name__ == "__main__":
    demo.launch()
  • 在终端使用以下命令运行上述 Python 文件以启动 Gradio 服务器:
$ python app.py  

输出:

Running the Gradio app locally.

  • 复制并粘贴生成的 URL 到浏览器即可访问 Grodio 界面。随后您可上传文件并开始就其内容提问。

Accessing the Gradio app on the browser.

在浏览器中访问 Gradio 应用。作者供图

本教程使用 LlamaIndex 来构建与部署 LLM 应用。您也可以通过短课程 Developing LLM Applications with LangChain 使用 LangChain 构建类似应用。

3. 创建 Dockerfile

  • 在项目中创建 Dockerfile,用于打包应用脚本、Python 依赖和服务器配置,并初始化 Gradio 服务器。 

Dockerfile 将完成以下任务:

  1. 设置 Python 3.9 环境。
  2. 定义工作目录。
  3. 将 requirements.txt 复制到 /app 目录。该文件包含所需 Python 包名称。
  4. 安装 requirements.txt 中指定的所有依赖。
  5. 将 app.py 复制到 /app 目录。
  6. 暴露端口并设置 Gradio 服务器 URL。
  7. 运行应用文件以启动服务器。

Dockerfile 示例:

# Dockerfile

# Use the official Python image with the desired version
FROM python:3.9-slim

# Set the working directory inside the container
WORKDIR /app

# Copy the requirements file to the working directory
COPY requirements.txt /app

# Install the dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code to the working directory
COPY app.py /app

# Expose the port that Gradio will run on (default is 7860)
EXPOSE 7860

ENV GRADIO_SERVER_NAME="0.0.0.0"

# Command to run your application
CMD ["python", "app.py"]
  • 以下是 requirements.txt 的内容,请一并添加到项目:
gradio
llama-index-embeddings-mixedbreadai
llama-index-llms-groq
llama-index

4. 构建 Docker 镜像并运行容器

  • 在终端输入以下命令构建 docqa 镜像。它会使用 Dockerfile 创建 Docker 镜像。
$ docker build -t docqa . 

构建 Docker 镜像时可查看相关过程日志:

building the LLM docker image

构建 LLM Docker 镜像。作者供图

  • 镜像构建成功后,打开 Docker Desktop,点击 Images 标签,选择该镜像以查看日志和运行指令。 

viewing the docker image on the Docker Desktop

在 Docker Desktop 查看镜像。作者供图

现在我们将基于该镜像在本地运行 Docker 容器。需要提供端口号、用于设置环境变量的 .env 文件、容器名称以及镜像标签。

  • 在终端运行如下命令: 
$ docker run -p 7860:7860 --env-file .env --name docqa-container docqa

容器启动后,您可以在浏览器中粘贴 URL 访问 Gradio 应用,例如 http://0.0.0.1:7860/。 

Testing the docker container LLM application.

测试 Docker 容器中的 LLM 应用。作者供图

  • 查看本地正在运行的所有容器信息,请输入:
$ docker ps

输出:

CONTAINER ID   IMAGE     COMMAND           CREATED          STATUS          PORTS                    NAMES
ff2a11da13d7   docqa     "python app.py"   17 seconds ago   Up 16 seconds   0.0.0.0:7860->7860/tcp   docqa-container
  • 可使用 stop 命令停止容器:
$ docker stop docqa-container 
  • 也可使用 rm 命令将其移除:
$ docker rm docqa-container 

构建出 Docker 镜像后,您即可将 LLM 应用部署到任意平台:GCP、AWS、Azure 或任何支持 Docker 的云服务器。

5. 使用 Docker 将 LLM 应用部署到 Hugging Face

为便于初学者,我们将通过 Hugging Face Cloud(Spaces)使用 Docker 部署应用。 

  • 前往 Hugging Face 仪表板,点击个人头像并选择 + New Space。
  • 为 Space 命名,添加简短描述,选择许可证与 SDK,并创建 Space 仓库。

Creating the new Hugging face Space using the Docker

使用 Docker 创建新的 Hugging Face Space。图片来源:Hugging Face

Space 仓库创建完成后,您将收到如何克隆并添加必要文件的说明。

  • 克隆仓库请使用以下命令(将 URL 替换为您 Space 的地址):
$ git clone https://huggingface.co/spaces/kingabzpro/doc-qa-docker
  • 将项目目录中的所有文件复制粘贴到新仓库:

项目目录应包含如下文件。务必确保不要推送 .env 文件,请将其加入 .gitignore。 

Project directory.

项目文件结构。作者供图

  • 暂存文件、提交信息并推送到 Hugging Face Space:
$ git add .                                                                                            
$ git commit -m "Deploying the App" 
$ git push

输出:

Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 16 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 7.60 KiB | 7.60 MiB/s, done.
Total 7 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To https://huggingface.co/spaces/kingabzpro/doc-qa-docker
   afb20ad..5ca6388  main -> main
  • 前往您的 Hugging Face Space 访问应用。系统将构建 Docker 镜像并启动服务:

Building the docker image in the Hugging Face cloud

在 Hugging Face Cloud 构建 Docker 镜像。图片来源:Doc Qa Docker

如果看到如下错误,不必担心。这是由于缺少环境变量。只需在 Hugging Face Space 中设置这些变量即可。

Runtime error on the deployed app.

已部署应用的运行时错误。图片来源:Doc Qa Docker

  • 要在 Space 中添加新的环境变量,前往 Settings,下滑并点击 New secret 按钮。然后按下图所示提供名称和值。本例中我们添加所需的 API 密钥:

add the secrets for the deployed app.

为已部署应用添加密钥。图片来源: Doc Qa Docker settings。

添加所有必要 API 密钥为环境变量后,密钥列表应如下所示:

all of the secrets for the deployed app.

已部署应用的密钥。图片来源: Doc Qa Docker settings。

设置密钥后,应用会自动重启,您应能看到应用已成功运行。尽情在云端使用您的文档问答应用吧!

LLM app on the Hugging Face Spaces.

Hugging Face Spaces 上的 LLM 应用。图片来源:Doc Qa Docker

如需复现结果,所有文件与配置可在 GitHub 仓库中找到:kingabzpro/Deploy-Doc-QA。

6. 监控已部署的应用

使用 AI 服务有诸多优势:无需自建或运维服务、吞吐高,而且提供带日志的仪表板。 

在 LlamaCloud 仪表板中,所有被解析的文档都会被记录。您可以查看请求历史并对用量进行比较。 

Llama Cloud Dashboard

Llama Cloud 仪表板。图片来源:LlamaCloud 

同样,您也可以查看我们在嵌入模型上的 token 使用量以及产生的请求数。 

Mixedbread Dashboard

Mixedbread 仪表板。图片来源:Mixedbread

最详尽的各 API 日志可在 GroqCloud 中查看,包括延迟、token 数、AI key 和请求 ID,便于您调试系统。

Groq Cloud logs

GroqCloud 日志。图片来源:GroqCloud

总结

本指南介绍了如何组合多项服务,在资源占用与计算开销最小化的前提下,构建高效的文档问答应用。文中使用的所有服务和工具均可免费试用,便于您搭建自己的应用。

通过使用多种开箱即用的 AI 服务,我们将 Docker 镜像体积减少了 600MB;若完全自建部署,镜像体积可能达到约 20GB 或更大。 

作为下一步学习建议,您可以选择 LLMOps Concepts: From Ideation to Deployment 课程,深入理解 LLM 开发生命周期与部署挑战,并学习如何有效应用相关理念。

Abid Ali Awan

作者

Abid Ali Awan

作为一名持证的数据科学家,我热衷于利用前沿技术打造创新的机器学习应用。凭借在语音识别、数据分析与报告、MLOps、对话式人工智能以及自然语言处理方面的扎实背景,我不断打磨构建智能系统的能力,力求带来切实影响。除技术专长外,我也擅长沟通,能够将复杂概念提炼为清晰、简明的表述。因此,我成为数据科学领域备受关注的博主,与不断壮大的数据专业人士社区分享洞见与实践经验。目前,我专注于内容创作与编辑,借助大语言模型打造有力且吸引人的内容,帮助企业与个人更好地发挥数据价值。

主题