DocFlare AI 开源文档问答机器人,全部跑在 Cloudflare 免费额度上
Show HN: DocFlare AI – Open-source docs chatbot on Cloudflare's free tier
DocFlare AI 是一个开源、零成本的文档站搜索与问答组件,完全基于 Cloudflare 原生服务,只需在文档页加一行 script 标签即可获得带来源引用的流式 RAG 问答,无需 OpenAI key、Pinecone 或服务器。
开源、零成本的 AI 站点搜索与聊天机器人组件。100% Cloudflare 原生。
在你的文档站点上放置一个 <script> 标签,就能获得一个流式 RAG 聊天机器人,它基于你的内容作答,并附上来源。
无需 OpenAI 密钥。无需 Pinecone。无需服务器。无需付费。
截图
| 嵌入文档站点的聊天组件 | 管理界面(/admin) |
|---|---|
![]() |
![]() |
左侧是带来源标签的流式回答;右侧是内置管理页面,用于注册站点地图、驱动索引并查看用户提问。
为什么
托管的“与你的文档对话”组件(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 login2. 创建 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。
许可证
来源:Hacker News · github.com

