亚马逊AWS官方博客

Xthings 基于 Amazon Bedrock AgentCore 与 Amazon Nova Lite 构建智能家居自然语言控制的实践

摘要:本文介绍 Xthings 如何在不重构既有设备控制平台的前提下,为智能家居 SaaS 平台增加自然语言控制能力。方案基于 Amazon Bedrock AgentCore 的托管 Agent 与 Amazon Nova Lite 模型,采用”上下文预加载 + 异步下发”的四段式架构,把该流程所需的模型调用从两轮减到一轮,受理延迟 P95 目标控制在 1 秒以内,Phase 1 的 MVP 在 2 到 3 周内投产。文中同时记录了选型时被否决的中间方案、长期记忆的写入治理经验,以及两处当前实现仍有的限制与改造计划。

一、背景与挑战

1.1 平台现状与这个功能做给谁

Xthings 是一家智能家居 SaaS 平台,为设备厂商与地产客户提供设备接入、控制与自动化能力,业务面向海外市场,整套系统部署在亚马逊云科技海外区域。截至统计时点,平台服务数十万级注册用户和家庭用户,接入设备达到百万级,其中数十万级设备通过 AWS IoT Core 接入;日常交互量千万级,峰值请求数百 QPS,其中相当一部分是多人共享的家庭。

每台设备有唯一 UUID,经 MQTT 接入 AWS IoT Core,由既有的设备控制服务(DCS)把标准指令转换为私有物模型格式下发。

立项前我们做的第一件事是确认这个功能该做给谁。2026 年 6 月,我们对约 1 万个家庭做了一次设备数分布统计:

  • 每户设备数中位数(P50):2 台
  • P90:5 台

这个分布直接改变了功能定位。平台上有大量只装了一两个灯泡的家庭,他们没有设备定位成本问题;真正需要”一句话控制”的是分布右侧那部分家庭。设备到了五台以上,”睡前把灯关掉、门锁上”就要在 App 里逐个找到设备卡片、逐个点开操作,找设备本身开始占掉大部分时间。首轮灰度因此按家庭设备数不少于 5 台圈选,而不是按全平台平均值推功能。

用户侧的痛点收敛为四条:操作繁琐、学习成本高、语音交互缺失、场景联动依赖预设。

[图 1 Xthings 用户侧业务架构]

Xthings 的研发团队规模不大,大部分精力要放在设备接入、新品类适配和客户交付上,没有专门的人去运维一套 Agent 的执行环境。这个功能立项时拿到的指令是”两三周内出一个能灰度的版本”,不是”先把基础设施补齐”。

凡是和业务没有直接关系的东西,能托管就托管。

1.2 为什么不是场景模板或关键词指令

平台原本已有场景联动功能,用户可以把”睡觉”配成一条规则一键触发。这条路线我们仍在继续优化,但它有一个前提:用户得先完成一次配置。对从未创建过任何规则的那部分用户,模板解决不了问题;让用户为了少点几次而先学一套规则编辑器,本身就是新的门槛。

另一条路线是扩展既有的关键词指令。它的限制在输入空间:像”把客厅那个灯调暗点”这样带指代和模糊参数的表达,需要词表和句式组合去穷举,每新增一类设备就要再扩一轮。我们引入语言模型的实际依据就在这里。反过来说,对指令范围固定、表达方式收敛的场景,规则方案成本更低,我们仍会优先评估。

1.3 三条硬约束

1. 受理延迟 P95 在 1 秒以内。 口径是后端收到请求到向 App 返回受理响应,不含设备物理执行完成。这条线不是拍出来的:App 上点一下设备卡片,用户早就习惯了几百毫秒内有反应,换成说一句话没有道理更慢。真正的麻烦在超时之后——用户不会等,他会再说一遍。灯多关一次无所谓,窗帘和门锁连着收到两条相反的指令就是事故。所以这条线既是体验要求,也是后面那几道校验必须存在的原因。

2. 不重构既有基础设施。 设备接入、权限体系与指令下发全部沿用现有系统。这一条是资源决定的,不是技术偏好。接入层和控制链路上跑着百万级在网设备,为一个新功能去动它,回归测试的工作量和线上风险都远超这个功能本身的价值;而我们能投进去的只有几个人、两三周时间。真顺着那条路做,大概率是基础设施改了一半、功能没上线,既有业务还多了一批线上问题。

3. 开锁类动作不由自然语言自动触发。 这是立项时定下的安全底线。门锁和别的设备不一样:关错灯用户自己再开一次就好,开错锁是安全事故,而且没有补救动作。模型对”睡觉”这类说法的解释空间很大,我们不接受任何一条链路上它能单独做出开锁决定。这条约束当前的落实程度见 4.4——提示词层面已经约束住,执行层的独立校验还没补上。

二、为什么选择 Amazon Bedrock AgentCore 与 Amazon Nova Lite

2.1 为什么是轻量模型

IoT 控制指令的生成,本质上是基于用户语句、设备清单和历史偏好做语义匹配并输出结构化结果。它是浅层推理,不需要多步规划,也不需要长链条的工具编排。这类任务适合轻量模型:单次调用成本低,延迟更容易落在 1 秒的受理预算内。我们因此通过 Amazon Bedrock 使用 Amazon Nova Lite。

2.2 一个被否决的中间方案:只接 Memory

轻量模型本身不提供状态管理与会话保持,这部分能力要从模型之外获得。选型时我们单独评估过一个中间方案:只接 AgentCore Memory,Agent 的执行代码继续留在现有的 AWS Lambda 函数里。

这个方案能满足偏好和设备别名的存储需求,短期内改动量也最小。否决它的原因是:Agent 的执行环境、会话隔离和扩缩容仍然要自己建设。由于本项目同时需要一个托管的 Agent 执行环境,并且需要它与 Memory、Gateway、Identity 打通,我们最终选择了 Amazon Bedrock AgentCore 的 Runtime 与 Memory 组合。

我们最初是因为要存用户偏好才开始看 AgentCore 的。但”要存偏好”只说明需要 Memory,并不能顺带说明执行代码也该搬进 Runtime。后者是另外算了一笔账:自己搭执行环境、会话隔离和扩缩容要几周,而我们没有这几周。

2.3 五个组件各自解决什么

组件 本项目用法 采用理由的性质
AgentCore Runtime 承载基于 Strands Agents SDK 编写的 Agent,最长 8 小时独立会话,按请求自动扩缩容,每个用户会话相互隔离 替代自建执行环境、会话隔离与扩缩容,即 2.2 里被否决方案要自己做的那块
AgentCore Memory 按 actor_id / session_id 分层存储,承载用户偏好(习惯亮度、色温)与”床头小蓝”这类口头昵称作为辅助上下文 偏好记忆是功能定义的一部分,没有它这个功能不成立
AgentCore Gateway 把既有 SaaS REST API 封装为工具:getUserDevices、controlDevice、queryDeviceStatus 复用既有后端,不必为 Agent 重写一套 API
AgentCore Identity 接管用户 JWT → Agent 身份 → 后端 API 的整条凭证链,代码中不留明文密钥 凭证链托管。注意边界:它管身份与凭证,业务授权仍在 Xthings 权限系统
AgentCore Observability Agent 侧全链路 Trace 与指标,用于看 Token 用量、工具调用成功率与延迟分布 覆盖 Agent 侧。DCS 与设备回执这段在其范围外,仍需自行插桩

getUserDevices 返回当前用户有权限控制的设备清单,含 UUID、名称、所属空间和可用指令集;queryDeviceStatus 查单台设备的实时状态;controlDevice 把一条标准指令交给既有的 DCS 下发。

三个都是我们原有 SaaS 后端已经在跑的 REST 接口,Gateway 只是按工具调用的约定在外面包了一层。Agent 侧看到的就是三个可调用的工具,后端一行没改。

其中 getUserDevices 最后并没有真的以工具形式被调用。设备清单改由应用侧在调模型之前预加载注入,原因见 3.1。

Agent 本身用 Strands Agents SDK 编写,这是一个开源的 Agent 开发框架。框架与托管运行环境相互解耦:Runtime 承载的是这份代码,Agent 的业务逻辑不与 Runtime 的接口绑定。

五项合起来看,我们买到的是时间。工程投入可以直接压在提示词调优、记忆策略和灰度节奏上,不用先花几周把 Agent 的执行环境、记忆存储和鉴权链路搭出来。按前面说的人力,这几周挤不出来。

托管不等于省事。应用逻辑、输出校验、偏好写入规则,以及和既有平台的集成,这些还是我们自己的活儿。

三、四段式架构:预加载与异步下发

整体结构如图 2。贯穿设计的一条原则是:模型只负责解释意图,设备接入、权限判断与指令下发继续走既有控制平面,App 与设备两端的既有链路保持不变。

[图 2 Xthings 自然语言控制架构]

3.1 一条请求怎样完成

以用户说”我要睡觉了”为例:

1. 接入层路由。 App 请求经既有的 Xthings Cloud(Amazon EC2)网关进入。自研智能音箱走的是另一条路:它本身就是平台上的一台 IoT 设备,语音转成文字后经 MQTT 直连 AWS IoT Core,复用现有设备通道,音箱侧不需要单独后端。

2. 转发到会话管理。 两个入口最终汇到同一个 AWS Lambda 函数,由它做 JWT 验证、取会话、再往后编排。新链路没有引入新的入口服务,这一层的改动只是多了一个路由分支。

3. 上下文预加载(核心优化)。 调用模型之前,Lambda 先从 Amazon ElastiCache(Redis OSS)读取该用户的设备列表、可用指令集与状态集,连同会话上下文一起随请求交给 Runtime。缓存命中约 5 毫秒,未命中回源既有 SaaS API 约 80 毫秒。这一步让 Agent 不必在推理过程中再发起一轮工具查询去拿设备清单,本流程所需的模型调用因此从两轮减到一轮,省下约 300 毫秒。

这属于请求编排层的设计,在自建方案里同样可以采用。

4. 托管推理。 Runtime 内的 Nova Lite 结合预加载的设备清单和 Memory 中的用户偏好,在单次推理中完成意图匹配并输出结构化 JSON。

5. 校验后并行下发。 Lambda 先过一遍 4.1 的硬校验,再把通过的动作并行交给 DCS,经 AWS IoT Core 送达设备,采用 fire-and-forget 方式,不等设备回执。

6. 返回受理响应。 指令一旦交出去,Lambda 立刻沿原路把受理文案返回给 App 或音箱。用户这时看到的是”已经收到并下发”,不是”已经执行完”。

图 3 把这六步展开到时序上,并把设备真实状态的回推单独画在分隔线以下,图中标为 [A][B][C]:设备状态变化经 AWS IoT 规则触发 Lambda,更新 ElastiCache 并经 MQTT 推送到 App 刷新界面。分隔线以上是用户可感知的受理路径,以下这段和它之间没有等待关系。这既是受理延迟能压进 1 秒的前提,也是下面 message 字段那个问题的来源。

[图 3 “我要睡觉了”的端到端时序(含异步状态回推链路)]

3.2 结构化输出:让模型的克制变成可读的数据

模型的输出同时包含要执行的动作,以及主动放弃操作的设备和原因:

{
  "type": "control",
  "message": "晚安!已为您下发指令:关闭卧室灯、客厅灯和卧室窗帘,前门锁保持当前状态。",
  "actions": [
    {"uuid": "550e...001", "name": "卧室灯",   "action": "turnOff"},
    {"uuid": "550e...002", "name": "客厅灯",   "action": "turnOff"},
    {"uuid": "550e...003", "name": "卧室窗帘", "action": "turnOff"}
  ],
  "skipped": [
    {"uuid": "550e...004", "name": "前门锁", "reason": "安全考虑,睡觉场景不自动操作门锁"}
  ]
}

把”放弃操作哪些设备、为什么”做成 skipped[] 这个结构化字段,是我们比较满意的一个决定。模型的克制变成了可以读取的数据,客服和监控都能直接用。

3.3 分段耗时:分位数不能相加

环节 动作 典型耗时(量级值,非分位数)
接入 接入层 + Lambda 取 session 15–25 ms
预加载 读取设备列表与指令集(ElastiCache / SaaS API) 5–80 ms
推理 InvokeAgentRuntime → Nova Lite + Memory 偏好注入 约 300 ms
下发 解析 JSON + 并行下发 DCS 约 50 ms
(端到端) 用户侧观察到的受理响应(独立测量,含返回链路) 400–500 ms
回推 异步:设备上报 → AWS IoT 规则 → ElastiCache 更新 → MQTT 推送 200 ms–2 s

这组分段值用于定位瓶颈。分位数不可相加,端到端的 P50 / P95 / P99 必须独立测量,不能由上表推算。本文给出明确口径与目标值的只有 1.3 里那条受理延迟 P95。

3.4 message 字段现在只是交互提示,不是执行确认

上面那段 JSON 里的 message 字段,含义比字面上窄。指令目前采用 fire-and-forget 下发,这句文案由模型生成,产生时刻可能早于 DCS 确认接受指令,因此”已为您下发指令”并不代表设备已经收到或执行。

我们已经识别这一风险,计划改为由应用层根据 DCS 的受理结果生成反馈,并结合设备回执或状态查询确认实际执行结果。在改造完成之前,看到这句文案只能说明请求已经被受理。

四、关键工程实践与踩坑经验

让这条新链路在生产环境稳定跑下来,要在模型自由度和工程确定性之间搭几层护栏。本节讨论的是自然语言链路自身的稳定性,它承载的请求量和平台既有的设备上报、App 控制流量是两组指标,不能混着读。

下面四条里有三条当初设计时没想到,是上线之后被真实用户改出来的。

4.1 代码级的 UUID 白名单兜底

即便提示词反复强调只能使用已返回的设备,模型仍可能编造或拼接 UUID。提示词是软约束,我们把硬约束放在 Lambda 层:

  • 校验模型输出的 uuid 是否在预加载的白名单内,不在的直接过滤;
  • 校验 action 是否落在该设备 commands[] 定义的范围内,避免”给锁 setBrightness”这类非法指令;
  • JSON 解析失败时返回固定的兜底提示并在后台记 warning,模型原始输出只进排障日志,不展示给终端用户。

把模型原文回显给终端用户,看起来像是”保持系统弹性”,实际上用户会看到无意义的文本,也可能暴露提示词结构。固定文案加后台告警是更稳妥的组合。

4.2 别名的权威来源只能有一个

Memory 里存别名有一个合理解释:用户口头起的昵称本来就没有配置入口。但 App 里同时存在显式的设备别名配置,两处都能定义别名就会产生权威来源冲突。

我们的处理是:App 的显式别名为权威配置,Memory 中的别名只作为辅助上下文,并且别名最终必须映射到当前用户有权限控制的设备 UUID。无法唯一匹配时,系统要求用户确认,不直接执行指令。

碰上”两个床头灯”这种话,我们宁可多问一句。多一轮交互用户确实会觉得啰嗦,但误开一次门锁的代价我们承担不起,这笔账在设备控制场景里不难算。

4.3 Memory 的写入治理:上线两周推翻重写

上线初期我们每次用户开关灯都写一条偏好。两周后发现 Memory 被无区分度的样本淹没,老用户真实的习惯反而被埋没:纯开关动作不携带参数,写进长期偏好不产生检索价值,却稀释了真正有用的记录。

原则 说明
只存显式偏好 只有用户明确给了非默认参数(亮度、色温、音量)才写入,单纯的开关不写
去重覆盖 同一设备的同类偏好只保留最新版本,不做无限追加
自动老化 三个月未触发的偏好自动淘汰,避免记忆无限膨胀与检索噪声

后来的结论很直接:长期记忆这东西,存得多不如写得挑,关键在写入口有没有判断力。这是整个项目里返工最彻底的一处,上线两周就把写入逻辑整个推翻重写了。

4.4 敏感动作的当前边界

场景联动有一个容易被忽略的安全问题:用户说睡觉,通常意味着落锁,但绝不意味着开锁。

策略应当按动作定义,而不是按设备类型一律禁止。 按类型禁止门锁会误伤合理需求,睡前落锁正是用户想要的。

我们当前的做法是在提示词中规定”门锁类设备需谨慎操作,睡觉场景不应自动开锁”,并强制模型把放弃的设备写入 skipped[]。执行层目前尚未实现独立的敏感动作校验:4.1 里的 UUID 白名单和 commands[] 范围校验管的是”设备存在吗、动作对这类设备合法吗”,不管”这个动作是不是敏感动作”。

我们已经识别这一风险,计划在后续版本中补充执行层校验,以及相应的拦截与授权机制。

同一条原则在 UUID 上已经落实,在敏感动作上还没有。这是我们目前最清楚的一处缺口。

五、效果:先说口径,再说数字

工程侧。 预加载把本流程的模型调用从两轮减到一轮,这一轮工具查询的往返省下约 300 毫秒,用户在 400 到 500 毫秒内收到受理响应。受理延迟 P95 目标为 1 秒以内。

关于成本。 调用轮次减半会降成本,但预加载让每次请求都携带完整设备清单,input token 随之增加,两者方向相反。净效果只能按 token 级账目计算,不能按调用次数推断。

交付节奏。 依托托管组件,我们在第 1 周跑通骨架,第 2 周开始迭代提示词与记忆策略,Phase 1 的 MVP 压缩到 2 到 3 周。首轮灰度按家庭设备数不少于 5 台圈选,阈值就是前面那条 P90。

关于业务效果。 首轮灰度只圈了设备数不少于 5 台的那部分家庭,占全平台不到一成,样本期只有几周,这期间平台本身还在正常迭代新版本。

所以我们内部定了一条规矩:没有做过 A/B 隔离的指标变化,只写成”同期观察到”,不写成”由此带来”。智能家居的活跃度本来就随季节和节假日波动,把这种波动算成一个新功能的功劳,对后面做决策的人是误导。

这一节因此只给工程口径的数字。转化与留存等灰度扩量、拿到可对照的样本之后再单独讲。

六、可迁移的四条经验

1. 让模型解释意图,让既有系统裁决权限。 这套方案的可靠性不来自模型足够聪明,而来自模型之外存在一个权威的权限与执行系统。这也划出了适用边界:如果控制平面需要和 Agent 一起从零建设,风险结构完全不同。

2. 提示词是软约束,代码是硬约束。 任何一个”绝对不能发生”的动作,都需要在模型之外有一道确定性校验。这条原则的落实程度需要逐项盘点,而不是笼统声称”有护栏”——我们自己就有一项已经识别但尚未落实(4.4)。

3. 让模型输出”打算做什么”,让系统确认”做完了什么”。 结构化的 skipped[] 属于前者:模型声明自己打算放弃哪些设备、为什么放弃,这份声明可以被审计。

而”晚安!已为您下发指令”这句文案看着像后者。它和 skipped[] 一样是模型在推理时顺手写出来的,与设备是否真的收到指令没有关系。两者挨在同一段 JSON 里,读的人很容易顺手给它们同一份信任:打算做什么只能由模型说,做完了什么必须由执行链路回来的信号说。

4. 按组件采用托管服务。 AgentCore 的五个组件解决的是不同性质的问题,采用理由的强度也不一样。对本项目来说 Memory 是功能定义的一部分,Runtime 解决的是执行环境与会话隔离的自建成本,Gateway 与 Identity 解决的是复用既有后端和凭证链。按自己的约束逐项对照,比整体采纳或整体拒绝更有效。

七、结语

回头看,这个功能能在两三周里出一个能灰度的版本,一半原因是托管组件省掉了执行环境、会话隔离和凭证链的自建时间,另一半原因是我们没有去动既有的接入和控制链路。只要控制平面本身是可信的,加一条解释意图的链路并不需要很大工程量。

还有两件没做完:执行层的敏感动作校验,以及灰度扩量后拿到可对照样本重新测业务指标。有结果我们会再写一篇。

➡️ 下一步行动:

相关产品:

八、参考链接

*前述特定亚马逊云科技生成式人工智能相关的服务目前在亚马逊云科技海外区域可用。亚马逊云科技中国区域相关云服务由西云数据和光环新网运营,具体信息以中国区域官网为准。

本篇作者

姜胜鹏

Xthings 全球平台架构师兼软件与平台技术总监,负责公司软件产品与云平台的整体架构设计、技术规划及持续演进。长期专注于物联网大数据、高并发系统与云原生架构,持续探索AI在物联网场景、云平台建设、软件研发、数据分析及业务流程自动化中的创新应用,推动技术能力与业务价值深度融合。

郑绍杰

亚马逊云科技资深解决方案架构师,专注于云计算与 AI 解决方案架构的咨询和设计。拥有十余年的 IT 及云计算行业经验,在网络、云原生应用架构、容器以及 AI/生成式 AI 等多个技术领域具备丰富的实践经验。


AWS 架构师中心:云端创新的引领者

探索 AWS 架构师中心,获取经实战验证的最佳实践与架构指南,助您高效构建安全、可靠的云上应用