feat(hass): 新增小爱音箱接入 Home Assistant Assist 的完整示例
- 新增完整的 Home Assistant 集成示例,支持文本对话与上下文会话 - 实现小爱音箱事件解析与 TTS 播报,支持实时打断功能 - 添加连续会话管理,支持区域上下文注入和会话超时结束 - 提供完整的配置系统、类型定义和错误处理 - 包含 Rust 扩展模块用于小爱音箱通信,支持音频流处理 - 添加单元测试、集成测试脚本和 Docker 部署支持 - 提供详细的使用文档和配置说明
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from aioresponses import aioresponses
|
||||
|
||||
from hass.errors import HomeAssistantAuthError
|
||||
from hass.ha_client import HomeAssistantClient
|
||||
from hass.memory import ConversationMemory
|
||||
from hass.typing import HomeAssistantConfig
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_process_text_updates_conversation_id(tmp_path):
|
||||
state_path = tmp_path / "state.json"
|
||||
memory = ConversationMemory(str(state_path))
|
||||
await memory.load()
|
||||
|
||||
config = HomeAssistantConfig(
|
||||
url="http://ha.local:8123",
|
||||
access_token="token",
|
||||
assistant_entity_id="home_assistant",
|
||||
language="zh-cn",
|
||||
timeout_seconds=2.5,
|
||||
)
|
||||
client = HomeAssistantClient(config, memory, concurrency=2)
|
||||
await client.start()
|
||||
|
||||
url = "http://ha.local:8123/api/conversation/process"
|
||||
with aioresponses() as mocked:
|
||||
mocked.post(
|
||||
url,
|
||||
payload={
|
||||
"continue_conversation": True,
|
||||
"conversation_id": "cid-1",
|
||||
"response": {
|
||||
"response_type": "action_done",
|
||||
"speech": {"plain": {"speech": "好的"}},
|
||||
},
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
reply = await client.process_text("打开客厅灯")
|
||||
|
||||
assert reply.speech == "好的"
|
||||
assert reply.continue_conversation is True
|
||||
assert memory.conversation_id == "cid-1"
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_process_text_unauthorized(tmp_path):
|
||||
memory = ConversationMemory(str(tmp_path / "state.json"))
|
||||
await memory.load()
|
||||
|
||||
config = HomeAssistantConfig(url="http://ha.local:8123", access_token="bad-token")
|
||||
client = HomeAssistantClient(config, memory)
|
||||
await client.start()
|
||||
|
||||
url = "http://ha.local:8123/api/conversation/process"
|
||||
with aioresponses() as mocked:
|
||||
mocked.post(url, payload={"message": "unauthorized"}, status=401)
|
||||
with pytest.raises(HomeAssistantAuthError):
|
||||
await client.process_text("hi")
|
||||
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_process_text_with_prefix_prompt(tmp_path):
|
||||
memory = ConversationMemory(str(tmp_path / "state.json"))
|
||||
await memory.load()
|
||||
|
||||
config = HomeAssistantConfig(url="http://ha.local:8123", access_token="token")
|
||||
client = HomeAssistantClient(config, memory)
|
||||
await client.start()
|
||||
|
||||
url = "http://ha.local:8123/api/conversation/process"
|
||||
with aioresponses() as mocked:
|
||||
mocked.post(
|
||||
url,
|
||||
payload={
|
||||
"continue_conversation": False,
|
||||
"conversation_id": "cid-2",
|
||||
"response": {
|
||||
"response_type": "query_answer",
|
||||
"speech": {"plain": {"speech": "在客厅"}},
|
||||
},
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
await client.process_text(
|
||||
"灯开了吗",
|
||||
prefix_prompt="当前用户对话所在区域:客厅,如后续对话未明确指定区域,则默认为此区域",
|
||||
)
|
||||
|
||||
request = mocked.requests[("POST", url)][0]
|
||||
payload = json.loads(request.kwargs["data"].decode("utf-8"))
|
||||
assert payload["text"].startswith("当前用户对话所在区域:客厅")
|
||||
assert "用户请求:灯开了吗" in payload["text"]
|
||||
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reset_conversation(tmp_path):
|
||||
state_path = tmp_path / "state.json"
|
||||
memory = ConversationMemory(str(state_path))
|
||||
await memory.load()
|
||||
memory.conversation_id = "cid-old"
|
||||
await memory.save()
|
||||
|
||||
config = HomeAssistantConfig(url="http://ha.local:8123", access_token="token")
|
||||
client = HomeAssistantClient(config, memory)
|
||||
await client.start()
|
||||
|
||||
await client.reset_conversation()
|
||||
|
||||
assert memory.conversation_id is None
|
||||
saved = json.loads(state_path.read_text(encoding="utf-8"))
|
||||
assert saved["conversation_id"] is None
|
||||
|
||||
await client.close()
|
||||
|
||||
Reference in New Issue
Block a user