feat: integrate production cockpit AI assistant
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
from collections import Counter
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import replace
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -64,6 +65,46 @@ def test_catalog_covers_every_authoritative_definition_exactly_once(
|
||||
)
|
||||
|
||||
|
||||
def test_snapshot_resolves_explicit_vehicle_and_keeps_no_argument_default(
|
||||
vehicle_service: SeatDemoService,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
catalog = _catalog(vehicle_service)
|
||||
registry = vehicle_service.vehicle_profiles
|
||||
original_get = registry.get
|
||||
default_vehicle = original_get(None)
|
||||
alternate_vehicle = replace(
|
||||
default_vehicle,
|
||||
id="alternate_vehicle",
|
||||
name="另一测试车型",
|
||||
)
|
||||
requested_ids: list[str | None] = []
|
||||
|
||||
def get(vehicle_id: str | None): # type: ignore[no-untyped-def]
|
||||
requested_ids.append(vehicle_id)
|
||||
if vehicle_id == alternate_vehicle.id:
|
||||
return alternate_vehicle
|
||||
return original_get(vehicle_id)
|
||||
|
||||
monkeypatch.setattr(registry, "get", get)
|
||||
|
||||
default_snapshot = catalog.snapshot()
|
||||
alternate_snapshot = catalog.snapshot(vehicle_id=alternate_vehicle.id)
|
||||
|
||||
assert requested_ids == [None, alternate_vehicle.id]
|
||||
assert default_snapshot["vehicle"] == {
|
||||
"id": default_vehicle.id,
|
||||
"name": default_vehicle.name,
|
||||
}
|
||||
assert alternate_snapshot["vehicle"] == {
|
||||
"id": alternate_vehicle.id,
|
||||
"name": alternate_vehicle.name,
|
||||
}
|
||||
assert catalog.scene_composition_context(alternate_snapshot)["vehicle"] == (
|
||||
alternate_snapshot["vehicle"]
|
||||
)
|
||||
|
||||
|
||||
def test_all_44_capabilities_preserve_authoritative_metadata(
|
||||
vehicle_service: SeatDemoService,
|
||||
) -> None:
|
||||
@@ -217,7 +258,9 @@ def test_all_modes_are_discoverable_and_reference_callable_capabilities(
|
||||
}
|
||||
modes = snapshot["modes"]
|
||||
|
||||
assert len(modes) == 13
|
||||
# Runtime user modes are intentionally discovered without a restart, so
|
||||
# the repository's 13 presets are a lower bound rather than a fixed total.
|
||||
assert len(modes) >= 13
|
||||
assert {item["id"] for item in modes} >= {
|
||||
"baby_nap",
|
||||
"dynamic_cradle",
|
||||
@@ -235,6 +278,116 @@ def test_all_modes_are_discoverable_and_reference_callable_capabilities(
|
||||
)
|
||||
|
||||
|
||||
def test_mode_catalog_projects_bounded_action_details_without_free_form_reasons(
|
||||
vehicle_service: SeatDemoService,
|
||||
) -> None:
|
||||
snapshot = _catalog(vehicle_service).snapshot()
|
||||
office = next(item for item in snapshot["modes"] if item["id"] == "office")
|
||||
|
||||
assert len(office["operations"]) == office["operation_count"] == 10
|
||||
temperature = next(
|
||||
item
|
||||
for item in office["operations"]
|
||||
if item["capability"] == "climate.set_temperature"
|
||||
)
|
||||
assert temperature == {
|
||||
"domain": "climate",
|
||||
"capability": "climate.set_temperature",
|
||||
"target_scope": "passenger",
|
||||
"operation": "set",
|
||||
"parameters": [{"name": "temperature_c", "value": 25}],
|
||||
}
|
||||
assert all("reason" not in operation for operation in office["operations"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"question",
|
||||
(
|
||||
"办公模式下会控制哪些车端设备,执行哪些动作?",
|
||||
"办公模式包含什么配置",
|
||||
"请展示一下办公模式的设备和参数",
|
||||
"请告诉我办公模式下执行哪些动作",
|
||||
),
|
||||
)
|
||||
def test_saved_mode_detail_fast_answer_is_exact_read_only_and_natural(
|
||||
vehicle_service: SeatDemoService,
|
||||
question: str,
|
||||
) -> None:
|
||||
catalog = _catalog(vehicle_service)
|
||||
answer = catalog.fast_answer(question, catalog.snapshot())
|
||||
|
||||
assert answer is not None
|
||||
assert answer.topic == "specific_mode_detail"
|
||||
assert all(
|
||||
fragment in answer.answer
|
||||
for fragment in (
|
||||
"办公模式",
|
||||
"座椅",
|
||||
"空调",
|
||||
"遮阳帘",
|
||||
"氛围灯",
|
||||
"副驾空调温度设为25℃",
|
||||
"共10项动作",
|
||||
"本次仅查询,未下发车辆控制",
|
||||
)
|
||||
)
|
||||
assert "Matched mode profile" not in answer.answer
|
||||
assert "climate.set_temperature" not in answer.answer
|
||||
|
||||
|
||||
def test_unknown_mode_detail_fast_answer_fails_closed_without_inventing_actions(
|
||||
vehicle_service: SeatDemoService,
|
||||
) -> None:
|
||||
catalog = _catalog(vehicle_service)
|
||||
answer = catalog.fast_answer(
|
||||
"火星模式中包含哪些设备和动作?",
|
||||
catalog.snapshot(),
|
||||
)
|
||||
|
||||
assert answer is not None
|
||||
assert answer.topic == "specific_mode_detail_not_found"
|
||||
assert "火星模式" in answer.answer
|
||||
assert "未找到" in answer.answer
|
||||
assert "未下发车辆控制" in answer.answer
|
||||
|
||||
|
||||
def test_common_scene_templates_are_read_only_callable_catalog_entries(
|
||||
vehicle_service: SeatDemoService,
|
||||
) -> None:
|
||||
snapshot = _catalog(vehicle_service).snapshot()
|
||||
templates = snapshot["scene_templates"]
|
||||
|
||||
assert snapshot["source_status"]["scene_templates"] == "ready"
|
||||
assert len(templates) == 12
|
||||
assert {item["name"] for item in templates} >= {
|
||||
"观影模式",
|
||||
"露营模式",
|
||||
"阅读模式",
|
||||
"放松模式",
|
||||
"雨天除雾模式",
|
||||
"夏季清凉模式",
|
||||
"冬季温暖模式",
|
||||
}
|
||||
assert all(item["read_only"] is True for item in templates)
|
||||
assert all(item["assistant_callable"] is True for item in templates)
|
||||
assert all(item["plan"]["metadata"]["template_id"] == item["id"] for item in templates)
|
||||
assert all("source" not in item for item in templates)
|
||||
|
||||
|
||||
def test_scene_template_provider_failure_is_explicit_and_empty(
|
||||
vehicle_service: SeatDemoService,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
def unavailable(*_args: object, **_kwargs: object) -> list[dict[str, object]]:
|
||||
raise OSError("template directory unavailable")
|
||||
|
||||
monkeypatch.setattr(vehicle_service.scene_templates, "public_templates", unavailable)
|
||||
snapshot = _catalog(vehicle_service).snapshot()
|
||||
|
||||
assert snapshot["source_status"]["scene_templates"] == "unavailable"
|
||||
assert snapshot["scene_templates"] == []
|
||||
|
||||
|
||||
def test_user_mode_is_discovered_on_the_next_snapshot_without_restart(
|
||||
vehicle_service: SeatDemoService,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
@@ -272,8 +425,7 @@ def test_user_mode_is_discovered_on_the_next_snapshot_without_restart(
|
||||
mode = next(item for item in after["modes"] if item["id"] == "user_focus")
|
||||
answer = catalog.fast_answer("当前有哪些模式", after)
|
||||
|
||||
assert len(before["modes"]) == 13
|
||||
assert len(after["modes"]) == 14
|
||||
assert len(after["modes"]) == len(before["modes"]) + 1
|
||||
assert after["catalog_revision"] != before["catalog_revision"]
|
||||
assert mode["assistant_callable"] is True
|
||||
assert answer is not None
|
||||
@@ -571,6 +723,157 @@ def test_fast_answer_never_consumes_a_direct_control_request(
|
||||
assert catalog.fast_answer(instruction, catalog.snapshot()) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"instruction",
|
||||
[
|
||||
"打开后备箱",
|
||||
"请开启后备箱门",
|
||||
"帮我把尾门打开一下",
|
||||
"打开尾门盖",
|
||||
"开启后备箱盖",
|
||||
"麻烦帮忙打开电动尾门",
|
||||
"关闭行李箱",
|
||||
"把行李厢盖关上",
|
||||
"打开物理天窗",
|
||||
"开启近光灯",
|
||||
"打开前大灯",
|
||||
"关闭后雾灯",
|
||||
"请锁车",
|
||||
"关闭墙壁灯",
|
||||
"打开方向盘加热",
|
||||
"调节方向盘",
|
||||
"打开雨刮",
|
||||
"请鸣笛",
|
||||
"按喇叭",
|
||||
"可以帮我打开后备箱吗",
|
||||
"能不能打开尾门",
|
||||
"后备箱能开吗",
|
||||
"支持后备箱控制吗",
|
||||
"是否可以打开后备箱",
|
||||
"是否能打开后备箱",
|
||||
"请问后备箱是否能打开",
|
||||
],
|
||||
)
|
||||
def test_known_unsupported_controls_return_catalog_backed_quick_suggestions(
|
||||
vehicle_service: SeatDemoService,
|
||||
instruction: str,
|
||||
) -> None:
|
||||
catalog = _catalog(vehicle_service)
|
||||
snapshot = catalog.snapshot()
|
||||
callable_ids = {
|
||||
item["id"] for item in snapshot["capabilities"] if item["assistant_callable"] is True
|
||||
}
|
||||
native_ids = {
|
||||
item["id"] for item in snapshot["capabilities"] if item["native_can_verified"] is True
|
||||
}
|
||||
|
||||
answer = catalog.fast_answer(instruction, snapshot)
|
||||
|
||||
assert answer is not None
|
||||
assert answer.topic == "unsupported_vehicle_control"
|
||||
assert answer.answer.startswith(
|
||||
"当前车端暂不支持该功能控制哦,您可以尝试以下控制:"
|
||||
)
|
||||
assert 1 <= len(answer.suggestions) <= 3
|
||||
assert all(suggestion.label in answer.answer for suggestion in answer.suggestions)
|
||||
assert all(
|
||||
set(suggestion.capability_ids) <= callable_ids for suggestion in answer.suggestions
|
||||
)
|
||||
assert all(set(suggestion.capability_ids) <= native_ids for suggestion in answer.suggestions)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"message",
|
||||
[
|
||||
"后备箱怎么打开?",
|
||||
"不要打开后备箱",
|
||||
"先别打开后备箱",
|
||||
"如果打开后备箱会怎么样?",
|
||||
"后备箱打不开",
|
||||
"打开后备箱失败了",
|
||||
"解释一下“打开后备箱”",
|
||||
"帮我写一个打开后备箱的故事",
|
||||
"今天聊聊后备箱",
|
||||
"把后备箱打开到一半",
|
||||
"可以介绍一下后备箱吗",
|
||||
"你支持回答后备箱相关问题吗",
|
||||
"哪些车型支持打开后备箱",
|
||||
"支持后备箱保养知识问答吗",
|
||||
],
|
||||
)
|
||||
def test_unsupported_control_fast_path_does_not_consume_non_commands(
|
||||
vehicle_service: SeatDemoService,
|
||||
message: str,
|
||||
) -> None:
|
||||
catalog = _catalog(vehicle_service)
|
||||
assert catalog.fast_answer(message, catalog.snapshot()) is None
|
||||
|
||||
|
||||
def test_unsupported_control_fast_path_leaves_mixed_supported_control_for_full_route(
|
||||
vehicle_service: SeatDemoService,
|
||||
) -> None:
|
||||
catalog = _catalog(vehicle_service)
|
||||
|
||||
assert catalog.fast_answer("打开后备箱并关闭所有车窗", catalog.snapshot()) is None
|
||||
assert catalog.fast_answer("打开天窗遮阳帘", catalog.snapshot()) is None
|
||||
|
||||
|
||||
def test_unsupported_control_fast_path_stands_down_when_runtime_adds_capability(
|
||||
vehicle_service: SeatDemoService,
|
||||
) -> None:
|
||||
catalog = _catalog(vehicle_service)
|
||||
snapshot = json.loads(json.dumps(catalog.snapshot(), ensure_ascii=False))
|
||||
snapshot["capabilities"].append(
|
||||
{
|
||||
"id": "body.toggle_tailgate",
|
||||
"domain": "door",
|
||||
"description": "Toggle the vehicle tailgate.",
|
||||
"target_devices": ["tailgate"],
|
||||
"assistant_callable": True,
|
||||
"target_scopes": ["vehicle"],
|
||||
}
|
||||
)
|
||||
|
||||
assert catalog.fast_answer("打开后备箱", snapshot) is None
|
||||
|
||||
|
||||
def test_quick_control_suggestions_are_filtered_by_current_callable_catalog(
|
||||
vehicle_service: SeatDemoService,
|
||||
) -> None:
|
||||
catalog = _catalog(vehicle_service)
|
||||
snapshot = json.loads(json.dumps(catalog.snapshot(), ensure_ascii=False))
|
||||
for capability in snapshot["capabilities"]:
|
||||
if capability["id"] in {"window.set_motion", "roof.set_shade_motion"}:
|
||||
capability["assistant_callable"] = False
|
||||
|
||||
answer = catalog.fast_answer("打开后备箱", snapshot)
|
||||
|
||||
assert answer is not None
|
||||
assert [suggestion.id for suggestion in answer.suggestions] == ["set-climate-24c"]
|
||||
assert "关闭所有车窗" not in answer.answer
|
||||
assert "关闭遮阳帘" not in answer.answer
|
||||
|
||||
|
||||
def test_quick_control_suggestions_require_native_can_verification(
|
||||
vehicle_service: SeatDemoService,
|
||||
) -> None:
|
||||
catalog = _catalog(vehicle_service)
|
||||
snapshot = json.loads(json.dumps(catalog.snapshot(), ensure_ascii=False))
|
||||
for capability in snapshot["capabilities"]:
|
||||
if capability["id"] == "climate.set_temperature":
|
||||
capability["native_can_verified"] = False
|
||||
|
||||
answer = catalog.fast_answer("打开后备箱", snapshot)
|
||||
|
||||
assert answer is not None
|
||||
assert "set-climate-24c" not in {item.id for item in answer.suggestions}
|
||||
assert all(
|
||||
capability_id != "climate.set_temperature"
|
||||
for item in answer.suggestions
|
||||
for capability_id in item.capability_ids
|
||||
)
|
||||
|
||||
|
||||
def test_control_terms_do_not_authorize_declared_but_unexecutable_controls(
|
||||
vehicle_service: SeatDemoService,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user