- 新增完整的 Home Assistant 集成示例,支持文本对话与上下文会话 - 实现小爱音箱事件解析与 TTS 播报,支持实时打断功能 - 添加连续会话管理,支持区域上下文注入和会话超时结束 - 提供完整的配置系统、类型定义和错误处理 - 包含 Rust 扩展模块用于小爱音箱通信,支持音频流处理 - 添加单元测试、集成测试脚本和 Docker 部署支持 - 提供详细的使用文档和配置说明
124 lines
3.8 KiB
Python
124 lines
3.8 KiB
Python
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()
|
|
|