feat(scenes): add deterministic romantic cinema template
This commit is contained in:
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -65,6 +65,204 @@ def _learned_light_power_on() -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
def _received_cinema_scene() -> dict[str, object]:
|
||||
actions = [
|
||||
{
|
||||
"type": "window_position",
|
||||
"at_ms": 0,
|
||||
"domain": "window",
|
||||
"capability": "window.set_position",
|
||||
"target_scope": "front_left",
|
||||
"operation": "set",
|
||||
"parameters": {"position_pct": 0},
|
||||
},
|
||||
{
|
||||
"type": "climate_temperature",
|
||||
"at_ms": 0,
|
||||
"domain": "climate",
|
||||
"capability": "climate.set_temperature",
|
||||
"target_scope": "vehicle",
|
||||
"operation": "set",
|
||||
"parameters": {"temperature_c": 25},
|
||||
},
|
||||
{
|
||||
"type": "seat_preset",
|
||||
"at_ms": 0,
|
||||
"domain": "local_vehicle",
|
||||
"capability": "local_scene.seat_preset",
|
||||
"target_scope": "vehicle",
|
||||
"operation": "set",
|
||||
"parameters": {"targets_pulse": [246, 112, 405, 342]},
|
||||
},
|
||||
{
|
||||
"type": "android_app_launch",
|
||||
"at_ms": 3000,
|
||||
"domain": "android",
|
||||
"capability": "android.launch_app",
|
||||
"target_scope": "headunit",
|
||||
"operation": "launch",
|
||||
"parameters": {
|
||||
"package_name": "com.byd.videoplay",
|
||||
"activity_name": "com.byd.videoplay.activity.WelComeActivity",
|
||||
},
|
||||
},
|
||||
]
|
||||
return {
|
||||
"id": "cinema",
|
||||
"label": "影院模式",
|
||||
"name": "影院模式",
|
||||
"description": "接收到的车机影院模式。",
|
||||
"icon": "film",
|
||||
"aliases": ["观影模式"],
|
||||
"origin": "headunit",
|
||||
"deletable": True,
|
||||
"behavior": "static",
|
||||
"domains": ["window", "climate", "local_vehicle", "android"],
|
||||
"operation_count": len(actions),
|
||||
"scope": {"common": False, "vehicle_ids": ["denza_n7"]},
|
||||
"profile_revision": "received-cinema-v1",
|
||||
"actions": actions,
|
||||
}
|
||||
|
||||
|
||||
def test_romantic_cinema_template_preserves_cinema_and_adds_authorized_light(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = SeatDemoService(REPOSITORY_ROOT)
|
||||
service.artifact_root = tmp_path / "runs"
|
||||
service.mode_profiles = ModeProfileRegistry(
|
||||
REPOSITORY_ROOT / "config" / "modes",
|
||||
user_root=tmp_path / "modes",
|
||||
)
|
||||
extension = SelfExtensionService(
|
||||
SimulationGateway(),
|
||||
catalog_db_path=tmp_path / "infrared.sqlite3",
|
||||
)
|
||||
extension.sync_peripheral_catalog([_learned_light_power_on()])
|
||||
received_cinema = _received_cinema_scene()
|
||||
service.import_n7_mode_scene(received_cinema)
|
||||
stale_profile = service.mode_profiles.save_mode(
|
||||
"浪漫影院模式",
|
||||
[
|
||||
{
|
||||
"domain": "climate",
|
||||
"capability": "climate.set_temperature",
|
||||
"target_scope": "vehicle",
|
||||
"operation": "set",
|
||||
"parameters": {"temperature_c": 30},
|
||||
}
|
||||
],
|
||||
vehicle_id="denza_n7",
|
||||
instruction="进入浪漫影院模式",
|
||||
)
|
||||
catalog = AssistantCapabilityCatalog(
|
||||
service,
|
||||
peripheral_provider=extension.list_peripheral_routing_capabilities,
|
||||
)
|
||||
handler = object.__new__(SeatDemoHandler)
|
||||
handler.service = service
|
||||
handler.extension = extension
|
||||
handler.nfc = SimpleNamespace(snapshot=lambda: {"card": None})
|
||||
handler.vehicle_display_sync = VehicleDisplaySync()
|
||||
client = MagicMock()
|
||||
client.available = True
|
||||
assistant = CodexAssistantService(client)
|
||||
snapshot = catalog.snapshot(vehicle_id="denza_n7")
|
||||
result: dict[str, object] = {}
|
||||
|
||||
def execute_control(
|
||||
instruction: str,
|
||||
progress,
|
||||
assistant_plan: dict[str, object],
|
||||
) -> dict[str, object]:
|
||||
nonlocal result
|
||||
arguments = {
|
||||
"utterance": instruction,
|
||||
"provider": "local",
|
||||
"conversation_id": "conv-romantic-cinema-template",
|
||||
"execution_mode": "hardware",
|
||||
"vehicle_id": "denza_n7",
|
||||
"simulation_fault": "normal",
|
||||
"hardware_access_bypass": True,
|
||||
"reasoning_effort": "low",
|
||||
"control_transport": "wired_can",
|
||||
"assistant_plan": assistant_plan,
|
||||
}
|
||||
result = handler._preview_mode_with_scene_peripherals(
|
||||
"/api/requests/preview",
|
||||
arguments,
|
||||
assistant_plan,
|
||||
on_commands_validated=progress,
|
||||
)
|
||||
return result
|
||||
|
||||
try:
|
||||
with patch.object(
|
||||
extension,
|
||||
"execute_gateway",
|
||||
side_effect=AssertionError("mode creation must not emit infrared"),
|
||||
):
|
||||
response = assistant.stream(
|
||||
"创建浪漫影院模式",
|
||||
conversation_id="conv-romantic-cinema-template",
|
||||
principal="romantic-cinema-template-test",
|
||||
history=[],
|
||||
emit=lambda _event: True,
|
||||
execute_control=execute_control,
|
||||
capability_snapshot=snapshot,
|
||||
capability_context=catalog.compact_context(snapshot),
|
||||
scene_capability_context=catalog.scene_composition_context(snapshot),
|
||||
control_terms=catalog.control_terms(snapshot),
|
||||
)
|
||||
|
||||
assert response["success"] is True
|
||||
assert response["route"] == "scene_template"
|
||||
client.start_structured_turn.assert_not_called()
|
||||
client.start_turn.assert_not_called()
|
||||
assert result["status"] == "succeeded"
|
||||
assert result["mode_profile_draft"]["generation_source"] == "scene_template_copy"
|
||||
assert result["mode_profile_draft"]["existing_mode_id"] == stale_profile.id
|
||||
assert result["mode_profile_draft"]["operation_count"] == 3
|
||||
|
||||
native_actions = {
|
||||
(
|
||||
action["capability"],
|
||||
action["target"].get("instance") or action["target"].get("zone"),
|
||||
): action["parameters"]
|
||||
for action in result["intent_ir"]["spec"]["actions"]
|
||||
if action["domain"] != "self_extension"
|
||||
}
|
||||
assert native_actions == {
|
||||
(action["capability"], action["target_scope"]): action["parameters"]
|
||||
for action in received_cinema["actions"]
|
||||
if action["domain"] in {"window", "climate"}
|
||||
}
|
||||
assert result["n7_transfer_candidates"] == received_cinema["actions"][2:]
|
||||
assert [action["slot"] for action in result["infrared_actions"]] == [1]
|
||||
|
||||
draft = result["mode_profile_draft"]
|
||||
saved = service.save_mode_profile_card(
|
||||
stale_profile.id,
|
||||
draft["name"],
|
||||
draft["instruction"],
|
||||
draft["source_request_id"],
|
||||
draft["vehicle_id"],
|
||||
)
|
||||
profile = service.get_mode_profile_card(saved["mode_profile"]["id"], "denza_n7")
|
||||
saved_selectors = {
|
||||
(operation.capability, operation.target_scope)
|
||||
for operation in profile.operations
|
||||
}
|
||||
assert saved_selectors == {
|
||||
(action["capability"], action["target_scope"])
|
||||
for action in received_cinema["actions"]
|
||||
} | {("infrared.emit", "romance-light")}
|
||||
_local_requests, transfer_actions = service._mode_requests_and_n7_transfers(profile)
|
||||
assert transfer_actions == received_cinema["actions"][2:]
|
||||
finally:
|
||||
service.close()
|
||||
|
||||
|
||||
def test_scene_composer_mixed_can_ir_preview_save_and_revalidate(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
@@ -165,7 +165,7 @@ def test_all_production_scene_templates_complete_the_full_local_dry_run(
|
||||
templates = snapshot["scene_templates"]
|
||||
|
||||
assert isinstance(templates, list)
|
||||
assert len(templates) == 16
|
||||
assert len(templates) == 17
|
||||
assert all(template["assistant_callable"] is True for template in templates)
|
||||
|
||||
with patch.object(
|
||||
@@ -211,7 +211,9 @@ def test_all_production_scene_templates_complete_the_full_local_dry_run(
|
||||
else:
|
||||
expected_requests = [
|
||||
request for request in expected_requests
|
||||
if request["domain"] != "seat" or request["target_scope"] == "passenger"
|
||||
if template["id"] == "romantic-cinema"
|
||||
or request["domain"] != "seat"
|
||||
or request["target_scope"] == "passenger"
|
||||
]
|
||||
assert draft["operation_count"] == len(expected_requests)
|
||||
expected = {
|
||||
@@ -246,6 +248,23 @@ def test_all_production_scene_templates_complete_the_full_local_dry_run(
|
||||
assert not list(user_root.rglob("*.yaml"))
|
||||
|
||||
|
||||
def test_romantic_cinema_template_is_an_exact_copy_of_all_cinema_functions(
|
||||
hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path],
|
||||
) -> None:
|
||||
service, _catalog, _user_root = hybrid_service
|
||||
cinema = service.scene_templates.get_template("cinema")
|
||||
romantic_cinema = service.scene_templates.get_template("romantic-cinema")
|
||||
|
||||
assert len(cinema.operations) == len(romantic_cinema.operations) == 11
|
||||
assert [
|
||||
(operation.domain, operation.capability, operation.target_scope, operation.parameters)
|
||||
for operation in romantic_cinema.operations
|
||||
] == [
|
||||
(operation.domain, operation.capability, operation.target_scope, operation.parameters)
|
||||
for operation in cinema.operations
|
||||
]
|
||||
|
||||
|
||||
def test_viewing_template_seeds_a_complete_unbound_preview_without_codex(
|
||||
hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path],
|
||||
) -> None:
|
||||
|
||||
@@ -372,7 +372,7 @@ def test_common_scene_templates_are_read_only_callable_catalog_entries(
|
||||
templates = snapshot["scene_templates"]
|
||||
|
||||
assert snapshot["source_status"]["scene_templates"] == "ready"
|
||||
assert len(templates) == 16
|
||||
assert len(templates) == 17
|
||||
assert {item["name"] for item in templates} >= {
|
||||
"观影模式",
|
||||
"露营模式",
|
||||
|
||||
@@ -701,6 +701,50 @@ def test_romantic_template_always_adds_authorized_decorative_lights() -> None:
|
||||
assert "ir:flash-close-00000000003" not in _peripheral_refs(observed[0])
|
||||
|
||||
|
||||
def test_romantic_cinema_template_keeps_native_base_and_adds_lights_without_model() -> None:
|
||||
client = StructuredSceneClient()
|
||||
assistant = CodexAssistantService(client)
|
||||
template = _scene_template(
|
||||
template_id="romantic-cinema",
|
||||
name="浪漫影院模式",
|
||||
aliases=["浪漫观影模式"],
|
||||
)
|
||||
observed: list[dict[str, object]] = []
|
||||
|
||||
def execute(
|
||||
_instruction: str,
|
||||
_progress: Callable[[dict[str, object]], None],
|
||||
assistant_plan: Mapping[str, object],
|
||||
) -> dict[str, object]:
|
||||
observed.append(deepcopy(dict(assistant_plan)))
|
||||
return _preview_result("浪漫影院模式")
|
||||
|
||||
result, _events = _invoke(
|
||||
assistant,
|
||||
"创建浪漫影院模式",
|
||||
execute,
|
||||
conversation_id="conv-romantic-cinema-deterministic",
|
||||
catalog=_catalog_with_templates(template),
|
||||
scene_context=_scene_context_with_flashing_light(),
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["route"] == "scene_template"
|
||||
assert client.structured_calls == []
|
||||
assert client.legacy_calls == []
|
||||
native_requests = [
|
||||
request
|
||||
for request in observed[0]["requests"]
|
||||
if request.get("domain") != "self_extension"
|
||||
]
|
||||
assert native_requests == template["plan"]["requests"]
|
||||
assert _peripheral_refs(observed[0]) == [
|
||||
"ir:flash-open-000000000001",
|
||||
"ir:ambient-open-0000000001",
|
||||
"ir:strip-open-000000000001",
|
||||
]
|
||||
|
||||
|
||||
def test_mid_autumn_cloud_composition_adds_required_native_and_external_controls() -> None:
|
||||
client = StructuredSceneClient()
|
||||
assistant = CodexAssistantService(client)
|
||||
|
||||
Reference in New Issue
Block a user