从零搭一个能跑的 AI 搜索:FastAPI + ChromaDB + Docker 完整源码部署教程
AI搜索 Docker部署教程|附源码
在搜索体验越来越“智能化”的今天,传统关键词搜索已经难以满足复杂语义查询的需求。比如用户搜索“适合新手部署的向量数据库”,传统搜索可能只能匹配标题或正文中的关键词,而 AI 搜索可以理解用户意图,返回更接近语义的结果。
本文将带你从零搭建一个 AI搜索系统,并使用 Docker 进行部署。系统支持:
- 文档上传与解析
- 文本切分
- 向量化存储
- 语义搜索
- 基于大模型的答案生成
- Docker 一键部署
本文适合希望学习 RAG 检索增强生成、AI搜索系统开发、Docker部署AI应用 的开发者阅读。
一、什么是 AI 搜索?
AI 搜索通常指结合 向量检索 和 大语言模型 的搜索方式。
传统搜索主要依赖关键词匹配,例如:
用户搜索:Docker 部署 AI 搜索
系统会查找包含“Docker”“部署”“AI搜索”等关键词的内容。
而 AI 搜索更关注语义理解,例如:
用户搜索:怎么把智能问答系统发布到服务器?
即使文档中没有完全相同的关键词,系统也可以理解“智能问答系统”“发布到服务器”与“AI搜索 Docker部署”之间的语义关联,从而返回相关内容。
一个典型的 AI 搜索流程如下:
- 用户上传文档;
- 系统将文档切分成小段;
- 使用 Embedding 模型将文本转换为向量;
- 将向量存储到向量数据库;
- 用户输入问题;
- 系统将问题转换成向量;
- 从向量数据库中检索相关文档片段;
- 将相关片段和问题一起交给大模型;
- 大模型生成最终答案。
这类架构通常被称为 RAG,即 Retrieval-Augmented Generation,中文一般翻译为“检索增强生成”。
二、项目技术选型
本文示例项目采用以下技术栈:
| 模块 | 技术 |
|---|---|
| 后端框架 | FastAPI |
| 向量数据库 | ChromaDB |
| Embedding 模型 | sentence-transformers |
| 大模型接口 | OpenAI API / 本地 Ollama 可替换 |
| 部署方式 | Docker + Docker Compose |
| 接口文档 | FastAPI Swagger |
| 开发语言 | Python 3.11 |
为什么选择这些技术?
1. FastAPI
FastAPI 性能优秀,开发体验好,自动生成接口文档,非常适合快速构建 AI 应用后端。
2. ChromaDB
ChromaDB 是一个轻量级向量数据库,适合个人项目、中小型知识库和原型系统。它可以本地持久化,不需要复杂的集群配置。
3. sentence-transformers
它可以在本地生成文本向量,不一定依赖外部 API。对于测试、内网部署和私有化项目来说非常方便。
4. Docker
Docker 可以解决不同服务器环境不一致的问题。只要服务器安装了 Docker,就可以快速运行整个 AI 搜索服务。
三、项目目录结构
完整项目目录如下:
ai-search-docker/
├── app/
│ ├── main.py
│ ├── config.py
│ ├── embedding.py
│ ├── vector_store.py
│ ├── rag.py
│ └── utils.py
├── data/
│ └── docs/
├── chroma_data/
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
├── .env
└── README.md
目录说明:
app/main.py:FastAPI 主入口;app/config.py:配置文件;app/embedding.py:文本向量化模块;app/vector_store.py:向量数据库操作;app/rag.py:AI 搜索和问答逻辑;app/utils.py:文本切分工具;data/docs/:存放上传文档;chroma_data/:ChromaDB 持久化目录;requirements.txt:Python 依赖;Dockerfile:镜像构建文件;docker-compose.yml:服务编排文件。
四、核心源码
下面给出一个可运行的简化版本源码。
1. 配置文件:app/config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Settings:
APP_NAME = "AI Search Docker Demo"
CHROMA_DIR = os.getenv("CHROMA_DIR", "./chroma_data")
COLLECTION_NAME = os.getenv("COLLECTION_NAME", "ai_search_docs")
EMBEDDING_MODEL = os.getenv(
"EMBEDDING_MODEL",
"sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
)
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
settings = Settings()
这里使用 .env 文件读取配置,便于本地开发和 Docker 部署时修改参数。
如果你希望接入本地模型,例如 Ollama,可以将 OPENAI_BASE_URL 改成 Ollama 兼容接口地址,例如:
http://host.docker.internal:11434/v1
2. 文本切分工具:app/utils.py
def split_text(text: str, chunk_size: int = 500, overlap: int = 80):
"""
将长文本切分成多个片段。
chunk_size 表示每个片段最大长度。
overlap 表示相邻片段之间的重叠字符数。
"""
text = text.strip()
if not text:
return []
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
chunks.append(chunk)
start = end - overlap
if start < 0:
start = 0
if start >= len(text):
break
return chunks
为什么需要文本切分?
因为大模型和向量模型通常都有上下文长度限制。将文档切成合适的小块,可以提升检索精度,也能避免一次性输入过长导致性能下降。
3. 向量模型:app/embedding.py
from sentence_transformers import SentenceTransformer
from app.config import settings
class EmbeddingModel:
def __init__(self):
self.model = SentenceTransformer(settings.EMBEDDING_MODEL)
def encode(self, texts):
if isinstance(texts, str):
texts = [texts]
vectors = self.model.encode(
texts,
normalize_embeddings=True
)
return vectors.tolist()
embedding_model = EmbeddingModel()
这里使用的是多语言向量模型:
sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
它支持中文和英文,体积相对较小,适合教程和轻量级项目。如果你追求更高质量,可以替换为更强的 Embedding 模型。
4. 向量数据库:app/vector_store.py
import uuid
import chromadb
from app.config import settings
from app.embedding import embedding_model
client = chromadb.PersistentClient(path=settings.CHROMA_DIR)
collection = client.get_or_create_collection(
name=settings.COLLECTION_NAME
)
def add_documents(texts, metadatas=None):
"""
添加文档片段到向量数据库。
"""
if not texts:
return {"count": 0}
ids = [str(uuid.uuid4()) for _ in texts]
embeddings = embedding_model.encode(texts)
if metadatas is None:
metadatas = [{"source": "unknown"} for _ in texts]
collection.add(
ids=ids,
documents=texts,
embeddings=embeddings,
metadatas=metadatas
)
return {"count": len(texts)}
def search_documents(query: str, top_k: int = 5):
"""
根据问题进行语义检索。
"""
query_embedding = embedding_model.encode(query)[0]
results = collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)
documents = results.get("documents", [[]])[0]
metadatas = results.get("metadatas", [[]])[0]
distances = results.get("distances", [[]])[0]
items = []
for doc, meta, distance in zip(documents, metadatas, distances):
items.append({
"content": doc,
"metadata": meta,
"distance": distance
})
return items
这一层主要负责两个动作:
- 将文本片段写入 ChromaDB;
- 根据用户问题查询最相关的文本片段。
5. RAG 问答逻辑:app/rag.py
from openai import OpenAI
from app.config import settings
from app.vector_store import search_documents
client = OpenAI(
api_key=settings.OPENAI_API_KEY,
base_url=settings.OPENAI_BASE_URL
)
def build_prompt(question: str, contexts: list):
context_text = "\n\n".join(
[f"资料{i+1}:{item['content']}" for i, item in enumerate(contexts)]
)
prompt = f"""
你是一个严谨的 AI 搜索助手。请根据给定资料回答用户问题。
要求:
1. 优先依据资料回答;
2. 如果资料中没有答案,请明确说明“资料中没有找到相关信息”;
3. 不要编造不存在的事实;
4. 回答要结构清晰,必要时使用列表。
用户问题:
{question}
参考资料:
{context_text}
"""
return prompt
def answer_question(question: str, top_k: int = 5):
contexts = search_documents(question, top_k=top_k)
prompt = build_prompt(question, contexts)
if not settings.OPENAI_API_KEY:
return {
"answer": "未配置 OPENAI_API_KEY,当前仅返回检索结果。",
"contexts": contexts
}
completion = client.chat.completions.create(
model=settings.OPENAI_MODEL,
messages=[
{"role": "system", "content": "你是一个专业、可靠的AI搜索助手。"},
{"role": "user", "content": prompt}
],
temperature=0.2
)
answer = completion.choices[0].message.content
return {
"answer": answer,
"contexts": contexts
}
这里采用了比较简单的 Prompt 设计。生产环境中可以继续优化,例如:
- 加入引用来源;
- 限制回答格式;
- 增加安全过滤;
- 对检索结果做 rerank;
- 对长文档做分层摘要。
6. FastAPI 接口:app/main.py
from fastapi import FastAPI, UploadFile, File
from pydantic import BaseModel
from app.utils import split_text
from app.vector_store import add_documents, search_documents
from app.rag import answer_question
app = FastAPI(title="AI Search Docker Demo")
class AddTextRequest(BaseModel):
text: str
source: str = "manual"
class SearchRequest(BaseModel):
query: str
top_k: int = 5
class AskRequest(BaseModel):
question: str
top_k: int = 5
@app.get("/")
def index():
return {
"message": "AI Search API is running",
"docs": "/docs"
}
@app.post("/add_text")
def add_text(req: AddTextRequest):
chunks = split_text(req.text)
metadatas = [{"source": req.source} for _ in chunks]
result = add_documents(chunks, metadatas)
return {
"message": "文本已写入向量库",
"chunks": result["count"]
}
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
content = await file.read()
try:
text = content.decode("utf-8")
except UnicodeDecodeError:
text = content.decode("gbk", errors="ignore")
chunks = split_text(text)
metadatas = [{"source": file.filename} for _ in chunks]
result = add_documents(chunks, metadatas)
return {
"filename": file.filename,
"chunks": result["count"]
}
@app.post("/search")
def search(req: SearchRequest):
results = search_documents(req.query, req.top_k)
return {
"query": req.query,
"results": results
}
@app.post("/ask")
def ask(req: AskRequest):
result = answer_question(req.question, req.top_k)
return result
该接口提供四个核心能力:
| 接口 | 说明 |
|---|---|
/add_text |
直接添加文本 |
/upload |
上传 txt 文档 |
/search |
语义搜索 |
/ask |
AI 问答 |
启动后可以访问:
http://localhost:8000/docs
查看 Swagger 接口文档。
五、依赖文件:requirements.txt
fastapi==0.115.6
uvicorn[standard]==0.32.1
python-dotenv==1.0.1
python-multipart==0.0.19
chromadb==0.5.23
sentence-transformers==3.3.1
openai==1.57.0
如果你在国内环境下载模型较慢,可以提前将模型缓存到服务器,或者使用镜像源安装依赖。
六、环境变量文件:.env
CHROMA_DIR=./chroma_data
COLLECTION_NAME=ai_search_docs
EMBEDDING_MODEL=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
OPENAI_API_KEY=your_api_key_here
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
如果你暂时不配置 OPENAI_API_KEY,系统仍然可以使用 /search 接口进行语义搜索,只是 /ask 接口不会调用大模型生成答案。
如果使用 Ollama,可以参考:
OPENAI_API_KEY=ollama
OPENAI_BASE_URL=http://host.docker.internal:11434/v1
OPENAI_MODEL=qwen2.5:7b
需要注意的是,Ollama 是否支持 OpenAI 兼容接口取决于版本和启动方式。
七、Dockerfile 编写
在项目根目录创建 Dockerfile:
FROM python:3.11-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
RUN apt-get update && apt-get install -y \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
这个 Dockerfile 做了以下事情:
- 使用 Python 3.11 slim 作为基础镜像;
- 安装必要编译依赖;
- 安装 Python 依赖;
- 拷贝项目代码;
- 暴露 8000 端口;
- 使用 Uvicorn 启动 FastAPI 服务。
八、Docker Compose 部署
创建 docker-compose.yml:
version: "3.9"
services:
ai-search:
build:
context: .
dockerfile: Dockerfile
container_name: ai-search
ports:
- "8000:8000"
env_file:
- .env
volumes:
- ./chroma_data:/app/chroma_data
- ./data:/app/data
restart: always
这里挂载了两个目录:
./chroma_data:/app/chroma_data
./data:/app/data
这样即使容器重启或重新构建,向量数据库数据也不会丢失。
九、本地运行方式
如果你不想一开始就使用 Docker,也可以本地运行。
1. 创建虚拟环境
python -m venv venv
Linux 或 macOS:
source venv/bin/activate
Windows:
venv\Scripts\activate
2. 安装依赖
pip install -r requirements.txt
3. 启动服务
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
访问:
http://localhost:8000/docs
十、Docker 部署步骤
下面是正式使用 Docker 部署的完整流程。
1. 克隆或上传项目
假设项目目录为:
cd ai-search-docker
2. 修改环境变量
编辑 .env:
vim .env
至少确认以下配置:
OPENAI_API_KEY=你的APIKey
OPENAI_MODEL=gpt-4o-mini
如果只想测试检索功能,可以不配置 API Key。
3. 构建镜像
docker compose build
4. 启动容器
docker compose up -d
5. 查看容器状态
docker ps
如果看到类似输出,说明启动成功:
CONTAINER ID IMAGE PORTS NAMES
xxxxxxx ai-search-docker 0.0.0.0:8000->8000/tcp ai-search
6. 查看日志
docker logs -f ai-search
首次启动时,sentence-transformers 可能会下载模型,因此启动时间可能较长。
7. 访问接口文档
浏览器打开:
http://服务器IP:8000/docs
如果是本机部署:
http://localhost:8000/docs
十一、接口测试示例
1. 添加文本
请求地址:
POST /add_text
请求体:
{
"text": "Docker 是一种容器化技术,可以将应用及其依赖打包到镜像中,从而实现跨环境一致部署。AI搜索系统通常结合向量数据库和大语言模型,实现语义检索和智能问答。",
"source": "demo"
}
返回示例:
{
"message": "文本已写入向量库",
"chunks": 1
}
2. 语义搜索
请求地址:
POST /search
请求体:
{
"query": "怎么部署智能搜索应用?",
"top_k": 3
}
返回示例:
{
"query": "怎么部署智能搜索应用?",
"results": [
{
"content": "Docker 是一种容器化技术,可以将应用及其依赖打包到镜像中...",
"metadata": {
"source": "demo"
},
"distance": 0.32
}
]
}
3. AI 问答
请求地址:
POST /ask
请求体:
{
"question": "AI搜索系统通常由哪些部分组成?",
"top_k": 5
}
返回示例:
{
"answer": "AI搜索系统通常由文档处理模块、文本切分模块、Embedding 向量化模块、向量数据库、语义检索模块以及大语言模型问答模块组成。",
"contexts": [
{
"content": "AI搜索系统通常结合向量数据库和大语言模型...",
"metadata": {
"source": "demo"
},
"distance": 0.28
}
]
}
十二、常见问题与解决方案
1. Docker 启动很慢怎么办?
首次启动时需要下载向量模型,耗时取决于网络环境。解决方法:
- 提前在本地下载模型;
- 使用 Hugging Face 镜像;
- 将模型目录挂载到容器;
- 换用更小的 Embedding 模型。
2. ChromaDB 数据丢失怎么办?
确认 docker-compose.yml 中已经配置 volume:
volumes:
- ./chroma_data:/app/chroma_data
如果没有挂载,容器删除后数据也会丢失。
3. 中文搜索效果不好怎么办?
可以尝试更适合中文的 Embedding 模型,例如:
BAAI/bge-small-zh-v1.5
BAAI/bge-base-zh-v1.5
moka-ai/m3e-base
替换 .env 中的配置即可:
EMBEDDING_MODEL=BAAI/bge-small-zh-v1.5
但需要注意,不同模型向量维度可能不同。更换模型后,建议清空旧的向量库数据重新入库。
4. 如何支持 PDF、Word 文档?
本文示例只支持 txt 文本文件。如果要支持 PDF,可以增加依赖:
pypdf
然后在上传接口中根据文件后缀解析。支持 Word 可以使用:
python-docx
生产环境中建议将“文件解析”和“文本入库”做成异步任务,避免大文件上传导致接口阻塞。
5. 如何让答案带引用来源?
可以在 Prompt 中要求模型输出引用,并把检索结果编号:
资料1:...
资料2:...
然后要求模型回答时标注:
根据资料1和资料2可知,...
不过需要注意,大模型可能会出现引用不准确的问题。更严格的做法是在后端返回检索片段,由前端展示引用来源。
十三、生产环境优化建议
如果你准备将该 AI 搜索系统用于真实业务,建议进一步优化以下方面。
1. 增加鉴权
当前示例接口没有鉴权,任何人都可以上传文档和查询。如果部署到公网,必须增加鉴权,例如:
- API Key;
- JWT;
- OAuth2;
- 企业内部 SSO。
2. 增加任务队列
大文件解析、批量向量化、重建索引都比较耗时,不适合在接口请求中同步完成。可以引入:
- Celery;
- Redis Queue;
- Dramatiq;
- FastAPI BackgroundTasks。
3. 增加数据库
可以使用 PostgreSQL 或 MySQL 存储:
- 用户信息;
- 文档信息;
- 上传记录;
- 问答历史;
- 权限配置。
向量数据库只负责语义检索,不建议承担所有业务数据存储。
4. 增加重排序模型
向量检索召回的结果不一定总是最准确。可以在向量检索之后加入 rerank 模型,例如:
BAAI/bge-reranker-base
BAAI/bge-reranker-large
流程变为:
用户问题 -> 向量检索召回 Top 20 -> Rerank 重排 Top 5 -> 大模型生成答案
这样可以显著提升答案质量。
5. 增加缓存
对于高频问题,可以使用 Redis 缓存搜索结果或最终答案,降低模型调用成本。
6. 增加日志和监控
建议记录:
- 请求耗时;
- 检索命中文档;
- 模型调用耗时;
- Token 消耗;
- 用户反馈;
- 异常日志。
可以结合 Prometheus、Grafana、ELK 等工具构建监控体系。
十四、完整启动命令汇总
# 进入项目目录
cd ai-search-docker
# 构建镜像
docker compose build
# 后台启动
docker compose up -d
# 查看容器
docker ps
# 查看日志
docker logs -f ai-search
# 停止服务
docker compose down
# 重启服务
docker compose restart
如果你修改了 Python 代码,需要重新构建:
docker compose up -d --build
如果想清空向量数据,可以删除:
rm -rf chroma_data
然后重新启动服务并重新导入文档。
十五、总结
本文从零实现了一个基于 FastAPI、ChromaDB、sentence-transformers 和大语言模型接口的 AI 搜索系统,并通过 Docker Compose 完成部署。
这个项目虽然是简化版本,但已经覆盖 AI 搜索的核心链路:
文档输入 -> 文本切分 -> 向量化 -> 向量存储 -> 语义检索 -> 大模型回答
你可以基于本文源码继续扩展,例如增加 PDF 解析、用户权限、知识库管理、前端页面、流式输出、多租户隔离、问答记录和搜索反馈等功能。
如果你只是想快速体验 AI 搜索,可以直接按照本文步骤运行 Docker;如果你希望深入理解 RAG 系统,也可以逐个阅读源码模块,理解每一层的职责。掌握这套基础架构后,你就可以构建企业知识库、智能客服、文档问答、代码检索、产品手册助手等多种 AI 应用。