项目背景
我们使用 Hermes Agent 作为 AI 助手,已经有一个基于 Halo 的博客系统运行在 halo.jikai666.top。随着内容需求增长,我们决定在已有的 WordPress 站点(www.jikai666.top)上也开通技术博客发布能力。
目标:让 Hermes Agent 的 Discord 博客频道同时支持 Halo 和 WordPress 两个平台,用户只需在频道里发内容,Agent 自动完成润色、排版、图片上传和草稿保存。
环境信息
| 组件 | Halo | WordPress |
|---|---|---|
| 站点地址 | halo.jikai666.top | www.jikai666.top |
| API 类型 | Halo UC API (Kubernetes-style) | WordPress REST API |
| 认证方式 | PAT (Bearer Token) | Application Password (Basic Auth) |
| 内容格式 | Markdown + HTML annotation | HTML |
| S3 图片前缀 | / | / |
两个博客共用同一个雨云 S3 桶,按前缀区分文件夹。
- Hermes Agent 版本:最新版(自部署)
- S3 兼容存储:雨云
- Discord 博客频道 ID:
实现过程
第一步:验证 WordPress REST API 连通性
使用 Application Password 进行 HTTP Basic Auth 认证。Application Password 不是 WordPress 登录密码,而是 WordPress 后台专为 REST API 生成的独立密码。
验证流程:
import requests
resp = requests.get(
"https://www.jikai666.top/wp-json/wp/v2/categories",
auth=("<api-user>@<domain>", "<app_password>"),
)
print(resp.status_code) # 200
同时查询了当前用户信息,确认了两个关键用户 ID:
- HermesClaw — API 调用身份
- WaNG — 站点管理员,文章目标作者
第二步:凭据存储到环境变量
WordPress Application Password 存储在 ~/.hermes/.env 文件中:
WP_BASE_URL=https://www.jikai666.top
WP_API_USER=<api-user>@<domain>
WP_APP_PASSWORD=<application_password>
WP_S3_PREFIX=<wp-prefix>/
关键陷阱:os.environ 读不到 .env
Hermes Agent 的 gateway 子进程不会自动加载 .env 文件。直接用 os.environ.get("WP_APP_PASSWORD") 会返回 None,导致 Agent 陷入无限循环尝试各种认证方式。
正确做法是逐行读取文件:
def read_env_var(name: str) -> str:
env_path = os.path.expanduser("~/.hermes/.env")
with open(env_path, "r") as f:
for line in f:
line = line.strip()
if line.startswith(f"{name}=") and not line.startswith("#"):
return line.split("=", 1)[1].strip()
raise ValueError(f"{name} not found in .env")
这个问题在 Halo 集成时已经踩过一次,WordPress 集成时直接复用了这个解决方案。
第三步:创建 wordpress-blog 技能
在 Hermes Agent 的技能系统中创建了一个新技能 wordpress-blog,包含:
- 完整的 WordPress REST API 端点参考(posts、media、tags、categories)
- Markdown 转 HTML 的代码模板(使用 markdown2 库)
- S3 图片上传脚本(boto3,上传到 S3 桶的 / 前缀)
- 草稿优先发布规则(禁止直接 publish)
- 安全规则(禁止自动删除、修改前备份原内容)
第四步:更新 Discord 频道配置
博客频道的 prompt 原来只包含 Halo 的 API 配置。更新后同时包含两个平台的配置信息,并增加了平台选择逻辑:
- 用户说 "WordPress" 或 "WP" → 发布到 WordPress
- 用户说 "Halo" → 发布到 Halo
- 未指定 → 询问用户
同时将 wordpress-blog 技能添加到频道的 skill 绑定列表中,与原有的 halo-blog 并列。
第五步:Markdown 转 HTML
WordPress REST API 的 content 字段接受 HTML 而非 Markdown。文章内容需要先用 markdown2 库转换:
import markdown2
html_content = markdown2.markdown(markdown_text, extras=[
"fenced-code-blocks",
"tables",
"header-ids",
"code-friendly",
])
这与 Halo 不同——Halo 同时接受 Markdown(raw)和 HTML(content),WordPress 只接受 HTML。
第六步:创建草稿
WordPress 的草稿创建比 Halo 简单得多。一个 POST 请求即可:
resp = requests.post(
f"{WP_BASE}/wp-json/wp/v2/posts",
auth=(WP_USER, WP_PASS),
json={
"title": "文章标题",
"content": html_content,
"status": "draft", # 草稿,不直接发布
"excerpt": "文章摘要",
},
)
Halo 需要四步(POST 创建 → GET 草稿 → PUT 更新草稿 → PUT 发布),WordPress 只需一步创建 + 一步发布,流程更简洁。
遇到的问题
问题一:HermesClaw 用户权限不足,无法更改文章作者
文档要求"发布后更改文章作者为 WaNG",但测试发现返回 403 Forbidden:
{
"code": "rest_cannot_edit_others",
"message": "抱歉,您不能作为此用户更新文章。"
}
原因:HermesClaw 用户的角色权限不够,WordPress 要求编辑(editor)或管理员(administrator)角色才能修改文章作者。
问题二:max_tokens 未配置导致回复被截断
在创建技能文件时,Agent 的回复频繁被截断("Response truncated due to output length limit")。
排查发现:火山 CodingPlan 的 GLM-5.2 模型配置中只设了 context_length: 1000000(1M 上下文),但没有设 max_tokens。这导致 API 回退到默认值(约 4096 tokens),远不够输出完整的技能文件。
关键概念区分:
- 上下文窗口(context_length):模型能"读"多少输入,1M tokens
- 最大输出(max_tokens):模型能"写"多少输出,未配置时回退到 API 默认值
这两个参数完全独立,1M 的上下文窗口不意味着 1M 的输出能力。
问题三:YAML 数字键 vs 字符串键
更新 Discord 频道配置时,Python 的 yaml.safe_load 把频道 ID <channel-id> 解析成了整数(int)而非字符串。第一次更新脚本用了字符串键,导致旧配置没被替换而是新建了一个重复条目。
修复方法:统一使用整数键,并在写入前清理可能存在的字符串键。
解决方案
作者权限问题
两个方案供选择:
- 方案 A:在 WordPress 后台把 HermesClaw 用户提升为编辑或管理员
- 方案 B:保持现状,文章以 HermesClaw 名义发布,需要时手动改作者
当前选择方案 B,在技能文档中记录了这个限制。
max_tokens 修复
在 config.yaml 的火山 CodingPlan provider 配置中,给 glm-5.2 模型添加了 max_tokens: 16384:
- name: 火山CodingPlan
base_url: https://ark.cn-beijing.volces.com/api/coding/v3
model: glm-5.2
models:
glm-5.2:
context_length: 1000000
max_tokens: 16384 # 添加这一行
端到端验证
使用 Python requests 完成了完整流程测试:
- 创建草稿(status=draft)→ ✅
- 读取文章(HTML 内容正确)→ ✅
- 发布文章(status=publish)→ ✅
- 删除文章(force=true)→ ✅
Halo vs WordPress API 对比
| 对比项 | Halo 2.x | WordPress |
|---|---|---|
| API 风格 | Kubernetes-style (UC API) | REST API |
| 认证 | Bearer Token (PAT) | Basic Auth (App Password) |
| 内容格式 | Markdown + HTML annotation | 纯 HTML |
| 草稿创建 | 4 步(POST → GET → PUT → PUT) | 1 步(POST) |
| 发布方式 | PUT /{name}/publish | POST /{id} 改 status |
| 分类/标签 | slug 或 name | 数字 ID |
| 媒体上传 | 需单独处理 | POST /wp/v2/media |
| 文章作者 | 固定为创建者 | 固定为 hermesclaw,不切换 |
WordPress 的 API 流程比 Halo 更简洁,但 Halo 的 Markdown 原生支持更好(WordPress 需要额外转换 HTML)。
技能规范审计与修正
初版技能上线后,对照原始需求文档进行了逐项审计,发现并修正了 6 处问题。
修正一:get_post() 增加 context=edit
原 get_post() 未传 context=edit,只能拿到 rendered 字段(经过 WordPress 过滤的 HTML),无法可靠取得修改前的原始正文。
修正后增加 params={"context": "edit"},返回 title.raw、content.raw、excerpt.raw 等原始字段,确保修改前的备份可用于精确回滚。
同时明确:禁止只保存 content.rendered 作为回滚源。修改文章前必须保存以下 6 个字段:title.raw、content.raw、excerpt.raw、status、categories、tags。
修正二:删除 status="any"
原 list_posts() 默认使用 status="any",这不是 WordPress REST API 的标准状态值,行为依赖服务器配置,存在不确定性。
修正后改为显式查询 5 种状态:publish、draft、pending、future、private,分别请求后合并结果,并按文章 ID 去重。
修正三:新增 snapshot_post() 函数
新增 snapshot_post(post) 函数,从 get_post(context=edit) 返回值中提取回滚所需的 6 个原始字段。所有字段使用 .get() 安全访问,避免 KeyError。
update_post() 修改前必须先调用 get_post() 获取原文,再用 snapshot_post() 提取快照保存,修改失败时可据此回滚。
修正四:分类与标签创建限制
Hermes Claw 默认只允许查询现有分类/标签并设置到文章上。禁止自行创建新分类或新标签。
创建分类或标签必须同时满足两个条件:
- 用户明确要求创建
- 已通过实际 API 测试确认 hermesclaw 具有对应权限
若权限未验证或返回 401/403,必须停止并报告,不得升级权限、改用管理员凭据或反复重试。
修正五:文章结构拆分环境信息
原技能规范中"环境信息"被放在"项目背景"的子项中。修正为两个独立的三级标题:### 项目背景 和 ### 环境信息,各自包含独立的内容要求。
修正六:图片处理逻辑明确
明确当前 WordPress 使用第三方主题,已实现自动生成文章封面和随机匹配二次元风格图片。因此:
- Hermes Claw 不负责文章封面管理
- 禁止主动上传封面图片或设置
featured_media - 正文插图优先上传 S3 用外链,不默认上传到 WordPress 媒体库
修正后的 API 参考表
| 操作 | 方法 | 路径 |
|---|---|---|
| 列出文章 | GET | /wp/v2/posts?status={status},分别查询 publish、draft、pending、future、private 后合并 |
| 获取文章 | GET | /wp/v2/posts/{id}?context=edit(获取 raw 字段用于回滚) |
总结
这次集成让 Discord 博客频道具备了双平台发布能力。用户发内容时指定平台即可,Agent 自动处理润色、图片上传、Markdown 转换和草稿保存。
主要收获:
1. WordPress REST API 比 Halo 更简单,草稿创建只需一个请求
2. Application Password 认证方式安全可靠,不影响正常登录
3. .env 读取陷阱在两个平台间复用了解决方案
4. max_tokens 和 context_length 是两个独立参数,都需要显式配置
5. WordPress 用户角色权限需要在后台提前配置好
6. status="any" 不是标准 API 参数,应显式查询各状态
7. 修改文章前必须用 context=edit 获取 raw 字段并保存快照
8. 分类和标签创建需要双条件:用户明确要求 + 权限已验证
S3 存储采用文件夹隔离策略(<halo-prefix>/ 和 <wp-prefix>/),共用一个桶,管理简单且成本可控。







Comments NOTHING