fix: support conversational mode draft updates

This commit is contained in:
vee
2026-09-07 23:59:42 +08:00
parent 6b2b644ee6
commit f0a248ede3
19 changed files with 2326 additions and 109 deletions
@@ -9,8 +9,13 @@ from unittest.mock import MagicMock, patch
import pytest
from cognitive_agent.application.codex_assistant import CodexAssistantService
from cognitive_agent.application.mode_profile_intent import (
is_mode_profile_query,
parse_mode_profile_update,
)
from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.native_can_runtime import NativeCanCompileError
from cognitive_agent.application.seat_service import (
REPOSITORY_ROOT,
SeatDemoService,
@@ -130,6 +135,68 @@ def _intent_actions(result: dict[str, object]) -> list[dict[str, object]]:
return actions
def _draft_reference(result: dict[str, object]) -> dict[str, str]:
draft = result["mode_profile_draft"]
assert isinstance(draft, dict)
return {
"name": str(draft["name"]),
"source_request_id": str(draft["source_request_id"]),
"vehicle_id": str(draft["vehicle_id"]),
}
def test_every_repository_mode_can_build_a_name_only_draft_without_exception(
vehicle_service: SeatDemoService,
) -> None:
modes = vehicle_service.mode_profiles.public_modes("denza_n7")
assert modes
for index, mode in enumerate(modes):
labels = [str(mode["name"]), *map(str, mode.get("aliases", []))]
label = next((item for item in labels if item.endswith("模式")), None)
assert label is not None, mode["id"]
result = vehicle_service.preview_native_can(
f"构建一个{label}",
provider="local",
conversation_id=f"conv-name-only-mode-{index:02d}",
execution_mode="hardware",
vehicle_id="denza_n7",
)
assert result["status"] == "succeeded", mode["id"]
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
assert result["mode_profile_draft"]["operation_count"] > 0
def test_name_only_mode_native_compile_exception_returns_scrubbed_failure(
vehicle_service: SeatDemoService,
) -> None:
with patch.object(
vehicle_service.native_can_runtime,
"compile",
side_effect=NativeCanCompileError("synthetic compiler rejection"),
):
result = vehicle_service.preview_native_can(
"构建一个办公模式",
provider="local",
conversation_id="conv-name-only-native-compile-failure",
execution_mode="hardware",
vehicle_id="denza_n7",
)
assert result["status"] == "validation_failed"
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
assert result["orchestration_route"] == "mode_profile_create_preview"
assert result.get("mode_profile_draft") is None
assert result["function_configs"] == {}
assert result["command_plans"] == {}
assert result["mode_creation_failure"]["code"] == (
"MODE_PROFILE_NATIVE_COMPILE_FAILED"
)
def test_name_only_nap_request_reuses_the_verified_repository_profile(
vehicle_service: SeatDemoService,
tmp_path: Path,
@@ -202,6 +269,537 @@ def test_inferred_nap_actions_are_bounded_by_the_runtime_capability_catalog(
assert all("media" not in str(action["capability"]) for action in actions)
def test_pending_nap_draft_temperature_can_be_revised_without_saving_or_execution(
vehicle_service: SeatDemoService,
) -> None:
conversation_id = "conv-nap-pending-draft-revision"
original_profile = vehicle_service.mode_profiles.get_profile("nap", "denza_n7")
original_revision = vehicle_service.mode_profiles.profile_revision(original_profile)
built = _build_nap_mode(vehicle_service, conversation_id)
built_draft = built["mode_profile_draft"]
assert isinstance(built_draft, dict)
revised = vehicle_service.preview_native_can(
"把小憩模式的空调温度调整到25度",
provider="local",
conversation_id=conversation_id,
execution_mode="hardware",
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(built),
)
draft = revised["mode_profile_draft"]
assert revised["status"] == "succeeded"
assert revised["execution_mode"] == "dry_run"
assert revised["hardware_execution_allowed"] is False
assert revised["orchestration_route"] == "mode_profile_create_preview"
assert draft["name"] == "小憩模式"
assert draft["operation_count"] == built_draft["operation_count"] == 9
assert draft["generation_source"] == "draft_revision"
assert draft["draft_revision"] == 2
assert draft["revises_source_request_id"] == built_draft["source_request_id"]
assert draft["source_request_id"] != built_draft["source_request_id"]
assert draft["existing_mode_id"] == "nap"
assert draft["existing_mode_revision"] == original_revision
revised_actions = _intent_actions(revised)
temperatures = [
action
for action in revised_actions
if action["capability"] == "climate.set_temperature"
]
assert len(temperatures) == 1
assert temperatures[0]["parameters"]["temperature_c"] == 25
assert (
temperatures[0]["target"].get("zone")
or temperatures[0]["target"].get("instance")
) == "vehicle"
def sibling_signature(action: dict[str, object]) -> str:
parameters = dict(action.get("parameters") or {})
if parameters.get("position_pct") is not None:
parameters.pop("angle_deg", None)
return json.dumps(
{
"domain": action.get("domain"),
"capability": action.get("capability"),
"target": action.get("target"),
"parameters": parameters,
},
ensure_ascii=False,
sort_keys=True,
)
assert {
sibling_signature(action)
for action in revised_actions
if action["capability"] != "climate.set_temperature"
} == {
sibling_signature(action)
for action in _intent_actions(built)
if action["capability"] != "climate.set_temperature"
}
# Generating and revising a card is configuration-only. The registered
# profile remains unchanged until the card's explicit save/update action.
unchanged = vehicle_service.mode_profiles.get_profile("nap", "denza_n7")
assert vehicle_service.mode_profiles.profile_revision(unchanged) == original_revision
assert next(
operation.parameters["temperature_c"]
for operation in unchanged.operations
if operation.capability == "climate.set_temperature"
) != 25
@pytest.mark.parametrize(
("message", "capability", "parameter", "expected"),
(
(
"请帮我 把 小憩模式 的 空调温度调整到25度",
"climate.set_temperature",
"temperature_c",
25,
),
(
"把小憩模式中的空调温度调整到25度",
"climate.set_temperature",
"temperature_c",
25,
),
(
"把小憩模式里空调温度设置为25度",
"climate.set_temperature",
"temperature_c",
25,
),
(
"小憩模式,空调温度调到25度。",
"climate.set_temperature",
"temperature_c",
25,
),
(
"把小憩模式的遮阳帘关闭",
"roof.set_shade_position",
"position_pct",
0,
),
(
"把小憩模式里的氛围灯打开",
"ambient_light.set_power",
"state",
"on",
),
),
)
def test_pending_draft_update_variants_never_enter_physical_execution(
vehicle_service: SeatDemoService,
message: str,
capability: str,
parameter: str,
expected: object,
) -> None:
conversation_id = "conv-nap-draft-variant-" + str(abs(hash(message)))[:12]
built = _build_nap_mode(vehicle_service, conversation_id)
with patch.object(
vehicle_service.hardware_execution,
"reserve",
side_effect=AssertionError("pending draft update reached hardware lease"),
):
revised = vehicle_service.preview_native_can(
message,
provider="local",
conversation_id=conversation_id,
execution_mode="hardware",
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(built),
)
assert revised["status"] == "succeeded"
assert revised["execution_mode"] == "dry_run"
assert revised["hardware_execution_allowed"] is False
assert revised["orchestration_route"] == "mode_profile_create_preview"
assert next(
action["parameters"][parameter]
for action in _intent_actions(revised)
if action["capability"] == capability
) == expected
def test_pending_draft_can_be_revised_repeatedly_from_latest_server_artifact(
vehicle_service: SeatDemoService,
) -> None:
conversation_id = "conv-nap-pending-draft-chain"
built = _build_nap_mode(vehicle_service, conversation_id)
second = vehicle_service.preview_native_can(
"把小憩模式的空调温度调整到25度",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(built),
)
third = vehicle_service.preview_native_can(
"把小憩模式的空调温度调整到26度",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(second),
)
second_draft = second["mode_profile_draft"]
third_draft = third["mode_profile_draft"]
assert third_draft["draft_revision"] == 3
assert third_draft["revises_source_request_id"] == second_draft["source_request_id"]
assert next(
action["parameters"]["temperature_c"]
for action in _intent_actions(third)
if action["capability"] == "climate.set_temperature"
) == 26
def test_pending_draft_reference_is_bound_to_conversation_name_and_vehicle(
vehicle_service: SeatDemoService,
) -> None:
built = _build_nap_mode(vehicle_service, "conv-nap-reference-owner")
reference = _draft_reference(built)
wrong_conversation = vehicle_service.preview_native_can(
"把小憩模式的空调温度调整到25度",
provider="local",
conversation_id="conv-nap-reference-attacker",
vehicle_id="denza_n7",
mode_profile_draft_context=reference,
)
assert wrong_conversation["status"] == "needs_confirmation"
assert wrong_conversation.get("mode_profile_draft") is None
assert any(
issue.get("code") == "MODE_PROFILE_DRAFT_UPDATE_REJECTED"
for issue in wrong_conversation["validation"]
)
for invalid_reference in (
{**reference, "vehicle_id": "another_vehicle"},
{**reference, "source_request_id": "req-forged-preview"},
):
rejected = vehicle_service.preview_native_can(
"把小憩模式的空调温度调整到25度",
provider="local",
conversation_id="conv-nap-reference-owner",
vehicle_id="denza_n7",
mode_profile_draft_context=invalid_reference,
)
assert rejected["status"] == "needs_confirmation"
assert rejected.get("mode_profile_draft") is None
assert rejected["hardware_execution_allowed"] is False
@pytest.mark.parametrize(
"message",
(
"把小憩模式的空调介绍一下",
"小憩模式的空调有什么作用",
"小憩模式的空调和普通空调有什么区别",
"小憩模式的空调设置有哪些?",
"小憩模式的空调该调几度",
"把小憩模式的空调调到25度合适吗",
"调整小憩模式的空调参数,列出来",
),
)
def test_air_conditioner_name_alone_is_not_parsed_as_mode_profile_update(
message: str,
) -> None:
assert SeatDemoService._parse_mode_profile_update(message) is None
@pytest.mark.parametrize(
"message",
(
"空调出风模式设置为吹面和吹脚",
"把空调出风模式设置为吹面和吹脚",
"调整空调出风模式为吹面和吹脚",
"主驾座椅按摩模式设置为放松",
"副驾座椅零重力模式设为开启",
"进入动态摇篮模式,空调24度,打开遮阳帘",
"开启女王副驾模式,天幕透光率调到20%",
),
)
def test_device_property_mode_is_not_a_named_mode_profile_update(
message: str,
) -> None:
assert SeatDemoService._parse_mode_profile_update(message) is None
@pytest.mark.parametrize(
("message", "expected"),
(
("把小憩模式的空调温度调整到25度", ("小憩模式", "空调温度调整到25度")),
("午休模式里的遮阳帘关闭", ("午休模式", "遮阳帘关闭")),
("修改露营模式,天幕透光率改到50%", ("露营模式", "天幕透光率改到50%")),
),
)
def test_named_mode_profile_update_requires_an_explicit_member_boundary(
message: str,
expected: tuple[str, str],
) -> None:
assert SeatDemoService._parse_mode_profile_update(message) == expected
def test_name_first_comma_update_requires_the_active_draft_name() -> None:
message = "小憩模式,空调温度调到25度。"
assert SeatDemoService._parse_mode_profile_update(message) is None
assert parse_mode_profile_update(
message,
expected_mode_name="小憩模式",
) == ("小憩模式", "空调温度调到25度")
assert parse_mode_profile_update(
message,
expected_mode_name="露营模式",
) is None
def test_name_first_comma_question_is_not_a_draft_update() -> None:
message = "小憩模式,空调温度调到25度合适吗?"
assert parse_mode_profile_update(
message,
expected_mode_name="小憩模式",
) is None
assert is_mode_profile_query(
message,
expected_mode_name="小憩模式",
)
@pytest.mark.parametrize(
"message",
(
"修改小憩模式的空调设置是什么",
"小憩模式的空调该调几度",
"把小憩模式的空调调到25度合适吗",
"调整小憩模式的空调参数,列出来",
"小憩模式的空调调到25度会怎样",
),
)
def test_mode_profile_query_is_fail_closed_even_on_direct_hardware_service_call(
vehicle_service: SeatDemoService,
message: str,
) -> None:
with patch.object(
vehicle_service.hardware_execution,
"reserve",
side_effect=AssertionError("mode-profile query reached hardware lease"),
):
result = vehicle_service.preview_native_can(
message,
provider="local",
conversation_id="conv-mode-profile-query-guard",
execution_mode="hardware",
vehicle_id="denza_n7",
)
assert result["status"] == "needs_confirmation"
assert result["execution_mode"] == "dry_run"
assert result["hardware_execution_allowed"] is False
assert result["orchestration_route"] == "mode_profile_query"
assert result["function_configs"] == {}
assert result["command_plans"] == {}
def test_pending_draft_rejects_unsafe_change_without_returning_a_partial_card(
vehicle_service: SeatDemoService,
) -> None:
conversation_id = "conv-nap-pending-invalid-change"
built = _build_nap_mode(vehicle_service, conversation_id)
rejected = vehicle_service.preview_native_can(
"把小憩模式的空调温度调整到99度",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(built),
)
assert rejected["status"] == "needs_confirmation"
assert rejected["execution_mode"] == "dry_run"
assert rejected["hardware_execution_allowed"] is False
assert rejected.get("mode_profile_draft") is None
assert rejected["function_configs"] == {}
assert rejected["command_plans"] == {}
def test_pending_draft_revision_fails_closed_if_full_plan_loses_any_action(
vehicle_service: SeatDemoService,
) -> None:
conversation_id = "conv-nap-pending-incomplete-revision"
built = _build_nap_mode(vehicle_service, conversation_id)
with patch.object(
vehicle_service,
"_scene_action_signatures",
return_value={},
):
rejected = vehicle_service.preview_native_can(
"把小憩模式的空调温度调整到25度",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(built),
)
assert rejected["status"] != "succeeded"
assert rejected.get("mode_profile_draft") is None
assert not _intent_actions(rejected)
assert rejected["function_configs"] == {}
assert rejected["command_plans"] == {}
assert any(
issue.get("code") == "MODE_PROFILE_SCENE_PLAN_INCOMPLETE"
for issue in rejected["validation"]
)
def test_pending_draft_update_rejects_partially_understood_change_atomically(
vehicle_service: SeatDemoService,
) -> None:
conversation_id = "conv-nap-pending-partial-change"
built = _build_nap_mode(vehicle_service, conversation_id)
rejected = vehicle_service.preview_native_can(
"把小憩模式的空调温度调整到25度,同时启动曲速引擎",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(built),
)
assert rejected["status"] == "needs_confirmation"
assert rejected.get("mode_profile_draft") is None
assert rejected["function_configs"] == {}
assert rejected["command_plans"] == {}
assert "未完整识别" in rejected["mode_creation_failure"]["reason"]
def test_revised_unknown_mode_draft_can_be_trialed_and_saved_with_all_actions(
vehicle_service: SeatDemoService,
) -> None:
conversation_id = "conv-custom-pending-save-after-revision"
built = vehicle_service.preview_native_can(
"构建一个午休模式",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
assistant_plan=LUNCH_BREAK_SCENE_PLAN,
)
revised = vehicle_service.preview_native_can(
"午休模式的空调温度改为25度",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(built),
)
draft = revised["mode_profile_draft"]
trial = vehicle_service.trial_mode_profile_draft(
str(draft["name"]),
str(draft["instruction"]),
str(draft["source_request_id"]),
str(draft["vehicle_id"]),
)
saved = vehicle_service.create_mode_profile_card(
str(draft["name"]),
str(draft["instruction"]),
str(draft["source_request_id"]),
str(draft["vehicle_id"]),
)
assert trial["status"] == "succeeded"
assert trial["mode_profile_draft"]["draft_revision"] == 2
assert len(_intent_actions(trial)) == draft["operation_count"] == 5
assert saved["mode_profile"]["operation_count"] == 5
profile = vehicle_service.mode_profiles.get_profile(
str(saved["mode_profile"]["id"]),
"denza_n7",
)
assert next(
operation.parameters["temperature_c"]
for operation in profile.operations
if operation.capability == "climate.set_temperature"
) == 25
def test_explicit_mode_with_close_motions_can_still_receive_a_temperature_revision(
vehicle_service: SeatDemoService,
) -> None:
conversation_id = "conv-explicit-motion-draft-revision"
built = vehicle_service.preview_native_can(
"创建一个躺平测试模式,副驾座椅靠背向后移动到最大位置,"
"遮阳帘关闭,车窗关闭,灯光全部关闭,空调调整到24度",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
)
assert built["status"] == "succeeded"
assert built["mode_profile_draft"]["operation_count"] == 7
revised = vehicle_service.preview_native_can(
"把躺平测试模式的空调温度调整到25度",
provider="local",
conversation_id=conversation_id,
execution_mode="hardware",
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(built),
)
actions = _intent_actions(revised)
assert revised["status"] == "succeeded"
assert revised["execution_mode"] == "dry_run"
assert revised["hardware_execution_allowed"] is False
assert revised["mode_profile_draft"]["operation_count"] == len(actions) == 7
assert next(
action["parameters"]["temperature_c"]
for action in actions
if action["capability"] == "climate.set_temperature"
) == 25
capabilities = {str(action["capability"]) for action in actions}
assert "roof.set_shade_position" in capabilities
assert "window.set_position" in capabilities
assert "roof.set_shade_motion" not in capabilities
assert "window.set_motion" not in capabilities
def test_unscoped_change_rejects_a_draft_with_multiple_targets_for_same_capability(
vehicle_service: SeatDemoService,
) -> None:
conversation_id = "conv-multi-zone-pending-draft"
multi_zone_plan = {
**LUNCH_BREAK_SCENE_PLAN,
"detected_domains": ["climate"],
"requests": [
_scene_request(
"climate", "climate.set_temperature", "driver", {"temperature_c": 23}
),
_scene_request(
"climate", "climate.set_temperature", "passenger", {"temperature_c": 24}
),
],
}
built = vehicle_service.preview_native_can(
"构建一个双区舒适模式",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
assistant_plan=multi_zone_plan,
)
assert built["status"] == "succeeded"
rejected = vehicle_service.preview_native_can(
"把双区舒适模式的空调温度调整到25度",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
mode_profile_draft_context=_draft_reference(built),
)
assert rejected["status"] == "needs_confirmation"
assert rejected.get("mode_profile_draft") is None
assert "多个目标" in rejected["mode_creation_failure"]["reason"]
def test_codex_composes_an_unregistered_name_only_scene_from_runtime_capabilities(
vehicle_service: SeatDemoService,
tmp_path: Path,