Bot 技术架构详解
Bot 技术架构详解
这份文档不是泛泛而谈的“聊天机器人介绍”,而是对当前仓库里这套 bot 实现方式的工程拆解。
目标是说明:
- 这套 bot 由哪些部分组成
- 每一层用了什么技术栈
- 问题从网页发出后是怎么到后端、再到模型的
- 本地运行和线上运行有什么区别
- 哪些文件在实际负责这些事情
1. 这个 Bot 的定位
这套 bot 不是一个通用 agent,也不是带工具调用的复杂智能体。
它更准确的定位是:
- 挂在 Hugo 文档站里的悬浮问答功能
- 基于仓库文档内容回答问题
- 先检索证据,再决定是否调用大模型总结
- 当大模型不可用时,退回到检索式回答
所以它本质上是一个轻量级、面向项目文档的 RAG bot。
2. 总体架构
flowchart TD
A["用户打开文档站"] --> B["Hugo 页面中的悬浮 Bot"]
B --> C["bot-launcher.js"]
C --> D["POST /api/chat"]
D --> E["FastAPI 后端"]
E --> F["从 content/ 检索证据"]
F --> G{"是否配置了 LLM?"}
G -->|是| H["调用 OpenAI-compatible provider"]
G -->|否| I["返回检索式 fallback 回答"]
H --> J["输出答案和引用"]
I --> J
可以把这套架构理解成三层:
- 静态网站层
- 动态问答后端层
- 可选的大模型总结层
3. 前端技术栈
3.1 网站框架
公开网站使用:
HugoHextra
配置入口:
这里主要控制:
- 是否启用 bot
- 公网环境下 bot 请求哪个 API
- 双语内容目录
content/en和content/zh
3.2 Bot 注入方式
bot 不是单独开一个新站,而是通过站点布局局部注入:
这个文件负责:
- 在全站页面插入悬浮 bot 根节点
- 通过
data-*属性注入中英文文案 - 同时暴露:
- 公网 API 地址
- 本地开发 API 地址
3.3 前端交互逻辑
浏览器端逻辑在:
它负责的事情很多:
- 右下角悬浮图标的拖拽
- 展开 / 收起聊天面板
- 记录图标位置到
localStorage - 页面加载后请求
/api/health - 提交问题到
/api/chat - 渲染回答和 citation
- 根据域名判断走本地 API 还是远端 API
也就是说,这个文件本质上就是 bot 的前端控制器。
3.4 样式层
样式在:
控制内容包括:
- 悬浮图标尺寸和位置
- 弹出面板外观
- 消息卡片
- citation 列表
- 移动端适配
4. 后端技术栈
4.1 基础框架
后端使用:
Python 3.13FastAPIUvicornPydantichttpx
依赖文件:
容器入口:
4.2 API 入口
问答 API 在:
暴露的接口有:
/api/health/api/chat/api/reindex
一次完整的问答流程是:
- 检查索引是否存在
- 从知识库检索候选证据
- 清洗输出里的文件路径
- 如果配置了 LLM,就进行证据约束下的总结回答
- 如果没配置或失败,则退回到 fallback
4.3 运行时缓存
本地运行时缓存目录是:
runtime/bot/index.json
这里必须区分两种东西:
runtime/是缓存content/才是知识源
所以:
- 删除
runtime/没关系,启动时会重建 - 删除并推送仓库里的
content/,bot 就失去知识来源
5. 检索层
检索逻辑在:
这部分不是向量数据库,也不是 embedding 检索,而是一个轻量级的词法检索实现。
5.1 知识源收集
来源由:
控制。
规则是:
- 如果配置了
KNOCKOFF_BOT_SOURCES,就按显式路径收集 - 否则默认只用
content/
5.2 文档解析
解析策略是:
- 对 markdown / rst 去掉 markdown 语法
- 正规化空白
- 变成纯文本后再切块
5.3 切块策略
当前切块大致是:
- 每块约
900个字符 - 重叠约
120个字符
切块目标不是做语义压缩,而是让后续检索命中更稳定。
5.4 排序策略
当前排序流程:
- 对问题 tokenize
- 对每个 chunk tokenize
- 统计词频
- 按类 TF-IDF 分数打分
- 排序
- 取 top-k
这是一个刻意保持轻量的设计:
- 不依赖向量库
- 部署简单
- 适合文档量不大的网站 bot
5.5 语言优先规则
因为网站是双语的,所以 bot 做了语言路由:
- 中文问题优先检索
content/zh/ - 英文问题优先检索
content/en/
这一步非常关键。 如果没有这个规则,中英文文档会同时命中,最后模型容易输出混合语言回答。
5.6 Fallback 输出
如果大模型不可用:
- 会从 top chunks 中抽取最匹配的句子
- 直接组成 grounded fallback answer
- 依然返回 citations
所以 bot 不会因为 provider 崩掉就完全不可用。
6. LLM 层
LLM 调用逻辑在:
6.1 接口形式
这层没有绑定某一家 SDK,而是统一走:
- OpenAI-compatible
/chat/completions
这样好处是 provider 很容易切换。
6.2 支持的 provider
provider 默认配置在:
当前支持:
- OpenAI
- Groq
- OpenRouter
- Gemini
- Ollama
它们共享同一套调用路径,区别只是:
base_url- API key 环境变量
model
6.3 Prompt 约束
当前系统提示词明确约束:
- 只能根据提供的 knockoff 证据回答
- 必须与问题语言一致
- 不允许中英文混写
- 需要带 inline citations
- 证据不足时要明确说不足
- 不允许捏造方法或参数
所以这不是自由生成,而是证据约束下的总结回答。
6.4 故障回退
如果 provider 报错:
- backend/app.py 会捕获异常
- 模式会切换为
extractive_fallback - 返回 grounded answer 而不是 500 崩溃
7. 部署架构
7.1 前端静态部署
文档站通过 GitHub Pages 部署:
这个 workflow 会:
- checkout 仓库
- 安装 Hugo
- 构建静态站
- 上传
public/ - 部署到 GitHub Pages
7.2 后端动态部署
bot API 独立部署到 Hugging Face Spaces:
这个 workflow 做的事情是:
- 检查
HF_SPACE_REPO与HF_TOKEN - 准备一个 API-only bundle
- bundle 包含:
backend/content/Dockerfilerequirements.txt- Space metadata README
- 强制推送到 Hugging Face Space 仓库
7.3 为什么要拆成两部分
原因很现实:
- Hugo 站点是静态网站,适合 GitHub Pages
- bot 后端需要 Python 运行环境和 provider secret
- 公网运行不能依赖开发机本地服务
最终形成:
GitHub Pages负责网站Hugging Face Spaces负责 API
8. 本地和线上有什么区别
本地开发
在本地开发时:
- 启动 Hugo 站和本地 FastAPI
bot-launcher.js识别到 localhost- 自动切到
http://127.0.0.1:8000/api
公网环境
线上用户打开网站时:
- launcher 使用
hugo.yaml配置的公开 API 地址 - 请求被发到 Hugging Face Space
这也是为什么同一份前端 HTML 可以同时支持本地和生产。
9. 文件职责地图
站点层
- hugo.yaml:bot 开关、公网 API、双语结构
- layouts/_partials/scripts.html:注入 bot 根节点
- static/bot-launcher.js:前端交互控制器
- assets/css/custom.css:bot 样式
后端层
- backend/app.py:接口和主流程编排
- backend/rag.py:解析、切块、检索、fallback
- backend/llm.py:LLM 调用
- backend/settings.py:provider 和知识源配置
部署层
- Dockerfile:API 容器入口
- requirements.txt:依赖
- .github/workflows/pages.yaml:静态站部署
- .github/workflows/hf-space.yaml:Space 同步
- space/README.md:Space 元信息
10. 当前限制
当前已知限制包括:
- 检索仍然是词法检索,不是 embedding 检索
/api/reindex仍然公开- 启动时还没有做 fingerprint 驱动的索引失效判断
- 没有持久化多轮会话记忆
- 前端还不是流式输出
11. 为什么这套方案适合当前项目
这套方案优先考虑的是:
- 部署简单
- 和仓库文档强绑定
- provider 易切换
- 出故障时可回退
- 兼容静态文档站
它并不追求:
- 通用 agent 能力
- 大规模向量检索基础设施
- 长会话记忆
- 高并发企业级服务
这个取舍是刻意的。
它更像一个挂在科研平台上的实用型文档助手,而不是独立的通用聊天产品。