我用 Python 搭了一套语义搜索系统:从向量数据库到智能检索,告别关键词搜索

适合想让搜索从"关键词匹配"升级到"语义理解"的开发者。
本文用 Python + ChromaDB + Sentence Transformers 搭了一套完整的语义搜索系统,附可运行代码。

背景:关键词搜索的局限

传统搜索是关键词匹配——搜"Python 自动化",只能找到包含这几个字的内容。

但用户的搜索意图往往是语义层面的:

  • 搜"怎么让电脑自动干活"→ 应该找到"Python 自动化脚本"
  • 搜"AI 写文章"→ 应该找到"LLM 内容生成"
  • 搜"省钱的 AI 工具"→ 应该找到"开源大模型本地部署"

关键词搜索做不到这些。语义搜索可以。

核心原理

文本 → Embedding 模型 → 向量(一组数字)→ 存入向量数据库
查询 → Embedding 模型 → 查询向量 → 在数据库中找最相似的向量

语义相近的文本,向量距离近。

技术选型

方案 优点 缺点 推荐
ChromaDB 纯 Python,零依赖 大规模性能一般 ⭐⭐⭐⭐⭐
FAISS Facebook 出品,性能强 需要自己管理元数据 ⭐⭐⭐⭐
Milvus 分布式,企业级 部署复杂 ⭐⭐⭐⭐
Pinecone 全托管云服务 收费 ⭐⭐⭐
Qdrant Rust 实现,高性能 生态较小 ⭐⭐⭐⭐

我选 ChromaDB,理由:纯 Python,不需要额外部署,5 分钟上手

模块 1:Embedding 模型

from sentence_transformers import SentenceTransformer

# 加载中文 Embedding 模型
model = SentenceTransformer("shibing624/text2vec-base-chinese")

def get_embedding(text):
    """把文本转成向量"""
    return model.encode(text).tolist()

# 示例
vec1 = get_embedding("Python 自动化脚本")
vec2 = get_embedding("怎么让电脑自动干活")
vec3 = get_embedding("今天天气真好")

import numpy as np

def cosine_similarity(a, b):
    """计算余弦相似度"""
    a, b = np.array(a), np.array(b)
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# "Python 自动化脚本" 和 "怎么让电脑自动干活" 语义相近
print(f"自动化 vs 自动干活: {cosine_similarity(vec1, vec2):.3f}")  # 约 0.75
print(f"自动化 vs 天气: {cosine_similarity(vec1, vec3):.3f}")      # 约 0.15

模块 2:向量数据库

import chromadb

# 创建数据库(持久化存储)
db = chromadb.PersistentClient(path="./vector_db")
collection = db.get_or_create_collection(
    name="my_docs",
    metadata={"hnsw:space": "cosine"}  # 使用余弦相似度
)

def add_document(doc_id, text, metadata=None):
    """添加文档到向量数据库"""
    embedding = get_embedding(text)
    collection.add(
        ids=[doc_id],
        embeddings=[embedding],
        documents=[text],
        metadatas=[metadata or {}]
    )

def search(query, top_k=5):
    """语义搜索"""
    query_embedding = get_embedding(query)
    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=top_k
    )
    return results

# 添加文档
add_document("doc1", "Python 自动化脚本可以帮你自动整理文件", {"category": "Python"})
add_document("doc2", "用 AI Agent 搭建内容发布流水线", {"category": "AI"})
add_document("doc3", "Docker 部署 AI 应用的完整指南", {"category": "DevOps"})
add_document("doc4", "RAG 知识库让 AI 读完你所有笔记", {"category": "AI"})
add_document("doc5", "LLM 大语言模型的核心原理", {"category": "AI"})

# 搜索
results = search("怎么让 AI 帮我写文章")
for i, (doc, score) in enumerate(zip(results["documents"][0], results["distances"][0])):
    print(f"  {i+1}. [{score:.3f}] {doc}")

输出示例:

  1. [0.312] 用 AI Agent 搭建内容发布流水线
  2. [0.387] RAG 知识库让 AI 读完你所有笔记
  3. [0.456] LLM 大语言模型的核心原理
  4. [0.621] Python 自动化脚本可以帮你自动整理文件
  5. [0.789] Docker 部署 AI 应用的完整指南

搜"怎么让 AI 帮我写文章",最相关的是"AI Agent 内容发布流水线"——这就是语义搜索的威力。

模块 3:批量导入

import os
from pathlib import Path

def batch_import(directory, extensions=(".md", ".txt")):
    """批量导入目录下的文档"""
    files = list(Path(directory).rglob("*"))
    files = [f for f in files if f.suffix in extensions]

    for i, file_path in enumerate(files):
        text = file_path.read_text(encoding="utf-8", errors="ignore")
        if len(text.strip()) < 50:  # 跳过太短的文件
            continue

        # 截取前 500 字作为索引内容
        truncated = text[:500]
        doc_id = f"doc_{i}"

        add_document(
            doc_id=doc_id,
            text=truncated,
            metadata={
                "source": str(file_path),
                "filename": file_path.name,
                "length": len(text)
            }
        )

        if (i + 1) % 50 == 0:
            print(f"已导入 {i + 1} 个文件")

    print(f"导入完成,共 {len(files)} 个文件")

# 使用
batch_import("./knowledge_base")

模块 4:语义搜索 API

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class SearchRequest(BaseModel):
    query: str
    top_k: int = 5

@app.post("/search")
def semantic_search(req: SearchRequest):
    """语义搜索接口"""
    results = search(req.query, req.top_k)

    items = []
    for doc, meta, dist in zip(
        results["documents"][0],
        results["metadatas"][0],
        results["distances"][0]
    ):
        items.append({
            "content": doc,
            "source": meta.get("source", ""),
            "relevance": 1 - dist  # 转成相似度(0-1)
        })

    return {"query": req.query, "results": items}

启动:

pip install fastapi uvicorn chromadb sentence-transformers
uvicorn search_api:app --reload --port 8000

使用:

curl -X POST http://localhost:8000/search \
  -H "Content-Type: application/json" \
  -d '{"query": "怎么让 AI 帮我写文章", "top_k": 3}'

模块 5:混合搜索(向量 + 关键词)

纯语义搜索有时会"跑偏"。混合搜索结合两种方式:

def hybrid_search(query, top_k=5, keyword_weight=0.3):
    """混合搜索:语义 + 关键词"""
    # 1. 语义搜索
    semantic_results = search(query, top_k=top_k * 2)

    # 2. 关键词匹配(简单实现)
    keyword_scores = {}
    for doc, meta, dist in zip(
        semantic_results["documents"][0],
        semantic_results["metadatas"][0],
        semantic_results["distances"][0]
    ):
        # 关键词匹配得分
        keyword_score = sum(1 for word in query if word in doc) / len(query)

        # 语义得分(距离转相似度)
        semantic_score = 1 - dist

        # 混合得分
        final_score = (1 - keyword_weight) * semantic_score + keyword_weight * keyword_score
        keyword_scores[doc] = {
            "score": final_score,
            "source": meta.get("source", "")
        }

    # 排序
    sorted_results = sorted(keyword_scores.items(), key=lambda x: x[1]["score"], reverse=True)
    return sorted_results[:top_k]

踩坑记录

坑 1:Embedding 模型选错

症状:搜"Python 自动化",返回的结果和 Python 无关。

原因:用的英文 Embedding 模型,中文语义理解差。

解决:用中文专用模型 shibing624/text2vec-base-chineseBAAI/bge-large-zh

坑 2:向量维度不一致

症状:添加文档时报 “dimension mismatch”。

原因:不同 Embedding 模型输出的向量维度不同。

解决:确保所有操作用同一个模型。

坑 3:文档太长,Embedding 质量下降

症状:5000 字的文档搜索结果不准确。

原因:Embedding 模型对长文本的语义压缩能力有限。

解决:长文档分段,每段 200-500 字单独索引。

坑 4:ChromaDB 持久化目录被删

症状:重启后搜索结果为空。

原因path="./vector_db" 目录被清理了。

解决:把向量数据库目录加到 .gitignore 的排除列表,定期备份。

坑 5:首次加载模型很慢

症状:第一次运行要下载 500MB 的模型文件。

解决:提前下载模型到本地,或者用更小的模型(如 text2vec-base-chinese 约 400MB)。

性能对比

指标 关键词搜索 语义搜索
“怎么让电脑自动干活” ❌ 找不到 ✅ 找到"Python 自动化"
“AI 写文章” ❌ 只匹配字面 ✅ 找到"LLM 内容生成"
“省钱的 AI 工具” ❌ 找不到 ✅ 找到"开源模型本地部署"
搜索速度 极快(<10ms) 较快(50-200ms)
存储空间 大(每个文档一个向量)

总结

3 条核心经验:

  1. 中文场景用中文 Embedding 模型。英文模型对中文语义理解差,搜索结果不准。推荐 text2vec-base-chinesebge-large-zh

  2. 长文档要分段索引。5000 字的文档直接索引效果差,分成 200-500 字的段落,每段单独索引。

  3. 混合搜索 > 纯语义搜索。纯语义搜索有时会"跑偏",结合关键词匹配(权重 20-30%)效果更好。


你有做过语义搜索吗?用什么方案?评论区交流。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐