SM-Weather-API 天气查询接入设计
版本: v1.0 日期: 2026-07-13 状态: 设计初稿 关联: SA_主动整备_Agent设计.md、空间智能整备(生产 Webhook).yml
1. 背景与目标
1.1 为什么需要天气
主动整备 Agent 在会议开始前触发时,LLM 需要结合 室外环境信息 做出更合理的设备控制决策。典型场景:
| 场景 | 无天气信息 | 有天气信息 |
|---|---|---|
| 晴天下午 + 高温 | 无法判断是否需要关窗帘遮阳 | 关窗帘 + 开空调制冷 |
| 阴雨天 | 无法判断是否需要补光 | 灯光调亮 |
| 冬季室外低温 | 无法判断是否需要预热 | 提前开制热 |
| 室外风力大 | 无法判断 | 可辅助判断新风策略 |
1.2 测试用例:天气对 LLM 决策的影响
以下用同一个会议场景,对比接入天气前后的 LLM 决策差异,直观说明天气数据的必要性。
测试场景:801 小会议室,7 月某日下午 14:00-15:00 研发周会
- 室内 RO 读数:
env_indoor_temp=29°C、env_illuminance=800lux - RW 能力清单:
curtain_power(窗帘开关 bool)、hvac_power(空调开关 bool)、hvac_target_temp(空调目标温度 number 16-30) - 触发时间:13:45(prep_window=15min)
❌ 无天气输入时的 LLM 输出
LLM 仅看到室内 RO 读数,判断逻辑:
- 室内 29°C > 舒适温度 24°C(知识库季节策略→夏季制冷)→ 开空调,目标 24°C
- 会前整备仅涉及 HVAC 温控,窗帘保持当前值
{
"recipes": [
{"semanticKey": "hvac_power", "value": true},
{"semanticKey": "hvac_target_temp", "value": 24}
],
"plannedSummary": "空调将开启,目标 24°C"
}问题:LLM 不知道外面是烈日还是阴天,无法做出遮阳判断。
✅ 有天气输入时的 LLM 输出
LLM 收到天气段落后,获得完整环境感知:
【室外天气】(当前)
室外温度:35°C
天气状况:晴
紫外线指数:强
【未来几小时预报】
14:00 — 35°C 晴
15:00 — 34°C 晴LLM 综合判断:
- 室外 35°C + 晴天 + 室内 29°C → 空调制冷 24°C 需要同时关窗帘阻挡太阳辐射,否则冷气流失、空调持续高负荷
- 窗帘 800lux 虽不暗,但紫外线强 → 优先遮阳,必要时灯光补亮(若空间逻辑映射中有
light_power)
{
"recipes": [
{"semanticKey": "curtain_power", "value": true},
{"semanticKey": "hvac_power", "value": true},
{"semanticKey": "hvac_target_temp", "value": 24}
],
"plannedSummary": "窗帘关闭遮阳,空调将开启,目标 24°C"
}差异总结:
| 维度 | 无天气 | 有天气 |
|---|---|---|
| 动作数量 | 2 条(仅空调) | 3 条(空调 + 窗帘遮阳) |
| 窗帘决策 | 保持现状(800lux 不暗) | 关闭(紫外线强 + 室外高温) |
| 能源影响 | 空调高负荷运行 | 窗帘辅助隔热,降低能耗 |
| 会议体验 | 阳光直射 | 遮阳后环境舒适 |
此用例同时说明了一个更隐蔽的依赖关系:开启天气后,窗帘类 RW 语义才真正产生整备价值。若无天气输入,
curtain_power等遮阳相关的语义在 LLM 决策中几乎不会被选中。
1.3 设计原则
- 可降级:天气数据是可选的增强输入,API 不可用时不影响整备主流程
- 配在 Dify:经纬度、API 凭证等作为 Dify 环境变量,按项目部署配置
- 完整上下文:同时提供当前实况 + 未来几小时预报,让 LLM 有完整判断依据
2. 天气数据源
2.1 首选:腾讯天气(QQ Weather,免费公开接口)
| 项目 | 说明 |
|---|---|
| 接口地址 | https://wis.qq.com/weather/common |
| 认证方式 | 无(免费公开接口,无需认证) |
| 入参 | source=pc、weather_type=observe|forecast_1h、province、city、county |
| 数据范围 | 实况 + 未来 48h 逐小时预报 |
weather_type 多类型组合:
| 类型 | 说明 |
|---|---|
observe | 当前实况:温度、湿度、气压、天气状况、风力风向 |
forecast_1h | 逐小时预报:keys 0-47 覆盖未来 48h |
示例调用:
GET https://wis.qq.com/weather/common?source=pc&weather_type=observe|forecast_1h&province=江苏&city=苏州&county=吴中区响应结构:
{
"status": 200,
"data": {
"observe": {
"degree": "32", // 当前温度°C
"humidity": "67", // 湿度%
"weather": "阴", // 天气状况
"weather_code": "02", // 天气代码
"wind_power": "6-7", // 风力
"wind_direction_name": "西南风",
"update_time": "202607131725"
},
"forecast_1h": {
"0": { "degree": "31", "weather": "小雨", "update_time": "20260713170000" },
"1": { "degree": "30", "weather": "小雨", "update_time": "20260713180000" },
"2": { "degree": "29", "weather": "多云", "update_time": "20260713190000" },
...
}
}
}优点:免费、无需 API Key、中文省市县入参比经纬度更直观、48h 预报覆盖充足。 缺点:腾讯前端公开接口(非商业授权 API),无 SLA 保障。
2.2 备选:墨迹天气(已购商业服务,当前欠费)
当项目甲方要求商业合规、内网部署等场景,可切换回墨迹天气:
| 项目 | 说明 |
|---|---|
| 实况接口 | https://aliv8.data.moji.com/whapi/json/aliweather/condition |
| 预报接口 | https://aliv8.data.moji.com/whapi/json/aliweather/forecast24hours |
| 认证方式 | HTTP Header: Authorization: APPCODE xxx |
| 入参 | lat 经度、lon 纬度 |
2.3 接口选择策略
天气开关(WEATHER_ENABLED) = true
→ 先尝试腾讯天气(免费,无认证)
→ 若失败,降级(不走墨迹,避免欠费报错)
→ 若项目配置了 MOJI_APPCODE,可切换到墨迹当前公司墨迹天气接口已欠费,优先用腾讯天气跑通链路。墨迹作为商业合规兜底方案,待续费后可切换。
2.4 API 响应字段映射(腾讯天气 → LLM Context)
天气实况(data.observe,用于当前时刻环境感知):
| LLM Context 字段 | API 字段 | 示例值 | 说明 |
|---|---|---|---|
| 室外温度 | degree | 32 | 当前温度(°C) |
| 天气状况 | weather | 晴 | 晴/阴/雨等中文描述 |
| 湿度 | humidity | 42 | 百分比 |
| 风向 | wind_direction_name | 西南风 | — |
| 风力 | wind_power | 3-4 | 风力等级 |
逐小时预报(data.forecast_1h,keys 0-47 对应未来 48h):
| LLM Context 字段 | API 字段 | 示例值 | 说明 |
|---|---|---|---|
| 预报时间 | update_time | 20260714140000 | 解析后取 HH:mm |
| 温度 | degree | 33 | 该小时温度 |
| 天气状况 | weather | 晴 | 预报天气 |
3. Dify 工作流改动
3.1 新增节点
在原工作流「构建LLM用户消息」→「LLM推理」之间,插入 2 个新节点:
解析Hub上下文 ─→ 构建LLM用户消息
→ HTTP: 获取天气 ─→ Code: 合并天气上下文 → LLM推理腾讯天气
weather_type=observe|forecast_1h一次请求同时返回实况 + 48h 预报,无需拆两个接口。
节点 1:HTTP: 获取天气
| 参数 | 值 |
|---|---|
| 方法 | GET |
| URL | https://wis.qq.com/weather/common |
| 认证 | 无 |
| Query | source=pc&weather_type=observe|forecast_1h&province={{#env.WEATHER_PROVINCE#}}&city={{#env.WEATHER_CITY#}}&county={{#env.WEATHER_COUNTY#}} |
| 超时 | 5s(快速降级) |
| Retry | 1 次 |
节点 2:Code: 合并天气上下文
输入:
user_message:来自「构建LLM用户消息」节点weather_body:来自 HTTP 天气节点的响应(可选,可能为空)
逻辑:
def main(user_message: str, weather_body: str) -> dict:
weather_lines = []
if not weather_body:
return {"user_message": user_message}
try:
data = json.loads(weather_body).get("data", {})
except json.JSONDecodeError:
return {"user_message": user_message}
# ── 解析天气实况 ──
observe = data.get("observe", {})
if observe.get("degree") is not None:
weather_lines.append("【室外天气】(当前)")
weather_lines.append(f"室外温度:{observe['degree']}°C")
if observe.get("weather"):
weather_lines.append(f"天气状况:{observe['weather']}")
if observe.get("humidity"):
weather_lines.append(f"湿度:{observe['humidity']}%")
if observe.get("wind_direction_name"):
weather_lines.append(f"风向:{observe['wind_direction_name']}")
if observe.get("wind_power"):
weather_lines.append(f"风力:{observe['wind_power']}级")
weather_lines.append("")
# ── 解析逐小时预报(取最近 4h) ──
hourly_dict = data.get("forecast_1h", {})
if hourly_dict:
sorted_keys = sorted(hourly_dict.keys(), key=int)[:4]
if sorted_keys:
weather_lines.append("【未来几小时预报】")
for key in sorted_keys:
h = hourly_dict[key]
ts = h.get("update_time", "")
hour_str = f"{ts[8:10]}:{ts[10:12]}" if len(ts) >= 12 else f"{key}:00"
temp = h.get("degree", "?")
cond = h.get("weather", "")
weather_lines.append(f" {hour_str} — {temp}°C{f' {cond}' if cond else ''}")
weather_lines.append("")
# 合并:有天气就拼在末尾,没有就原样输出
combined = (user_message.rstrip() + "\n\n" + "\n".join(weather_lines)) if weather_lines else user_message
return {"user_message": combined}处理后的 Weather Context 示例(拼入 user_message 末尾的段落):
【室外天气】(当前)
室外温度:32°C
天气状况:阴
湿度:67%
风向:西南风
风力:6-7级
【未来几小时预报】
17:00 — 31°C 小雨
18:00 — 30°C 小雨
19:00 — 29°C 多云
20:00 — 28°C 多云对应腾讯天气 API 原始返回 → 处理后的关键映射:
| 原始 API 字段 | 值 | 处理后输出 |
|---|---|---|
observe.degree=32 | 32 | 室外温度:32°C |
observe.weather=阴 | 阴 | 天气状况:阴 |
observe.humidity=67 | 67 | 湿度:67% |
forecast_1h[0].update_time=20260713170000 | 20260713 17:0000 | 17:00 |
forecast_1h[0].degree=31 | 31 | — 31°C |
forecast_1h[0].weather=小雨 | 小雨 | 小雨 |
| 最终拼装 | 全部 | 17:00 — 31°C 小雨 |
3.2 新增环境变量
| 变量名 | 示例值 | 必填 | 适用接口 | 说明 |
|---|---|---|---|---|
WEATHER_ENABLED | true | 否 | 通用 | 天气开关,关闭时不调 API(默认 true) |
WEATHER_PROVINCE | 江苏 | ✅ | 腾讯天气 | 项目所在省份 |
WEATHER_CITY | 苏州 | ✅ | 腾讯天气 | 项目所在城市 |
WEATHER_COUNTY | 吴中区 | ✅ | 腾讯天气 | 项目所在区县 |
MOJI_LAT | 31.320114 | 备选 | 墨迹天气 | 项目所在地纬度(墨迹备选时可用) |
MOJI_LNG | 120.725805 | 备选 | 墨迹天气 | 项目所在地经度 |
MOJI_APPCODE | a479c72dca2... | 备选 | 墨迹天气 | 墨迹天气 API AppCode(当前欠费) |
4. 天气 → LLM Context 示例
当室外 32°C、晴天下午 2 点开会,工作流拼入的消息段落如下:
【室外天气】(当前)
室外温度:32°C
体感温度:34°C
天气状况:晴
紫外线指数:强
【未来几小时预报】
14:00 — 33°C 晴
15:00 — 32°C 晴
16:00 — 30°C 晴转多云LLM 拿到这段后,结合 RO 室内温度(如 28°C)即可判断:
- 晴天 + 高温 + 会议室有窗帘 RW → 关窗帘遮阳
- 室外 32°C > 室内 28°C → 空调制冷模式
- 紫外线强 + 窗帘已关 → 灯光需补亮
5. 降级与异常处理
| 场景 | 行为 | 影响 |
|---|---|---|
WEATHER_ENABLED = false | 跳过两个 HTTP 调用 | user_message 不带天气段落 |
| 墨迹 API 超时/报错 | HTTP 节点输出空值,Code 节点跳过 | 同降级 |
| 墨迹 API 返回异常 JSON | Code 节点 try/except 捕获 | 同降级 |
| 网络不通(内网部署) | HTTP 节点超时报错 | 同降级 |
天气数据不影响主流程决策:LLM 仅凭 RO 室内环境读数(温度、湿度、CO₂)、产品语义和会议上下文,仍然可以输出合理的整备方案。
6. 与 LLM Prompt 的关系
这段天气信息是拼接到 user message 末尾,不需要改动现有 System Prompt。LLM 会自动上下文理解并使用天气信息。System Prompt 中原有的知识库规则(标准语义操作规则表、季节策略表、通用行为规则)保持不变。
7. 下一步
- 开发同事按本设计在 Dify 生产工作流中插入天气节点
- 测试验证:开启天气 vs 关闭天气的 LLM 决策差异
- 在 801 小会议室验证方案 中补充天气场景用例
