跳到正文
Hacker News· pierreneter·· 10 天前AI 评分60

DocFlare AI 开源文档问答机器人,全部跑在 Cloudflare 免费额度上

Show HN: DocFlare AI – Open-source docs chatbot on Cloudflare's free tier

AI 导读

DocFlare AI 是一个开源、零成本的文档站搜索与问答组件,完全基于 Cloudflare 原生服务,只需在文档页加一行 script 标签即可获得带来源引用的流式 RAG 问答,无需 OpenAI key、Pinecone 或服务器。

正文 · AI 翻译

开源、零成本的 AI 站点搜索与聊天机器人组件。100% Cloudflare 原生。

在你的文档站点上放置一个 <script> 标签,就能获得一个流式 RAG 聊天机器人,它基于你的内容作答,并附上来源。 无需 OpenAI 密钥。无需 Pinecone。无需服务器。无需付费。

Deploy to Cloudflare License: MIT Built with Hono Cloudflare Workers


截图

嵌入文档站点的聊天组件 管理界面(/admin)
DocFlare AI chat widget streaming an answer with sources DocFlare AI admin UI showing indexing progress and recent questions

左侧是带来源标签的流式回答;右侧是内置管理页面,用于注册站点地图、驱动索引并查看用户提问。

为什么

托管的“与你的文档对话”组件(Chatbase、CustomGPT、Mendable 等)每月收费 20 到 400 美元,而本质上它只是一个爬虫、一个嵌入模型、一个向量存储和一个 LLM。Cloudflare 现在以慷慨的免费额度提供所有这些组件:

组件 Cloudflare 服务 免费额度(撰写本文时)
API / 爬虫 Workers + Hono 每天 10 万次请求
嵌入 + LLM Workers AI(bge-small-en-v1.5、bge-reranker-base、llama-3.1-8b-instruct-fp8) 每天 1 万神经元
向量搜索 Vectorize(384 维,余弦) 存储 500 万维,每月查询 3000 万维
元数据 + 聊天日志 D1(SQLite) 5 GB,每天 500 万次读取
后台索引 Cron Triggers 包含在内
组件托管 Static Assets 包含在内

DocFlare AI 将它们整合为一个 Worker,一条命令即可部署。

功能

  • 单标签嵌入:<script src=".../widget.js" data-site-id="my-docs">。原生 JS、Shadow DOM、< 10 KB、零依赖、无 CSS 泄漏。
  • 流式回答通过 Server-Sent Events 传输,带来源标签,并使用提问所用的语言。
  • 站点地图爬虫:支持站点地图索引、gzip 站点地图,通过原生 HTMLRewriter 剥离样板内容(导航/页眉/页脚/脚本)。
  • 感知章节的分块:标题会开启新块,因此落地页的“开源”卡片不会被埋没在关于定价的块中。块大小约 350 个 token,重叠 40 个 token,并按每种文字估算 token 数,使越南语/CJK 页面保持在嵌入模型 512 token 的窗口内。
  • 两阶段检索:在 top-K 送入 LLM 之前,先用 bge-reranker-base 交叉编码器对 20 个最佳向量匹配重新打分。那些仅与错误页面共享词汇的问题(法律页面上的站点名称、产品名称)会落到真正回答它们的段落上。
  • 多站点 / 多租户:一次部署可服务多个文档站点,每个站点隔离在自己的 Vectorize 命名空间中,并锁定到自己的域名。
  • 设计上对免费额度安全:索引以小批量可恢复方式运行(cron + 按需),当未检索到相关内容时跳过 LLM,AI 速率限制会优雅降级。
  • 深色 / 浅色 / 自动主题、自定义强调色、左侧/右侧位置、键盘友好。
  • 聊天历史存储在 D1,因此你可以看到用户问了什么,以及你的文档在哪里存在空白。
  • 内置管理界面位于 /admin:注册站点地图、查看索引进度、处理批次、重新爬取、删除站点、复制嵌入代码片段并浏览最近的问题。单个静态 HTML 文件,无需构建步骤。
  • 端到端 TypeScript、Hono 路由、严格类型、无 any。使用 Bun 进行工具和测试。

工作原理

flowchart LR
    subgraph Host["Your docs site"]
        W["widget.js<br/>(Shadow DOM)"]
    end

    subgraph CF["Cloudflare (free tier)"]
        direction TB
        API["Worker + Hono<br/>src/index.ts"]
        AI["Workers AI<br/>bge-small-en-v1.5<br/>bge-reranker-base<br/>llama-3.1-8b-instruct-fp8"]
        VX["Vectorize<br/>384-dim cosine<br/>namespace = siteId"]
        D1["D1<br/>sites / pages / queries"]
        CRON["Cron trigger<br/>every minute"]
        ASSETS["Static assets<br/>public/"]
    end

    SM["sitemap.xml + HTML pages"]

    W -- "POST /api/chat (SSE)" --> API
    ASSETS -- "GET /widget.js" --> W
    API -- "embed question" --> AI
    API -- "topK query" --> VX
    API -- "RAG prompt" --> AI
    API -- "log query" --> D1
    CRON -- "process pending pages" --> API
    API -- "fetch + extract" --> SM
    API -- "embed chunks" --> AI
    API -- "upsert vectors" --> VX

加载中

索引:POST /api/sites/index 读取站点地图并将每个 HTML URL 排入 D1 队列。然后 cron 触发器(或重复调用 /process)每次调用爬取几个页面:提取文本、分块、嵌入、上传到 Vectorize、标记页面已索引。

Chat:小部件发送问题;Worker 将其嵌入,从站点的命名空间中拉取最接近的 20 个块,用交叉编码器对它们重新排序,保留前 K 个(每页最多 2 个),构建有依据的提示词,以 SSE 流式返回 Llama 3.1 的回答,并将交互记录在 D1 中。

完整设计见 docs/ARCHITECTURE.md,端点见 docs/API_REFERENCE.md。

快速开始

前置条件

  • 一个启用了 Workers 的免费 Cloudflare 账户。
  • Bun 1.1+(用于安装、脚本、构建小部件和运行测试)。

1. 克隆并安装

git clone https://github.com/p10node/docflare-ai.git
cd docflare-ai
bun install
bunx wrangler login

2. 创建 D1 数据库

bunx wrangler d1 create docflare-db

从输出中复制 database_id。将其粘贴到 wrangler.toml 中:

[[d1_databases]]
binding = "DB"
database_name = "docflare-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"   # <- paste here

……或者,为了不把它提交到 git,改为放入 .env 文件(当你推送 fork 时很有用):

echo 'D1_DATABASE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' > .env

设置 .env 后,bun run deploy 和 bun run db:migrate 会从 wrangler.toml 生成 wrangler.local.toml(已被 gitignore),填入 id 并使用它。wrangler.toml 保留占位符。

应用 schema:

bun run db:migrate

3. 创建 Vectorize 索引

bunx wrangler vectorize create docflare-index --dimensions=384 --metric=cosine

(384 维与 @cf/baai/bge-small-en-v1.5 匹配。索引名称已在 wrangler.toml 中设置。)

4. 设置管理员令牌

管理端点由 bearer token 保护。选一个又长又随机的值:

bunx wrangler secret put ADMIN_TOKEN

5. 部署

bun run deploy

bun run deploy 构建小部件并部署(schema 已在第 2 步应用;每当 migrations/ 新增文件时重新运行 bun run db:migrate)。Wrangler 会打印你的 Worker URL,例如 https://docflare-ai.<your-subdomain>.workers.dev。打开它即可看到挂载了小部件的演示页面。

Deploy to Cloudflare 按钮:本 README 顶部的按钮会将仓库克隆到你的 GitHub 账户,根据 wrangler.toml 配置 Worker、D1 数据库和 Vectorize 索引,询问 ADMIN_TOKEN(来自 .dev.vars.example)并运行 bun run deploy。向导无法从 wrangler.toml 读取的两个值:

  • Vectorize 索引:输入 Dimensions = 384 和 Metric = cosine(表单默认留空;任何其他形状都会破坏索引)。
  • ADMIN_TOKEN:将示例值替换为又长又随机的内容,例如 openssl rand -hex 32。

构建过程不会应用 D1 schema(构建令牌没有 D1 访问权限),因此在首次部署后从你的新仓库运行一次迁移。Cloudflare 在配置期间会将你的 database_id 写入该仓库的 wrangler.toml,因此不需要 .env(如果它仍显示占位符,请执行第 2 步):

git clone https://github.com/<you>/docflare-ai.git && cd docflare-ai
bun install && bunx wrangler login
bun run db:migrate
bunx wrangler vectorize get docflare-index   # expect dimensions: 384, metric: cosine

如果索引缺失或形状不同,请运行第 3 步(先用 bunx wrangler vectorize delete docflare-index 删除它)并重新部署。如果向导没有询问 ADMIN_TOKEN,请运行第 4 步。

为你的文档建立索引

export WORKER_URL="https://docflare-ai.<your-subdomain>.workers.dev"
export ADMIN_TOKEN="the-token-you-set"

curl -X POST "$WORKER_URL/api/sites/index" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"siteId":"my-docs","sitemapUrl":"https://docs.example.com/sitemap.xml"}'

响应:

{
  "siteId": "my-docs",
  "domain": "docs.example.com",
  "sitemapUrl": "https://docs.example.com/sitemap.xml",
  "queued": 142,
  "progress": { "siteId": "my-docs", "total": 142, "pending": 142, "indexed": 0, "failed": 0 },
  "next": "Pending pages are crawled 3 at a time by the cron trigger. Call POST /api/sites/my-docs/process repeatedly to finish faster."
}

更喜欢 UI?打开 https://docflare-ai.<your-subdomain>.workers.dev/admin,粘贴管理员令牌并使用 Register / re-index a site 表单。同一页面会显示每个站点的进度条、一个循环执行 /process 直到队列为空的 Index all pending 按钮,以及最近的问题日志。

现在页面由 cron 触发器自动抓取,每分钟几个。要更快完成,可以从 CLI 自行驱动队列:

until curl -sf -X POST "$WORKER_URL/api/sites/my-docs/process" \
        -H "Authorization: Bearer $ADMIN_TOKEN" | grep -q '"pending":0'; do
  sleep 1
done

随时检查进度:

curl "$WORKER_URL/api/sites/my-docs/status" -H "Authorization: Bearer $ADMIN_TOKEN"

对同一个 siteId 重新运行 /api/sites/index 会重新排队每个页面,因此 cron 任务或 CI 步骤可以让索引保持最新。Vectorize 是最终一致的:新 upsert 的块会在几秒内可搜索。

在将 DocFlare 升级到会改变分块的版本后(参见提交信息中的 changelog),请对每个站点重新索引一次(管理 UI 中的 Re-crawl sitemap 或 POST /api/sites/index),以便页面重新分块。在此之前旧向量仍可正常工作。

嵌入小部件

在已注册域名的任何页面上,于 </body> 之前添加一行:

<script src="https://docflare-ai.<your-subdomain>.workers.dev/widget.js"
        data-site-id="my-docs"
        defer></script>

小部件选项

属性 默认值 描述
data-site-id 必填 你索引的 siteId。
data-api 脚本来源 Worker 的基础 URL,如果你从其他地方(例如 CDN)提供 widget.js。
data-theme auto light、dark 或 auto(跟随 prefers-color-scheme)。
data-color #f6821f 气泡和按钮的强调色。
data-position right right 或 left 角。
data-title Ask AI 面板标题文字。
data-placeholder Ask a question about these docs… 输入框占位符。
data-welcome Hi! Ask me anything… 第一条助手消息。

一个微小的 JavaScript API 以 window.DocFlare 的形式暴露:

DocFlare.open();            // open the panel
DocFlare.close();
DocFlare.ask('How do I deploy?');   // open and submit a question

只有从站点注册的 domain(以及 ALLOWED_ORIGINS 中的任何内容)提供的页面,才能为该 siteId 调用 /api/chat。来自其他源的请求会得到 403。

配置

非机密设置位于 wrangler.toml 的 [vars] 部分:

变量 默认值 用途
MAX_PAGES 200 每次索引运行从 sitemap 中获取的最大 URL 数。
INDEX_BATCH_SIZE 3 每次 cron 触发或 /process 调用时爬取并嵌入的页面数。在 Workers Paid 计划上可提高到 10-20。
TOP_K 4 每个问题交给 LLM 的块数(最多 20)。
RERANK true 在挑选 TOP_K 之前,用 @cf/baai/bge-reranker-base 对 20 个最佳向量匹配进行重排序。false = 仅按向量顺序。
ALLOWED_ORIGINS "" 以逗号分隔的额外源,允许为任何站点调用 /api/chat。* 允许一切。

机密(使用 bunx wrangler secret put NAME 设置):

机密 用途
ADMIN_TOKEN /api/sites/* 的 Bearer 令牌。必需;在设置之前,管理路由会返回 503。

想要更大的模型或不同的嵌入维度?在 src/services/ai.ts 中更改 CHAT_MODEL / EMBEDDING_MODEL,并创建一个维度匹配的 Vectorize 索引。

本地开发

cp .dev.vars.example .dev.vars          # sets ADMIN_TOKEN for local runs
bunx wrangler d1 migrations apply docflare-db --local
bun run dev                             # http://localhost:8787

注意事项:

  • Workers AI 和 Vectorize 没有本地模拟器;wrangler dev 会将这些绑定代理到你的 Cloudflare 账户(你必须已登录)。D1 在本地运行。
  • wrangler dev 使用本地 D1 并忽略 database_id,因此本地运行既不需要 wrangler.toml 中的真实 id,也不需要 .env。只有 bun run deploy 和 bun run db:migrate 需要它。
  • 在 wrangler dev 能使用之前,Vectorize 索引必须已存在于远程。
  • 使用 bun run dev:cron 在本地测试 cron 处理器,然后使用 curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"。
  • bun test 运行单元测试(token 估算、章节分块器、HTML 提取、sitemap 过滤、CORS 策略、重排序器、提示构建器)。
  • bun run build:check 进行类型检查并执行一次 dry-run 打包,而不部署。
  • bun run build:widget 使用 bun build 将 widget/widget.src.js 压缩为 public/widget.js(在编辑小组件后运行)。

API 概览

方法 路径 认证 描述
GET /api/health 无 存活检查 + 模型名称。
POST /api/sites/index 管理员 注册站点并将其 sitemap URL 加入队列。
POST /api/sites/:siteId/process 管理员 爬取并嵌入下一批待处理页面。
GET /api/sites/:siteId/status 管理员 索引进度。
DELETE /api/sites/:siteId 管理员 移除站点、其页面、聊天日志和向量。
POST /api/chat 源检查 提问(JSON 或 SSE 流)。
GET /widget.js 无 可嵌入的小组件。

完整的请求/响应结构、SSE 协议和 OpenAPI 文档位于 docs/API_REFERENCE.md。

保持在免费额度内

一个典型文档站点的粗略预算,基于截至 2026-10-01 的 Cloudflare 定价(请查看 Workers AI、Vectorize、D1 和 Workers 定价页面以获取最新数字):

  • 索引:bge-small 每百万输入 token 消耗 1,841 个神经元,即大约每页 1 个神经元。一个 1,000 页的站点可以轻松容纳在一天的免费配额内。
  • 聊天:回答一个问题大约消耗 30-70 个神经元:提示为 1.5k-3.5k token(系统提示 + TOP_K=4 个约 350 token 的块 + 对话历史),按 13,778 神经元/M 计算;回答为 300-768 token,按 26,128 神经元/M 计算;查询嵌入可忽略不计,而对 20 个候选进行重排序(约 6k token,按 283 神经元/M 计算)约消耗 2 个神经元。每天有 10k 免费神经元,这大约相当于每天免费回答 120-280 个问题。没有相关上下文的问题永远不会触及 LLM,成本约为 0。
  • Vectorize:500 万免费存储维度 / 384 ≈ 13,000 个块 ≈ 所有站点合计 1,000-2,500 页(章节感知的块比固定的 500 token 块更小且数量更多)。每次查询读取 384 个维度,因此即使每天 2,500 个问题也仍低于每月 3000 万查询维度的额度。
  • D1 和 Workers 请求:一次聊天是读取 1 行 + 写入 1 行;每分钟的 cron 每天增加约 1.4k 次读取。两者都远低于免费额度。
  • CPU 时间:Workers 免费计划允许每次调用 10 毫秒 CPU。爬取是 I/O 密集型的,且 HTMLRewriter 是原生的,因此一批 3 个页面不会超出限制;在付费计划上可提高 INDEX_BATCH_SIZE。

超出免费额度

Workers AI 是唯一会超出免费额度的部分。超过每日神经元配额后,AI 绑定会失败,/api/chat 返回 429 rate_limited,直到配额重置。要更进一步,请升级到 Workers 付费计划($5/月):它保留每天 10k 免费神经元,超出部分按每 1,000 个神经元 $0.011 计费。无需更改代码。

每天问题数 计划 Workers AI 每月总计(约)
最多约 280 免费 $0 $0
1,000 付费 $9-24 $14-29
5,000 付费 $56-132 $61-137

超出免费额度后,每个问题约 $0.0004-0.0009。差异来自检索到的块、历史和答案的长度。

要降低每个问题的成本:

  • 将 TOP_K 从 4 降到 3(提示词 token 减少约 25%,模型的上下文略少)。
  • 降低 src/services/ai.ts 中的 MAX_OUTPUT_TOKENS(默认 768);输出 token 是最昂贵的类型。
  • 如果您的网站上后续问题很少,请精简 src/services/ai.ts 中的 MAX_HISTORY_TURNS / MAX_HISTORY_CHARS。
  • 将 CHAT_MODEL 替换为 Workers AI 目录中更便宜的模型;响应解析器同时接受 Llama 风格和 OpenAI 风格的输出。

限制与路线图

  • 针对英语优化的嵌入模型(bge-small-en)。对于多语言文档,请切换到 @cf/baai/bge-m3(1024 维)并重建索引。
  • JavaScript 渲染的网站必须暴露服务器渲染的 HTML(大多数文档生成器都会这样做)。
  • 私有文档尚无身份验证(该小部件面向公共网站)。
  • 计划中:使用 sitemap <lastmod> 进行增量重新爬取、答案上的反馈点赞、Markdown/llms.txt 摄取、管理 UI 中的按站点小部件设置。

项目结构

docflare-ai/
├── docs/
│   ├── images/               # README screenshots
│   ├── ARCHITECTURE.md       # System design & RAG data flow
│   └── API_REFERENCE.md      # REST + SSE endpoint reference, OpenAPI
├── migrations/
│   └── 0000_init.sql         # D1 schema
├── scripts/
│   └── wrangler.ts           # Wrapper: swaps D1_DATABASE_ID from .env (optional) into a gitignored wrangler.local.toml
├── public/
│   ├── _headers              # Static asset headers (CORS / caching for widget.js)
│   ├── admin.html            # Admin UI (served at /admin)
│   ├── index.html            # Demo page
│   └── widget.js             # Minified embeddable widget (built from widget/)
├── widget/
│   └── widget.src.js         # Readable widget source (bun build → public/widget.js)
├── test/
│   └── unit.test.ts          # bun test: chunker, crawler filters, CORS, prompt
├── src/
│   ├── db/schema.ts          # D1 statements, row types, repository
│   ├── services/
│   │   ├── ai.ts             # Workers AI embeddings, RAG prompt, generation
│   │   ├── crawler.ts        # Sitemap fetcher, HTMLRewriter text extractor, chunker
│   │   ├── indexer.ts        # Batch indexing pipeline
│   │   └── vectorize.ts      # Vectorize upsert / query / delete
│   ├── utils/
│   │   ├── cors.ts           # Multi-domain CORS + origin policy
│   │   └── hash.ts           # SHA-256 ids, constant-time compare
│   ├── index.ts              # Hono app, routes, cron handler
│   └── types.ts              # Env bindings & shared types
├── wrangler.toml
├── .dev.vars.example         # ADMIN_TOKEN etc. for `wrangler dev`
├── package.json
├── bun.lock
└── tsconfig.json

贡献

欢迎提交 issue 和 pull request。在打开 PR 之前运行 bun test 和 bun run build:check;它们会运行单元测试、类型检查并试运行打包 Worker。

许可证

MIT © 2026 p10node

来源:Hacker News · github.com