Commit 1a53ae75 authored by 郭阔's avatar 郭阔

feat(harvest): automatic retry + fallback provider for failed runs

attempt_needs_retry triggers on runtime error / empty output / non-object
result (GLM StructuredOutput text-return case) / is_error (1301 etc);
content-quality failures do NOT trigger (a provider switch won't fix them).
Flow: attempt1 primary → attempt2 same provider (10s backoff) → attempt3
FALLBACK_PROVIDER via resolve_continue_provider with its budget override
(ccswitch-gpt=30). Idempotent end-to-end (7-day registry dedup + slug-keyed
push), so re-runs only fill gaps; per-attempt raw outputs kept as *.attemptN.
Verified decision matrix: success/runtime-error/invalid-output/api-error/
business-fail.
parent 20ce81f8
......@@ -102,9 +102,11 @@ echo "采集中" > "$LOG_DIR/${SOURCE}.phase"
# 无人在场时防挂死:claude 卡住(网络/服务无响应)最迟 MAX_RUNTIME_MIN 分钟被终止,
# 走正常失败路径 → trap 回收浏览器实例,不阻塞次日 launchd 触发。
# 可在栏目 conf 覆盖(如 MAX_RUNTIME_MIN=90),默认 60。
set +e
MAX_RUNTIME_MIN="${MAX_RUNTIME_MIN:-60}"
claude -p \
run_claude_once() { # 用当前 CLAUDE_SETTINGS_ARGS 执行一次采集会话(带看门狗)
set +e
claude -p \
"${CLAUDE_SETTINGS_ARGS[@]}" \
--permission-mode bypassPermissions \
--allowed-tools Bash Write \
......@@ -124,8 +126,8 @@ claude -p \
- 列表页候选与 registry(近7天)对照后,若所有候选均已在库(无任何新文章):立即收尾返回结果——articles 留空、succeeded_count=0、source_exhausted=true、exhaustion_reason="all_seen",不要再做单篇核验、摘要或重复确认,这是正常且成功的结果。
${RESULT_REQUIREMENTS}" \
> "$JSON_OUT" 2> "$RAW_OUT" &
_claude_pid=$!
(
_claude_pid=$!
(
_deadline=$(( SECONDS + MAX_RUNTIME_MIN * 60 ))
while kill -0 "$_claude_pid" 2>/dev/null; do
if [ "$SECONDS" -ge "$_deadline" ]; then
......@@ -137,11 +139,50 @@ _claude_pid=$!
fi
sleep 15
done
) &
_watchdog_pid=$!
wait "$_claude_pid"
RC=$?
kill "$_watchdog_pid" 2>/dev/null
) &
_watchdog_pid=$!
wait "$_claude_pid"
RC=$?
kill "$_watchdog_pid" 2>/dev/null
}
attempt_needs_retry() { # 运行错误/输出无效/API 报错 → 重试;内容质量类失败不触发
if [ "$RC" -ne 0 ]; then return 0; fi
if [ ! -s "$JSON_OUT" ]; then return 0; fi
command -v jq >/dev/null 2>&1 || return 1
jq empty "$JSON_OUT" 2>/dev/null || return 0
jq -e '(.result | type) == "object"' "$JSON_OUT" >/dev/null 2>&1 || return 0
if jq -e '.is_error == true' "$JSON_OUT" >/dev/null 2>&1; then return 0; fi
return 1
}
# 失败自动恢复:①失败(运行错误/输出无效/API 错)→ ②同 provider 重试 →
# ③仍失败切备用 provider(FALLBACK_PROVIDER,如 ccswitch-gpt/Claude)重跑。
# 数据侧全程幂等(registry 按近7天去重、push 按 slug 去重),重跑只补缺不重复;
# 各次尝试的原始输出保留为 *.attemptN 供审计。
for ATTEMPT in 1 2 3; do
if [ "$ATTEMPT" = "2" ]; then
cp -f "$JSON_OUT" "${JSON_OUT}.attempt1" 2>/dev/null || true
cp -f "$RAW_OUT" "${RAW_OUT}.attempt1" 2>/dev/null || true
echo "[attempt 2/3] 同 provider 重试(${USED_PROVIDER:-default})…"
fi
if [ "$ATTEMPT" = "3" ]; then
if [ -n "${FALLBACK_PROVIDER:-}" ] && [ "${FALLBACK_PROVIDER}" != "${PROVIDER:-}" ]; then
cp -f "$JSON_OUT" "${JSON_OUT}.attempt2" 2>/dev/null || true
cp -f "$RAW_OUT" "${RAW_OUT}.attempt2" 2>/dev/null || true
resolve_continue_provider # PROVIDER → FALLBACK_PROVIDER,重建 CLAUDE_SETTINGS_ARGS
resolve_max_budget_usd # 按备用 provider 的预算覆盖(如 ccswitch-gpt=30)
echo "[attempt 3/3] 切换备用 provider:${USED_PROVIDER}…"
else
break # 无可用备用通道,不再空转
fi
fi
echo "采集中" > "$LOG_DIR/${SOURCE}.phase"
run_claude_once
if ! attempt_needs_retry; then break; fi
if [ "$ATTEMPT" = "3" ]; then break; fi
sleep 10
done
set -e
# 如果 claude 完全没输出(可能 PATH 未生效),写入诊断信息到 RAW_OUT
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment