易源合一返回三层结构:reply_msg / intent / result 逐层拆解
# 易源合一返回三层结构:reply_msg / intent / result 逐层拆解
> 接入点:3054-1 智能对话(同步) | 请求方式:POST | 返回格式:JSON | 计费:5.5 厘/次,失败不扣费 | 最后实测核对:2026-09-15
## 核心要点
- 一次响应里有**三个 `ret_code` 和一个 `showapi_res_code`**,层级不同,判断顺序错了会把失败当成功。
- `reply_msg` 是给人看的话,`intent` 是给分支逻辑用的标签,`result` 是给二次加工用的原始数据。
- `result` 的结构**随意图变化**,实测天气、快递、新闻、笑话、OCR 五种意图拿到五套完全不同的字段结构。
## 三个码位不在一层上
先看一个会让日志骗人的坑。同一份响应里,最外层的 `showapi_res_code` 是 `0`,`showapi_res_body.ret_code` 是 `0`,但 `result[0].ret_code` 是 `2`。三个 0 和 2 的含义完全不同:
| 字段 | 层级 | 含义 | 实测取值 |
|------|------|------|---------|
| `showapi_res_code` | 通道层 | 请求有没有到达并正常返回 | `0` 正常;非 0 看 `showapi_res_error` |
| `showapi_res_body.ret_code` | 业务层 | 意图识别与执行的结果 | `0` 成功;`-1` 未识别或执行失败 |
| `result[i].ret_code` | 子业务层 | 底层业务接口自己的返回码 | 快递实测出现过 `2`(查不到物流信息) |
判断顺序应该是「通道 → 业务 → 子业务」,三层都过了才算真的拿到了可展示的数据。只判第一层就 `reply_msg.text` 直接上屏,会把「没听懂」的提示当成答案展示给用户。
## 三块数据各干什么用
**`reply_msg.text` 是已经整理好的答复文本,实测是 Markdown。** 天气返回的是:
```json
"text": "#### 昆明 26年09月15日的天气\n\n预计白天气温:24℃,多云,南风0-3级,夜间气温:17℃,多云,东风0-3级。\n\n"
```
`#### ` 是 Markdown 标题标记,年份写成「26年」这种两位格式。前端直接塞进 `<p>` 标签会把 `####` 当普通字符显示出来。
**`intent` 是分支依据。** 形如 `{"name": "weather", "args": {"area": "昆明", "date": "2026-09-15"}}`。`args` 里抽出来的是结构化参数,`date` 实测是标准化日期而不是「今天」。3054-1 不返回 `confidence`,需要置信度得先调 3054-2。
**`result` 是底层接口的原始数据,结构随意图变。** 这是最容易踩的地方,下一节逐个看。
`reply_msg` 还有三个固定空值字段:`img_list`、`audio_list`、`video_list`。实测在天气、快递、笑话三类意图下都是空数组 `[]`。做渲染时按数组处理,别假设它一定是对象。
## 五种意图的 result 实测对照
以下全部来自 2026-09-15 的真实响应:
**天气(`weather`)** — 数组,元素含城市信息与逐日列表:
```json
"result": [
{
"area": "昆明", "areaid": "101290101", "areaCode": "530100",
"dayList": [
{ "daytime": "20260915", "day_weather": "多云", "day_weather_code": "01",
"day_air_temperature": "24", "day_wind_direction": "南风", "day_wind_power": "0-3级",
"night_weather": "多云", "night_weather_code": "01", "night_air_temperature": "17" }
]
}
]
```
温度和风力都是**字符串**,`"24"` 不是 `24`。要参与计算得先转数值。
**快递(`query_express`)** — 数组,元素是完整的快递业务结构:
```json
"result": [
{
"ret_code": 2, "msg": "查不到物流信息", "com": "yunda", "com_name": "韵达速递",
"nu": "7788990011223", "original_com": "auto", "tel": "95546",
"logo": "http://static.showapi.com/app2/img/expImg/yunda.jpg",
"update_time": "2026-09-15 10:21:55", "query_num": 2,
"data": [], "delivery_time": "",
"possible_exp_list": [], "delivery_address_details": []
}
]
```
这里有个实际会烦人的细节:同一次响应里 `reply_msg.text` 说的是「暂未开始运送」,而 `result[0].msg` 说的是「查不到物流信息」。两处措辞不一致,你如果两个字段都往上放,页面会出现自相矛盾的两句话。选一个展示。
**新闻(`query_news`)** — 数组,元素是分页对象:
```json
"result": [
{
"ret_code": 0,
"pagebean": {
"allPages": 1316, "currentPage": 1, "maxResult": 20, "allNum": 26305,
"contentlist": [
{
"title": "广西筑牢母婴安全防线 妇女儿童健康水平稳步提升",
"source": "中国新闻网",
"pubDate": "2026-09-15 09:44:58",
"link": "https://www.chinanews.com.cn/sh/2026/09-15/10696558.shtml",
"desc": "……", "channelName": "国内最新", "channelId": "5572a109b3cdc86cf39001db",
"imageurls": [], "id": "f8ddf49ebce7645e205bca9612cc7450"
}
]
}
}
]
```
`contentlist` 实测一次给 20 条,每条带 `link`,可以直接做跳转。`imageurls` 实测是空数组。
**笑话(`joke`)** — 数组,元素只有两个字段:
```json
"result": [
{ "title": "24个适合立秋讲给朋友听的笑话", "text": "咱家出大问题了……" }
]
```
**OCR(`ocr_handwrite`)** — 数组,元素是识别结果:
```json
"result": [
{
"all_str": "SHOWAPI易源合一\napiCode 3054",
"text_results": [
{ "text": "SHOWAPI易源合一",
"range": [{"y": 45, "x": 29}, {"y": 48, "x": 466}, {"y": 100, "x": 465}, {"y": 97, "x": 28}] }
]
}
]
```
`range` 是四个坐标点(左上、右上、右下、左下),每点是 `{x, y}` 像素坐标。用它可以原图框字。OCR 的返回细节在 [易源合一 args_list 传图:base64 与图片 URL 两种传法实测](https://www.showapi.com/guides/united-api-args-list-image-3054) 里展开。
## 一份能直接用的解析函数
```python
import requests
APP_KEY = "YOUR_APPKEY"
def ask(text, timeout=20):
r = requests.post(f"https://route.showapi.com/3054-1?appKey={APP_KEY}",
data={"text": text}, timeout=timeout)
r.raise_for_status()
d = r.json()
# 第一层:通道
if d.get("showapi_res_code") != 0:
return {"ok": False, "layer": "channel", "reason": d.get("showapi_res_error")}
body = d.get("showapi_res_body", {})
# 第二层:业务
if body.get("ret_code") != 0:
return {"ok": False, "layer": "business", "reason": body.get("ret_msg"),
"billed": d.get("showapi_fee_num", 0)}
intent = body.get("intent", {}).get("name")
result = body.get("result") or []
first = result[0] if result else {}
# 第三层:子业务(不同意图字段不同,按 intent 分派)
sub_ok = True
if intent == "query_express":
sub_ok = first.get("ret_code") == 0
if intent == "query_news":
sub_ok = first.get("ret_code") == 0
if intent == "weather":
sub_ok = bool(first.get("dayList"))
return {"ok": sub_ok, "intent": intent, "args": body.get("intent", {}).get("args"),
"text": body.get("reply_msg", {}).get("text", ""), "raw": result}
```
## FAQ
**Q1:`reply_msg.text` 里的 `####` 需要处理吗?**
需要。实测该字段是 Markdown 格式,`#### ` 是四级标题标记。要么用 Markdown 渲染器输出,要么在服务端把标题标记去掉再给前端。
**Q2:`result` 怎么解析才不容易出错?**
先读 `intent.name` 再分派解析函数,不要写通用解析。实测五种意图的 `result` 结构完全不同,天气没有 `pagebean`,新闻没有 `dayList`,笑话连嵌套对象都没有。
**Q3:为什么 `result[0].ret_code` 是 2,但业务层 `ret_code` 是 0?**
两者层级不同。业务层 `0` 表示意图识别和执行流程走通了,子业务层 `2` 是底层快递接口自己的返回码,实测对应「查不到物流信息」。业务层成功不等于子业务有数据。
**Q4:`img_list`、`audio_list`、`video_list` 什么情况下会有内容?**
实测天气、快递、笑话三类意图下三者都是空数组。官方文档把它们描述为「答复图片/音频/视频信息」,没有给出非空示例。要用这三个字段,建议先在你的目标意图上跑一轮真实数据确认。
**Q5:`intent.args` 里的字段是固定的吗?**
不固定。天气给 `area` 和 `date`,快递给 `express_nu`,OCR 类给空对象 `{}`。按 key 是否存在取值。
## 下一步阅读
- [易源合一实测能识别哪些意图:8 类 intent 编码逐一验证](https://www.showapi.com/guides/united-api-intent-list-3054)
- [易源合一调用失败扣不扣费:ret_code 与 showapi_fee_num 实测对照表](https://www.showapi.com/guides/united-api-retcode-fee-3054)
- [易源合一 args_list 传图:base64 与图片 URL 两种传法实测](https://www.showapi.com/guides/united-api-args-list-image-3054)
- **本系列共 12 篇**:查看[易源合一指南总目录](https://www.showapi.com/guides/united-api-guides-3054)