实战:基于 Cloudflare Vectorize 与 Gemini 构建全自动 AI 语义搜索

在 2026 年,给个人博客加上 AI 搜索已经不是什么新鲜事。但如何零成本全自动高性能地实现这一功能,依然是一个值得探讨的技术话题。

本文将详细拆解本站 AI Search 功能背后的技术架构,展示如何组合 Cloudflare WorkersVectorizeD1 以及 Google Gemini,构建一套闭环的 RAG(检索增强生成)系统。

1. 核心架构设计

我们的目标是实现一个“全自动”的流程:写作即部署。作者只需 Push Markdown 文章,剩下的向量生成、索引更新、前端部署全部自动化完成。

graph TD
    subgraph "Control Plane (GitHub Actions)"
        Push[Git Push Markdown] --> Action[Sync Workflow]
        Action -->|Extract Text| Script[Python Script]
        Script -->|Embed| Gemini[Gemini API]
        Script -->|Upsert Vectors| Vectorize[Cloudflare Vectorize]
    end

    subgraph "Data Plane (Cloudflare)"
        User[User Query] -->|Request| Worker[Cloudflare Worker]
        Worker -->|Embed Query| Gemini
        Worker -->|Search| Vectorize
        Worker -->|Log & Stats| D1[D1 SQL Database]
        Worker -->|Return Matches| User
    end
graph TD
    subgraph "Control Plane (GitHub Actions)"
        Push[Git Push Markdown] --> Action[Sync Workflow]
        Action -->|Extract Text| Script[Python Script]
        Script -->|Embed| Gemini[Gemini API]
        Script -->|Upsert Vectors| Vectorize[Cloudflare Vectorize]
    end

    subgraph "Data Plane (Cloudflare)"
        User[User Query] -->|Request| Worker[Cloudflare Worker]
        Worker -->|Embed Query| Gemini
        Worker -->|Search| Vectorize
        Worker -->|Log & Stats| D1[D1 SQL Database]
        Worker -->|Return Matches| User
    end
graph TD
    subgraph "Control Plane (GitHub Actions)"
        Push[Git Push Markdown] --> Action[Sync Workflow]
        Action -->|Extract Text| Script[Python Script]
        Script -->|Embed| Gemini[Gemini API]
        Script -->|Upsert Vectors| Vectorize[Cloudflare Vectorize]
    end

    subgraph "Data Plane (Cloudflare)"
        User[User Query] -->|Request| Worker[Cloudflare Worker]
        Worker -->|Embed Query| Gemini
        Worker -->|Search| Vectorize
        Worker -->|Log & Stats| D1[D1 SQL Database]
        Worker -->|Return Matches| User
    end

关键组件选型:

  • Embedding 模型text-embedding-004 (Google Gemini),维度 768,免费且效果优秀。
  • 向量数据库:Cloudflare Vectorize,边缘原生,查询延迟极低。
  • 持久化存储:Cloudflare D1 (SQLite),用于存储搜索日志和统计数据。
  • 计算运行时:Cloudflare Workers,处理业务逻辑。

2. 自动化向量同步(Control Plane)

为了避免“发了文章还要手动跑脚本”的繁琐,我们利用 GitHub Actions 构建了自动同步管道。

递归文件扫描与向量化

传统的同步脚本往往只扫描根目录,但 Hugo 博客通常采用 Page Bundle 结构(content/posts/xxx/index.md)。我们需要递归查找所有 Markdown 文件,并提取 Frontmatter 中的元数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# scripts/sync_vectors.py 核心逻辑
def sync_all():
    # 1. 递归查找所有文章
    files = glob.glob("content/posts/**/*.md", recursive=True)
    
    for filepath in files:
        post = frontmatter.load(filepath)
        # 2. 构建语义指纹:标题 + 描述 + 正文摘要
        text_chunk = f"{post.metadata.get('title')} {post.metadata.get('description')} {post.content[:800]}"
        
        # 3. 调用 Gemini 生成向量
        embedding = get_embedding(text_chunk)
        
        # 4. 准备 Upsert 数据
        vectors.append({
            "id": slug,
            "values": embedding,
            "metadata": { "title": post.title, "url": post.url }
        })

GitHub Actions 触发器

配置 Workflow 监听 content/** 的变更,一旦检测到 Push,立即触发同步。

1
2
3
4
5
# .github/workflows/ai-sync.yml
on:
  push:
    paths:
      - 'content/**'  # 只有内容变更才触发

3. 边缘侧语义检索(Data Plane)

Worker 承载了前端的搜索请求,它的核心职责是“翻译”:将用户的自然语言查询翻译成向量,再去数据库中寻找“距离最近”的文章。

向量空间距离与阈值控制

在实现过程中,我们发现一个关键问题:RAG 总是倾向于返回结果,哪怕是完全不相关的。 例如搜“法学硕士”,向量数据库可能会强行返回一篇关于“Prometheus 监控”的文章,只是因为它们的某些隐含维度(如“学习”、“考试”)有一点点重合。

为了解决这个问题,我们在 Worker 层引入了动态阈值判定逻辑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// my-blog-ai/src/index.js
const matches = await env.VECTOR_INDEX.query(vector, { topK: 5 });

// 判定是否为“有效搜索”
// 只有当最相关结果的相似度 > 0.40 时,才认为找到了答案
const hasResults = matches.matches.length > 0 && matches.matches[0].score > 0.40;

// 异步记录日志到 D1,用于后续分析
ctx.waitUntil(
  env.DB.prepare("INSERT INTO search_logs (query, has_results) VALUES (?, ?)")
    .bind(query, hasResults ? 1 : 0).run()
);

这个 0.40 是经过多次测试得出的经验值。低于此分数的匹配通常是噪音。


4. Content Gap 洞察系统

这是本站 AI 搜索最独特的功能:不仅告诉用户有什么,还告诉作者缺什么

我们利用 D1 数据库记录了每一次搜索的 has_results 状态。通过聚合查询 has_results = 0 的记录,我们可以生成一个**“待解答问题(Content Gaps)”**列表。

SQL 聚合分析

Worker 开放了一个 action=stats 接口,执行如下 SQL:

1
2
3
4
5
6
7
-- 找出用户搜了但没结果的高频问题
SELECT query, COUNT(*) as count 
FROM search_logs 
WHERE has_results = 0 
GROUP BY query 
ORDER BY count DESC 
LIMIT 10

前端会将其渲染为“大家都在问(待解答)”面板:

Content Gap Dashboard (示意图:左侧为热门有效搜索,右侧为用户感兴趣但本站缺失的内容)

这形成了一个完美的内容生产闭环

  1. 用户搜索无果。
  2. 系统记录为 Content Gap。
  3. 作者在 Dashboard 看到需求。
  4. 作者撰写新文章。
  5. 自动同步向量,填补 Gap。

5. 总结

通过 Cloudflare 全家桶(Workers + Vectorize + D1),我们仅用不到 200 行代码就构建了一个具备企业级特性的 AI 搜索系统。它不仅极快(边缘计算),而且完全免费(对于个人博客的规模)。

最重要的是,它让博客从一个单向输出的静态站点,变成了一个能够感知用户需求、引导内容创作的动态系统。