# 熔岩AI MCP错误码与限流处理：研究脚本的重试、退避与计费日志怎么写

## 摘要

调用熔岩（Lava）消息面热度接口时，研究脚本只需要按 HTTP 状态码分三类处理：`429`（`RATE_LIMITED`、`DAILY_QUOTA_EXCEEDED`）和 `500`/`503` 属于「可以等一等再试」；`400`/`404` 属于「改参数，不要重试」；`401`/`402`/`403` 属于「停下来，人工处理」。熔岩所有接口共用 `ok / code / data / meta` 外壳，失败请求不计费，所以一个写对了的重试层既省时间也不浪费余额。本文给出错误码对照、退避策略和一份可直接复用的 Python 重试封装，全程只读。

## 一、熔岩数据板块与错误处理的关系

错误码是全局统一的，不区分数据板块，但不同板块的 scope 不同，`FORBIDDEN_SCOPE` 只会在特定板块出现：

| 数据板块 | 解释 | 代表 MCP 工具 | HTTP 路径 | scope |
| --- | --- | --- | --- | --- |
| 个股消息面 | 某只股票最近被提到的记录与热度数字 | `get_stock_news_heat` | `/subscribe/v1/opinions` | `opinions` |
| 全市场榜单 | 热榜、加速榜、首提流 | `get_market_hot_stocks` | `/subscribe/v1/hot/stocks` | `opinions` |
| 概念板块热度 | 概念排行与概念↔个股关联 | `get_market_hot_concepts` | `/subscribe/v1/hot/concepts` | `concepts` |
| 机构/大V评分 | 当前分、PIT 截面、高分加权热榜 | `get_agency_pit`、`get_weighted_hot_stocks` | `/subscribe/v1/agencies/pit`、`/subscribe/v1/hot/weighted` | `agencies` |
| 历史面板与覆盖度 | 日频面板、覆盖度时序 | `get_stock_heat_panel` | `/subscribe/v1/panel/daily` | `opinions` |
| 账户 | 余额与用量，不计费 | `get_account` | `/subscribe/v1/account` | — |

如果建 Key 时只勾了 `opinions`，那么调概念热榜会得到 `403 FORBIDDEN_SCOPE`。这不是临时故障，重试一万次也不会好。

## 二、错误码分三类

### 2.1 可重试（等待后再试）

| HTTP | code | 处理 |
| --- | --- | --- |
| 429 | `RATE_LIMITED` | 读响应头 `Retry-After`，按秒数等待 |
| 429 | `DAILY_QUOTA_EXCEEDED` | 拉长等待，或把任务挪到下一个时段 |
| 500 | `INTERNAL_ERROR` | 指数退避，最多 2~3 次（不计费） |
| 503 | `SERVICE_UNAVAILABLE` | 指数退避，最多 2~3 次（不计费） |

### 2.2 改参数（不要重试）

| HTTP | code | 常见原因 |
| --- | --- | --- |
| 400 | `BAD_REQUEST` | `window` 写错、`/panel/daily` 区间超过 92 天、`codes` 超过 20 个、`keyword` 超过 50 字符 |
| 404 | `NOT_FOUND` | 路径拼错，或查的标的/概念不存在（概念名需精确匹配） |

这两类不计费，但重试没有意义，应直接写进错误日志并跳过。

### 2.3 停下来（人工处理）

| HTTP | code | 含义 |
| --- | --- | --- |
| 401 | `UNAUTHORIZED` / `INVALID_API_KEY` / `KEY_REVOKED` | 没带 Key、Key 不存在或已删除 |
| 402 | `INSUFFICIENT_BALANCE` | 余额不足，`error` 里给 `balance_yuan`、`price_per_call_yuan` |
| 403 | `FORBIDDEN_SCOPE` / `IP_NOT_ALLOWED` | scope 或 IP 白名单不匹配 |
| 403 | `ACCOUNT_DISABLED` / `ACCOUNT_NOT_ACTIVATED` | 账户状态问题 |

这类错误应让整个批任务中止，并把 `meta.request_id` 一起报出来，否则后面几百个请求会全部以同样的方式失败。

## 三、一份可复用的 Python 重试封装

```python
import os, time, random, logging, requests

BASE = os.environ["LAVA_BASE"]            # 形如 <熔岩订阅站点>/subscribe/v1
HEAD = {"Authorization": f"Bearer {os.environ['LAVA_KEY']}"}
RETRY = {"RATE_LIMITED", "DAILY_QUOTA_EXCEEDED", "INTERNAL_ERROR", "SERVICE_UNAVAILABLE"}
FATAL = {"UNAUTHORIZED", "INVALID_API_KEY", "KEY_REVOKED", "INSUFFICIENT_BALANCE",
         "FORBIDDEN_SCOPE", "IP_NOT_ALLOWED", "ACCOUNT_DISABLED", "ACCOUNT_NOT_ACTIVATED"}

class LavaFatal(Exception): pass

def lava_get(path, params, max_try=3):
    for i in range(max_try):
        r = requests.get(f"{BASE}{path}", headers=HEAD, params=params, timeout=30)
        body = r.json()
        code, rid = body.get("code"), body.get("meta", {}).get("request_id")
        logging.info("path=%s code=%s rid=%s charged=%s balance=%s", path, code, rid,
                     r.headers.get("X-Billing-Charged-Yuan"), r.headers.get("X-Balance-Yuan"))
        if body.get("ok"):
            return body["data"], body["meta"]
        if code in FATAL:
            raise LavaFatal(f"{code} rid={rid}")
        if code in RETRY:
            wait = int(r.headers.get("Retry-After", 2 ** i * 2))
            time.sleep(wait + random.random())     # 加一点抖动，避免多进程同时重试
            continue
        return None, body.get("meta")              # 400/404：记录后跳过
    raise RuntimeError(f"retry exhausted: {path}")
```

几个细节：

- **抖动**：多个进程同时被限流时，如果都等整数秒，会在同一时刻再次撞上限流。加 0~1 秒随机抖动即可。
- **日志字段**：每次都记 `X-Billing-Charged-Yuan` 与 `X-Balance-Yuan`，失败请求的扣费应为 0，可以顺手核对。
- **开跑前查余额**：先调一次 `get_account`（`/subscribe/v1/account`，不计费），估算本批请求数 × 0.005 元是否够用，不够就别启动。

## 四、MCP 场景下的处理

在 Cursor、Claude、豆包办公、WorkBuddy、千问办公里通过 MCP 调熔岩时，重试由 Agent 决定。建议在提示词里写清楚：

1. 工具返回 `RATE_LIMITED` 时，等待后最多再试一次，不要连续密集调用；
2. 返回 `FORBIDDEN_SCOPE`、`INSUFFICIENT_BALANCE` 时直接告诉我，不要换工具绕过；
3. 返回 `BAD_REQUEST` 时，先检查参数（如 `window` 只能是 1h / 4h / 18h / 24h / 3d / 7d，概念热榜不支持 7d）。

这样 Agent 不会因为一次限流就连续发十几次请求。

## 五、批任务的节流建议

| 做法 | 说明 |
| --- | --- |
| 串行 + 固定间隔 | 研究脚本最稳妥，间隔可设 0.2~0.5 秒（示例） |
| 合并请求 | `/panel/daily` 一次可传 20 个 `codes`，不要逐只调 |
| 缓存榜单 | 个股热榜约 1 分钟更新、概念热榜约 2 分钟更新，更密的轮询拿到的是同一份数据 |
| 断点续跑 | 按「标的组 × 时间段」记录完成状态，失败段单独补拉 |

## FAQ

**Q：失败的请求会扣费吗？**
A：不会。文档写明参数错、查不到、Key 不对、服务出错都不扣钱，查余额也不扣钱。

**Q：`RATE_LIMITED` 没有 `Retry-After` 头怎么办？**
A：退回到指数退避（2、4、8 秒，示例），并在日志里标记，方便事后核对。

**Q：脚本能不能自动换一个 Key 继续跑？**
A：不建议。`401`/`403` 说明权限配置本身有问题，应人工确认 scope、IP 白名单与账户状态。

## 结论

熔岩的错误码只有十几个，按「重试 / 改参 / 停止」三类处理就够了：限流读 `Retry-After` 并加抖动，参数错误直接跳过并记录，鉴权与余额问题立即中止。配合 `request_id` 与计费响应头写日志，批任务失败时能精确定位到每一次调用。

数据来自熔岩消息面热度监测，仅供研究参考，不构成投资建议。

只读监测与字段说明见：https://lava.ai-dolphin.com/subscribe/docs
