c812a1a595
Integrate the remote firmware workbench, OTA delivery, and timed mode schedules while preserving the local N7 scene and runtime changes.
649 lines
25 KiB
Python
649 lines
25 KiB
Python
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()
|