1. 项目概述:当AI大模型遇见智能家居

最近在折腾智能家居的朋友,估计都听过一个词叫“AI Agent”。这玩意儿说白了,就是让一个更聪明的“大脑”来接管你家里那些零零散散的设备,让它们从“听从指令”变成“主动服务”。我这次分享的,就是基于Anthropic的Claude模型,搭建一个真正能理解你、能思考的智能家居中枢的实战案例。这不仅仅是把语音助手从“小爱同学”换成“Claude”那么简单,而是整个交互逻辑和自动化层级的跃升。

传统的智能家居,无论是通过手机APP点按,还是用固定的“如果...就...”场景联动,本质上还是人在给机器下命令。你晚上回家,得对音箱喊“打开客厅灯”,或者提前设置好“当手机GPS到家时开灯”。但如果你只是躺在沙发上说了一句“有点暗”,传统的系统可能就懵了。而引入像Claude这样具备强大自然语言理解和推理能力的大模型后,系统能听懂你的言外之意。“有点暗”可能意味着需要调亮灯光,也可能是窗帘太厚,甚至是你心情的写照,系统可以结合环境光线传感器数据、时间、你的习惯,主动给出最合理的解决方案:缓缓调亮灯光,并询问是否需要播放一些轻松的音乐。

这个项目的核心价值,就是解决智能家居“不够智能”的痛点,让它从工具变为管家。它适合有一定技术基础的智能家居爱好者、IoT开发者,或者任何对AI落地真实场景感兴趣的朋友。你不必是AI专家,但需要对API调用、网络通信和基本的智能家居平台(如Home Assistant)有所了解。接下来,我会完整拆解从思路设计、环境搭建、核心代码实现到问题排查的全过程,这些都是我一步步踩坑踩出来的经验。

2. 整体架构设计与核心思路拆解

在动手写代码之前,想清楚架构至关重要。我们的目标不是重新发明轮子去造智能硬件,而是利用现有的、成熟的智能家居生态,在上面叠加一个AI大脑。因此,我的设计原则是“承上启下,非侵入式集成”。

2.1 核心架构图与组件角色

整个系统可以划分为三层:交互层、AI决策层和设备执行层。

  1. 交互层 :这是用户入口。可以是语音(通过智能音箱、手机麦克风)、文字(手机APP、聊天窗口)甚至未来可能是图像。这一层负责收集用户的原始指令,比如“我睡了”或“客厅太热了”。
  2. AI决策层(核心) :这是本项目的心脏。它接收来自交互层的自然语言指令,并结合 家庭状态上下文 进行理解和推理。上下文包括:所有传感器的实时数据(温度、湿度、光照、人体移动)、设备当前状态(灯是否亮、空调模式)、时间、甚至用户的历史偏好。然后,Claude模型会生成一个结构化的“操作意图”,例如 {"action": "adjust", "device": "living_room_ac", "params": {"power": "on", "mode": "cool", "temperature": 24}}
  3. 设备执行层 :这一层是现有的智能家居基础设施。它接收AI决策层下发的结构化指令,通过对应的协议(如MQTT、HomeKit API、厂商私有云API)控制真实的物理设备。为了最大化兼容性,我选择了 Home Assistant 作为设备执行层的集控中心。HA几乎支持所有主流智能家居设备,并提供了强大且统一的RESTful API。

所以,数据流是这样的:用户说“有点热” -> 交互层捕获并转换为文本 -> 发送给AI决策服务 -> AI服务从Home Assistant拉取当前客厅温度、空调状态 -> Claude模型推理出“需要打开空调并设定为24度制冷” -> AI服务将结构化指令发送给Home Assistant API -> Home Assistant控制空调执行。

2.2 为什么选择Claude与Home Assistant的组合?

这个选择背后有非常实际的考量。

首先,为什么是 Anthropic的Claude ,而不是其他大模型?在多次对比测试中,我发现Claude在遵循指令、安全性和推理的稳定性上表现尤为突出。智能家居关乎安全和隐私,一个“脑补”过多或容易产生有害指令的模型是危险的。Claude在系统提示词(System Prompt)的遵守上非常严格,这让我能更好地设定其行为边界,例如禁止它执行“关闭所有安防设备”或“将温度调到极端值”这类操作。此外,其API的响应格式相对稳定,便于后续解析。当然,你也可以用OpenAI的GPT系列或开源的本地模型,但需要额外花精力在提示工程和输出格式控制上。

其次,为什么是 Home Assistant ?因为它解决了智能家居领域最头疼的“碎片化”问题。你家可能有小米的灯、格力的空调、博联的插座,每个品牌一个APP,协议互不相通。Home Assistant通过大量的集成(Integration),将这些设备统一管理起来,并对外提供一致的接口。这意味着,我的AI大脑只需要学会和Home Assistant对话,就能控制成百上千种不同的设备,极大地降低了开发复杂度。它就像一个“设备翻译官”和“统一指挥官”。

2.3 关键技术栈选型

基于以上架构,我的技术栈如下:

  • AI服务端 :Python + FastAPI。Python是AI生态的首选语言,库支持最全。FastAPI轻量、异步性能好,能快速构建提供AI推理能力的Web API。
  • 大模型API :Anthropic Claude API(主要是 claude-3-haiku 模型)。Haiku模型速度最快、成本最低,对于智能家居这种需要快速响应的场景非常合适。复杂的场景分析可以用Sonnet,但日常指令Haiku绰绰有余。
  • 智能家居平台 :Home Assistant Core(Docker部署)。采用Docker部署方便迁移和备份。
  • 通信与集成
    • AI服务与Home Assistant :通过Home Assistant的REST API和WebSocket API进行通信。REST API用于主动查询状态,WebSocket用于实时接收设备状态更新事件,让AI能感知家庭环境的动态变化。
    • 交互层与AI服务 :简单的HTTP请求。未来可扩展为WebSocket实现更流畅的对话。
  • 上下文管理 :使用简单的内存字典或Redis。用于存储当前会话的上下文(如用户ID、对话历史、最近的家庭状态快照),确保Claude在回答时具有连贯性。

注意 :整个项目运行在你的本地网络或你可控的服务器上。所有数据(用户指令、家庭状态、AI推理)都在内部流转,不经过不必要的第三方云服务,这是保障隐私和安全的核心前提。

3. 核心模块解析与实操要点

明确了架构,我们开始深入核心模块的构建。这里面的每一个环节都有细节需要注意。

3.1 Home Assistant的配置与服务暴露

这是所有控制的基础。首先,你需要在你的NAS、树莓派或一台常开机的电脑上安装好Home Assistant。具体安装教程官方很详细,这里不赘述。安装完成后,关键是要正确配置,让我们的AI服务能够安全地连接它。

  1. 创建长期访问令牌(Long-Lived Access Token) :这是AI服务访问HA的“密码”。在HA网页侧边栏,点击你的用户名 -> 拉到最下方 -> “创建令牌”。给它起个名字,比如“AI_Server”。 务必复制并妥善保存弹出的令牌字符串,它只显示一次。

  2. 配置API访问与内网地址 :确保HA的配置( configuration.yaml )中允许API访问。通常默认是开启的。你需要知道HA的内网IP地址和端口(默认8123)。你的AI服务将通过 http://[HA_IP]:8123/api 这个地址与HA通信。

  3. 集成你的设备 :在HA的“设备与服务”中,添加你所有的智能设备。这个过程因设备而异,可能需要登录厂商账号或通过本地发现。确保所有你希望AI控制的设备都在HA中有了对应的实体(Entity),例如 light.living_room climate.bedroom_ac

  4. 测试API连通性 :你可以先用 curl 命令测试令牌是否有效:

    curl -X GET \
      -H "Authorization: Bearer YOUR_LONG_LIVED_TOKEN" \
      -H "Content-Type: application/json" \
      http://[HA_IP]:8123/api/states
    

    如果返回一串JSON格式的设备状态信息,说明配置成功。

3.2 AI决策服务(FastAPI后端)的构建

这个服务是整个系统的“大脑”,它主要做三件事:接收用户查询、获取家庭状态、调用Claude推理、执行控制指令。

项目结构概览:

ai_home_assistant/
├── main.py          # FastAPI应用主入口
├── core/
│   ├── claude_client.py    # 封装Claude API调用
│   ├── ha_client.py        # 封装Home Assistant API调用
│   └── context_manager.py  # 管理对话上下文
├── config.py        # 配置文件(API密钥、HA地址等)
└── requirements.txt # 项目依赖

1. 环境配置与依赖安装 创建 requirements.txt 文件:

fastapi>=0.104.0
uvicorn[standard]>=0.24.0
httpx>=0.25.0
anthropic>=0.7.0
python-dotenv>=1.0.0
redis>=5.0.0  # 如果使用Redis管理上下文

使用 pip install -r requirements.txt 安装。使用 python-dotenv 管理敏感信息,创建 .env 文件:

ANTHROPIC_API_KEY=your_anthropic_api_key_here
HA_BASE_URL=http://192.168.1.100:8123
HA_ACCESS_TOKEN=your_long_lived_token_here

2. 封装Home Assistant客户端 ( ha_client.py ) 这个模块负责与HA的所有通信,需要实现状态查询和设备控制。

import httpx
from typing import Dict, Any, List
import asyncio

class HomeAssistantClient:
    def __init__(self, base_url: str, access_token: str):
        self.base_url = base_url.rstrip('/')
        self.access_token = access_token
        self.headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json"
        }
        self.client = httpx.AsyncClient(timeout=30.0) # 设置合理超时

    async def get_all_states(self) -> List[Dict[str, Any]]:
        """获取HA中所有实体的状态"""
        url = f"{self.base_url}/api/states"
        try:
            resp = await self.client.get(url, headers=self.headers)
            resp.raise_for_status()
            return resp.json()
        except httpx.RequestError as e:
            print(f"获取HA状态失败: {e}")
            return []

    async def call_service(self, domain: str, service: str, service_data: Dict[str, Any]) -> bool:
        """调用HA服务(控制设备的根本方式)"""
        url = f"{self.base_url}/api/services/{domain}/{service}"
        try:
            resp = await self.client.post(url, headers=self.headers, json=service_data)
            resp.raise_for_status()
            return True
        except httpx.RequestError as e:
            print(f"调用HA服务失败: {e}")
            return False

    # 示例:封装一个具体的开灯操作
    async def turn_on_light(self, entity_id: str, brightness: int = None) -> bool:
        service_data = {"entity_id": entity_id}
        if brightness:
            service_data["brightness"] = brightness # 亮度值通常0-255
        return await self.call_service("light", "turn_on", service_data)

    async def close(self):
        await self.client.aclose()

实操心得 :HA的API设计非常RESTful,控制设备的本质是调用“服务”。每个设备领域(domain)如 light climate 都有对应的服务如 turn_on set_temperature 。在 developer-tools -> services 标签下,你可以看到所有可用的服务及其参数,这是你编写控制代码的“字典”。

3. 封装Claude客户端与提示工程 ( claude_client.py ) 这是AI推理的核心。如何设计提示词(Prompt)直接决定了Claude的表现。

import anthropic
from typing import List, Dict, Any
import json

class ClaudeHomeAssistant:
    def __init__(self, api_key: str, model: str = "claude-3-haiku-20240307"):
        self.client = anthropic.Anthropic(api_key=api_key)
        self.model = model
        # 系统提示词 - 定义AI的角色、能力和限制
        self.system_prompt = """你是一个智能家居控制助手,集成在Home Assistant系统中。你的任务是理解用户的自然语言指令,并根据提供的家庭设备状态上下文,生成准确、安全、可执行的JSON格式操作指令。

家庭状态上下文将以JSON格式提供,包含家中各个传感器的读数和设备当前状态。

请遵循以下规则:
1. **安全第一**:绝不能生成任何可能导致危险的操作,例如关闭所有灯光(如果用户明确要求除外且需确认)、关闭安防系统、将恒温器设置为极端温度(低于16°C或高于30°C)。
2. **输出格式**:你只能输出一个合法的JSON对象,包含以下字段:
   - `reasoning`: 简要说明你的推理过程。
   - `action`: 主操作类型,如 "control_device", "query_status", "multi_step"。
   - `targets`: 一个列表,包含要操作或查询的实体ID(如 `["light.living_room"]`)。
   - `params`: 一个字典,包含操作的具体参数(如 `{"state": "on", "brightness": 200}`)。对于查询,此字段可为空。
   - `response`: 给用户的自然语言回复,告知用户你将要或已经做了什么。
3. **基于上下文**:必须严格依据提供的上下文进行推理。如果用户说“打开灯”,但上下文显示`light.living_room`的状态已是`on`,你应该在`response`中告知用户灯已经开了,而不是重复发送打开指令。
4. **主动澄清**:如果用户指令模糊(例如“调一下温度”),但上下文中有多个温控设备,你应在`response`中询问用户具体指哪个设备。

现在,开始处理用户指令。"""
    
    async def process_command(self, user_input: str, ha_context: List[Dict]) -> Dict[str, Any]:
        # 将HA状态上下文格式化成字符串,便于模型阅读
        context_str = json.dumps(ha_context, ensure_ascii=False, indent=2)
        
        user_message = f"""当前家庭设备状态上下文:
{context_str}

用户指令:{user_input}

请根据以上信息生成控制指令。"""
        
        try:
            response = self.client.messages.create(
                model=self.model,
                max_tokens=500,
                system=self.system_prompt,
                messages=[{"role": "user", "content": user_message}]
            )
            # 解析Claude返回的文本,期望它是JSON
            result_text = response.content[0].text
            # 有时Claude会在JSON外包裹```json ```标记,需要处理
            if "```json" in result_text:
                result_text = result_text.split("```json")[1].split("```")[0].strip()
            elif "```" in result_text:
                result_text = result_text.split("```")[1].split("```")[0].strip()
            
            return json.loads(result_text)
        except json.JSONDecodeError as e:
            print(f"解析Claude返回的JSON失败: {e}, 原始返回: {result_text}")
            return {"action": "error", "response": "指令解析失败,请重试。"}
        except Exception as e:
            print(f"调用Claude API失败: {e}")
            return {"action": "error", "response": "AI服务暂时不可用。"}

核心技巧 :系统提示词是项目的灵魂。我花了大量时间迭代这个提示词。要点包括:明确角色、严格规定输出格式(JSON)、强调安全规则、要求基于上下文推理。将HA的完整状态直接塞给模型可能会超出上下文长度,实践中我会先进行一步预处理,只筛选出与用户指令可能相关的设备状态(例如,用户提到“灯”,就只发送 light 领域的实体状态),这能显著提高推理速度和准确性。

4. 构建主API与上下文管理 ( main.py ) 现在我们将各个模块串联起来,创建一个FastAPI应用。

from fastapi import FastAPI, HTTPException, BackgroundTasks
from pydantic import BaseModel
from typing import Optional
import asyncio
from core.claude_client import ClaudeHomeAssistant
from core.ha_client import HomeAssistantClient
from core.context_manager import ContextManager  # 假设一个简单的上下文管理器
import config

app = FastAPI(title="AI Home Assistant Brain")

# 初始化全局客户端
claude_agent = ClaudeHomeAssistant(api_key=config.ANTHROPIC_API_KEY)
ha_client = HomeAssistantClient(base_url=config.HA_BASE_URL, access_token=config.HA_ACCESS_TOKEN)
ctx_manager = ContextManager()  # 可以用内存字典,生产环境建议用Redis

class UserRequest(BaseModel):
    user_id: str  # 用于区分不同用户/会话
    command: str
    session_id: Optional[str] = None

@app.post("/v1/process")
async def process_command(request: UserRequest, background_tasks: BackgroundTasks):
    """处理用户指令的主入口"""
    # 1. 获取当前家庭状态上下文
    try:
        all_states = await ha_client.get_all_states()
        # 可以在此处根据指令关键词对all_states进行过滤,减少token消耗
        filtered_states = _filter_relevant_states(request.command, all_states)
    except Exception as e:
        raise HTTPException(status_code=503, detail=f"无法获取家庭状态: {e}")
    
    # 2. 获取或创建会话上下文
    session_id = request.session_id or f"{request.user_id}_{int(time.time())}"
    context = ctx_manager.get_context(session_id)
    
    # 3. 调用Claude进行推理
    ai_decision = await claude_agent.process_command(request.command, filtered_states)
    
    # 4. 执行AI决策
    execution_result = {"success": [], "failed": []}
    if ai_decision["action"] == "control_device":
        for target in ai_decision.get("targets", []):
            # 这里需要将通用的控制指令映射到具体的HA服务调用
            # 例如,根据target的domain(light, climate)和params调用不同的方法
            success = await _execute_ha_command(target, ai_decision.get("params", {}))
            if success:
                execution_result["success"].append(target)
            else:
                execution_result["failed"].append(target)
    
    # 5. 更新上下文(例如,存储本次交互)
    ctx_manager.update_context(session_id, {
        "last_command": request.command,
        "last_decision": ai_decision,
        "execution_result": execution_result
    })
    
    # 6. 异步更新状态(可选):执行操作后,稍等片刻再获取一次状态,确保操作生效
    background_tasks.add_task(_update_state_after_delay, session_id)
    
    # 7. 返回结果给用户
    return {
        "session_id": session_id,
        "ai_response": ai_decision.get("response", ""),
        "decision": ai_decision,
        "execution": execution_result
    }

def _filter_relevant_states(command: str, states: list) -> list:
    """简单的关键词过滤,优化上下文"""
    relevant_domains = []
    command_lower = command.lower()
    if any(word in command_lower for word in ["灯", "亮", "暗", "light"]):
        relevant_domains.append("light")
    if any(word in command_lower for word in ["空调", "热", "冷", "温度", "climate", "ac"]):
        relevant_domains.append("climate")
    if any(word in command_lower for word in ["窗帘", "窗", "cover"]):
        relevant_domains.append("cover")
    # ... 其他领域判断
    if not relevant_domains:
        return states[:20]  # 如果无法判断,返回前20个状态,防止上下文过长
    return [s for s in states if s.get("entity_id", "").split(".")[0] in relevant_domains]

async def _execute_ha_command(entity_id: str, params: dict) -> bool:
    """根据实体ID和参数调用对应的HA服务"""
    domain = entity_id.split(".")[0]
    # 这里需要根据domain和params进行复杂的映射,是核心控制逻辑
    # 举例:entity_id = "light.living_room", params = {"state": "on", "brightness": 200}
    if domain == "light":
        state = params.get("state")
        if state == "on":
            brightness = params.get("brightness")
            return await ha_client.turn_on_light(entity_id, brightness)
        elif state == "off":
            return await ha_client.call_service("light", "turn_off", {"entity_id": entity_id})
    elif domain == "climate":
        # 处理温度、模式设置等
        service_data = {"entity_id": entity_id}
        if "temperature" in params:
            service_data["temperature"] = params["temperature"]
        if "mode" in params:
            service_data["hvac_mode"] = params["mode"] # 如 "cool", "heat"
        return await ha_client.call_service("climate", "set_temperature", service_data)
    # ... 其他设备类型处理
    return False

async def _update_state_after_delay(session_id: str, delay: int = 2):
    """延迟后更新状态,用于确认操作结果"""
    await asyncio.sleep(delay)
    # 可以获取最新状态并更新到上下文,供下次推理使用
    pass

@app.on_event("shutdown")
async def shutdown_event():
    await ha_client.close()

4. 系统集成与交互层实现

有了强大的AI决策后台,我们需要为它打造一个“入口”,让用户能方便地发出指令。

4.1 多种交互方式接入

方案一:Home Assistant前端集成(最无缝) 这是体验最好的方式。你可以通过开发一个Home Assistant的“自定义面板”或利用其“RESTful命令”集成,在HA的UI里直接添加一个聊天窗口。

  1. 创建RESTful传感器/命令 :在HA的 configuration.yaml 中,添加一个RESTful传感器,指向你的AI服务API ( http://你的AI服务IP:端口/v1/process )。然后创建一个输入框实体和自动化,当用户发送消息时,触发这个RESTful命令,并将AI的回复显示在界面上。
  2. 开发自定义Lovelace卡片 :如果你熟悉前端,可以编写一个简单的Lovelace卡片,提供一个聊天界面,直接通过JavaScript调用你的AI服务API。这样集成度更高,界面更美观。

方案二:独立Web应用或移动端APP 如果你希望有一个独立的界面,可以用任何前端框架(如Vue、React)开发一个简单的网页,或者用Flutter、React Native开发移动端APP。前端只需要做一件事:收集用户输入,发送POST请求到你的AI服务端点,并展示返回的 ai_response 。这种方式更灵活,可以设计更复杂的交互逻辑。

方案三:语音助手桥接 让AI服务支持语音!你可以利用一些开源语音识别(如Vosk、Whisper本地部署)和语音合成(如Edge-TTS)库,构建一个本地的语音交互管道。

  1. 用户说话 -> 语音识别为文本。
  2. 文本发送给AI服务。
  3. 将AI返回的 ai_response 文本,通过TTS转换为语音播放出来。 这样,你就拥有了一个完全本地化的、私有的“Claude智能音箱”。你可以将这个服务运行在树莓派上,配合一个USB麦克风和音箱,放在客厅使用。

4.2 实现状态同步与事件驱动

一个真正智能的管家不应该只在被询问时才工作。我们需要让它能“感知”环境变化,并主动做出反应或提供建议。这可以通过Home Assistant的 WebSocket API 实现。

核心思路 :让AI服务订阅HA的事件总线(Event Bus)。当传感器状态发生变化(如有人移动、温度超过阈值),HA会发出事件。AI服务接收到事件后,可以主动进行一次推理,判断是否需要执行操作或向用户推送通知。

代码示例(简化的WebSocket监听):

import asyncio
import websockets
import json

async def listen_ha_events():
    """监听Home Assistant的WebSocket事件"""
    uri = f"ws://{config.HA_IP}:8123/api/websocket"
    async with websockets.connect(uri) as websocket:
        # 1. 认证
        auth_msg = {"type": "auth", "access_token": config.HA_ACCESS_TOKEN}
        await websocket.send(json.dumps(auth_msg))
        auth_result = json.loads(await websocket.recv())
        if auth_result["type"] != "auth_ok":
            print("WebSocket认证失败")
            return
        
        # 2. 订阅状态变化事件
        subscribe_msg = {
            "id": 1,
            "type": "subscribe_events",
            "event_type": "state_changed" # 订阅所有状态变化事件
        }
        await websocket.send(json.dumps(subscribe_msg))
        
        # 3. 持续监听
        async for message in websocket:
            event = json.loads(message)
            if event.get("type") == "event":
                event_data = event["event"]["data"]
                entity_id = event_data.get("entity_id")
                new_state = event_data.get("new_state")
                old_state = event_data.get("old_state")
                
                # 判断是否为重要事件(例如,有人移动、温度骤变)
                if _is_significant_event(entity_id, new_state, old_state):
                    # 触发AI的主动推理流程
                    asyncio.create_task(handle_proactive_ai(entity_id, new_state))

def _is_significant_event(entity_id, new_state, old_state):
    """判断事件是否重要,需要AI介入"""
    # 示例:人体传感器从off变为on(有人进入)
    if entity_id.startswith("binary_sensor.") and "motion" in entity_id:
        if old_state.get("state") == "off" and new_state.get("state") == "on":
            return True
    # 示例:温度传感器变化超过2度
    if entity_id.startswith("sensor.temperature"):
        try:
            old_temp = float(old_state.get("state"))
            new_temp = float(new_state.get("state"))
            if abs(new_temp - old_temp) > 2.0:
                return True
        except:
            pass
    return False

async def handle_proactive_ai(entity_id, new_state):
    """处理主动AI推理"""
    # 获取当前全面的家庭状态
    all_states = await ha_client.get_all_states()
    # 构建一个“虚拟”的用户指令,例如“刚刚客厅有人移动了,分析一下当前情况”
    virtual_command = f"系统事件:{entity_id} 状态变为 {new_state.get('state')}。请分析家庭当前状态,并判断是否需要执行任何自动化操作或提醒用户。"
    
    ai_decision = await claude_agent.process_command(virtual_command, all_states)
    
    # 如果AI决策中包含控制指令,则执行
    if ai_decision["action"] == "control_device":
        # ... 执行控制逻辑
        pass
    # 如果AI决策认为需要通知用户,可以通过其他渠道(如APP推送、TTS)发送`ai_decision['response']`

通过这种方式,你的智能家居就从“响应式”进化到了“主动式”。例如,晚上检测到有人起夜,可以自动点亮通往卫生间的低亮度夜灯;检测到室内空气质量变差,可以自动打开空气净化器。

5. 部署、优化与问题排查实录

将代码跑起来只是第一步,让它稳定、高效、可靠地运行才是真正的挑战。

5.1 本地化部署与性能优化

部署方案 :我强烈推荐使用 Docker Compose 进行部署。它将AI服务、Redis(用于上下文缓存)、以及可能用到的其他服务(如本地TTS服务)容器化,管理起来非常方便。

docker-compose.yml 示例:

version: '3.8'
services:
  ai-brain:
    build: ./ai_home_assistant
    container_name: ai_home_brain
    restart: unless-stopped
    ports:
      - "8000:8000" # 将FastAPI服务暴露给内网
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - HA_BASE_URL=http://host.docker.internal:8123 # 从容器内访问宿主机HA
      - HA_ACCESS_TOKEN=${HA_ACCESS_TOKEN}
      - REDIS_URL=redis://redis:6379
    depends_on:
      - redis
    volumes:
      - ./logs:/app/logs # 挂载日志目录

  redis:
    image: redis:7-alpine
    container_name: ai_brain_redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

volumes:
  redis_data:

性能与成本优化技巧:

  1. 上下文长度管理 :Claude API按Token收费和消耗上下文。每次都将全部HA状态(可能上百个实体)发送给Claude是昂贵且低效的。务必实现 _filter_relevant_states 这样的过滤函数,只发送相关领域的状态。还可以对状态描述进行压缩,例如只发送 entity_id state ,省略不必要属性。
  2. 缓存策略 :家庭状态的变化频率是有限的。可以对HA的状态查询结果进行短期缓存(如5-10秒),避免频繁调用HA API。对于Claude的响应,如果用户重复相同指令,也可以考虑缓存。
  3. 模型选择 :日常指令使用 claude-3-haiku ,它在速度和成本上完美平衡。只有遇到非常复杂的、需要多步推理的规划指令时,才动态切换到 claude-3-sonnet
  4. 异步处理 :确保你的FastAPI路径操作函数是 async 的,并且使用异步的HTTP客户端(如 httpx.AsyncClient )来调用HA和Claude API,这样可以高效处理并发请求。

5.2 常见问题与排查技巧

在实际搭建和运行中,我遇到了不少坑,这里把典型问题和解决方法记录下来。

问题1:调用Claude API失败,提示“Unable to connect to Anthropic services”或网络错误。

  • 现象 :AI服务日志显示连接Claude API超时或失败。
  • 排查步骤
    1. 检查网络连通性 :在运行AI服务的机器上,使用 curl -v https://api.anthropic.com 测试是否能访问Anthropic API。如果超时,可能是网络出口问题。
    2. 检查API密钥 :确认 .env 文件中的 ANTHROPIC_API_KEY 正确无误,且没有过期或被禁用。
    3. 检查代理设置 :如果你的服务器需要通过代理访问外网,需要在代码中为 anthropic 库或底层的 httpx 客户端配置代理。例如,在创建 Anthropic 客户端时: client = anthropic.Anthropic(api_key=key, http_client=httpx.Client(proxies="http://your-proxy:port"))
    4. 地区限制 :确认Anthropic服务在你所在地区是否可用。有时可能需要特定的API端点。
  • 解决方案 :确保服务器有稳定、低延迟的国际网络出口。对于网络环境复杂的场景,可以考虑将AI服务部署在能稳定访问国际互联网的云服务器上,然后通过安全的内部通道(如WireGuard VPN,此处指合法的内网穿透或组网技术,非用于突破网络管控)与家庭内的Home Assistant通信。

问题2:AI返回的指令无法被正确解析或执行。

  • 现象 :Claude返回的JSON格式错误,或者其中的 entity_id 在HA中不存在, params 不符合HA服务要求。
  • 排查步骤
    1. 打印并审查原始返回 :在 claude_client.py process_command 方法中,打印出 result_text ,检查Claude是否严格遵循了你的JSON输出格式。常见问题是它会在JSON外添加解释性文字。
    2. 强化提示词 :在系统提示词中更严厉地强调“ 只能输出JSON,不能有任何其他文字 ”。可以使用类似“你必须以 { 开头,以 } 结尾”的指令。
    3. 验证实体ID :在 _execute_ha_command 函数中,在执行前先验证 entity_id 是否存在于从HA获取的状态列表中。如果不存在,则记录错误并让AI重新推理。
    4. 参数映射表 :维护一个 domain service params 的映射表。例如,对于 light.turn_on 服务, brightness 参数的值范围是0-255,如果AI返回了百分比,需要在这里进行转换。
  • 解决方案 :实现一个“指令验证与标准化”层。在真正调用HA服务前,对AI生成的指令进行清洗、验证和转换,确保其完全符合HA API的规范。对于无法处理的指令,反馈一个错误信息给用户,并可以将其作为“坏样本”记录下来,用于后续优化提示词。

问题3:系统响应慢,用户体验不佳。

  • 现象 :从发出指令到设备执行,耗时超过5秒。
  • 瓶颈分析
    1. 网络延迟 :AI服务与HA之间,或与Claude API之间的网络延迟。
    2. 模型响应慢 :使用了较大的模型(如Claude-3 Opus)或提示词过长。
    3. 串行操作 :代码逻辑是“获取状态->等待AI推理->执行控制”的串行流程。
  • 优化方案
    1. 并行化 :如果用户指令不依赖最新的全量状态,可以在发送指令给Claude的同时,并行地获取HA状态。或者,对于多设备控制,可以并行执行多个 call_service 调用。
    2. 预加载与缓存 :将一些不常变的状态(如设备名称、类型、支持的功能)缓存起来,减少每次推理前与HA的交互数据量。
    3. 流式响应 :对于AI的文本回复,可以采用流式输出,让用户先看到“思考过程”(如“正在分析客厅情况...”),再看到最终确认和执行结果,从感知上降低等待感。

问题4:AI做出了错误或不受控的决策。

  • 现象 :AI误解指令,或试图执行危险操作(如关闭所有门锁)。
  • 根本原因 :提示词的安全边界设定不够严格,或上下文信息不足/有误。
  • 加固措施
    1. 黑名单机制 :在 _execute_ha_command 函数中,维护一个关键实体(如 lock.* , alarm_control_panel.* )的黑名单。任何针对这些实体的控制指令,都必须经过额外的确认(例如,要求用户二次输入密码或通过APP确认)。
    2. 参数范围限制 :在代码层面对控制参数进行强制限制。例如,无论AI返回什么温度值,都将其钳制在18-28摄氏度之间。
    3. 引入确认环节 :对于非日常的、复杂的或涉及多设备的操作,AI的 response 可以设计为询问用户确认:“我将关闭客厅灯、打开空调并设定为26度,确认吗?”只有在收到用户确认后,才真正执行。这可以通过在上下文中记录“待确认指令”来实现。

搭建这样一个AI驱动的智能家居系统,是一个持续迭代和优化的过程。它不是一个一蹴而就的产品,而是一个需要你不断“调教”和“训练”的伙伴。从最基础的开关灯,到根据你的作息习惯自动调节环境,再到能理解“我想要一点浪漫的氛围”这样的抽象指令,每一次提示词的优化、每一个异常情况的处理,都让它变得更聪明、更可靠。这个过程本身,就是智能家居最大的乐趣所在。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐