关于通过LLM及一点Agent搭建我的智能生活这件事

乐云一
  • 设计
  • 设计
  • AI
  • Agent
  • IoT
About 3223 wordsAbout 11 min

关于通过LLM及一点Agent搭建我的智能生活这件事

序:一切从那一声"真爽"开始

最近买了个小度,顺手把我那台空调连了上去。

于是这个夏天,每天下班进门的第一件事,就是冲着空气喊一嗓子:

"小度小度,打开我的空调。"

"好的,已为您打开空调。"

呼——冷风糊脸的那一瞬间,真爽。

科技改变生活,诚不欺我。

"真爽未果"

不过科技改变的不够彻底,爽的有些憋屈在某些角度上:我想要的其实不是"开空调"。

我想要的是计划

  • 十一点半我差不多睡着了,空调自动调到 27 度,别半夜冻醒我
  • 凌晨四点关掉,省点电费
  • 明天休息,早上八点用卧室灯把我晃醒,别用闹铃(回忆起读书的时候被起床铃支配的恐惧

这些需求小度 APP 能不能搞?

答:能

定时、场景、自动化,入口藏得不深也不浅,但是能编排的动作就那么几种,条件一多就歇菜。

至于"我睡着之后"这种模糊概念?不存在的,你自己换算成具体时间点去。

说白了,传统智能家居的"智能",是研发者们根据需求预先编排好的智能。

你能做到的,等于产品想到的。产品没想到的,就只能苦等升级咯。

但是等待别人这种事,生理性地不舒服。

灵光

巧了,我这段时间正好在写一个 Agent应用,MCP 开发以及LLM的思考编排略有理解,不过体验得很深

天天盯着 LLM 的 ReAct 思考过程看:看它怎么拆一句话、怎么在工具列表里挑、怎么填参数、填错了怎么自己掰回来。

然后某天晚上,空调的定时又双叒没设置对的时候,脑子里灵光一闪:

这活不该我来干,也不该小度的产品经理来干,该 LLM 来干。

与其面对一个死应用,不如使其像Iphone的siri一样:HI Siri帮我定一个6点的闹钟

说干就干!

设想很简单:

手机上装一个应用,一个网页或者一个安卓/IOS应用,然后抛弃小度 APP,我只负责对着这个应用说人话

"十一点半把空调调到 27 度,凌晨四点关掉。"

"明天早上七点叫我,先把卧室灯打开,别开太亮。"

"如果这几天降温了,睡前那档调高一度。"

应用里的 Agent 听懂这句话,把它编排成一个可执行的计划,存下来,到点执行。

控制链路也是现成的:

我 → 我的 Agent → 小度的第三方 API → 空调

而"对接第三方 API"这个事,我在物联网公司干了好几年,云云对接就是老本行(这段历史在《物联网语音云云接入》里写过),啧啧最难的高强就这么轻易跨过了。

当晚,配合着Claude编写了一本PRD文档

我管这东西叫:伪智能家居 APP

"伪"在哪?它一个设备都没有,一个厂商协议都不碰,站在小度的肩膀上,只把"编排"这一层抢过来,交给 LLM。小度继续当手,脑子换成我自己的。

系统的本质

把这件事拆开看,其实就是一次职责的重新分配:

传统智能家居 APP 里,"智能"是编译期就定死的:产品经理把用户可能的需求穷举成一个个功能,你在里面挑。

伪智能家居 APP 里,"智能"是运行期的:功能上限 = LLM 的理解力 × 开放 API 的能力

LLM 负责把"睡前别冻醒我"翻译成机器能执行的计划,API 负责让计划真的砸到空调上。中间所有原来需要产品经理预判的环节,全部消失。

整个系统要解决的,说穿了就三个问题:

  1. 怎么听懂人话 — 意图识别 + 参数抽取
  2. 怎么把人话变成计划 — 计划编排
  3. 怎么让他有时间观念

架构

四个部分:

手机端。人话接收端

Agent 后端。Spring AI的架子,传统Agent开发的模式

MCP 工具层。把小度的开放接口封装成 MCP 工具,对外就三个能力:

  • discoverDevices — 拉设备列表
  • controlDevice — 下发指令(开关、温度、模式)
  • queryDeviceStatus — 查设备状态

封装成 MCP 而不是直接写死在代码里,是有私心的:这样这套工具不光我这个 APP 能用,任何支持 MCP 的客户端——包括后续有任何应用都可以通过调用MCP接口直接指挥我家空调。

计划内核。存计划、算下次触发时间、到点回调。这里全是老朋友了:时间片的 zset、时间轮,都是我在《自动化场景业务理解》和《处理夏令时转化业务》里推演过的东西。当年给公司推演的方案,如今砸在自家空调上,还挺感慨。

意图分诊:不是每句话都值得花钱

第一道关卡是意图识别,我把用户输入分成三类:

public IntentResult dispatch(String text) {
    // 三分类:闲聊 / 即时控制 / 计划编排
    // 别看就三类,这里偷懒后面全是坑
    Intent intent = intentClassifier.classify(text);

    switch (intent) {
        case CHAT:        // "你好" "今天天气咋样"
            return chatAgent.reply(text);

        case INSTANT:     // "开空调" "调到26度"
            return instantControl(text);   // 直接走工具,不落库

        case PLAN:        // "十一点半关空调" "明早七点叫我"
            return planOrchestrate(text);  // 编排成计划,落库

        default:
            return askBack(text);          // 听不懂就反问,别硬猜
    }
}

画成图就一条分岔路:

有个细节值得单独说:简单指令绝不让 LLM 走完整 ReAct

"打开空调"这种话,意图明确、参数为零,让大模型思考一轮纯属烧钱。所以意图识别用的是小模型,命中 INSTANT 之后走的是规则短路,只有 PLAN 这种真正需要"理解"的,才请大模型出场。

一天几毛钱也是钱,积少成多就是一台空调的钱(并不是)。

计划编排

真正有意思的是 PLAN 这条路。

用户说的是自然语言,机器要的是结构化数据。中间这个翻译,就是 LLM 的主场:

public Plan planOrchestrate(String text) {
    // Step 1: 把当前时间喂进去
    // LLM 不知道"现在几点",也不知道"明天"是哪天
    String systemPrompt = SYSTEM_PROMPT
            + "\n当前时间: " + LocalDateTime.now()
            + "\n可用设备: " + deviceClient.discoverDevices();

    // Step 2: 让它填表,不是让它作文
    // entity() 底层是 JSON Schema 约束 + 结构化反序列化,填歪直接抛异常
    Plan plan = chatClient.prompt()
            .system(systemPrompt)
            .user(text)
            .call()
            .entity(Plan.class);

    // Step 3: 参数归一 + 校验,LLM 的输出在入库前一律当"不可信输入"处理
    for (Action action : plan.getActions()) {
        action.setTemperature(
                paramNormalizer.normalize(action.getTemperature()));  // "二十六度"→26
        paramChecker.checkRange(action.getTemperature(), 16, 30);     // 999?拦下
        paramChecker.checkDevice(action.getDeviceId());               // 设备在不在这屋里
    }

    // Step 4: 落库,算下次触发时间
    planRepository.save(plan);
    triggerScheduler.schedule(plan.nextTriggerTime(), plan.getId());

    return plan;
}

Plan 这个实体长这样,编排的目标就是把一句人话灌进这个结构里:

@Data
public class Plan {
    private String planName;          // "睡觉空调"
    private List<Trigger> triggers;   // 触发器:什么时候动
    private List<Action> actions;     // 动作序列:动什么、怎么动
}

@Data
public class Trigger {
    private TriggerType type;         // CRON / FIXED_TIME / DEVICE_EVENT
    private String expr;              // "0 30 23 * * *",每天23:30
}

@Data
public class Action {
    private String device;            // "卧室空调"
    private String command;           // "setTemp" / "powerOff"
    private Integer value;            // 27
    private Duration delay;           // 相对上一条的延迟,比如 4.5 小时
}

"凌晨四点关掉"这种相对描述,编排期就换算成绝对时间点或 cron,执行期不做任何理解——理解在写入时发生一次就够了,执行的时候它最好是个没有感情的机器

这是我写防飘移那篇以来一以贯之的偏见:LLM 负责模糊到精确的翻译,翻译完,请它离场。

到点之后的链路就很无聊了:

踩的坑

坑一:LLM 不知道"现在"是几点

第一次测试,我说"明早七点叫我",它给我安排到了昨天早上七点。

原因很蠢:LLM 的世界里没有时钟。"明早"是相对概念,相对的是当前时间——而这个信息默认不在它的上下文里。

解法:每次请求把当前时间硬塞进 system prompt,并且要求所有时间先解析成绝对时间再输出。

坑二:温度的十种说法

"二十六度"、"26.5"、"调低点"、"最凉快那种"。

表单 Schema 约束了字段类型,约束不了值的野性。"最凉快"这种,归一层直接映射成 16 度并回复确认。参数归一这套闸门,防飘移那篇的第 5 道防线原样搬来,一个字没改——好用的设计是会在别的地方再救你一次的

坑三:下发成功 ≠ 空调开了

API 返回 200,空调没动。

设备离线、指令丢了、红外没对准,任何一环都可能导致"我说了"但"没成"。如果不做状态回读,Agent 就会一本正经地通知你"已为您关闭空调"——它没撒谎,它只是不知道自己没做到。

所以执行完必须 queryDeviceStatus 回读一次,对不上就重试,重试还不行就老老实实通知失败。执行闭环这东西,聊天机器人可以没有,设备控制必须有。

坑四:token 半夜过期

凌晨三点触发的计划,挂在了 OAuth token 过期上。

老熟人了,桉树的 Task Processor 里写过 token 提前一小时刷新,这次原样抄回来。历史经验这种东西,就是让你在同一类坑里只摔一次。

坑五:安卓的后台,说杀就杀

手机端本来还想做地理围栏触发(到家前半小时开空调),后来发现安卓的后台进程活得跟案板上的鱼一样,指不定哪个瞬间就没了。

现在的做法是保底逻辑:本地闹铃 + 计划列表全在服务端,手机端挂了不影响执行,只是通知晚点到。

初步测试

跑通的那天晚上,我对着手机说了一句:

"十一点半空调调 27 度,凌晨四点关掉,明早七点半用卧室灯叫我。"

三秒钟,三条计划躺在列表里,参数一个没错。

那一瞬间的感觉很奇妙——我什么都没设置,我只是说了一句话,然后世界就照办了

而且这句话不管我怎么表述,多么奇葩,多么杂糅,甚至夹杂着English,目前LLM模型的能力都足以将其思考成型

在小度 APP 里点自动化的时候,我可从来没有这种感觉。

总结

这玩意现在离"产品"十万八千里:断网就哑火;LLM 偶尔还是会抽风,把 27 度听成 17 度;家里所有动静都要过一遍大模型,隐私敏感的人怕是要睡不着。

不过对于智能化必须要划死底线:门锁、摄像头这类设备,永远不接进这个系统。幻觉答错一句话,赔的是用户体验;幻觉开错一次门,赔的就不是体验了。

家居控制只是个例子。

以前想把生活里某个环节"智能化",要么等厂商产品经理开恩,要么自己撸代码硬写——门槛是技术。

现在门槛变成了:你会不会把需求说清楚。

LLM 负责理解,代码负责执行,开放平台负责连接。三者拼起来,一个后端开发加一个周末,就能给自己长出一个智能家居 APP——这在三年前是妄想。

而且这件事会越来越便宜。模型再往前跑两代,编排成本趋近于零,到时候限制你的只剩下想象力和你家设备的开放接口。

智能家居的下一步,不是设备更聪明,而是编排设备的那颗脑子,归你自己。

小度小度,辛苦了,下班吧。

以后的活,我让 Agent 派给你。

Last update:
Contributors: LeYunone
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.14.7