Skip to content

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°Cenv_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 温控,窗帘保持当前值
json
{
  "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
json
{
  "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 设计原则

  1. 可降级:天气数据是可选的增强输入,API 不可用时不影响整备主流程
  2. 配在 Dify:经纬度、API 凭证等作为 Dify 环境变量,按项目部署配置
  3. 完整上下文:同时提供当前实况 + 未来几小时预报,让 LLM 有完整判断依据

2. 天气数据源

2.1 首选:腾讯天气(QQ Weather,免费公开接口)

项目说明
接口地址https://wis.qq.com/weather/common
认证方式(免费公开接口,无需认证)
入参source=pcweather_type=observe|forecast_1hprovincecitycounty
数据范围实况 + 未来 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=吴中区

响应结构

json
{
  "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 字段示例值说明
室外温度degree32当前温度(°C)
天气状况weather晴/阴/雨等中文描述
湿度humidity42百分比
风向wind_direction_name西南风
风力wind_power3-4风力等级

逐小时预报data.forecast_1h,keys 0-47 对应未来 48h):

LLM Context 字段API 字段示例值说明
预报时间update_time20260714140000解析后取 HH:mm
温度degree33该小时温度
天气状况weather预报天气

3. Dify 工作流改动

3.1 新增节点

在原工作流「构建LLM用户消息」→「LLM推理」之间,插入 2 个新节点:

解析Hub上下文 ─→ 构建LLM用户消息
               → HTTP: 获取天气 ─→ Code: 合并天气上下文 → LLM推理

腾讯天气 weather_type=observe|forecast_1h 一次请求同时返回实况 + 48h 预报,无需拆两个接口。

节点 1:HTTP: 获取天气

参数
方法GET
URLhttps://wis.qq.com/weather/common
认证
Querysource=pc&weather_type=observe|forecast_1h&province={{#env.WEATHER_PROVINCE#}}&city={{#env.WEATHER_CITY#}}&county={{#env.WEATHER_COUNTY#}}
超时5s(快速降级)
Retry1 次

节点 2:Code: 合并天气上下文

输入

  • user_message:来自「构建LLM用户消息」节点
  • weather_body:来自 HTTP 天气节点的响应(可选,可能为空)

逻辑

python
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=3232室外温度:32°C
observe.weather=阴天气状况:阴
observe.humidity=6767湿度:67%
forecast_1h[0].update_time=2026071317000020260713 17:000017:00
forecast_1h[0].degree=3131— 31°C
forecast_1h[0].weather=小雨小雨小雨
最终拼装全部17:00 — 31°C 小雨

3.2 新增环境变量

变量名示例值必填适用接口说明
WEATHER_ENABLEDtrue通用天气开关,关闭时不调 API(默认 true)
WEATHER_PROVINCE江苏腾讯天气项目所在省份
WEATHER_CITY苏州腾讯天气项目所在城市
WEATHER_COUNTY吴中区腾讯天气项目所在区县
MOJI_LAT31.320114备选墨迹天气项目所在地纬度(墨迹备选时可用)
MOJI_LNG120.725805备选墨迹天气项目所在地经度
MOJI_APPCODEa479c72dca2...备选墨迹天气墨迹天气 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 返回异常 JSONCode 节点 try/except 捕获同降级
网络不通(内网部署)HTTP 节点超时报错同降级

天气数据不影响主流程决策:LLM 仅凭 RO 室内环境读数(温度、湿度、CO₂)、产品语义和会议上下文,仍然可以输出合理的整备方案。


6. 与 LLM Prompt 的关系

这段天气信息是拼接到 user message 末尾,不需要改动现有 System Prompt。LLM 会自动上下文理解并使用天气信息。System Prompt 中原有的知识库规则(标准语义操作规则表、季节策略表、通用行为规则)保持不变。


7. 下一步

  1. 开发同事按本设计在 Dify 生产工作流中插入天气节点
  2. 测试验证:开启天气 vs 关闭天气的 LLM 决策差异
  3. 801 小会议室验证方案 中补充天气场景用例

Released under the Private License.