目录

OpenClaw 博客流水线 v2.7 完整迭代:从 11:31 到 20:20 的 9 小时

记录一个完整的 blog-pipeline 迭代周期:模型降级、路由规则、可观测性增强

背景:从 11:31 到 20:20 的 9 小时迭代

2026 年 8 月 2 日,一个普通的周日,我从 11:31 开始折腾 blog-pipeline,一直搞到 20:20。9 个小时,从 v2.2 一路迭代到 v2.7,改了 5 个问题,踩了 3 个坑,学了一堆教训。

这篇博客就是记录这 9 小时的完整经过——不是为了炫耀,而是为了下次遇到类似问题能直接翻出来看。

💡 与 v2.5 文章的关系:本文记录了从 v2.2 到 v2.7 的完整迭代全景,其中问题四(路由规则升级到 v2.5)的详细分析已在 [[OpenClaw 博客流水线 v2.5:从模型降级到路由规则的完整修复]] 中展开。两篇互为补充,建议交叉阅读。

时间线流程图,从左到右展示 9 小时的迭代时间线,11:31 开始到 20:20 结束,标注 v2.2→v2.7 的版本演进节点,每个节点用不同颜色标识

问题一:子 Agent 模型降级(step-router-v1 → agnes-2.5-flash)

症状

故事从一次模型调用异常开始。我在跑博客流水线时发现,design-image-prompt-engineer 这个子 agent 居然在用 step-router-v1 模型。这是一个明显过时的小模型,生成的图片提示词质量惨不忍睹——构图描述模糊、光线方向缺失、焦段参数随机。

根因排查

顺着日志往上翻,发现配置链是这样的:

# agents.defaults.subagents → 所有子 agent 继承
agents:
  defaults:
    subagents:
      model: "step-router-v1"  # ← 坑在这里

agents.defaults.subagents.model 配置的默认值居然是 step-router-v1,从 OpenClaw 的默认配置继承下来的。我从来没改过这个字段,所以所有子 agent 都被静默降级了。

配置继承关系图,展示 agents.defaults.subagents.model 的配置链,从默认配置到子 agent 的实际生效路径,用箭头标注继承关系

修复

agents:
  defaults:
    subagents:
      model: "sapiens/agnes-2.5-flash"  # 改为主力模型

改完后重启 Gateway,重新跑了一次流水线,design-image-prompt-engineer 的输出质量立刻恢复正常。

教训

agents.defaults.subagents.model 这个配置非常隐蔽。它不像主 agent 的 model 字段那样显眼,但影响力巨大——所有子 agent 都继承它。如果你从来没显式设置过,赶快去检查一下。

问题二:中文 Slug 的 URL 编码陷阱

症状

鬼谷子那篇文章写完后,部署到 Cloudflare Pages 直接 404。URL 里出现了 %8N%E9%AC%BC%E8%B0%B7... 这种编码字符串,浏览器显示乱码,分享链接更是惨不忍睹。

根因

Slug 生成逻辑太粗糙了:

# 旧代码
slug = title.lower().replace(" ", "-")  # 直接用中文标题

中文标题转 slug 后变成 %E9%AC%BC%E8%B0%B7...,Cloudflare Pages 的静态文件路由不支持中文 URL 编码,直接 404。

修复

从内容中提取英文关键词,自动生成 slug:

STOP_WORDS = {"the", "and", "for", "blog", "post", "openclaw", "with", "from"}

def generate_slug(title, content):
    """生成英文 slug,必须是英文字母、数字和短横线"""
    ts = str(int(time.time()))[-4:]
    if any('\u4e00' <= c <= '\u9fff' for c in title):
        # 从内容中提取英文关键词
        words = re.findall(r'[a-zA-Z]+', content)
        if words:
            meaningful = [w.lower() for w in words 
                         if len(w) > 3 and w.lower() not in STOP_WORDS]
            slug = '-'.join(meaningful[:4]) if meaningful else 'blog-post'
        else:
            slug = 'blog-post-' + ts
    else:
        slug = re.sub(r'[^a-zA-Z0-9]+', '-', title).lower().strip('-')
    if len(slug) < 3:
        slug = 'blog-post-' + ts
    return slug

核心逻辑:如果标题包含中文,就从正文中提取前 4 个有意义的英文词拼成 slug。如果正文一个英文词都没有,就用时间戳兜底。

教训

Slug 必须只有英文字母、数字和短横线。 这是静态网站的硬性要求,不是审美问题。

问题三:图片路径规范化

症状

图片路径有多处不一致:有的地方用 {slug}.webp,有的地方用 {slug}-cover.webpfeatured-image 的资源 name 和 src 也不匹配。

修复

统一为 {slug}.webp

resources:
  - name: "featured-image"
    src: "{slug}.webp"
  - name: "featured-image-preview"
    src: "{slug}.webp"
featuredImage: "/images/Code-Art-Studio-images/{slug}/{slug}.webp"
featuredImagePreview: "/images/Code-Art-Studio-images/{slug}/{slug}.webp"
路径规范化对比图,左侧展示旧路径的混乱状态(多种命名格式混合),右侧展示规范化后的统一格式({slug}.webp),用红色标记旧路径问题,绿色标记新路径

问题四:路由规则升级到 v2.5

背景

之前的分类路由规则只有两级:技术 → Code Art Studio,非技术 → Digital Asset。但 Code Art Studio 下的文章越来越多,全部堆在 code-art-studio/ 根目录下,管理起来很痛苦。

修复

v2.5 引入了更细粒度的路由规则:

# 目录路由规则
obsidian_routes = {
    "配置": ["配置", "config", "settings"],
    "排障": ["排障", "debug", "fix", "error", "problem"],
    "工作流": ["workflow", "pipeline", "DAG", "自动化", "流程"],
    "架构": ["架构", "architecture", "拓扑", "设计", "多智能体"],
    "技能": ["skill", "技能", "插件", "plugin"],
    "核心概念": ["概念", "基础", "introduction", "入门"],
}

# Hugo 发布目录路由
openclaw_keywords = ["openclaw", "agent", "workflow", "DAG", "多智能体", 
                     "技能", "skill", "配置", "排障", "Gateway", "MCP"]
is_openclaw = any(kw.lower() in content.lower() for kw in openclaw_keywords) and tech_score >= 3

category_config = {
    "post_dir": "/data/oklifeme/content/posts/code-art-studio/openclaw/" 
                if is_openclaw 
                else "/data/oklifeme/content/posts/code-art-studio/",
}

路由逻辑:

  • OpenClaw 相关(命中 OpenClaw 关键词 + 技术分 ≥ 3)→ code-art-studio/openclaw/
  • 其他技术文章 → code-art-studio/ 根目录
  • 非技术文章 → digital-asset/

问题五:可观测性增强 v2.6-v2.7

为什么需要可观测性

之前跑流水线就像在盲打。不知道每个 Phase 花了多长时间,不知道每个 agent 消耗了多少 token,出错了也不清楚是在哪个环节。

增强方案

v2.6 引入了完整的可观测性系统:

1. Phase 耗时记录

def log_phase_start(phase_name):
    run_state["phases"][phase_name] = {"start": time.time(), "status": "running"}

def log_phase_end(phase_name, success=True, tokens=None):
    elapsed = time.time() - run_state["phases"][phase_name]["start"]
    run_state["phases"][phase_name].update({
        "end": time.time(),
        "elapsed_s": round(elapsed, 2),
        "status": "success" if success else "failed"
    })

2. Token 消耗追踪

记录每个 agent 的 input/output tokens,汇总到 run_state 中,最终输出到摘要。

3. 错误日志结构化

def log_error(phase_name, error_msg):
    error = {
        "phase": phase_name,
        "timestamp": datetime.now().isoformat(),
        "message": str(error_msg)
    }
    run_state["errors"].append(error)
    logger.error(f"[ERROR] {phase_name}: {error_msg}")

4. 运行状态摘要

运行摘要示例截图,展示完整的运行摘要输出,包含 Trace ID、Total Time、每个 Phase 的耗时、Token 统计、错误列表,用不同颜色高亮各部分

完成后输出完整的执行摘要:

=== Blog Pipeline Run Summary ===
Trace ID: 20260802-193000
Total Time: 180.5s
Status: success

Phases:
  Phase 1: 写稿 — 30.0s ✅
  Phase 2a: 质检 — 25.0s ✅
  Phase 2b: SEO — 20.0s ✅
  Phase 3: 改稿 — 35.0s ✅
  Phase 3.5: 提示词 — 15.0s ✅
  Phase 4: 生图 — 55.5s ✅

Tokens:
  Input: 45,000
  Output: 12,000
  Total: 57,000

Errors: 0
Log file: /data/blog_pipeline_logs/blog-20260802-193000.log

日志路径变更

v2.6 时日志路径是 /tmp/blog_pipeline_logs/,用户反馈后改为 /data/blog_pipeline_logs/(v2.7),避免系统重启后日志丢失。

总结:9 小时迭代全景

#问题修复版本影响
1子 agent 模型降级配置 agents.defaults.subagents.modelv2.2修复图片质量
2中文 Slug 部署失败英文关键词自动生成 slugv2.4修复部署
3图片路径不一致统一为 {slug}.webpv2.4规范管理
4目录结构混乱细粒度路由规则v2.5管理效率
5缺少可观测性Phase 计时 + Token 追踪 + 错误日志v2.6-v2.7运维效率

关联阅读

  • [[OpenClaw 博客流水线 v2.5:从模型降级到路由规则的完整修复]]
  • [[OpenClaw 多智能体博客流水线 v3.0:破解 3 层 Subagent 嵌套限制的 2 层 DAG 架构演进]]
  • [[OpenClaw 博客流水线 v2.1 修复全记录:模型降级、动态路由与并发限制规避]]

这 9 小时最大的收获不是写了多少代码,而是建立了一个可观测、可诊断、可修复的博客流水线。每次出问题,日志会告诉你哪里慢了、哪里错了、哪里该优化。下次再跑流水线,不再是盲打,而是有数据支撑的工程决策。