from __future__ import annotations import hashlib import json from collections.abc import Callable, Mapping from copy import deepcopy from pathlib import Path from unittest.mock import MagicMock, patch import pytest from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog from cognitive_agent.application.codex_assistant import CodexAssistantService from cognitive_agent.application.mode_profile import ModeProfileRegistry from cognitive_agent.application.scene_composer import normalize_scene_output from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService @pytest.fixture def hybrid_service( tmp_path: Path, ) -> tuple[SeatDemoService, AssistantCapabilityCatalog, Path]: service = SeatDemoService(REPOSITORY_ROOT) service.artifact_root = tmp_path / "runs" user_root = tmp_path / "user-modes" service.mode_profiles = ModeProfileRegistry( REPOSITORY_ROOT / "config" / "modes", user_root=user_root, ) try: yield service, AssistantCapabilityCatalog(service), user_root finally: service.close() def _target(action: Mapping[str, object]) -> str: target = action.get("target") assert isinstance(target, Mapping) return str(target.get("instance") or target.get("zone") or "") def _actions(result: Mapping[str, object]) -> list[dict[str, object]]: intent = result.get("intent_ir") if isinstance(intent, Mapping): spec = intent.get("spec") raw_actions = spec.get("actions") if isinstance(spec, Mapping) else None else: raw_actions = result.get("actions") assert isinstance(raw_actions, list) assert all(isinstance(action, dict) for action in raw_actions) return raw_actions def _action_map( result: Mapping[str, object], ) -> dict[tuple[str, str], dict[str, object]]: return { (str(action["capability"]), _target(action)): deepcopy(dict(action.get("parameters") or {})) for action in _actions(result) } def _draft_reference(result: Mapping[str, object]) -> dict[str, str]: draft = result.get("mode_profile_draft") assert isinstance(draft, Mapping) return { "name": str(draft["name"]), "source_request_id": str(draft["source_request_id"]), "vehicle_id": str(draft["vehicle_id"]), } def _template_hashes() -> dict[str, str]: root = REPOSITORY_ROOT / "config" / "scene_templates" return { path.name: hashlib.sha256(path.read_bytes()).hexdigest() for path in sorted(root.glob("*.yaml")) } def _assistant_preview( service: SeatDemoService, catalog: AssistantCapabilityCatalog, message: str, *, conversation_id: str, ) -> tuple[ dict[str, object], dict[str, object], list[dict[str, object]], MagicMock, list[dict[str, object] | None], ]: client = MagicMock() client.available = False assistant = CodexAssistantService(client) snapshot = catalog.snapshot(vehicle_id="denza_n7") fast_answer = catalog.fast_answer(message, snapshot) events: list[dict[str, object]] = [] raw_results: list[dict[str, object]] = [] passed_plans: list[dict[str, object] | None] = [] def execute_control( instruction: str, progress: Callable[[dict[str, object]], None], assistant_plan: Mapping[str, object] | None = None, ) -> dict[str, object]: plan = deepcopy(dict(assistant_plan)) if assistant_plan is not None else None passed_plans.append(plan) result = service.preview_native_can( instruction, provider="local", conversation_id=conversation_id, execution_mode="hardware", vehicle_id="denza_n7", on_commands_validated=progress, assistant_plan=plan, ) raw_results.append(result) return result response = assistant.stream( message, conversation_id=conversation_id, principal="scene-template-hybrid-test", history=[], authoritative_answer=fast_answer.answer if fast_answer else None, emit=lambda event: events.append(event) or 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 len(raw_results) == 1 return response, raw_results[0], events, client, passed_plans def test_all_production_scene_templates_complete_the_full_local_dry_run( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], ) -> None: service, catalog, user_root = hybrid_service snapshot = catalog.snapshot(vehicle_id="denza_n7") templates = snapshot["scene_templates"] assert isinstance(templates, list) assert len(templates) == 16 assert all(template["assistant_callable"] is True for template in templates) with patch.object( service.hardware_execution, "reserve", side_effect=AssertionError("a scene template preview reached hardware execution"), ): for index, template in enumerate(templates): plan = deepcopy(template["plan"]) result = service.preview_native_can( f"构建一个{template['name']}", provider="local", conversation_id=f"conv-template-matrix-{index:02d}", execution_mode="hardware", vehicle_id="denza_n7", assistant_plan=plan, ) assert result["status"] == "succeeded", template["id"] assert result["execution_mode"] == "dry_run", template["id"] assert result["hardware_execution_allowed"] is False, template["id"] assert result["orchestration_route"] == "mode_profile_create_preview" draft = result["mode_profile_draft"] assert draft["generation_source"] == "scene_template_copy" assert draft["source_template_id"] == template["id"] assert draft["source_template_revision"] == template["template_revision"] assert "existing_mode_id" not in draft expected_requests = plan["requests"] if plan.get("schedule"): expected_schedule = deepcopy(plan["schedule"]) for phase in expected_schedule["phases"]: phase["operations"] = [ op for op in phase["operations"] if op["domain"] != "seat" or op["target_scope"] == "passenger" ] assert draft["schedule"] == expected_schedule expected_requests = [ {**op, "parameters": [{"name": key, "value": value} for key, value in op["parameters"].items()]} for phase in expected_schedule["phases"] for op in phase["operations"] ] else: expected_requests = [ request for request in expected_requests if request["domain"] != "seat" or request["target_scope"] == "passenger" ] assert draft["operation_count"] == len(expected_requests) expected = { ( str(request["capability"]), str(request["target_scope"]), json.dumps( { parameter["name"]: parameter["value"] for parameter in request["parameters"] }, ensure_ascii=False, sort_keys=True, ), ) for request in expected_requests } actual = { ( str(action["capability"]), _target(action), json.dumps( action.get("parameters") or {}, ensure_ascii=False, sort_keys=True, ), ) for action in _actions(result) } assert actual == expected, template["id"] assert not list(user_root.rglob("*.yaml")) def test_viewing_template_creates_an_unbound_audited_copy_without_codex( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], ) -> None: service, catalog, user_root = hybrid_service with patch.object( service.hardware_execution, "reserve", side_effect=AssertionError("template copy reached hardware execution"), ): response, raw_result, events, client, passed_plans = _assistant_preview( service, catalog, "构建一个观影模式", conversation_id="conv-template-viewing-copy", ) draft = raw_result["mode_profile_draft"] assert response["route"] == "scene_template" assert response["success"] is True assert raw_result["status"] == "succeeded" assert raw_result["execution_mode"] == "dry_run" assert raw_result["hardware_execution_allowed"] is False assert draft["name"] == "观影模式" assert draft["generation_source"] == "scene_template_copy" assert draft["source_template_id"] == "cinema" assert draft["source_template_revision"] assert "existing_mode_id" not in draft assert "existing_mode_revision" not in draft assert raw_result["mode_creation"]["source_template_id"] == "cinema" assert passed_plans[0]["metadata"]["source"] == "scene_template" assert events[0]["engine"] == "n7web-scene-template" assert "独立草稿" in response["answer"] assert not list(user_root.rglob("*.yaml")) client.start_structured_turn.assert_not_called() client.start_turn.assert_not_called() @pytest.mark.parametrize( ("case_id", "message", "expected_name", "template_id"), ( ( "reading-purpose", "帮我构建一个适合看书的功能模式", "看书模式", "reading", ), ( "reading-purpose-preview", "我想生成一个用来读书的模式并预览", "读书模式", "reading", ), ( "relax-purpose", "构建一个适合放松的功能模式", "放松模式", "relaxation", ), ( "relax-purpose-polite", "请生成用于放松的模式,先看看", "放松模式", "relaxation", ), ( "cinema-purpose", "帮我构建一个适合看电影的功能模式", "看电影模式", "cinema", ), ), ) def test_purpose_descriptions_use_a_concise_name_and_zero_model_template_route( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], case_id: str, message: str, expected_name: str, template_id: str, ) -> None: service, catalog, user_root = hybrid_service with patch.object( service.hardware_execution, "reserve", side_effect=AssertionError("purpose template reached hardware execution"), ): response, raw_result, events, client, passed_plans = _assistant_preview( service, catalog, message, conversation_id=f"conv-template-{case_id}", ) template = service.scene_templates.get_template(template_id) draft = raw_result["mode_profile_draft"] assert response["route"] == "scene_template" assert response["success"] is True assert expected_name in response["answer"] assert raw_result["status"] == "succeeded" assert raw_result["execution_mode"] == "dry_run" assert raw_result["hardware_execution_allowed"] is False assert draft["name"] == expected_name assert draft["generation_source"] == "scene_template_copy" assert draft["source_template_id"] == template_id assert draft["operation_count"] == sum( operation.domain != "seat" or operation.target_scope == "passenger" for operation in template.operations ) assert passed_plans[0]["metadata"]["template_id"] == template_id assert events[0]["engine"] == "n7web-scene-template" assert not list(user_root.rglob("*.yaml")) client.start_structured_turn.assert_not_called() client.start_turn.assert_not_called() def test_explicit_settings_on_a_purpose_scene_bypass_template_defaults( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], ) -> None: service, catalog, _user_root = hybrid_service response, raw_result, events, client, passed_plans = _assistant_preview( service, catalog, "构建一个适合看书的功能模式,空调温度调整到25度", conversation_id="conv-template-purpose-explicit-settings", ) draft = raw_result["mode_profile_draft"] assert response["route"] == "vehicle_control" assert response["success"] is True assert draft["name"] == "看书模式" assert draft["generation_source"] == "explicit_scene_requirements" assert draft["operation_count"] == 1 assert passed_plans == [None] assert events[0]["engine"] == "n7web-mode-builder" client.start_structured_turn.assert_not_called() client.start_turn.assert_not_called() @pytest.mark.parametrize( ("message", "expected_name", "template_id"), ( ("构建一个座椅舒适的功能模式", "座椅舒适模式", "seat-comfort"), ("构建一个让人坐着放松的功能模式", "坐着放松模式", "relaxation"), ("请帮我创建一个适合乘坐舒适的模式吧", "乘坐舒适模式", "seat-comfort"), ("帮我构建一个让我们坐着舒服的功能模式", "坐着舒服模式", "seat-comfort"), ("创建一个座椅放松模式并预览", "座椅放松模式", "relaxation"), ("构建一个长途舒适模式", "长途舒适模式", "long-trip-comfort"), ), ) def test_seating_comfort_requests_preview_only_passenger_with_no_model_or_hardware_call( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], message: str, expected_name: str, template_id: str, ) -> None: service, catalog, user_root = hybrid_service with patch.object( service.hardware_execution, "reserve", side_effect=AssertionError("seat comfort preview reached hardware execution"), ): response, result, _events, client, _plans = _assistant_preview( service, catalog, message, conversation_id="conv-seat-comfort-preview", ) assert response["route"] == "scene_template" assert response["success"] is True assert result["status"] == "succeeded" assert result["execution_mode"] == "dry_run" assert result["hardware_execution_allowed"] is False assert result["mode_profile_draft"]["name"] == expected_name assert result["mode_profile_draft"]["source_template_id"] == template_id seat_actions = { (capability, target): parameters for (capability, target), parameters in _action_map(result).items() if capability.startswith("seat.") } assert {target for _capability, target in seat_actions} == {"passenger"} assert all( "driver_axis" not in capability and "driver_position" not in capability for capability, _target in seat_actions ) assert ("seat.set_driver_massage_native", "passenger") not in seat_actions if template_id in {"seat-comfort", "long-trip-comfort"}: assert seat_actions["seat.set_ventilation", "passenger"] == { "state": "on", "level": 1, } client.start_structured_turn.assert_not_called() client.start_turn.assert_not_called() assert not list(user_root.rglob("*.yaml")) @pytest.mark.parametrize("name", ("云端座椅舒展模式", "晚间乘坐释压模式")) def test_unregistered_seat_scene_keeps_both_explicit_targets_through_native_merge( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], name: str, ) -> None: service, catalog, user_root = hybrid_service plan = normalize_scene_output({ "requests": [ {"capability": "seat.set_driver_massage_native", "target_scope": "driver", "parameters": {"action": "mode", "mode": "relax"}}, {"capability": "seat.set_driver_heating_native", "target_scope": "driver", "parameters": {"state": "on", "level": 2}}, {"capability": "seat.set_backrest_recline", "target_scope": "passenger", "parameters": {"position_pct": 45}}, {"capability": "seat.set_heating", "target_scope": "passenger", "parameters": {"state": "on", "level": 1}}, ], "ambiguities": [], "confidence": 0.95, }, catalog.scene_composition_context()) with patch.object( service.hardware_execution, "reserve", side_effect=AssertionError("scene composition reached hardware execution"), ): result = service.preview_native_can( f"构建一个主驾和副驾的{name}", provider="local", conversation_id="conv-seat-generated", execution_mode="hardware", vehicle_id="denza_n7", assistant_plan=plan, ) assert result["status"] == "succeeded" assert result["execution_mode"] == "dry_run" assert result["mode_profile_draft"]["generation_source"] == "codex_scene_composition" assert set(_action_map(result)) == { ("seat.set_driver_massage_native", "driver"), ("seat.set_driver_heating_native", "driver"), ("seat.set_backrest_recline", "passenger"), ("seat.set_heating", "passenger"), } assert not list(user_root.rglob("*.yaml")) def test_unscoped_seat_instruction_inside_mode_creation_defaults_to_passenger( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], ) -> None: service, _catalog, _user_root = hybrid_service result = service.preview_native_can( "构建一个舒展模式,座椅靠背调整到45%", provider="local", conversation_id="conv-seat-explicit-target", execution_mode="hardware", vehicle_id="denza_n7", ) assert result["status"] == "succeeded" assert _action_map(result) == { ("seat.set_backrest_recline", "passenger"): {"position_pct": 45}, } assert result.get("mode_profile_draft") assert result["hardware_execution_allowed"] is False def test_template_metadata_cannot_authorize_modified_or_stale_template_actions( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], ) -> None: service, catalog, user_root = hybrid_service snapshot = catalog.snapshot(vehicle_id="denza_n7") template = next( item for item in snapshot["scene_templates"] if item["id"] == "cinema" ) modified = deepcopy(template["plan"]) modified["requests"][0]["parameters"][0]["value"] = "off" with pytest.raises(ValueError, match="模板已更新"): service.preview_native_can( "构建一个观影模式", provider="local", conversation_id="conv-template-plan-tamper", execution_mode="hardware", vehicle_id="denza_n7", assistant_plan=modified, ) stale = deepcopy(template["plan"]) stale["metadata"]["template_revision"] = "0123456789abcdef" with pytest.raises(ValueError, match="模板已更新"): service.preview_native_can( "构建一个观影模式", provider="local", conversation_id="conv-template-plan-stale", execution_mode="hardware", vehicle_id="denza_n7", assistant_plan=stale, ) assert not list(user_root.rglob("*.yaml")) def test_template_alias_keeps_the_user_requested_draft_name( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], ) -> None: service, catalog, _user_root = hybrid_service response, raw_result, _events, client, passed_plans = _assistant_preview( service, catalog, "创建一个影院模式", conversation_id="conv-template-alias-name", ) draft = raw_result["mode_profile_draft"] assert response["route"] == "scene_template" assert draft["name"] == "影院模式" assert draft["source_template_id"] == "cinema" assert passed_plans[0]["metadata"]["template_id"] == "cinema" client.start_structured_turn.assert_not_called() client.start_turn.assert_not_called() def test_template_copy_can_be_revised_twice_without_losing_sibling_actions( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], ) -> None: service, catalog, user_root = hybrid_service conversation_id = "conv-template-viewing-revision-chain" with patch.object( service.hardware_execution, "reserve", side_effect=AssertionError("template draft revision reached hardware execution"), ): _response, built, _events, _client, _plans = _assistant_preview( service, catalog, "构建一个观影模式", conversation_id=conversation_id, ) shade_revised = service.preview_native_can( "把观影模式的遮阳帘变成100%", provider="local", conversation_id=conversation_id, execution_mode="hardware", vehicle_id="denza_n7", mode_profile_draft_context=_draft_reference(built), ) final = service.preview_native_can( "把观影模式的空调温度调整到25度", provider="local", conversation_id=conversation_id, execution_mode="hardware", vehicle_id="denza_n7", mode_profile_draft_context=_draft_reference(shade_revised), ) built_actions = _action_map(built) shade_actions = _action_map(shade_revised) final_actions = _action_map(final) shade_key = ("roof.set_shade_position", "vehicle") temperature_key = ("climate.set_temperature", "vehicle") assert shade_revised["status"] == final["status"] == "succeeded" assert shade_revised["execution_mode"] == final["execution_mode"] == "dry_run" assert shade_revised["hardware_execution_allowed"] is False assert final["hardware_execution_allowed"] is False assert len(built_actions) == len(shade_actions) == len(final_actions) == 10 assert built_actions[shade_key] == {"position_pct": 0} assert shade_actions[shade_key] == final_actions[shade_key] == {"position_pct": 100} assert built_actions[temperature_key] == shade_actions[temperature_key] == {"temperature_c": 24} assert final_actions[temperature_key] == {"temperature_c": 25} untouched_keys = set(built_actions) - {shade_key, temperature_key} assert {key: final_actions[key] for key in untouched_keys} == { key: built_actions[key] for key in untouched_keys } first_draft = built["mode_profile_draft"] second_draft = shade_revised["mode_profile_draft"] final_draft = final["mode_profile_draft"] assert first_draft["draft_revision"] == 1 assert second_draft["draft_revision"] == 2 assert final_draft["draft_revision"] == 3 assert second_draft["generation_source"] == "draft_revision" assert final_draft["generation_source"] == "draft_revision" assert second_draft["source_template_id"] == final_draft["source_template_id"] == ("cinema") assert "existing_mode_id" not in final_draft assert not list(user_root.rglob("*.yaml")) def test_saving_a_template_copy_never_mutates_template_and_registered_mode_wins_next( hybrid_service: tuple[SeatDemoService, AssistantCapabilityCatalog, Path], ) -> None: service, catalog, user_root = hybrid_service hashes_before = _template_hashes() template_revision = service.scene_templates.get_template("cinema").revision _response, built, _events, first_client, first_plans = _assistant_preview( service, catalog, "构建一个观影模式", conversation_id="conv-template-save-source", ) draft = built["mode_profile_draft"] saved = service.create_mode_profile_card( str(draft["name"]), str(draft["instruction"]), str(draft["source_request_id"]), str(draft["vehicle_id"]), ) saved_profile = saved["mode_profile"] assert saved["status"] == "succeeded" assert saved_profile["name"] == "观影模式" assert saved_profile["origin"] == "user" assert saved_profile["id"] != "cinema" assert list(user_root.rglob(f"{saved_profile['id']}.yaml")) assert _template_hashes() == hashes_before assert service.scene_templates.get_template("cinema").revision == template_revision assert first_plans[0]["metadata"]["template_id"] == "cinema" first_client.start_structured_turn.assert_not_called() response, registered, events, second_client, passed_plans = _assistant_preview( service, catalog, "构建一个观影模式", conversation_id="conv-template-saved-mode-priority", ) registered_draft = registered["mode_profile_draft"] assert response["route"] == "vehicle_control" assert response["success"] is True assert events[0]["engine"] == "n7web-mode-builder" assert passed_plans == [None] assert registered_draft["generation_source"] == "registered_mode_profile" assert registered_draft["existing_mode_id"] == saved_profile["id"] assert "source_template_id" not in registered_draft second_client.start_structured_turn.assert_not_called() second_client.start_turn.assert_not_called()