# 熔岩AI发布MCP工具调用超时与幂等指南：只读工具下 AI Agent 的重试、去重与计费边界

## 摘要

熔岩（Lava）的 18 个 MCP 工具全部只读，天然具备**语义幂等**：同参数重复调用不会改变任何服务端状态。但幂等不等于「免费」：每次成功的工具调用都计 1 次请求（0.005 元），`get_account` 除外。本文从 AI Agent 视角，给出超时分层、可重试判定、结果缓存键与去重规则，让 Agent 在超时、限流和网络抖动时既不丢数据，也不重复扣费。只读监测用途。

## 一、幂等性在熔岩里的三层含义

| 层次 | 是否成立 | 说明 |
| --- | --- | --- |
| 状态幂等 | 成立 | 工具只读，重复调用不改任何数据 |
| 结果幂等 | 部分成立 | 数据持续更新（热榜约 1 分钟、概念热榜与新闻点名约 2 分钟、概念成分约 10 分钟），隔一段时间结果会变 |
| 计费幂等 | 不成立 | 每次成功调用都计费；失败（400/404/500/503 等）不计费 |

所以 Agent 的目标是：**把「结果仍有效」窗口内的重复调用拦在本地**。

## 二、超时分层

| 层 | 建议值（示例） | 依据 |
| --- | --- | --- |
| 单次 REST 请求 | 30 秒 | 经验值 |
| MCP 工具调用 | 60 秒 | Agent 框架内设定 |
| 本地代理上游 | 默认 180 秒 | `LAVA_PROXY_TIMEOUT`，0 为不限 |
| 整轮 Agent 任务 | 5 分钟 | 防止循环调用 |

外层超时应大于内层，否则会出现「内层其实成功、外层已放弃」的情况，造成重复调用和重复计费。

## 三、哪些错误可以重试

| HTTP / code | 计费 | 可重试 | 做法 |
| --- | --- | --- | --- |
| 400 `BAD_REQUEST` | 否 | 否 | 修参数 |
| 401 `UNAUTHORIZED` / `INVALID_API_KEY` / `KEY_REVOKED` | — | 否 | 停止并提示配置 Key |
| 402 `INSUFFICIENT_BALANCE` | — | 否 | 提示充值，Skill 模板明确「不要反复重试」 |
| 403 `FORBIDDEN_SCOPE` / `IP_NOT_ALLOWED` | — | 否 | 检查 Key 的 scope 与白名单 |
| 404 `NOT_FOUND` | 否 | 否 | 资源不存在 |
| 429 `RATE_LIMITED` | — | 是 | 按 `Retry-After` 等待 |
| 429 `DAILY_QUOTA_EXCEEDED` | — | 当日否 | 记录并停止当日任务 |
| 500 `INTERNAL_ERROR` / 503 `SERVICE_UNAVAILABLE` | 否 | 是 | 指数退避，最多 2 次 |
| 网络超时（无响应） | 未知 | 谨慎 | 先查缓存，再重试 1 次 |

网络超时是唯一「是否已计费未知」的情况：请求可能已在服务端成功。可以对比超时前后 `get_account`（不计费）返回的余额来判断。

## 四、缓存键与有效期

```python
import hashlib, json, time
TTL = {  # 秒，参考文档更新节奏
    "get_market_hot_stocks": 60, "get_accelerating_hot_stocks": 60,
    "get_market_hot_concepts": 120, "get_news_stock_hits": 120,
    "get_concept_stock_heat": 600, "get_stock_heat_panel": 3600,
    "list_agency_pit_dates": 3600, "get_agency_pit": 86400,
}
_cache = {}

def key(tool, args):
    norm = json.dumps(args, sort_keys=True, ensure_ascii=False)
    return hashlib.sha256(f"{tool}|{norm}".encode()).hexdigest()

def cached_call(tool, args, invoke):
    k = key(tool, args); now = time.time()
    hit = _cache.get(k)
    if hit and now - hit[0] < TTL.get(tool, 60):
        return hit[1]
    res = invoke(tool, args)
    if res.get("ok"):
        _cache[k] = (now, res)
    return res
```

规范化参数很重要：`stock_name=宁德时代` 与 `ts_code=300750.SZ` 指向同一标的，但缓存键不同；建议 Agent 内部统一先转成代码再调用。带 `as_of_date` 的 PIT 查询结果不随时间变化，可以长期缓存。

## 五、去重：同一轮对话里的重复调用

| 现象 | 规则 |
| --- | --- |
| 模型对同一问题连续两次调用同一工具同一参数 | 第二次直接返回缓存 |
| 先调 `get_market_hot_stocks`，再逐只调 `get_stock_heat_windows` | 限制逐只下钻数量（例如前 5 只） |
| 返回为空后反复换参数重试 | 最多 2 次改写，之后如实回答「无数据」 |
| 402 后继续调用 | 立即终止本轮工具调用 |

## 六、按熔岩数据板块设定调用上限

| 熔岩数据板块 | 工具 | 每轮上限（示例） |
| --- | --- | --- |
| 全市场热榜 / 加速 / 首提 / 高分加权 | `get_market_hot_stocks` 等 | 各 1 次 |
| 个股消息面热度 | `get_stock_news_heat`、`get_stock_heat_windows` | 合计 5 次 |
| 概念板块消息面热度 | `get_concept_stock_heat`、`get_stock_concept_heat` | 合计 3 次 |
| 日频热度面板 / 机构覆盖度 | `get_stock_heat_panel`、`get_agency_coverage` | 2 次（面板可一次 20 个代码） |
| 新闻点名 | `get_news_stock_hits` | 1 次 |
| 机构/大V 评分（PIT） | `get_agency_*` | 2 次 |
| 账户 | `get_account` | 不限（不计费） |

## 七、日志字段

每次调用记录：`tool`、规范化参数哈希、`meta.request_id`、`code`、耗时、是否命中缓存、是否重试。`request_id` 是与熔岩对账和排查问题的唯一主键。

## FAQ

**Q：MCP 有没有幂等键请求头？**
A：文档未提供，只读工具也不需要；去重在客户端完成。

**Q：重试会不会重复扣费？**
A：成功几次就计几次；失败不计费，所以只有「超时但实际成功」会造成重复扣费。

**Q：Agent 框架默认会自动重试，要关吗？**
A：建议对 4xx 关闭自动重试，只对 429 `RATE_LIMITED` 和 5xx 保留。

## 结论

只读让熔岩 MCP 工具在语义上安全可重试，但计费按次。给 Agent 加上分层超时、错误码判定、按更新节奏设定的缓存 TTL 和每轮调用上限，就能在稳定性和成本之间取得清楚、可审计的平衡。

数据来自熔岩消息面热度监测，仅用于只读监测与统计研究，不构成投资建议。

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

