Leo小李头像
关注

AI 辅助智能家居场景编排:从语音指令到 UI 自动化的设计方法

AI 辅助智能家居场景编排:从语音指令到 UI 自动化的设计方法

一、引子:当"电影模式"变成薛定谔的场景

上个月给家里的智能家居设定了"电影模式":关主灯、拉窗帘、开氛围灯、投影仪开机、音响切到 HDMI ARC。这套流程在执行到第三步时,有 40% 的概率失败——因为窗帘电机偶尔离线,氛围灯的色温 API 返回值格式变了。

场景编排的痛点不是"编不出",而是"编了不可靠"。传统方法是用户手动拖拽条件-动作规则(IFTTT 的 if-this-then-that 范式),这在设备数量超过 15 个后彻底失效。每个新增设备都会与所有已有设备产生潜在交互(组合爆炸),用户无法预见到"拉开窗帘时如果空调正以最大功率运行,应该先调低风速避免冷气外泄"这样的边缘场景。

AI 辅助场景编排的核心价值不是替代条件规则,而是从自然语言意图中自动生成规则,并持续监控执行效果,在异常时自动修复。这需要 AI 同时理解三件事:用户说的自然语言是什么意思、当前设备状态能否支持这个意图、执行失败时有哪些替代路径。

二、底层机制:意图解析到规则生成的流水线

场景编排需要一条从模糊自然语言到可执行规则链的完整管道。

意图解析器是整个流水线的入口,也是出错率最高的环节。用户说"我要看电影",AI 需要推理出:目标空间是客厅、涉及设备包括灯光/窗帘/投影/音响、期望的环境状态是低光照+安静、对执行时间的容忍度是中。如果用户说"我要看恐怖片",AI 还需要额外推理出:氛围灯偏冷色调、音量略大、可以加入突然的灯光变化作为跳吓配合(当然,这个功能需要用户确认开关)。

冲突检测器的价值在场景编排中被严重低估。两个常见冲突类型:

  1. 时序冲突:投影仪需要 15 秒预热,但音响切输入源只要 2 秒,先切音响会导致短暂播放无信号噪音
  2. 状态冲突:氛围灯设为"极暗"(亮度 5%)和人体传感器联动规则"有人时亮度 60%"矛盾

三、生产级代码:LLM 驱动的场景编排引擎

/**
 * AI 驱动的智能家居场景编排引擎
 * 
 * 核心能力:
 * 1. 将自然语言意图解析为结构化的场景操作序列
 * 2. 检测和解决设备间的操作冲突
 * 3. 执行过程监控与自动降级
 */

// 场景操作步骤
interface SceneStep {
  id: string;
  deviceId: string;
  action: string;         // 'setPower' | 'setBrightness' | 'setColorTemp' | ...
  params: Record<string, number | string | boolean>;
  preconditions: string[]; // 前置步骤 ID 列表
  timeout: number;         // 执行超时(ms)
  fallback?: SceneStep;    // 失败时的降级操作
}

// 场景定义
interface SceneDefinition {
  name: string;
  description: string;
  steps: SceneStep[];
  estimatedDuration: number;
  tags: string[];
}

// LLM 意图解析的结果
interface ParsedIntent {
  action: string;           // 动作类型
  room?: string;            // 目标房间
  atmosphere?: string;      // 氛围描述
  constraints: {
    brightness?: 'dim' | 'normal' | 'bright';
    colorTemp?: 'warm' | 'neutral' | 'cool';
    sound?: 'silent' | 'quiet' | 'normal' | 'loud';
  };
  confidence: number;
}

/**
 * 场景编排器
 * 
 * 核心流程:
 * intent → device matching → rule generation → conflict resolution → execution
 */
class SceneOrchestrator {
  private devices: SmartDevice[];
  private llmClient: LLMClient; // 假设的 LLM 接口

  constructor(devices: SmartDevice[], llmClient: LLMClient) {
    this.devices = devices;
    this.llmClient = llmClient;
  }

  /**
   * 从自然语言创建场景
   * 
   * @param userInput - 如 "晚上在客厅用投影看文艺片"
   */
  async createSceneFromNaturalLanguage(
    userInput: string
  ): Promise<SceneDefinition> {
    // 1. 意图解析
    const intent = await this.parseIntent(userInput);

    // 2. 设备匹配
    const matchedDevices = this.matchDevices(intent);

    // 3. 规则生成
    const rawSteps = await this.generateSteps(intent, matchedDevices);

    // 4. 冲突检测与解决
    const resolvedSteps = await this.resolveConflicts(rawSteps);

    // 5. 排序(拓扑排序,满足前置依赖)
    const orderedSteps = this.topologicalSort(resolvedSteps);

    return {
      name: this.generateSceneName(intent),
      description: userInput,
      steps: orderedSteps,
      estimatedDuration: this.estimateDuration(orderedSteps),
      tags: this.extractTags(intent),
    };
  }

  /**
   * LLM 意图解析
   * 将自然语言映射为结构化的意图对象
   */
  private async parseIntent(userInput: string): Promise<ParsedIntent> {
    const prompt = `
      你是一个智能家居意图解析器。将用户的输入解析为 JSON 格式。
      
      规则:
      - action: 必须是 "watch_movie", "sleep", "reading", "party", "leave_home", "work" 之一
      - room: 从 "living_room", "bedroom", "study", "kitchen", "bathroom" 中推断
      - atmosphere: 描述氛围的形容词
      - constraints: 从用户描述中提取约束条件
      
      用户输入: "${userInput}"
    `;

    const response = await this.llmClient.complete({
      messages: [{ role: 'user', content: prompt }],
      responseFormat: 'json',
    });

    return JSON.parse(response.content) as ParsedIntent;
  }

  /**
   * 设备匹配:根据意图筛选相关设备
   * 优先级:目标房间 > 功能匹配 > 性能等级
   */
  private matchDevices(intent: ParsedIntent): SmartDevice[] {
    const candidates = this.devices.filter((d) => {
      // 房间过滤
      if (intent.room) {
        const roomMap: Record<string, string> = {
          living_room: '客厅', bedroom: '卧室', study: '书房',
          kitchen: '厨房', bathroom: '卫生间',
        };
        if (d.room !== roomMap[intent.room]) return false;
      }

      // 功能匹配
      return this.deviceSupportsIntent(d, intent);
    });

    return candidates;
  }

  /**
   * 检查设备是否支持某个意图
   */
  private deviceSupportsIntent(
    device: SmartDevice,
    intent: ParsedIntent
  ): boolean {
    const actionDeviceMap: Record<string, string[]> = {
      watch_movie: ['light', 'curtain', 'speaker', 'projector'],
      sleep: ['light', 'curtain', 'thermostat'],
      reading: ['light'],
      party: ['light', 'speaker'],
      leave_home: ['lock', 'light', 'curtain', 'thermostat'],
    };

    return actionDeviceMap[intent.action]?.includes(device.type) ?? false;
  }

  /**
   * 规则生成:为每个匹配的设备生成操作步骤
   * 
   * 每种意图对应一套预设的操作模板,
   * 通过 LLM 可以根据上下文微调参数
   */
  private async generateSteps(
    intent: ParsedIntent,
    devices: SmartDevice[]
  ): Promise<SceneStep[]> {
    const steps: SceneStep[] = [];
    let stepIndex = 0;

    for (const device of devices) {
      const step = this.generateStepForDevice(device, intent, stepIndex++);
      if (step) steps.push(step);
    }

    return steps;
  }

  /**
   * 为单个设备生成操作步骤
   */
  private generateStepForDevice(
    device: SmartDevice,
    intent: ParsedIntent,
    index: number
  ): SceneStep | null {
    // 意图 → 设备操作映射表
    const actionMap: Record<string, Record<string, any>> = {
      watch_movie: {
        light: { action: 'setBrightness', value: intent.constraints.brightness === 'dim' ? 5 : 10 },
        curtain: { action: 'setPosition', value: 0 },
        speaker: { action: 'setInput', value: 'hdmi_arc' },
        projector: { action: 'setPower', value: true },
      },
      sleep: {
        light: { action: 'setPower', value: false },
        curtain: { action: 'setPosition', value: 0 },
        thermostat: { action: 'setTemperature', value: 26 },
      },
    };

    const deviceAction = actionMap[intent.action]?.[device.type];
    if (!deviceAction) return null;

    const step: SceneStep = {
      id: `step_${index}_${device.id}`,
      deviceId: device.id,
      action: deviceAction.action,
      params: this.resolveParams(deviceAction),
      preconditions: this.getPreconditions(device.type, intent.action),
      timeout: this.getTimeout(device.type),
      fallback: this.getFallbackStep(device, intent, index),
    };

    return step;
  }

  /**
   * 冲突检测与解决
   * 
   * 检测两类冲突:
   * 1. 时序冲突:操作顺序不当
   * 2. 状态冲突:设备目标状态与其他规则矛盾
   */
  private async resolveConflicts(steps: SceneStep[]): Promise<SceneStep[]> {
    const resolved = [...steps];

    // 时序冲突检测:投影仪需要预热,应放在音响前面
    const projectorStep = resolved.find((s) =>
      s.deviceId.includes('projector')
    );
    const speakerStep = resolved.find((s) => s.deviceId.includes('speaker'));

    if (projectorStep && speakerStep) {
      // 确保投影仪在音响之前(添加前置依赖)
      if (!speakerStep.preconditions.includes(projectorStep.id)) {
        speakerStep.preconditions.push(projectorStep.id);
      }
    }

    // 灯光亮度冲突:氛围灯 5% 与传感器规则 60% 冲突
    // 场景环境下的规则优先级高于通用规则,通过标记解决
    for (const step of resolved) {
      if (step.action === 'setBrightness') {
        step.params['_overrideSensor'] = true; // 场景优先标记
      }
    }

    return resolved;
  }

  /**
   * 拓扑排序:确保前置步骤先执行
   */
  private topologicalSort(steps: SceneStep[]): SceneStep[] {
    const sorted: SceneStep[] = [];
    const visited = new Set<string>();
    const stepMap = new Map(steps.map((s) => [s.id, s]));

    function visit(stepId: string) {
      if (visited.has(stepId)) return;
      visited.add(stepId);

      const step = stepMap.get(stepId);
      if (!step) return;

      for (const preId of step.preconditions) {
        visit(preId);
      }

      sorted.push(step);
    }

    for (const step of steps) {
      visit(step.id);
    }

    return sorted;
  }

  /**
   * 执行场景,监控每个步骤的成功/失败
   * 失败时自动切换到降级方案
   */
  async executeScene(scene: SceneDefinition): Promise<{
    success: boolean;
    failedSteps: string[];
    usedFallbacks: string[];
  }> {
    const failedSteps: string[] = [];
    const usedFallbacks: string[] = [];

    for (const step of scene.steps) {
      try {
        await this.executeStep(step);
      } catch (error) {
        failedSteps.push(step.id);

        // 尝试降级操作
        if (step.fallback) {
          try {
            await this.executeStep(step.fallback);
            usedFallbacks.push(step.id);
          } catch {
            // 降级也失败,记录并继续
          }
        }
      }
    }

    return {
      success: failedSteps.length === 0,
      failedSteps,
      usedFallbacks,
    };
  }

  private async executeStep(step: SceneStep): Promise<void> {
    // 模拟:通过 MQTT/HTTP 下发指令到设备
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => reject(new Error('timeout')), step.timeout);
      // 实际项目中这里是设备通信代码
      clearTimeout(timer);
      resolve();
    });
  }

  // 辅助方法
  private resolveParams(action: any): Record<string, any> {
    const { action: _, ...params } = action;
    return params;
  }

  private getPreconditions(
    deviceType: string,
    actionType: string
  ): string[] {
    // 投影仪操作前需要电源就绪
    if (deviceType === 'projector') return [];
    return [];
  }

  private getTimeout(deviceType: string): number {
    const timeouts: Record<string, number> = {
      projector: 30000, light: 5000, curtain: 15000,
      speaker: 5000, thermostat: 10000, lock: 8000,
    };
    return timeouts[deviceType] || 5000;
  }

  private getFallbackStep(
    device: SmartDevice,
    intent: ParsedIntent,
    index: number
  ): SceneStep | undefined {
    // 窗帘离线 → 提示用户手动关闭
    if (device.type === 'curtain') {
      return {
        id: `fallback_${index}_${device.id}`,
        deviceId: 'notification',
        action: 'sendNotification',
        params: { message: `请手动关闭${device.room}窗帘` },
        preconditions: [],
        timeout: 1000,
      };
    }
    return undefined;
  }

  private generateSceneName(intent: ParsedIntent): string {
    const nameMap: Record<string, string> = {
      watch_movie: '观影模式', sleep: '睡眠模式',
      reading: '阅读模式', party: '派对模式',
      leave_home: '离家模式', work: '工作模式',
    };
    return nameMap[intent.action] || intent.action;
  }

  private estimateDuration(steps: SceneStep[]): number {
    return steps.reduce((sum, s) => sum + s.timeout, 0);
  }

  private extractTags(intent: ParsedIntent): string[] {
    const tags = [intent.action];
    if (intent.room) tags.push(intent.room);
    if (intent.atmosphere) tags.push(intent.atmosphere);
    return tags;
  }
}

这段代码中最容易被忽略的设计是 fallback 降级机制。生产环境中设备离线率在 3%-8% 范围内波动(取决于网络质量),没有降级方案的场景编排在 1/20 的概率下就是不可用的。

四、边界分析

LLM 意图解析的不确定性:同一个输入"我要看电影",不同模型或同模型不同温度参数下,可能解析出不同的 action 值。需要构建一组金标准测试用例(至少 200 条),每次更新模型后跑回归测试,确保意图解析的一致性。

设备匹配的冷启动问题:新用户没有历史数据时,设备匹配完全依赖设备类型的硬编码映射。如果用户说"看电影"但家里没有投影仪(只有电视),系统需要智能退化为"电视模式"。这需要更多的设备能力语义理解。

执行监控的实时性:每个步骤的超时需要动态调整。如果在凌晨 3 点执行场景(网络空闲),超时可以缩短;如果在晚高峰执行,需要适当放宽。

五、总结

  1. 场景编排的核心价值在于自然语言到可执行规则的自动转化,而非手动拖拽
  2. 流水线包含意图解析 → 设备匹配 → 规则生成 → 冲突解决 → 执行监控五个环节
  3. 时序冲突(操作顺序错误)和状态冲突(目标状态矛盾)是需要重点检测的两类问题
  4. 降级机制是场景编排可靠性的最后防线,没有降级方案的场景有 3%-8% 的概率执行失败
  5. LLM 意图解析需要金标准回归测试保证一致性
  6. 设备离线时自动推送通知引导手动操作,比静默失败好 100 倍
  7. 拓扑排序确保有前置依赖的操作严格按序执行

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/leopold_man/article/details/163219205

文章来源crawl

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--