Workflow 系列(08):运营与成本——跨 Phase 成本追踪与故障排查

成本盲区

单个 Skill 的成本很容易算:input_tokens × 单价 + output_tokens × 单价。

工作流里有 7 个 Phase,每个 Phase 可能有多个子 Agent,Phase 4 还有 3 个并发候选。运行一次工作流到底花了多少钱?大多数团队说不清楚,也就没有办法优化。


跨 Phase 成本追踪

在状态文件里记录 Token 消耗

每次子 Agent 调用完成后,把 Token 消耗记录进 workflow_state.json

json 复制代码
{
  "workflow_id": "wf-bug-e2e-AE-33995-20260601",
  "cost_tracking": {
    "phase_1_jira": {
      "model": "claude-sonnet-4-6",
      "input_tokens": 850,
      "output_tokens": 420,
      "cost_usd": 0.0019
    },
    "phase_2_logs": {
      "model": "claude-sonnet-4-6",
      "input_tokens": 3200,
      "output_tokens": 380,
      "cost_usd": 0.0061
    },
    "phase_3_analyze": {
      "model": "claude-opus-4-8",
      "input_tokens": 15000,
      "output_tokens": 500,
      "cost_usd": 0.2625
    },
    "phase_4_candidate_a": {
      "model": "claude-sonnet-4-6",
      "input_tokens": 8000,
      "output_tokens": 2000,
      "cost_usd": 0.046
    },
    "phase_4_candidate_b": {
      "model": "claude-sonnet-4-6",
      "input_tokens": 8100,
      "output_tokens": 1800,
      "cost_usd": 0.0423
    },
    "phase_4_candidate_c": {
      "model": "claude-sonnet-4-6",
      "input_tokens": 8200,
      "output_tokens": 2200,
      "cost_usd": 0.0498
    },
    "total_usd": 0.5145
  }
}

采集方式:调用 LLM API 时,response 对象里有 usage 字段,在子 Agent 完成后写入状态文件。

python 复制代码
def invoke_subagent(phase_id: str, prompt: str, model: str) -> dict:
    response = llm_client.messages.create(
        model=model,
        messages=[{"role": "user", "content": prompt}]
    )
    
    # 计算成本(以 Claude Sonnet 为例,实际单价从配置读取)
    prices = {"claude-sonnet-4-6": {"input": 0.003, "output": 0.015},
              "claude-opus-4-8":   {"input": 0.015, "output": 0.075}}
    
    input_tokens = response.usage.input_tokens
    output_tokens = response.usage.output_tokens
    price = prices[model]
    cost_usd = (input_tokens * price["input"] + output_tokens * price["output"]) / 1000
    
    return {
        "output": response.content[0].text,
        "cost": {
            "model": model,
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
            "cost_usd": round(cost_usd, 4)
        }
    }

成本热点分析

统计几次真实运行的数据后,通常会看到这样的分布:

bash 复制代码
Phase               平均成本   占比
─────────────────────────────────────
phase_3_analyze      $0.26    51%   ← 最贵:使用 Opus,输入大(日志)
phase_4_fix (×3)     $0.14    27%   ← 第二贵:3 个候选并发
phase_2_logs         $0.006    1%
phase_1_jira         $0.002    0.4%
phase_5_commit       $0.003    0.6%
phase_7_notify       $0.002    0.4%
─────────────────────────────────────
总计                 $0.51   100%

Phase 3 占 51% 的成本,原因是两个:使用了 Opus 模型,以及输入包含大量日志文本(15000 tokens)。Phase 4 占 27% 是因为并发跑 3 个候选。

优化方向

Phase 3 降成本:

python 复制代码
# 当前:用 Opus 分析完整日志
phase_3_config = {"model": "claude-opus-4-8", "context": "full_logs"}  # 贵

# 优化方案 A:先用 Sonnet 提取关键行,再用 Opus 分析关键行
# 第一步:Sonnet 提取关键 log lines(便宜)
# 第二步:Opus 分析关键 log lines(输入小,贵但少)
phase_3_config = {
    "pre_filter": {"model": "claude-sonnet-4-6", "task": "extract_key_lines"},
    "analysis": {"model": "claude-opus-4-8", "context": "key_lines_only"}
}

# 优化方案 B:先用 Sonnet 做分析,只有置信度低于阈值才升级到 Opus
phase_3_config = {
    "model": "claude-sonnet-4-6",
    "fallback_model": "claude-opus-4-8",  # 置信度 < 0.7 时升级
    "fallback_threshold": 0.7
}

Phase 4 降成本:

Phase 4 的 3 个候选是为了提高"至少 1 个通过"的概率。如果历史数据显示候选通过率超过 80%,可以先跑 1 个,通过了就不跑剩下的:

yaml 复制代码
phase_4_fix:
  strategy: lazy_parallel    # 先跑 1 个,通过则结束;失败才跑剩余 2 个
  max_candidates: 3
  stop_on_first_pass: true   # 有通过就停

故障排查方法论

Workflow 运行失败时,不要从头翻日志,先判断属于哪一类故障,再用对应的定向检查。

故障分类树

arduino 复制代码
工作流没有完成
├── 从未开始
│   └── 触发条件问题
│       → 检查 AGENTS.md 的触发关键词是否匹配
│       → 检查输入参数格式(jira_key 格式是否正确)
│
├── 在某个 Phase 卡住
│   ├── 子 Agent spawn 失败
│   │   → 检查 sessions_spawn 的参数
│   │   → 检查网络和认证配置
│   │
│   ├── 子 Agent 超时(输出文件不存在)
│   │   → 检查 task prompt 长度(过长导致 LLM 响应慢)
│   │   → 检查模型的 RPM/TPM 限制
│   │
│   └── 子 Agent 失败(输出文件存在但 passed=false)
│       → 读取 output_file 中的 error 字段
│       → 检查对应 template 的输出契约声明
│
├── 在确认门等待超时
│   └── 检查 timeout_action 是否设置为 pause
│       → 如果是 continue,检查默认选项是否正确
│
└── 续接时从错误位置继续
    └── 检查 workflow_state.json 的 phase/step 状态
        → in_progress 的 Phase 会被重新执行(正常)
        → 检查版本绑定(W3 篇)

5 步标准诊断

bash 复制代码
# Step 1:看当前状态
cat $WS/workflow_state.json | python3 -m json.tool | grep -A3 '"phase"'

# Step 2:找第一个未完成的 Phase
cat $WS/workflow_state.json | python3 -c "
import json, sys
state = json.load(sys.stdin)
for phase_id, phase in state['phases'].items():
    if phase.get('status') != 'done':
        print(f'Stuck at: {phase_id} ({phase.get(\"status\", \"unknown\")})')
        break
"

# Step 3:检查该 Phase 的输出文件
ls -la $WS/phase_4/

# Step 4:如果输出文件存在,读取 error 字段
cat $WS/phase_4/candidate_a.json | python3 -c "
import json, sys
r = json.load(sys.stdin)
if not r.get('passed'):
    print('Error:', r.get('error', 'no error field'))
"

# Step 5:(如果有 Trace)在 Langfuse 搜索对应 workflow_id
# 直接在 Langfuse UI 里搜 workflow_id,查看各 Phase 的 Span 详情

常见故障场景速查

场景 1:工作流停在 Phase 3,没有输出文件

ini 复制代码
症状:phase_3 status=in_progress,但 analysis_final.json 不存在
     已等待超过 5 分钟

诊断:子 Agent 超时。可能原因:
  1. task prompt 过长(日志文件全部注入了 prompt)→ 检查 Phase 3 的输入大小
  2. 模型限速 → 检查 API 调用日志
  3. 子 Agent spawn 失败但没有错误记录 → 检查 sessions_spawn 日志

修复:手动设置 phase_3 status=pending,重新触发续接

场景 2:Phase 4 的 3 个候选都 passed=false

bash 复制代码
症状:candidate_a/b/c.json 都存在,但 passed=false
     gate_B 被触发

诊断:修复策略失败。可能原因:
  1. 根因分析有误(analysis_final.json 的 root_cause 不正确)
     → 读取 analysis_final.json 的 root_cause 字段,手动判断是否合理
  2. 测试用例本身有问题(test_runner 报错不是修复代码的问题)
     → 读取 candidate_a.json 的 error 字段

修复:通过 Gate B 的确认门选择"重新分析根因"

月度成本报告

python 复制代码
# tools/cost_report.py
import json
from pathlib import Path
from collections import defaultdict

def generate_monthly_report(state_dir: Path) -> dict:
    """汇总一个月内所有工作流运行的成本"""
    totals = defaultdict(float)
    run_count = 0
    
    for state_file in state_dir.glob("**/workflow_state.json"):
        state = json.loads(state_file.read_text())
        cost_tracking = state.get("cost_tracking", {})
        
        for phase_id, phase_cost in cost_tracking.items():
            if phase_id != "total_usd" and isinstance(phase_cost, dict):
                totals[phase_id] += phase_cost.get("cost_usd", 0)
        
        totals["total"] += cost_tracking.get("total_usd", 0)
        run_count += 1
    
    return {
        "run_count": run_count,
        "total_cost_usd": round(totals["total"], 4),
        "avg_cost_per_run": round(totals["total"] / run_count, 4) if run_count else 0,
        "by_phase": {k: round(v, 4) for k, v in totals.items() if k != "total"},
        "top_cost_driver": max(
            (k for k in totals if k != "total"),
            key=lambda k: totals[k],
            default=None
        )
    }

设计 Checklist

成本追踪

  • 每个子 Agent 调用后把 Token 消耗写入状态文件
  • 状态文件包含 cost_tracking.total_usd 字段
  • 有工具汇总跨运行的成本,识别成本热点 Phase

成本优化

  • 最贵的 Phase 评估是否可以用更便宜的模型(Sonnet 替换 Opus)
  • 并发候选数量有历史通过率数据支撑
  • 高输入量 Phase 评估是否可以先做预过滤再调用

故障排查

  • 熟悉故障分类树(从未开始 / Phase 卡住 / 确认门超时 / 续接位置错误)
  • 能在 5 分钟内用 4 步 shell 命令定位问题 Phase
  • 常见故障场景有对应的修复操作记录

总结

  1. 成本热点通常集中在 1-2 个 Phase:Phase 3(高质量模型 + 大输入)和 Phase 4(并发候选 × 3)加起来通常占总成本的 75%,优化这两个比优化其他所有 Phase 更有效
  2. 故障分类先于排查:遇到问题先判断属于哪一类(从未开始/Phase 卡住/确认门/续接错误),每类都有对应的定向检查,比从头翻日志快得多
  3. 诊断工具要提前准备cost_report.pydiagnose.sh 在问题出现之前就应该存在,出问题时打开即用

欢迎访问 PrimeSkills ------ 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。

更多实用知识和有趣产品,欢迎访问我的个人主页

相关推荐
冬奇Lab1 小时前
开源项目第151期:codex-plugin-cc — 在 Claude Code 里直接调用 OpenAI Codex
人工智能·开源·claude
Weigang1 小时前
用 LlamaIndex 做 RAG 前,先把 Reader、Index、Retriever 的边界写清楚
人工智能·python·开源
轩渃1 小时前
Cline接入国产大模型完整教程(以DeepSeek为例)
人工智能·deepseek·cline
阿新聊ai2 小时前
从 Prompt 到 Loop:AI 编程 Agent 四代循环的演进全景
人工智能·后端
wu8587734572 小时前
从 Obsidian 到 AI 助手:2026年,你的终极本地知识库方案该怎么选?
人工智能
Briwisdom2 小时前
Agent 不是工具调用器——理解 Agent 的工作机制
人工智能·codex·ai-agent·claude code·opencode·agent机制
带娃的IT创业者2 小时前
深度解析 GPT-5.6 Sol:当 AI 模型开始具备“物理世界“的感知力
人工智能·gpt·大模型·技术演进·gpt-5.6·物理世界感知·认知架构
青风972 小时前
16-ADAPTRACK:基于自适应阈值的多目标跟踪匹配算法
人工智能·算法·目标跟踪