Learning
VOL. XIII · NO. 04 · Elixir · 25 JUL 2026

Extensions Cf Ai Rag

Elixir 编程 · 25 JUL 2026 · 9 min read · 810 words
· · ·

Cloudflare Workers AI + Vectorize 零后端 RAG

杀手锏:用 CF 边缘算力 + 向量数据库,0 业务服务器搭一个 RAG(Retrieval-Augmented Generation)应用。

适用:agquant.com/learning/ 的”语义搜索”——搜”雪球怎么对冲”能匹配到所有相关章节,不只是精确关键词。


一、为什么这是”花活”级别

传统 RAG 架构(自建):

┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ Frontend     │ →  │ Backend      │ →  │ Postgres     │
│ (React)      │    │ (FastAPI)    │    │ + pgvector   │
└──────────────┘    │ + LangChain  │    └──────────────┘
                    │ + OpenAI API │
                    └──────────────┘

成本

  • Frontend hosting: $0-50/月
  • Backend VM (4GB): $20-40/月
  • Postgres + pgvector: $15-30/月 (RDS) 或自建
  • OpenAI Embeddings: $0.02/1M tokens
  • OpenAI GPT-4: $30/1M tokens
  • 月成本:$60-150

CF 零后端 RAG 架构:

┌──────────────┐
│ 浏览器       │ ← 只用 CF Worker(< 5ms cold start)
│ (静态页面)   │
└──────┬───────┘

┌──────────────────────────────────────┐
│ CF Worker (边缘)                     │
│ - Workers AI: Llama 3 + BGE embed    │ ← 全球 200+ 城市节点
│ - Vectorize: 向量数据库              │ ← 一致性 + 边缘缓存
│ - D1: 元数据 + 源文                  │ ← SQLite at edge
└──────────────────────────────────────┘

       所有数据 + AI 全在 CF,无业务服务器

成本

  • Workers requests: $0.30/1M(10K MAU ≈ 100K queries/月 = $0.03)
  • Vectorize: $0.05/1M query-vec(100K = $0.005)
  • Vectorize storage: $0.05/GB-月(10K chunks ≈ 100 MB = $0.005)
  • D1 reads: $0.001/1K
  • D1 writes: $1.00/1M
  • 月成本:$0.05-0.50

省 100x。0 业务服务器 = 0 维护。


二、agquant.com 实战:learning 内容语义搜索

2.1 目标

用户在 agquant.com/learning/ 搜:

  • “雪球怎么对冲” → 找出所有讲雪球对冲的章节(含同义词)
  • “CVA 怎么算” → 找出 CVA 计算公式、Python 实现、风险对冲
  • “Elixir GenServer” → 跨 lessons/0002 / 0003 / 0005 的内容

比传统搜索强:用关键词搜索”雪球”能匹配到”雪球结构”和”自动赎回”,但搜”怎么对冲雪球”可能漏掉”Delta 中性策略”。

RAG 解决:用向量相似度,“怎么对冲雪球” 跟”Delta 中性 + 雪球”内容相似度 > 0.85 → 命中。

2.2 数据流

                    ┌─────────────────────────────┐
                    │ 一次性:建立索引               │
                    │ (build.mjs 或 CI 里跑)        │
                    └─────────────────────────────┘

   learning/otc-derivatives/*.md  ───→  切 chunk(500 字/段)

                            Workers AI: BGE embeddings

                          Vectorize index (向量 + metadata)

                          D1: chunk_text + chunk_id 映射

                    ┌─────────────────────────────┐
                    │ 运行时:用户搜                  │
                    └─────────────────────────────┘

                  用户输入 "雪球怎么对冲"

                     Workers AI: embed (BGE)

                      Vectorize: query (top 5)

                       D1: 查 chunk_text

                  Workers AI: Llama 3.1 8B 拿 chunks 生成答案

                          返回 {answer, sources[]}

三、完整代码(CF Worker + Vectorize + D1)

3.1 wrangler.toml 配置

# wrangler.toml
name = "agquant-rag"
main = "src/worker.ts"
compatibility_date = "2024-09-01"

[[vectorize]]
binding = "VECTOR_INDEX"
index_name = "agquant-learning"

[[d1_databases]]
binding = "DB"
database_name = "agquant-rag"
database_id = "your-d1-id"

[ai]
binding = "AI"

3.2 worker.ts(核心:embed + query + generate)

// src/worker.ts
export interface Env {
  VECTOR_INDEX: VectorizeIndex;
  DB: D1Database;
  AI: Ai;
}

export default {
  async fetch(req: Request, env: Env): Promise&lt;Response&gt; {
    const url = new URL(req.url);

    // 1. 索引 API(一次性,CI 跑)
    if (url.pathname === "/api/index" && req.method === "POST") {
      return await handleIndex(req, env);
    }

    // 2. 查询 API(运行时)
    if (url.pathname === "/api/search" && req.method === "POST") {
      return await handleSearch(req, env);
    }

    return new Response("Not found", { status: 404 });
  },
};

async function handleIndex(req: Request, env: Env): Promise&lt;Response&gt; {
  const { chunks } = await req.json&lt;{ chunks: { id: string; text: string; url: string; title: string }[] }&gt;();

  // 1. embed 每个 chunk
  const embeddings = await env.AI.run(
    "@cf/baai/bge-base-en-v1.5",  // 768 维向量
    { text: chunks.map(c =&gt; c.text) }
  );

  // 2. 写 Vectorize(带 metadata)
  const vectors = chunks.map((chunk, i) =&gt; ({
    id: chunk.id,
    values: embeddings.data[i],
    metadata: { url: chunk.url, title: chunk.title }
  }));
  await env.VECTOR_INDEX.upsert(vectors);

  // 3. 写 D1(chunk_text 持久化,Vectorize 不存原文)
  const stmt = env.DB.prepare(`
    INSERT OR REPLACE INTO chunks (id, url, title, text)
    VALUES (?, ?, ?, ?)
  `);
  const batch = chunks.map(c =&gt; stmt.bind(c.id, c.url, c.title, c.text));
  await env.DB.batch(batch);

  return Response.json({ ok: true, count: chunks.length });
}

async function handleSearch(req: Request, env: Env): Promise&lt;Response&gt; {
  const { query } = await req.json&lt;{ query: string }&gt;();

  // 1. embed 查询
  const queryEmbed = await env.AI.run(
    "@cf/baai/bge-base-en-v1.5",
    { text: [query] }
  );

  // 2. Vectorize 查 top 5
  const results = await env.VECTOR_INDEX.query(queryEmbed.data[0], {
    topK: 5,
    returnMetadata: "all"
  });

  // 3. 从 D1 拿原文
  const ids = results.matches.map(m =&gt; m.id);
  const placeholders = ids.map(() =&gt; "?").join(",");
  const { results: rows } = await env.DB.prepare(
    `SELECT * FROM chunks WHERE id IN (${placeholders})`
  ).bind(...ids).all();

  // 4. 组装 context
  const context = rows.map((r: any) =&gt;
    `### ${r.title}\n来源: ${r.url}\n${r.text}`
  ).join("\n\n---\n\n");

  // 5. Workers AI Llama 3 生成答案
  const answer = await env.AI.run(
    "@cf/meta/llama-3.1-8b-instruct",
    {
      messages: [
        {
          role: "system",
          content: "你是 agquant.com 学习助手。基于以下 context 回答用户问题。如果 context 没说,诚实说不知道。回答末尾用 [1] [2] 标注引用。"
        },
        { role: "user", content: `Context:\n${context}\n\nQuestion: ${query}` }
      ]
    }
  );

  return Response.json({
    answer: answer.response,
    sources: results.matches.map(m =&gt; ({
      title: m.metadata!.title,
      url: m.metadata!.url,
      score: m.score
    }))
  });
}

3.3 D1 schema

-- wrangler d1 execute agquant-rag --file=schema.sql
CREATE TABLE IF NOT EXISTS chunks (
  id TEXT PRIMARY KEY,
  url TEXT NOT NULL,
  title TEXT NOT NULL,
  text TEXT NOT NULL,
  indexed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_chunks_url ON chunks(url);

3.4 索引脚本(CI 跑)

// scripts/index-learning.ts
import { readdir, readFile } from "fs/promises";
import { join } from "path";

const CHUNK_SIZE = 500;
const OVERLAP = 50;

async function chunkMarkdown(text: string): Promise&lt;string[]&gt; {
  const chunks: string[] = [];
  // 按段落切(每段最多 500 字,相邻重叠 50)
  const paragraphs = text.split(/\n\n+/);
  let current = "";
  for (const para of paragraphs) {
    if (current.length + para.length &gt; CHUNK_SIZE) {
      chunks.push(current.trim());
      current = current.slice(-OVERLAP) + "\n\n" + para;
    } else {
      current += "\n\n" + para;
    }
  }
  if (current) chunks.push(current.trim());
  return chunks;
}

async function main() {
  // 1. 收集所有 .md
  const files: string[] = [];
  async function walk(dir: string) {
    for (const entry of await readdir(dir, { withFileTypes: true })) {
      const path = join(dir, entry.name);
      if (entry.isDirectory()) await walk(path);
      else if (entry.name.endsWith(".md")) files.push(path);
    }
  }
  await walk("./learning");

  // 2. 切 chunk
  const allChunks: any[] = [];
  for (const file of files) {
    const text = await readFile(file, "utf-8");
    const chunks = await chunkMarkdown(text);
    chunks.forEach((c, i) =&gt; {
      allChunks.push({
        id: `${file}#${i}`,
        text: c,
        url: `https://agquant.com/learning/${file.replace("./learning/", "").replace(".md", ".html")}`,
        title: c.split("\n")[0].replace(/^#+\s*/, "") || file
      });
    });
  }

  // 3. 分批调 /api/index(每批 100 个)
  for (let i = 0; i &lt; allChunks.length; i += 100) {
    const batch = allChunks.slice(i, i + 100);
    const res = await fetch("https://agquant-rag.workers.dev/api/index", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ chunks: batch })
    });
    console.log(`Indexed ${i + batch.length}/${allChunks.length}`);
  }
}

main();

四、前端嵌入(搜索框 + 流式输出)

&lt;div class="agquant-search"&gt;
  &lt;input id="q" placeholder="搜索 agquant.com 学习内容..."&gt;
  &lt;button onclick="search()"&gt;🔍&lt;/button&gt;
&lt;/div&gt;
&lt;div id="result"&gt;&lt;/div&gt;

&lt;script&gt;
  async function search() {
    const query = document.getElementById("q").value;
    document.getElementById("result").innerHTML = "搜索中...";

    const res = await fetch("https://agquant-rag.workers.dev/api/search", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ query })
    });
    const { answer, sources } = await res.json();

    document.getElementById("result").innerHTML = `
      &lt;div class="answer"&gt;${answer}&lt;/div&gt;
      &lt;ul class="sources"&gt;
        ${sources.map(s =&gt; `
          &lt;li&gt;
            &lt;a href="${s.url}" target="_blank"&gt;${s.title}&lt;/a&gt;
            &lt;span class="score"&gt;${(s.score * 100).toFixed(0)}%&lt;/span&gt;
          &lt;/li&gt;
        `).join("")}
      &lt;/ul&gt;
    `;
  }
&lt;/script&gt;

五、3 个真实使用场景

5.1 语义搜索(替代 Ctrl+F)

用户搜 "雪球怎么对冲"
→ 匹配 5 段:
  - product-snowball.html §对冲策略
  - 06-otc-options.html §Delta 中性
  - 14-Systems-and-Data.html §系统设计
  - glossary.html §Delta
  - 17-End-to-End-Case.html §案例

5.2 AI 助教

用户问 "我是 IT 转 PM,CVA 怎么入门?"
→ RAG 返回 800 字答案 + 5 个引用链接
→ 末尾 CTA:"想系统学?报名 14 节 OTC PM 课"

5.3 关联内容推荐

用户看完 "雪球结构"
→ 自动推荐:
  - "Delta 对冲"(相似度 0.89)
  - "波动率曲面"(相似度 0.82)
  - "希腊字母"(相似度 0.78)

六、模型选择

用途模型维度价格
英文 Embedding@cf/baai/bge-base-en-v1.5768$0.02/1M tokens
多语 Embedding(含中文)@cf/baai/bge-m31024$0.03/1M tokens
LLM 8B@cf/meta/llama-3.1-8b-instruct-$0.30/1M tokens
LLM 70B@cf/meta/llama-3.1-70b-instruct-$2.00/1M tokens
重排 Rerank@cf/baai/bge-reranker-base-$0.10/1M queries

agquant.com 推荐

  • Embedding: bge-m3(中文友好)
  • LLM: llama-3.1-8b-instruct(速度 + 成本平衡)
  • Rerank: 流量大再加

七、跟自建 RAG 对比

维度CF 零后端自建(LangChain + pgvector)
月成本(10K MAU)$0.05-0.50$60-150
冷启动0ms(边缘)100-500ms
维护0自己
数据主权CF 服务器你服务器
可扩展自动自己 scale
月成本(100K MAU)$5-20$300-1000+
复杂(Agent + Tool Use)⚠️ 有限✅ 完整 LangChain

何时选自建

  • 需要复杂 Agent(多步推理 + 工具调用 + 反思)
  • 数据必须完全自有(金融合规)
  • 已有 K8s + DevOps 团队

何时选 CF 零后端

  • 中小流量(< 100K MAU)
  • 想要”5 分钟搭好”
  • 不愿运维

八、30 分钟搭起来

  1. 5 分钟:在 CF Dashboard 建 Vectorize index + D1 database + 装 wrangler
  2. 15 分钟:复制上面 worker.ts,改 env 名
  3. 5 分钟wrangler deploy → 拿到 worker URL
  4. 3 分钟:跑 scripts/index-learning.ts → 索引所有内容
  5. 2 分钟:在 agquant.com 加搜索框 + fetch 调用

30 分钟后你有:全球 200+ 节点的 RAG 搜索,0 业务服务器,月成本 < $1。


九、相关资源

十、自检清单

  • 知道 RAG = 检索 + LLM 生成,能用自己的话解释
  • 知道 CF Worker 跑 Workers AI 和 Vectorize 都是边缘 200+ 节点
  • 知道 chunk 切分用 500 字 + 50 字重叠
  • 知道 bge-m3 是中英双语 embedding 模型
  • 知道 D1 存原文 + Vectorize 存向量的分工
  • 知道 0 业务服务器 = 0 维护,CF 全包
  • 能在 30 分钟内从零搭好