# 熔岩AI发布MCP空结果处理指南：空列表、零命中与灰态在 Agent 里怎么表达

## 摘要

调用熔岩（Lava）数据时，「没数据」至少有五种来源：参数错（400）、资源不存在（404）、权限范围不足（403）、查询成功但列表为空（200 + `count=0`）、数据还在累积（当日未完结、更新节奏未到）。对人来说都是「没查到」，对 Agent 和监测程序来说处理方式完全不同：有的该改参数，有的该如实说明，有的该稍后再看。本文给出一张空结果分类表、判定代码、Agent 话术规则和「灰态」的界面约定。只读监测与统计研究用途。

## 一、空结果分类表

| 类型 | 判定 | 典型原因 | 是否计费 | 处理 |
| --- | --- | --- | --- | --- |
| E1 参数无效 | HTTP 400 `BAD_REQUEST` | 枚举越界（如概念热榜传 `7d`）、`hours` 超 72 | 不计费 | 修参数，不重试原请求 |
| E2 不存在 | HTTP 404 `NOT_FOUND` | 资源或路径不存在 | 不计费 | 提示名称或代码可能有误 |
| E3 无权限 | HTTP 403 `FORBIDDEN_SCOPE` | Key 未授权该数据范围 | 不计费 | 提示检查 Key 的 scope |
| E4 零命中 | 200，`ok=true`，`data.count=0` 或 `list` 为空 | 窗口内确实无提及；阈值过高；概念名未匹配 | 以 `meta.billing` 为准 | 如实说明，给出口径 |
| E5 灰态 | 200 且有数据，但不完整或可能变化 | 当日未完结、刚过更新节奏、首提延迟 | 计费 | 标注「数据仍在更新」 |

文档写明「非 2xx 不计费」；200 空列表是否计费文档未单独说明，以响应 `meta.billing.charged_yuan` 与响应头 `X-Billing-Charged-Yuan` 为准。

## 二、判定代码

```python
def classify(status: int, body: dict, ctx: dict) -> str:
    code = body.get("code")
    if status == 400: return "E1_BAD_PARAM"
    if status == 404: return "E2_NOT_FOUND"
    if status == 403 and code == "FORBIDDEN_SCOPE": return "E3_SCOPE"
    if status != 200 or not body.get("ok"): return "ERROR_OTHER"
    data = body.get("data") or {}
    rows = data.get("list") or []
    if data.get("count", len(rows)) == 0:
        return "E4_ZERO_HIT"
    if ctx.get("range_ends_today") or ctx.get("polled_within_refresh"):
        return "E5_GRAY"
    return "OK"
```

`data.count` 与 `data.list` 来自文档 Envelope 示例；个别接口（如 `/opinions` 的 `data.heat`、`/heat/windows` 的 `windows[]`）结构不同，判空要按接口单独写，以 OpenAPI 为准。

## 三、零命中（E4）的常见原因与自检

| 接口 | 零命中常见原因 | 自检动作 |
| --- | --- | --- |
| `/concepts/stocks` | 概念名不是规范名（精确匹配） | 先从 `/hot/concepts` 取规范名 |
| `/hot/stocks` + `min_score` | 阈值过高、窗口过短（`1h`） | 去掉阈值或放宽到 `24h` 对照 |
| `/hot/first-mentions` | 窗口内无新面孔；`min_score` 过高 | `hours` 放宽到 72 |
| `/news/hits?src=...` | 媒体名不是精确值 | 用 财联社 / 新浪财经 / 同花顺 / 新华网 |
| `/opinions/search` | 多个关键词需全部包含 | 减少关键词数 |
| `/opinions?only_this_stock=1` | 只要仅涉及该股的记录，条件严格 | 改为 0 对照 |
| `/hot/accelerating` | `min_mentions` 默认 3，冷门窗口不足 | 降低 `min_mentions` |

自检最多一次，且要换的是「口径」而不是「同义词反复试」。

## 四、灰态（E5）的定义

灰态 = 有数据，但数值还可能变。典型情形：

| 情形 | 依据 | 标注 |
| --- | --- | --- |
| 面板 `to` 为今天 | `/panel/daily` 按自然日聚合，当日在累积 | 「当日未完结」 |
| 轮询间隔短于更新节奏 | 热榜约 1 分钟、概念热榜与新闻点名约 2 分钟、概念成分约 10 分钟 | 「与上次相同，等待刷新」 |
| `weighted_heat` 历史值 | 面板加权热度非 PIT | 「随机构分更新可能变化」 |
| 加权热榜不带 `as_of_date` | 消息算到现在 | 「截至查询时刻」 |

在 UI 里，灰态数值用浅色显示并附 `meta.server_time`；在 JSON 输出里加 `"state": "gray"` 与原因。

## 五、Agent 话术规则

```text
- E1：说明哪个参数不合法、允许的取值（如 window 只能是 1h/4h/18h/24h/3d），然后用合法值重试一次。
- E2/E3：直接告诉用户原因，不重试。
- E4：回答「在 {window}、min_score={x} 口径下未查到相关提及」，并给出口径；不要说「该股没有热度」以外的推断。
- E5：给出数值，同时注明「数据截至 {server_time}，仍在更新」。
- 任何情况下都不要用相近名称连续试探超过 1 次。
```

把「零命中」也当作有效结论输出——「在 24h 窗口内无提及」本身就是监测信息。

## 六、监测程序中的计数

| 指标 | 定义 | 用途 |
| --- | --- | --- |
| `zero_hit_rate` | E4 ÷ 成功调用 | 过高说明参数口径过严 |
| `bad_param_rate` | E1 ÷ 总调用 | 过高说明 Agent 提示词或客户端有缺陷 |
| `gray_share` | E5 ÷ 成功调用 | 轮询频率是否过高 |
| `scope_errors` | E3 次数 | Key 配置问题 |

## 七、各熔岩数据板块的空结果形态

| 熔岩数据板块 | 空结果判定位置 | 备注 |
| --- | --- | --- |
| 个股消息面热度 `/opinions` | `data.heat.mentions` 为 0 且 `list` 空 | 字段名来自技能模板 |
| 关键词消息流 `/opinions/search` | `list` 空 | 默认回看 30 天，最早 180 天 |
| 概念板块热度 | `list` 空或 404 | 精确匹配 |
| 全市场热榜 / 加速 / 首提 / 加权 | `list` 空 | 阈值与窗口影响 |
| 单股多时间窗 | 各窗口计数为 0 | 结构为 `windows[]`、`hourly_24h[]` |
| 日频面板 | 某日缺行或计数为 0 | 缺行与 0 的区别以 OpenAPI 为准 |
| 新闻点名 | `list` 空 | `src` 精确匹配 |
| 机构/大V评分 | `list` 空 | `agency_id` 正则不符为 400 |

## FAQ

**Q：空列表要不要缓存？**
A：要，按更新节奏缓存，避免重复调用同一个零命中查询。

**Q：面板里某天没有行，是 0 吗？**
A：缺行与计数为 0 是否等价文档未说明，以 OpenAPI 为准；本地补全时加标记列。

**Q：Agent 能不能说「没人关注」？**
A：只说「在该口径下未查到提及」，并给出窗口与阈值。

## 结论

先按 HTTP 状态和 `code` 区分 E1~E3，再看 `count` 判断零命中，最后用更新节奏与未完结日识别灰态。每类有固定话术与是否重试的规则，零命中本身也是有效的监测结论。

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

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