ChatHexo 是一套给 Hexo 博客加入 AI 问答能力的完整方案,由前端插件 hexo-chathexo 和后端服务 chathexo-server 两部分组成。本文从架构角度出发,梳理各组件的职责划分、博客索引的生成方式、请求的完整链路以及部署方案。如果你是想直接上手使用的读者,可以先参考 ChatHexo 安装和使用指南


整体架构

ChatHexo 的架构可以分为三个层次:运行在博客构建阶段的前端插件、部署在服务器上的后端服务,以及连接两者的网络链路。

  • hexo-chathexo 是 Hexo 插件,在 hexo generate 阶段把聊天组件的 CSS、JS 和配置参数注入到每个静态页面中。
  • chathexo-server 是 FastAPI 后端服务,启动时扫描博客文章目录生成服务端索引,并对外提供聊天 API。
  • 用户在博客页面提问时,浏览器将请求发送到 Nginx,经反向代理转发给后端,由 LangGraph Agent 调用工具搜索索引并借助 LLM 生成回答。

前端插件:hexo-chathexo

hexo-chathexo 是一个标准的 Hexo 插件,安装后不修改任何博客源文件,所有前端资源都在 hexo generate 阶段通过 Hexo 的扩展机制注入到生成的静态文件中。

插件利用三类 Hexo 扩展点完成工作:

  • before_generate 过滤器:在生成开始前合并用户配置与插件默认配置。
  • injector:向每个页面的 <head> 末尾注入 CSS 链接和若干 <meta> 标签(包含 API 地址、索引文件路径、聊天窗口标题等),向 <body> 末尾注入 chathexo.js 脚本。
  • generator:将插件自带的 chathexo.csschathexo.js 复制到生成目录的 public/chathexo/ 路径下。

chathexo.js 在浏览器中运行时,先从页面的 <meta> 标签读取 API 地址等配置,然后在页面右下角挂载一个悬浮聊天按钮和对话面板。界面启动时会调用 /models 接口获取可用模型列表,让用户可以在界面上切换使用不同的 LLM。用户发送消息后,脚本以 POST 请求调用后端聊天接口,将 Agent 返回的 Markdown 格式回答渲染后展示在对话面板中,同时展示工具调用记录(默认折叠,可展开查看)。

当后端不可用时,前端会降级到本地兜底模式:从 chathexo/index.json 下载客户端索引,在浏览器内做简单的关键词匹配,返回相关文章列表作为备用响应。


后端服务:chathexo-server

chathexo-server 是基于 FastAPI 构建的后端服务,默认绑定 127.0.0.1:4317,只接受来自本机 Nginx 的转发请求。对外暴露以下接口:

  • /chathexo-api/health:健康检查,确认服务是否正常运行。
  • /chathexo-api/models:返回当前配置的可用模型列表和默认模型。
  • /chathexo-api/chat:核心聊天接口,接收用户问题、会话 ID 和模型选择,调用 LangGraph Agent 生成回答,返回答案文本和工具调用记录。
  • /chathexo-api/visit:页面访问记录接口,用于统计访客来源和地理位置。

服务支持配置多个 LLM 提供方,只要对方提供兼容 OpenAI 格式的接口(如 Qwen、Kimi、OpenAI 等),填入 base_urlapi_key 即可接入。每次聊天请求都会记录客户端 IP、地理位置、来源页面、使用的模型和用户问题,写入日志文件便于分析。


Agent 工具层

后端的核心是一个基于 LangGraph create_agent 构建的 Agent,每次请求会用指定模型初始化并通过 MemorySaver 持久化会话状态。同一个 thread_id 代表同一次对话,Agent 会记住历史消息,支持多轮问答。

Agent 目前配备了四个工具:

  • grep_tool:在博客索引中做关键词搜索,支持逗号或空格分隔的多关键词 OR 搜索,返回匹配文章的标题和链接,建议每次提供 5-10 个相关词以扩大覆盖范围。
  • list_all_posts:列出所有博客文章的标题和路径,适合回答”有哪些文章”类的问题。
  • list_recent_posts:按更新时间倒序列出最近若干篇文章,适合回答”最近写了什么”类问题。
  • get_post_content:根据标题或路径获取指定文章的完整正文内容,供 Agent 深入阅读后针对具体内容作答。

Agent 在处理问题时,会根据问题类型灵活组合这些工具:先用 grep_tool 粗筛相关文章,再用 get_post_content 精读具体内容,最后结合 LLM 生成有上下文的回答。工具调用的过程和结果都会原样返回给前端,用户可以在聊天界面展开查看 Agent 的检索过程。


博客索引

博客索引是 ChatHexo 实现内容检索的基础,由 generate_index.py 在服务启动时自动生成,保存为后端项目目录下的 data/index.json

生成过程如下:

  • 扫描配置文件中指定的所有 _posts 目录,递归遍历其中的 .md 文件。
  • 解析每篇文章的 YAML Front Matter,提取标题、发布时间、abbrlink、标签、分类等元信息。
  • 清理文章正文内容,移除图片链接、HTML 标签和 Hexo 模板标签({% ... %}),得到适合检索的纯文本。
  • 按发布时间倒序排列所有文章,输出包含完整正文的 index.json 文件。

索引采用直接的磁盘文件形式,工具层通过字符串匹配检索内容,无需搭建向量数据库或分词服务。这一设计的好处是部署依赖极少,对个人博客规模的数据量而言,检索速度也完全够用。


请求链路

以用户在博客页面发送一个问题为例,完整的请求链路如下:

整个链路的关键设计是:用户只感知域名和路径;Nginx 是唯一的对外入口,同时负责静态文件服务和 API 反向代理;chathexo-server 只监听本机地址,外部无法直接访问。


部署架构

博客运行在宝塔面板管理的 Nginx 上,监听 50001 端口,通过 Cloudflare Tunnel 实现内网穿透,公网用户通过 tianlejin.top 访问。

几个关键的设计点:

  • 只需一条 Cloudflare Tunnel 指向 localhost:50001,博客静态文件和聊天 API 共用同一个入口,无需额外端口映射。
  • HTTPS 由 Cloudflare 在边缘统一处理,隧道内部只走 HTTP,本机无需配置 SSL 证书。
  • chathexo-server 绑定 127.0.0.1:4317,只有通过 Nginx /api_chat_hexo/ 路径代理的请求才能到达后端,安全隔离。
  • Nginx 直接指向 Hexo 的 public 目录,更新博客内容只需执行 hexo generate,无需重启 Nginx 或重新部署。
  • 重新生成博客后,如果文章有变化,需要重启 chathexo-server 以触发索引重建,使新文章内容进入检索范围。

参考资料