【软件工程】ChatHexo 架构设计
ChatHexo 是一套给 Hexo 博客加入 AI 问答能力的完整方案,由前端插件 hexo-chathexo 和后端服务 chathexo-server 两部分组成。本文从架构角度出发,梳理各组件的职责划分、博客索引的生成方式、请求的完整链路以及部署方案。如果你是想直接上手使用的读者,可以先参考 ChatHexo 安装和使用指南。
整体架构
ChatHexo 的架构可以分为三个层次:运行在博客构建阶段的前端插件、部署在服务器上的后端服务,以及连接两者的网络链路。
graph TB
subgraph 构建时
Posts[source/_posts<br/>Markdown 文章]
HexoPlugin[hexo-chathexo 插件<br/>hexo generate]
Posts --> HexoPlugin
HexoPlugin --> Public[public/<br/>HTML + CSS + JS]
end
subgraph 浏览器
JSUI[chathexo.js<br/>聊天 UI]
end
subgraph 服务端
FastAPI[chathexo-server:4317<br/>FastAPI]
Agent[LangGraph Agent]
Tools[工具层<br/>grep / list / get_content]
Idx[data/index.json<br/>博客全文索引]
LLM[LLM 服务<br/>OpenAI 兼容接口]
FastAPI --> Agent
Agent --> Tools
Tools <--> Idx
Agent <-->|API 调用| LLM
end
Public --> JSUI
JSUI -->|POST /api_chat_hexo/...| Nginx[nginx:50001]
Nginx --> FastAPI
FastAPI -->|JSON 响应| JSUI
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.css和chathexo.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_url 和 api_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文件。
索引采用直接的磁盘文件形式,工具层通过字符串匹配检索内容,无需搭建向量数据库或分词服务。这一设计的好处是部署依赖极少,对个人博客规模的数据量而言,检索速度也完全够用。
请求链路
以用户在博客页面发送一个问题为例,完整的请求链路如下:
sequenceDiagram
participant 用户 as 用户浏览器
participant CF as Cloudflare Tunnel
participant Nginx as Nginx:50001
participant FastAPI as chathexo-server:4317
participant Agent as LangGraph Agent
participant Tools as 工具层
participant LLM as LLM 服务
用户->>CF: 访问 tianlejin.top,加载静态页面
CF->>Nginx: 内网穿透转发
Nginx-->>用户: 返回 HTML/CSS/chathexo.js
用户->>CF: POST /api_chat_hexo/chathexo-api/chat
CF->>Nginx: 内网穿透转发
Nginx->>FastAPI: proxy_pass http://127.0.0.1:4317/chathexo-api/chat
FastAPI->>Agent: agent_answer(query, thread_id, model_id)
Agent->>Tools: 调用工具搜索博客索引
Tools-->>Agent: 返回匹配文章内容
Agent->>LLM: 发送问题和检索结果
LLM-->>Agent: 生成回答
Agent-->>FastAPI: 返回 answer + tool_calls
FastAPI-->>Nginx: JSON 响应
Nginx-->>CF: 转发
CF-->>用户: 展示回答和工具调用记录
整个链路的关键设计是:用户只感知域名和路径;Nginx 是唯一的对外入口,同时负责静态文件服务和 API 反向代理;chathexo-server 只监听本机地址,外部无法直接访问。
部署架构
博客运行在宝塔面板管理的 Nginx 上,监听 50001 端口,通过 Cloudflare Tunnel 实现内网穿透,公网用户通过 tianlejin.top 访问。
graph TB
User[公网用户] -->|"https://tianlejin.top"| CF[Cloudflare CDN + Tunnel]
CF -->|"内网穿透到 localhost:50001"| Nginx
subgraph 本机
Nginx["nginx:50001<br/>宝塔面板管理"]
Nginx -->|"/*<br/>静态文件直接服务"| Public["txm_blog/public/<br/>Hexo 生成的静态站点"]
Nginx -->|"location /api_chat_hexo/<br/>反向代理"| ChatHexo["chathexo-server:4317<br/>仅监听 127.0.0.1"]
ChatHexo --> Idx["data/index.json<br/>博客全文索引"]
end
ChatHexo -->|"OpenAI 兼容 API"| LLM[LLM 服务]
几个关键的设计点:
- 只需一条 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以触发索引重建,使新文章内容进入检索范围。
参考资料
- ChatHexo 安装和使用指南: https://tianlejin.top/blog/ChatHexo/
- hexo-chathexo GitHub 仓库: https://github.com/Telogen/hexo-chathexo
- chathexo-server GitHub 仓库: https://github.com/Telogen/chathexo-server
