2139 lines
73 KiB
Python
2139 lines
73 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from pathlib import Path
|
|
from threading import Barrier
|
|
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.mode_profile_intent import (
|
|
is_mode_profile_query,
|
|
parse_mode_profile_detail_query,
|
|
parse_mode_profile_update,
|
|
)
|
|
from cognitive_agent.application.native_can_runtime import NativeCanCompileError
|
|
from cognitive_agent.application.seat_service import (
|
|
REPOSITORY_ROOT,
|
|
SeatDemoService,
|
|
)
|
|
|
|
NAP_REQUEST = "构建一个小憩模式"
|
|
INTERNAL_SCENE_PLACEHOLDER = "请根据该场景名称使用当前可控原子能力构建并执行场景"
|
|
EXPECTED_NAP_CAPABILITIES = {
|
|
"seat.set_longitudinal_position",
|
|
"seat.set_cushion_tilt",
|
|
"seat.set_backrest_recline",
|
|
"seat.set_legrest_height",
|
|
"climate.set_temperature",
|
|
"climate.set_fan_level",
|
|
"climate.set_cycle_mode",
|
|
"roof.set_shade_position",
|
|
"ambient_light.set_power",
|
|
}
|
|
|
|
|
|
def _scene_request(
|
|
domain: str,
|
|
capability: str,
|
|
target_scope: str,
|
|
parameters: dict[str, object],
|
|
) -> dict[str, object]:
|
|
return {
|
|
"domain": domain,
|
|
"capability": capability,
|
|
"target_scope": target_scope,
|
|
"operation": "set",
|
|
"duration_ms": None,
|
|
"parameters": [
|
|
{"name": name, "value": value}
|
|
for name, value in parameters.items()
|
|
],
|
|
"reason": "根据午休场景组合当前工程已登记能力。",
|
|
}
|
|
|
|
|
|
LUNCH_BREAK_SCENE_PLAN = {
|
|
"detected_domains": ["seat", "climate", "roof", "ambient_light"],
|
|
"requests": [
|
|
_scene_request(
|
|
"seat",
|
|
"seat.set_backrest_recline",
|
|
"passenger",
|
|
{"position_pct": 70},
|
|
),
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"vehicle",
|
|
{"temperature_c": 23},
|
|
),
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_fan_level",
|
|
"vehicle",
|
|
{"level": 1},
|
|
),
|
|
_scene_request(
|
|
"roof",
|
|
"roof.set_shade_position",
|
|
"vehicle",
|
|
{"position_pct": 0},
|
|
),
|
|
_scene_request(
|
|
"ambient_light",
|
|
"ambient_light.set_power",
|
|
"vehicle",
|
|
{"state": "off"},
|
|
),
|
|
],
|
|
"ambiguities": [],
|
|
"decision_summary": ["组合安静、遮光且温度适宜的午休场景。"],
|
|
"confidence": 0.92,
|
|
}
|
|
|
|
VIEWING_SCENE_PLAN = {
|
|
"detected_domains": ["ambient_light", "roof", "climate", "interior_light"],
|
|
"requests": [
|
|
_scene_request(
|
|
"ambient_light",
|
|
"ambient_light.set_mode",
|
|
"vehicle",
|
|
{"mode": "cinema"},
|
|
),
|
|
_scene_request(
|
|
"roof",
|
|
"roof.set_shade_position",
|
|
"vehicle",
|
|
{"position_pct": 0},
|
|
),
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"vehicle",
|
|
{"temperature_c": 24},
|
|
),
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_fan_level",
|
|
"vehicle",
|
|
{"level": 2},
|
|
),
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_cycle_mode",
|
|
"vehicle",
|
|
{"mode": "internal"},
|
|
),
|
|
_scene_request(
|
|
"interior_light",
|
|
"interior_light.set_indoor_power",
|
|
"vehicle",
|
|
{"state": "off"},
|
|
),
|
|
],
|
|
"ambiguities": [],
|
|
"decision_summary": ["组合遮光、低照明和舒适温度的观影场景。"],
|
|
"confidence": 0.94,
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def vehicle_service(tmp_path: Path) -> SeatDemoService:
|
|
service = SeatDemoService(REPOSITORY_ROOT)
|
|
service.artifact_root = tmp_path / "runs"
|
|
service.mode_profiles = ModeProfileRegistry(
|
|
REPOSITORY_ROOT / "config" / "modes",
|
|
user_root=tmp_path / "user-modes",
|
|
)
|
|
return service
|
|
|
|
|
|
def _build_nap_mode(service: SeatDemoService, conversation_id: str) -> dict[str, object]:
|
|
with patch.object(
|
|
service.openai,
|
|
"interpret",
|
|
side_effect=AssertionError(
|
|
"a repository-backed named mode must not depend on a cloud model"
|
|
),
|
|
):
|
|
return service.preview_native_can(
|
|
NAP_REQUEST,
|
|
provider="local",
|
|
conversation_id=conversation_id,
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
|
|
def _build_viewing_mode(
|
|
service: SeatDemoService,
|
|
conversation_id: str,
|
|
) -> dict[str, object]:
|
|
return service.preview_native_can(
|
|
"构建一个观影模式",
|
|
provider="local",
|
|
conversation_id=conversation_id,
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=VIEWING_SCENE_PLAN,
|
|
)
|
|
|
|
|
|
def _intent_actions(result: dict[str, object]) -> list[dict[str, object]]:
|
|
intent_ir = result.get("intent_ir")
|
|
assert isinstance(intent_ir, dict)
|
|
spec = intent_ir.get("spec")
|
|
assert isinstance(spec, dict)
|
|
actions = spec.get("actions")
|
|
assert isinstance(actions, list)
|
|
assert all(isinstance(action, dict) for action in actions)
|
|
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,
|
|
) -> None:
|
|
preset = vehicle_service.mode_profiles.get_profile("nap", "denza_n7")
|
|
|
|
result = _build_nap_mode(vehicle_service, "conv-nap-name-inference")
|
|
|
|
assert result["status"] == "succeeded"
|
|
assert result["execution_mode"] == "dry_run"
|
|
assert result["hardware_execution_allowed"] is False
|
|
assert result["orchestration_route"] == "mode_profile_create_preview"
|
|
assert result["mode_profile"] is None
|
|
draft = result["mode_profile_draft"]
|
|
assert isinstance(draft, dict)
|
|
assert draft["name"] == "小憩模式"
|
|
assert draft["operation_count"] == 9
|
|
assert len(preset.operations) == 10 # The registry retains both optional seat variants.
|
|
assert set(draft["domains"]) == {"seat", "climate", "roof", "ambient_light"}
|
|
assert draft["existing_mode_id"] == "nap"
|
|
assert isinstance(draft["existing_mode_revision"], str)
|
|
assert len(draft["existing_mode_revision"]) == 16
|
|
assert draft["generation_source"] == "registered_mode_profile"
|
|
|
|
actions = _intent_actions(result)
|
|
assert {action["capability"] for action in actions} == {
|
|
operation.capability for operation in preset.operations
|
|
if operation.domain != "seat" or operation.target_scope in {"passenger", "front_right"}
|
|
} == EXPECTED_NAP_CAPABILITIES
|
|
expected_targets_and_parameters = {
|
|
operation.capability: (
|
|
"passenger"
|
|
if operation.domain == "seat" and operation.target_scope == "front_right"
|
|
else operation.target_scope,
|
|
operation.parameters,
|
|
)
|
|
for operation in preset.operations
|
|
if operation.domain != "seat" or operation.target_scope in {"passenger", "front_right"}
|
|
}
|
|
actual_targets_and_parameters = {
|
|
action["capability"]: (
|
|
action["target"].get("instance") or action["target"].get("zone"),
|
|
action["parameters"],
|
|
)
|
|
for action in actions
|
|
}
|
|
assert actual_targets_and_parameters == expected_targets_and_parameters
|
|
assert not list((tmp_path / "user-modes").rglob("*.yaml"))
|
|
|
|
public_result = json.dumps(result, ensure_ascii=False, default=str)
|
|
assert INTERNAL_SCENE_PLACEHOLDER not in public_result
|
|
|
|
|
|
def test_inferred_nap_actions_are_bounded_by_the_runtime_capability_catalog(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
result = _build_nap_mode(vehicle_service, "conv-nap-capability-boundary")
|
|
actions = _intent_actions(result)
|
|
catalog = {
|
|
capability["name"]: capability
|
|
for capability in vehicle_service.runtime_info()["capabilities"]
|
|
}
|
|
|
|
for action in actions:
|
|
capability = catalog[action["capability"]]
|
|
assert capability.get("execution_support") != "observational_only"
|
|
assert capability.get("physical_control_supported", True) is not False
|
|
assert action["domain"] in {"seat", "climate", "roof", "ambient_light"}
|
|
|
|
assert "door.set_vehicle_lock" not in {
|
|
action["capability"] for action in actions
|
|
}
|
|
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_viewing_draft_shade_close_then_100_percent_is_an_atomic_dry_run_chain(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
conversation_id = "conv-viewing-shade-revision-chain"
|
|
built = _build_viewing_mode(vehicle_service, conversation_id)
|
|
assert built["status"] == "succeeded"
|
|
assert built["mode_profile_draft"]["operation_count"] == 6
|
|
|
|
def semantic_actions(result: dict[str, object]) -> dict[tuple[str, str, str], object]:
|
|
return {
|
|
(
|
|
str(action["domain"]),
|
|
str(action["capability"]),
|
|
str(
|
|
action["target"].get("instance")
|
|
or action["target"].get("zone")
|
|
),
|
|
): action["parameters"]
|
|
for action in _intent_actions(result)
|
|
}
|
|
|
|
with patch.object(
|
|
vehicle_service.hardware_execution,
|
|
"reserve",
|
|
side_effect=AssertionError("viewing draft revision reached hardware lease"),
|
|
):
|
|
closed = vehicle_service.preview_native_can(
|
|
"把观影模式的遮阳帘变成关闭状态。",
|
|
provider="local",
|
|
conversation_id=conversation_id,
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
mode_profile_draft_context=_draft_reference(built),
|
|
)
|
|
opened = vehicle_service.preview_native_can(
|
|
"把观影模式的遮阳帘变成100%",
|
|
provider="local",
|
|
conversation_id=conversation_id,
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
mode_profile_draft_context=_draft_reference(closed),
|
|
)
|
|
|
|
built_actions = semantic_actions(built)
|
|
closed_actions = semantic_actions(closed)
|
|
opened_actions = semantic_actions(opened)
|
|
shade_key = ("roof", "roof.set_shade_position", "vehicle")
|
|
|
|
assert closed["status"] == opened["status"] == "succeeded"
|
|
assert closed["execution_mode"] == opened["execution_mode"] == "dry_run"
|
|
assert closed["hardware_execution_allowed"] is False
|
|
assert opened["hardware_execution_allowed"] is False
|
|
assert closed["orchestration_route"] == "mode_profile_create_preview"
|
|
assert opened["orchestration_route"] == "mode_profile_create_preview"
|
|
assert len(built_actions) == len(closed_actions) == len(opened_actions) == 6
|
|
assert closed_actions[shade_key] == {"position_pct": 0}
|
|
assert opened_actions[shade_key] == {"position_pct": 100}
|
|
assert {key: value for key, value in closed_actions.items() if key != shade_key} == {
|
|
key: value for key, value in built_actions.items() if key != shade_key
|
|
}
|
|
assert {key: value for key, value in opened_actions.items() if key != shade_key} == {
|
|
key: value for key, value in built_actions.items() if key != shade_key
|
|
}
|
|
|
|
closed_draft = closed["mode_profile_draft"]
|
|
opened_draft = opened["mode_profile_draft"]
|
|
assert closed_draft["draft_revision"] == 2
|
|
assert closed_draft["requested_operation_count"] == 1
|
|
assert closed_draft["changed_operation_count"] == 0
|
|
assert opened_draft["draft_revision"] == 3
|
|
assert opened_draft["revises_source_request_id"] == closed_draft[
|
|
"source_request_id"
|
|
]
|
|
assert opened_draft["requested_operation_count"] == 1
|
|
assert opened_draft["changed_operation_count"] == 1
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("change", "expected_position"),
|
|
(
|
|
("遮阳帘调整为1%", 1),
|
|
("遮阳帘调为20%", 20),
|
|
("遮阳帘改为30%", 30),
|
|
("遮阳帘改成40%", 40),
|
|
("遮阳帘变为50%", 50),
|
|
("遮阳帘变成99%", 99),
|
|
("遮阳帘换成100%", 100),
|
|
("遮阳帘设置为0%", 0),
|
|
("遮阳帘变成打开状态", 100),
|
|
("遮阳帘变成半开状态", 50),
|
|
),
|
|
)
|
|
def test_viewing_draft_accepts_supported_shade_absolute_expressions(
|
|
vehicle_service: SeatDemoService,
|
|
change: str,
|
|
expected_position: int,
|
|
) -> None:
|
|
conversation_id = f"conv-viewing-shade-alias-{expected_position}-{len(change)}"
|
|
built = _build_viewing_mode(vehicle_service, conversation_id)
|
|
|
|
with patch.object(
|
|
vehicle_service.hardware_execution,
|
|
"reserve",
|
|
side_effect=AssertionError("shade draft alias reached hardware lease"),
|
|
):
|
|
revised = vehicle_service.preview_native_can(
|
|
f"把观影模式的{change}",
|
|
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"] == 6
|
|
assert next(
|
|
action["parameters"]["position_pct"]
|
|
for action in actions
|
|
if action["capability"] == "roof.set_shade_position"
|
|
) == expected_position
|
|
|
|
|
|
def test_viewing_draft_updates_percent_integer_and_state_in_one_atomic_turn(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
conversation_id = "conv-viewing-multi-parameter-revision"
|
|
built = _build_viewing_mode(vehicle_service, conversation_id)
|
|
|
|
with patch.object(
|
|
vehicle_service.hardware_execution,
|
|
"reserve",
|
|
side_effect=AssertionError("multi-parameter draft update reached hardware"),
|
|
):
|
|
revised = vehicle_service.preview_native_can(
|
|
"把观影模式的遮阳帘变成100%,空调温度变成25度,室内灯变成打开状态",
|
|
provider="local",
|
|
conversation_id=conversation_id,
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
mode_profile_draft_context=_draft_reference(built),
|
|
)
|
|
|
|
by_capability = {
|
|
str(action["capability"]): action["parameters"]
|
|
for action in _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"] == 6
|
|
assert revised["mode_profile_draft"]["requested_operation_count"] == 3
|
|
assert revised["mode_profile_draft"]["changed_operation_count"] == 3
|
|
assert by_capability["roof.set_shade_position"] == {"position_pct": 100}
|
|
assert by_capability["climate.set_temperature"] == {"temperature_c": 25}
|
|
assert by_capability["interior_light.set_indoor_power"] == {"state": "on"}
|
|
assert by_capability["ambient_light.set_mode"] == {"mode": "cinema"}
|
|
assert by_capability["climate.set_fan_level"] == {"level": 2}
|
|
assert by_capability["climate.set_cycle_mode"] == {"mode": "internal"}
|
|
|
|
|
|
@pytest.mark.parametrize("position", (-1, 101))
|
|
def test_viewing_draft_rejects_out_of_range_shade_without_partial_evidence(
|
|
vehicle_service: SeatDemoService,
|
|
position: int,
|
|
) -> None:
|
|
conversation_id = f"conv-viewing-invalid-shade-{abs(position)}"
|
|
built = _build_viewing_mode(vehicle_service, conversation_id)
|
|
|
|
with patch.object(
|
|
vehicle_service.hardware_execution,
|
|
"reserve",
|
|
side_effect=AssertionError("invalid shade draft update reached hardware"),
|
|
):
|
|
rejected = vehicle_service.preview_native_can(
|
|
f"把观影模式的遮阳帘变成{position}%",
|
|
provider="local",
|
|
conversation_id=conversation_id,
|
|
execution_mode="hardware",
|
|
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["orchestration_route"] == "mode_profile_create_preview"
|
|
assert rejected.get("mode_profile_draft") is None
|
|
assert rejected["function_configs"] == {}
|
|
assert rejected["command_plans"] == {}
|
|
assert rejected["mode_creation_failure"]["code"] == (
|
|
"MODE_PROFILE_DRAFT_UPDATE_REJECTED"
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"message",
|
|
(
|
|
"把观影模式的遮阳帘变成100%吗",
|
|
"把观影模式的遮阳帘调整为50%好吗?",
|
|
"把观影模式的遮阳帘变成关闭状态吗。",
|
|
"把观影模式的遮阳帘变成100%合适么",
|
|
"观影模式里的遮阳帘应该变成多少呢",
|
|
),
|
|
)
|
|
def test_viewing_draft_shade_question_never_updates_or_reaches_hardware(
|
|
vehicle_service: SeatDemoService,
|
|
message: str,
|
|
) -> None:
|
|
conversation_id = "conv-viewing-shade-question"
|
|
built = _build_viewing_mode(vehicle_service, conversation_id)
|
|
reference = _draft_reference(built)
|
|
|
|
assert (
|
|
parse_mode_profile_update(message, expected_mode_name=reference["name"])
|
|
is None
|
|
)
|
|
assert is_mode_profile_query(message, expected_mode_name=reference["name"])
|
|
with patch.object(
|
|
vehicle_service.hardware_execution,
|
|
"reserve",
|
|
side_effect=AssertionError("shade question reached hardware lease"),
|
|
):
|
|
result = vehicle_service.preview_native_can(
|
|
message,
|
|
provider="local",
|
|
conversation_id=conversation_id,
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
mode_profile_draft_context=reference,
|
|
)
|
|
|
|
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.get("mode_profile_draft") is None
|
|
assert result["function_configs"] == {}
|
|
assert result["command_plans"] == {}
|
|
|
|
|
|
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",
|
|
(
|
|
"空调出风模式设置为吹面和吹脚,主驾空调温度设置为24度",
|
|
"空调出风模式设置为吹面和吹脚;副驾空调温度设置为24度",
|
|
"空调出风模式设置为吹面和吹脚,空调设置为自动控制",
|
|
"空调出风模式设置为吹面和吹脚,主驾座椅按摩模式设置为放松",
|
|
"主驾座椅按摩模式设置为放松,按摩强度设置为中等",
|
|
"把空调出风模式的出风方向设置为吹面和吹脚",
|
|
"调整主驾座椅按摩模式的强度为中等",
|
|
"副驾座椅零重力模式的状态设为开启",
|
|
"空调出风模式有哪些选项?",
|
|
"主驾座椅按摩模式是什么?",
|
|
),
|
|
)
|
|
def test_device_property_mode_commands_and_questions_are_not_saved_mode_queries(
|
|
message: str,
|
|
) -> None:
|
|
assert parse_mode_profile_detail_query(message) is None
|
|
assert not is_mode_profile_query(message)
|
|
assert parse_mode_profile_update(message) is None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"message",
|
|
(
|
|
"空调出风模式设置为吹面和吹脚,副驾空调温度设置为24度",
|
|
"主驾座椅按摩模式设置为放松,氛围灯亮度设置为3档",
|
|
),
|
|
)
|
|
def test_device_property_mode_combinations_continue_to_execute_as_vehicle_control(
|
|
vehicle_service: SeatDemoService,
|
|
message: str,
|
|
) -> None:
|
|
result = vehicle_service.process(
|
|
message,
|
|
provider="local",
|
|
conversation_id="conv-device-property-mode-control",
|
|
execution_mode="dry_run",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
assert result["status"] == "succeeded"
|
|
assert result.get("orchestration_route") != "mode_profile_query"
|
|
assert not SeatDemoService.is_mode_profile_configuration_request(message)
|
|
|
|
|
|
def test_quoted_device_property_name_can_explicitly_address_a_saved_mode() -> None:
|
|
assert parse_mode_profile_detail_query("查看“空调出风模式”的配置") == (
|
|
"空调出风模式"
|
|
)
|
|
assert parse_mode_profile_update(
|
|
"把“空调出风模式”的温度调整到25度"
|
|
) == ("空调出风模式", "温度调整到25度")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"message",
|
|
(
|
|
"办公模式的设备?",
|
|
"办公模式配置?",
|
|
"办公模式下的动作?",
|
|
),
|
|
)
|
|
def test_terminal_question_mark_keeps_short_saved_mode_detail_queries_read_only(
|
|
message: str,
|
|
) -> None:
|
|
assert parse_mode_profile_detail_query(message) == "办公模式"
|
|
assert is_mode_profile_query(message)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"message",
|
|
(
|
|
"办公模式的设备。",
|
|
"办公模式配置。",
|
|
"空调出风模式设置为吹面和吹脚,温度设置为24度",
|
|
),
|
|
)
|
|
def test_plain_mode_nouns_without_a_read_cue_are_not_detail_queries(
|
|
message: str,
|
|
) -> None:
|
|
assert parse_mode_profile_detail_query(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,
|
|
) -> None:
|
|
with patch.object(
|
|
vehicle_service.openai,
|
|
"interpret",
|
|
side_effect=AssertionError("the embedded Codex plan must not invoke legacy GPT"),
|
|
):
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个午休模式",
|
|
provider="local",
|
|
conversation_id="conv-codex-scene-composition",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=LUNCH_BREAK_SCENE_PLAN,
|
|
)
|
|
|
|
draft = result["mode_profile_draft"]
|
|
assert result["status"] == "succeeded"
|
|
assert result["execution_mode"] == "dry_run"
|
|
assert result["hardware_execution_allowed"] is False
|
|
assert result["provider_used"] == "local_rules+codex_app_server"
|
|
assert result["orchestration_route"] == "mode_profile_create_preview"
|
|
assert draft["name"] == "午休模式"
|
|
assert draft["operation_count"] == 5
|
|
assert draft["generation_source"] == "codex_scene_composition"
|
|
assert "existing_mode_id" not in draft
|
|
assert {action["capability"] for action in _intent_actions(result)} == {
|
|
"seat.set_backrest_recline",
|
|
"climate.set_temperature",
|
|
"climate.set_fan_level",
|
|
"roof.set_shade_position",
|
|
"ambient_light.set_power",
|
|
}
|
|
assert not list((tmp_path / "user-modes").rglob("*.yaml"))
|
|
|
|
trial = vehicle_service.trial_mode_profile_draft(
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
assert trial["status"] == "succeeded"
|
|
assert trial["execution_mode"] == "dry_run"
|
|
assert trial["hardware_execution_allowed"] is False
|
|
assert trial["orchestration_route"] == "mode_profile_draft_trial"
|
|
assert trial["trial_source"] == "server_validated_preview_artifact"
|
|
assert _intent_actions(trial) == _intent_actions(result)
|
|
|
|
with pytest.raises(ValueError, match="名称不匹配"):
|
|
vehicle_service.trial_mode_profile_draft(
|
|
"另一个模式",
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
|
|
result_path = vehicle_service.artifact_root / str(draft["source_request_id"]) / "result.json"
|
|
tampered = json.loads(result_path.read_text(encoding="utf-8"))
|
|
tampered["mode_profile_draft"]["source_request_id"] = "req-another-draft"
|
|
result_path.write_text(json.dumps(tampered, ensure_ascii=False), encoding="utf-8")
|
|
with pytest.raises(ValueError, match="来源请求编号不匹配"):
|
|
vehicle_service.trial_mode_profile_draft(
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
|
|
|
|
def test_name_only_scene_label_does_not_leak_device_words_into_local_parser(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
external_cycle_plan = {
|
|
"detected_domains": ["climate"],
|
|
"requests": [
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_cycle_mode",
|
|
"vehicle",
|
|
{"mode": "external"},
|
|
)
|
|
],
|
|
"ambiguities": [],
|
|
"decision_summary": ["使用外循环通风。"],
|
|
"confidence": 0.96,
|
|
}
|
|
|
|
with patch.object(
|
|
vehicle_service.hardware_execution,
|
|
"reserve",
|
|
side_effect=AssertionError("name-only scene reached hardware lease"),
|
|
):
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个外循环通风模式",
|
|
provider="local",
|
|
conversation_id="conv-name-only-label-is-not-an-action",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=external_cycle_plan,
|
|
)
|
|
|
|
actions = _intent_actions(result)
|
|
assert result["status"] == "succeeded"
|
|
assert result["execution_mode"] == "dry_run"
|
|
assert result["hardware_execution_allowed"] is False
|
|
assert result["provider_used"] == "local_rules+codex_app_server"
|
|
assert result["orchestration_route"] == "mode_profile_create_preview"
|
|
assert result["mode_profile_draft"]["name"] == "外循环通风模式"
|
|
assert result["mode_profile_draft"]["operation_count"] == 1
|
|
assert result["mode_profile_draft"]["generation_source"] == (
|
|
"codex_scene_composition"
|
|
)
|
|
assert not result["intent_ir"]["spec"]["ambiguities"]
|
|
assert [
|
|
(
|
|
action["domain"],
|
|
action["capability"],
|
|
action["target"],
|
|
action["parameters"],
|
|
)
|
|
for action in actions
|
|
] == [
|
|
(
|
|
"climate",
|
|
"climate.set_cycle_mode",
|
|
{"device": "climate", "zone": "vehicle"},
|
|
{"mode": "external"},
|
|
)
|
|
]
|
|
|
|
|
|
def test_codex_scene_composition_still_rejects_capabilities_outside_the_registry(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
invalid_plan = {
|
|
**LUNCH_BREAK_SCENE_PLAN,
|
|
"requests": [
|
|
_scene_request(
|
|
"flight",
|
|
"vehicle.fly",
|
|
"vehicle",
|
|
{"altitude_m": 100},
|
|
)
|
|
],
|
|
}
|
|
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个午休模式",
|
|
provider="local",
|
|
conversation_id="conv-codex-scene-unknown-capability",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=invalid_plan,
|
|
)
|
|
|
|
assert result["status"] != "succeeded"
|
|
assert result.get("mode_profile_draft") is None
|
|
assert not _intent_actions(result)
|
|
|
|
|
|
def test_codex_scene_composition_rejects_out_of_range_parameters_atomically(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
invalid_plan = {
|
|
**LUNCH_BREAK_SCENE_PLAN,
|
|
"requests": [
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"vehicle",
|
|
{"temperature_c": 99},
|
|
),
|
|
_scene_request(
|
|
"ambient_light",
|
|
"ambient_light.set_power",
|
|
"vehicle",
|
|
{"state": "off"},
|
|
),
|
|
],
|
|
}
|
|
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个午休模式",
|
|
provider="local",
|
|
conversation_id="conv-codex-scene-range-rejected",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=invalid_plan,
|
|
)
|
|
|
|
assert result["status"] != "succeeded"
|
|
assert result.get("mode_profile_draft") is None
|
|
assert not _intent_actions(result)
|
|
assert any("32" in str(issue.get("message") or "") for issue in result["validation"])
|
|
|
|
|
|
def test_codex_scene_rejects_fan_zero_before_native_compile_and_clears_all_evidence(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
invalid_plan = {
|
|
**LUNCH_BREAK_SCENE_PLAN,
|
|
"detected_domains": ["climate", "ambient_light"],
|
|
"requests": [
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_fan_level",
|
|
"vehicle",
|
|
{"level": 0},
|
|
),
|
|
_scene_request(
|
|
"ambient_light",
|
|
"ambient_light.set_power",
|
|
"vehicle",
|
|
{"state": "off"},
|
|
),
|
|
],
|
|
}
|
|
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个静享模式",
|
|
provider="local",
|
|
conversation_id="conv-codex-scene-fan-zero",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=invalid_plan,
|
|
)
|
|
|
|
assert result["status"] != "succeeded"
|
|
assert result["execution_mode"] == "dry_run"
|
|
assert result["hardware_execution_allowed"] is False
|
|
assert result.get("mode_profile_draft") is None
|
|
assert not _intent_actions(result)
|
|
assert result["function_config"] is None
|
|
assert result["function_configs"] == {}
|
|
assert result["command_plan"] is None
|
|
assert result["command_plans"] == {}
|
|
assert result["scene_config"] is None
|
|
assert result["execution_report"] is None
|
|
assert not result["deferred_requests"]
|
|
assert any("1-7" in str(issue.get("message") or "") for issue in result["validation"])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("case_id", "invalid_request"),
|
|
[
|
|
(
|
|
"wrong-type",
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"vehicle",
|
|
{"temperature_c": "温暖"},
|
|
),
|
|
),
|
|
(
|
|
"unknown-parameter",
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"vehicle",
|
|
{"temperature_c": 24, "invented": 1},
|
|
),
|
|
),
|
|
(
|
|
"missing-required",
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"vehicle",
|
|
{},
|
|
),
|
|
),
|
|
(
|
|
"invalid-target",
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"trunk",
|
|
{"temperature_c": 24},
|
|
),
|
|
),
|
|
(
|
|
"relative-seat-delta",
|
|
_scene_request(
|
|
"seat",
|
|
"seat.set_backrest_recline",
|
|
"passenger",
|
|
{"angle_delta_deg": 3},
|
|
),
|
|
),
|
|
(
|
|
"native-missing-required-axis",
|
|
_scene_request(
|
|
"outlet",
|
|
"outlet.set_position_percent",
|
|
"driver_left",
|
|
{"horizontal_pct": 50},
|
|
),
|
|
),
|
|
(
|
|
"conflicting-seat-representations",
|
|
_scene_request(
|
|
"seat",
|
|
"seat.set_backrest_recline",
|
|
"passenger",
|
|
{"position_pct": 0, "angle_deg": 163},
|
|
),
|
|
),
|
|
],
|
|
)
|
|
def test_codex_scene_adapter_rejects_one_invalid_action_without_partial_draft(
|
|
vehicle_service: SeatDemoService,
|
|
case_id: str,
|
|
invalid_request: dict[str, object],
|
|
) -> None:
|
|
plan = {
|
|
**LUNCH_BREAK_SCENE_PLAN,
|
|
"requests": [
|
|
invalid_request,
|
|
_scene_request(
|
|
"ambient_light",
|
|
"ambient_light.set_power",
|
|
"vehicle",
|
|
{"state": "off"},
|
|
),
|
|
],
|
|
}
|
|
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个午休模式",
|
|
provider="local",
|
|
conversation_id=f"conv-codex-scene-adapter-{case_id}",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=plan,
|
|
)
|
|
|
|
assert result["status"] != "succeeded"
|
|
assert result.get("mode_profile_draft") is None
|
|
assert not _intent_actions(result)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"native_request",
|
|
[
|
|
_scene_request(
|
|
"interior_light",
|
|
"interior_light.set_reading_power",
|
|
"all",
|
|
{"state": "off"},
|
|
),
|
|
_scene_request(
|
|
"outlet",
|
|
"outlet.set_switch_state",
|
|
"driver_left",
|
|
{"state": "close"},
|
|
),
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_control_mode",
|
|
"vehicle",
|
|
{"mode": "auto"},
|
|
),
|
|
],
|
|
)
|
|
def test_codex_scene_keeps_valid_native_can_actions_in_the_complete_draft(
|
|
vehicle_service: SeatDemoService,
|
|
native_request: dict[str, object],
|
|
) -> None:
|
|
plan = {
|
|
**LUNCH_BREAK_SCENE_PLAN,
|
|
"requests": [
|
|
native_request,
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"vehicle",
|
|
{"temperature_c": 24},
|
|
),
|
|
],
|
|
}
|
|
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个静享模式",
|
|
provider="local",
|
|
conversation_id=(
|
|
"conv-codex-scene-native-"
|
|
+ str(native_request["capability"]).replace(".", "-").replace("_", "-")
|
|
),
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=plan,
|
|
)
|
|
|
|
draft = result["mode_profile_draft"]
|
|
assert result["status"] == "succeeded"
|
|
assert draft["operation_count"] == 2
|
|
assert {action["capability"] for action in _intent_actions(result)} == {
|
|
str(native_request["capability"]),
|
|
"climate.set_temperature",
|
|
}
|
|
assert not result["deferred_requests"]
|
|
|
|
|
|
def test_codex_scene_promotes_rear_temperature_to_verified_native_can(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
plan = {
|
|
**LUNCH_BREAK_SCENE_PLAN,
|
|
"detected_domains": ["climate"],
|
|
"requests": [
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"rear",
|
|
{"temperature_c": 24},
|
|
)
|
|
],
|
|
}
|
|
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个后排舒适模式",
|
|
provider="local",
|
|
conversation_id="conv-codex-scene-rear-climate",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=plan,
|
|
)
|
|
|
|
draft = result["mode_profile_draft"]
|
|
assert result["status"] == "succeeded"
|
|
assert result["execution_mode"] == "dry_run"
|
|
assert result["hardware_execution_allowed"] is False
|
|
assert draft["operation_count"] == 1
|
|
assert result["deferred_requests"] == []
|
|
assert set(result["function_configs"]) == {"native_climate"}
|
|
actions = _intent_actions(result)
|
|
assert [(action["capability"], action["target"]["zone"]) for action in actions] == [
|
|
("climate.set_temperature", "rear")
|
|
]
|
|
|
|
|
|
def test_codex_composed_scene_can_be_saved_and_replayed_without_reinterpretation(
|
|
vehicle_service: SeatDemoService,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
built = vehicle_service.preview_native_can(
|
|
"构建一个午休模式",
|
|
provider="local",
|
|
conversation_id="conv-codex-scene-save",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=LUNCH_BREAK_SCENE_PLAN,
|
|
)
|
|
draft = built["mode_profile_draft"]
|
|
|
|
saved = vehicle_service.create_mode_profile_card(
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
mode_id = str(saved["mode_profile"]["id"])
|
|
|
|
assert saved["status"] == "succeeded"
|
|
assert saved["mode_profile"]["name"] == "午休模式"
|
|
assert saved["mode_profile"]["operation_count"] == 5
|
|
assert list((tmp_path / "user-modes").rglob(f"{mode_id}.yaml"))
|
|
|
|
with patch.object(
|
|
vehicle_service,
|
|
"_interpret",
|
|
side_effect=AssertionError("saved scene must replay trusted command flow"),
|
|
):
|
|
replayed = vehicle_service.execute_mode_profile_card(
|
|
mode_id,
|
|
conversation_id="conv-codex-scene-replay",
|
|
execution_mode="dry_run",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
assert replayed["status"] == "succeeded"
|
|
assert replayed["provider_used"] == "saved_mode_command_flow"
|
|
assert {action["capability"] for action in _intent_actions(replayed)} == {
|
|
request["capability"] for request in LUNCH_BREAK_SCENE_PLAN["requests"]
|
|
}
|
|
|
|
|
|
def test_inferred_nap_draft_can_be_confirmed_saved_and_replayed_without_reinterpretation(
|
|
vehicle_service: SeatDemoService,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
built = _build_nap_mode(vehicle_service, "conv-nap-build-save")
|
|
draft = built["mode_profile_draft"]
|
|
assert isinstance(draft, dict)
|
|
|
|
saved = vehicle_service.save_mode_profile_card(
|
|
"nap",
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
|
|
profile = saved["mode_profile"]
|
|
assert saved["status"] == "succeeded"
|
|
assert profile["id"] == "nap"
|
|
assert profile["name"] == "小憩模式"
|
|
assert profile["origin"] == "customized"
|
|
assert profile["operation_count"] == 9
|
|
assert (
|
|
tmp_path / "user-modes" / "vehicles" / "denza_n7" / "user" / "nap.yaml"
|
|
).is_file()
|
|
|
|
with patch.object(
|
|
vehicle_service,
|
|
"_interpret",
|
|
side_effect=AssertionError("saved mode must replay its trusted command flow"),
|
|
):
|
|
executed = vehicle_service.execute_mode_profile_card(
|
|
"nap",
|
|
conversation_id="conv-nap-saved-execution",
|
|
execution_mode="dry_run",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
assert executed["status"] == "succeeded"
|
|
assert executed["provider_used"] == "saved_mode_command_flow"
|
|
assert executed["orchestration_route"] == "saved_mode_command_flow"
|
|
assert executed["execution_report"]["spec"]["mode"] == "dry_run"
|
|
replayed_capabilities = {
|
|
operation["capability"]
|
|
for config in executed["function_configs"].values()
|
|
for operation in config["spec"]["operations"]
|
|
}
|
|
assert replayed_capabilities == EXPECTED_NAP_CAPABILITIES
|
|
|
|
|
|
def test_assistant_name_only_nap_request_stays_useful_without_codex(
|
|
vehicle_service: SeatDemoService,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
codex_client = MagicMock()
|
|
codex_client.available = False
|
|
assistant = CodexAssistantService(codex_client)
|
|
events: list[dict[str, object]] = []
|
|
|
|
def execute_control(
|
|
instruction: str,
|
|
progress: object,
|
|
) -> dict[str, object]:
|
|
callback = progress if callable(progress) else None
|
|
return vehicle_service.preview_native_can(
|
|
instruction,
|
|
provider="local",
|
|
conversation_id="conv-nap-assistant-route",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
on_commands_validated=callback,
|
|
)
|
|
|
|
response = assistant.stream(
|
|
NAP_REQUEST,
|
|
conversation_id="conv-nap-assistant-route",
|
|
principal="mode-regression-test",
|
|
history=[],
|
|
emit=lambda event: events.append(event) or True,
|
|
execute_control=execute_control,
|
|
capability_snapshot=AssistantCapabilityCatalog(vehicle_service).snapshot(),
|
|
)
|
|
|
|
assert response["route"] == "vehicle_control"
|
|
assert response["success"] is True
|
|
assert response["result"]["mode_profile_draft"]["operation_count"] == 9
|
|
assert response["result"]["mode_profile_draft"]["existing_mode_id"] == "nap"
|
|
assert (
|
|
response["result"]["mode_profile_draft"]["generation_source"]
|
|
== "registered_mode_profile"
|
|
)
|
|
assert "小憩模式" in response["answer"]
|
|
assert "预览" in response["answer"]
|
|
assert "更新现有模式" in response["answer"]
|
|
assert "功能模式未生成" not in response["answer"]
|
|
assert INTERNAL_SCENE_PLACEHOLDER not in json.dumps(
|
|
events,
|
|
ensure_ascii=False,
|
|
default=str,
|
|
)
|
|
assert not list((tmp_path / "user-modes").rglob("*.yaml"))
|
|
codex_client.start_turn.assert_not_called()
|
|
|
|
|
|
def test_existing_mode_draft_is_bound_to_its_update_endpoint(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
built = _build_nap_mode(vehicle_service, "conv-nap-update-binding")
|
|
draft = built["mode_profile_draft"]
|
|
assert isinstance(draft, dict)
|
|
|
|
with pytest.raises(ValueError):
|
|
vehicle_service.create_mode_profile_card(
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
|
|
with pytest.raises(ValueError):
|
|
vehicle_service.save_mode_profile_card(
|
|
"office",
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
|
|
|
|
def test_concurrent_existing_mode_saves_use_revision_compare_and_swap(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
drafts = []
|
|
for index, temperature in enumerate((24, 25), start=1):
|
|
result = vehicle_service.preview_native_can(
|
|
f"创建一个小憩模式,空调调整到{temperature}度",
|
|
provider="local",
|
|
conversation_id=f"conv-mode-cas-update-{index}",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
drafts.append(result["mode_profile_draft"])
|
|
|
|
assert drafts[0]["existing_mode_revision"] == drafts[1]["existing_mode_revision"]
|
|
barrier = Barrier(2)
|
|
original_save = vehicle_service.mode_profiles.save_mode
|
|
|
|
def synchronized_save(*args: object, **kwargs: object) -> object:
|
|
barrier.wait(timeout=5)
|
|
return original_save(*args, **kwargs)
|
|
|
|
def persist(draft: dict[str, object]) -> tuple[str, str]:
|
|
try:
|
|
result = vehicle_service.save_mode_profile_card(
|
|
"nap",
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
return "saved", str(result["mode_profile"]["id"])
|
|
except ValueError as exc:
|
|
return "rejected", str(exc)
|
|
|
|
with patch.object(
|
|
vehicle_service.mode_profiles,
|
|
"save_mode",
|
|
side_effect=synchronized_save,
|
|
):
|
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
outcomes = list(executor.map(persist, drafts))
|
|
|
|
assert [status for status, _detail in outcomes].count("saved") == 1
|
|
assert [status for status, _detail in outcomes].count("rejected") == 1
|
|
assert any("已被其他会话更新" in detail for _status, detail in outcomes)
|
|
|
|
|
|
def test_concurrent_same_name_creates_are_create_if_absent(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
drafts = []
|
|
for index, temperature in enumerate((24, 25), start=1):
|
|
plan = {
|
|
**LUNCH_BREAK_SCENE_PLAN,
|
|
"requests": [
|
|
_scene_request(
|
|
"climate",
|
|
"climate.set_temperature",
|
|
"vehicle",
|
|
{"temperature_c": temperature},
|
|
)
|
|
],
|
|
}
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个午休模式",
|
|
provider="local",
|
|
conversation_id=f"conv-mode-cas-create-{index}",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
assistant_plan=plan,
|
|
)
|
|
drafts.append(result["mode_profile_draft"])
|
|
|
|
barrier = Barrier(2)
|
|
original_save = vehicle_service.mode_profiles.save_mode
|
|
|
|
def synchronized_save(*args: object, **kwargs: object) -> object:
|
|
barrier.wait(timeout=5)
|
|
return original_save(*args, **kwargs)
|
|
|
|
def persist(draft: dict[str, object]) -> tuple[str, str]:
|
|
try:
|
|
result = vehicle_service.create_mode_profile_card(
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
return "saved", str(result["mode_profile"]["id"])
|
|
except ValueError as exc:
|
|
return "rejected", str(exc)
|
|
|
|
with patch.object(
|
|
vehicle_service.mode_profiles,
|
|
"save_mode",
|
|
side_effect=synchronized_save,
|
|
):
|
|
with ThreadPoolExecutor(max_workers=2) as executor:
|
|
outcomes = list(executor.map(persist, drafts))
|
|
|
|
assert [status for status, _detail in outcomes].count("saved") == 1
|
|
assert [status for status, _detail in outcomes].count("rejected") == 1
|
|
assert any("已由其他会话创建" in detail for _status, detail in outcomes)
|
|
|
|
|
|
def test_name_only_mode_does_not_bypass_exact_resolution_with_substring_match(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个我的小憩模式",
|
|
provider="local",
|
|
conversation_id="conv-mode-substring-rejected",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
assert result["status"] == "needs_confirmation"
|
|
assert result["matched_modes"] == []
|
|
assert result.get("mode_profile_draft") is None
|
|
assert not _intent_actions(result)
|
|
|
|
|
|
def test_name_only_mode_does_not_combine_profiles_when_exact_name_is_ambiguous(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
nap = vehicle_service.mode_profiles.get_profile("nap", "denza_n7")
|
|
vehicle_service.mode_profiles.save_mode(
|
|
"午休模式",
|
|
list(nap.operations),
|
|
mode_id="duplicate-nap-name",
|
|
aliases=["小憩模式"],
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
result = vehicle_service.preview_native_can(
|
|
NAP_REQUEST,
|
|
provider="local",
|
|
conversation_id="conv-mode-ambiguous-rejected",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
assert result["status"] == "needs_confirmation"
|
|
assert result["matched_modes"] == []
|
|
assert result.get("mode_profile_draft") is None
|
|
assert not _intent_actions(result)
|
|
|
|
|
|
def test_explicit_actions_for_existing_name_are_bound_to_that_mode_update(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
result = vehicle_service.preview_native_can(
|
|
"创建一个小憩模式,空调调整到24度",
|
|
provider="local",
|
|
conversation_id="conv-existing-mode-explicit-action",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
draft = result["mode_profile_draft"]
|
|
assert result["status"] == "succeeded"
|
|
assert draft["existing_mode_id"] == "nap"
|
|
assert draft["generation_source"] == "explicit_scene_requirements"
|
|
assert draft["operation_count"] == 1
|
|
answer = CodexAssistantService._mode_profile_preview_answer(result, True)
|
|
assert "按你提供的设置" in answer
|
|
assert "载入预览" not in answer
|
|
|
|
saved = vehicle_service.save_mode_profile_card(
|
|
"nap",
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
assert saved["status"] == "succeeded"
|
|
assert saved["mode_profile"]["id"] == "nap"
|
|
assert saved["mode_profile"]["operation_count"] == 1
|
|
|
|
|
|
def test_new_mode_draft_cannot_overwrite_an_unrelated_existing_mode(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
result = vehicle_service.preview_native_can(
|
|
"创建一个清凉测试模式,空调调整到24度",
|
|
provider="local",
|
|
conversation_id="conv-new-mode-update-rejected",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
draft = result["mode_profile_draft"]
|
|
assert result["status"] == "succeeded"
|
|
assert "existing_mode_id" not in draft
|
|
|
|
with pytest.raises(ValueError, match="新功能模式预览不能"):
|
|
vehicle_service.save_mode_profile_card(
|
|
"nap",
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
|
|
|
|
def test_exact_mode_resolver_supports_aliases_but_rejects_substrings_and_ambiguity(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
registry = vehicle_service.mode_profiles
|
|
nap = registry.get_profile("nap", "denza_n7")
|
|
|
|
assert registry.resolve_exact_name("小憩模式", "denza_n7") == nap
|
|
assert registry.resolve_exact_name("小憩", "denza_n7") == nap
|
|
assert registry.resolve_exact_name("NAP", "denza_n7") == nap
|
|
assert registry.resolve_exact_name("我的小憩模式", "denza_n7") is None
|
|
assert registry.resolve_exact_name("小憩模式", "unsupported_vehicle") is None
|
|
|
|
registry.save_mode(
|
|
"午休模式",
|
|
list(nap.operations),
|
|
mode_id="duplicate-nap-alias",
|
|
aliases=["小憩"],
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
assert registry.resolve_exact_name("小憩", "denza_n7") is None
|
|
assert registry.resolve_exact_name("小憩模式", "denza_n7").id == "nap"
|
|
|
|
|
|
def test_existing_mode_alias_keeps_the_canonical_name_when_updated(
|
|
vehicle_service: SeatDemoService,
|
|
) -> None:
|
|
result = vehicle_service.preview_native_can(
|
|
"构建一个摇篮模式",
|
|
provider="local",
|
|
conversation_id="conv-mode-alias-canonical-name",
|
|
execution_mode="hardware",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
draft = result["mode_profile_draft"]
|
|
assert result["status"] == "succeeded"
|
|
assert draft["existing_mode_id"] == "dynamic_cradle"
|
|
assert draft["name"] == "动态摇篮模式"
|
|
|
|
saved = vehicle_service.save_mode_profile_card(
|
|
"dynamic_cradle",
|
|
str(draft["name"]),
|
|
str(draft["instruction"]),
|
|
str(draft["source_request_id"]),
|
|
str(draft["vehicle_id"]),
|
|
)
|
|
assert saved["mode_profile"]["name"] == "动态摇篮模式"
|
|
|
|
|
|
def test_internal_scene_builder_placeholder_is_never_returned_to_the_user() -> None:
|
|
result = {
|
|
"status": "needs_confirmation",
|
|
"mode_creation_failure": {
|
|
"code": "MODE_PROFILE_DRAFT_INCOMPLETE",
|
|
"reason": (
|
|
f"设置“{INTERNAL_SCENE_PLACEHOLDER}”未被识别为当前支持的座舱控制动作"
|
|
),
|
|
"next_step": "请按设备、动作和目标值重新说明",
|
|
"unresolved_clauses": [INTERNAL_SCENE_PLACEHOLDER],
|
|
},
|
|
"unresolved_clauses": [INTERNAL_SCENE_PLACEHOLDER],
|
|
}
|
|
|
|
answer = CodexAssistantService._mode_profile_preview_answer(result, False)
|
|
safe_result = CodexAssistantService._safe_vehicle_result(result)
|
|
|
|
assert INTERNAL_SCENE_PLACEHOLDER not in answer
|
|
assert INTERNAL_SCENE_PLACEHOLDER not in json.dumps(
|
|
safe_result,
|
|
ensure_ascii=False,
|
|
)
|
|
assert "内部" not in answer
|
|
assert "失败原因" in answer
|
|
assert "下一步" in answer
|
|
|
|
|
|
def test_model_cannot_create_a_mode_with_an_unregistered_capability(
|
|
vehicle_service: SeatDemoService,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
hallucinated_plan = {
|
|
"detected_domains": ["flight"],
|
|
"requests": [
|
|
{
|
|
"domain": "flight",
|
|
"capability": "vehicle.fly",
|
|
"target_scope": "vehicle",
|
|
"operation": "set",
|
|
"parameters": [{"name": "altitude_m", "value": 100}],
|
|
"reason": "模型臆造的飞行能力。",
|
|
}
|
|
],
|
|
"ambiguities": [],
|
|
"decision_summary": ["切换飞行模式。"],
|
|
"confidence": 0.99,
|
|
}
|
|
|
|
with patch.object(
|
|
vehicle_service.openai,
|
|
"interpret",
|
|
return_value=hallucinated_plan,
|
|
):
|
|
result = vehicle_service.process(
|
|
"构建一个飞行模式",
|
|
"openai",
|
|
"conv-hallucinated-mode",
|
|
execution_mode="dry_run",
|
|
vehicle_id="denza_n7",
|
|
)
|
|
|
|
assert result["status"] != "succeeded"
|
|
assert result.get("mode_profile_draft") is None
|
|
assert result.get("mode_profile") is None
|
|
assert not _intent_actions(result)
|
|
assert any(
|
|
issue["severity"] == "error"
|
|
for issue in result.get("validation", [])
|
|
)
|
|
assert not list((tmp_path / "user-modes").rglob("*.yaml"))
|