feat: 完善 N7 智能座舱交互与控制链路
This commit is contained in:
@@ -5,6 +5,7 @@ import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from cognitive_agent.application.codex_assistant import CodexAssistantService
|
||||
from cognitive_agent.application.domain_runtime import ConfiguredDomainRuntime
|
||||
from cognitive_agent.application.mode_profile import ModeProfileRegistry
|
||||
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
|
||||
@@ -791,6 +792,12 @@ operations:
|
||||
},
|
||||
},
|
||||
)
|
||||
saved_public_mode = next(
|
||||
mode
|
||||
for mode in self.service.mode_profiles.public_modes("denza_n7")
|
||||
if mode["id"] == "nap"
|
||||
)
|
||||
self.assertEqual(saved_public_mode["behavior"], "cyclic")
|
||||
|
||||
with patch.object(
|
||||
self.service,
|
||||
@@ -824,7 +831,7 @@ operations:
|
||||
}
|
||||
self.assertIn("roof.set_shade_motion", replayed_capabilities)
|
||||
|
||||
def test_build_named_mode_executes_and_persists_in_one_turn(self) -> None:
|
||||
def test_build_named_mode_returns_draft_before_explicit_save(self) -> None:
|
||||
mode_root = Path(self.temporary_directory.name) / "modes"
|
||||
self.service.mode_profiles = ModeProfileRegistry(mode_root)
|
||||
model_output = {
|
||||
@@ -864,9 +871,41 @@ operations:
|
||||
)
|
||||
|
||||
self.assertEqual(built["status"], "succeeded")
|
||||
self.assertEqual(built["mode_profile"]["name"], "K歌模式")
|
||||
self.assertIsNone(built["mode_profile"])
|
||||
self.assertEqual(
|
||||
built["orchestration_route"], "mode_profile_create_preview"
|
||||
)
|
||||
draft = built["mode_profile_draft"]
|
||||
self.assertEqual(draft["name"], "K歌模式")
|
||||
self.assertEqual(draft["operation_count"], 2)
|
||||
self.assertEqual(set(draft["domains"]), {"ambient_light", "climate"})
|
||||
self.assertEqual(built["mode_creation"]["source_status"], "succeeded")
|
||||
self.assertEqual(built["mode_creation"]["operation_count"], 2)
|
||||
self.assertNotIn(
|
||||
"K歌模式",
|
||||
{
|
||||
mode["name"]
|
||||
for mode in self.service.runtime_info()["modes"]
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
self.service.mode_profiles.match_text(
|
||||
"进入K歌模式", vehicle_id="denza_n7"
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
saved = self.service.create_mode_profile_card(
|
||||
draft["name"],
|
||||
draft["instruction"],
|
||||
draft["source_request_id"],
|
||||
draft["vehicle_id"],
|
||||
)
|
||||
|
||||
self.assertEqual(saved["status"], "succeeded")
|
||||
self.assertEqual(saved["mode_profile"]["name"], "K歌模式")
|
||||
self.assertEqual(saved["mode_profile"]["operation_count"], 2)
|
||||
self.assertTrue(saved["mode_profile"]["command_flow"]["plans"])
|
||||
self.assertIn(
|
||||
"K歌模式",
|
||||
{
|
||||
@@ -878,7 +917,7 @@ operations:
|
||||
"进入K歌模式", vehicle_id="denza_n7"
|
||||
)
|
||||
self.assertEqual([profile.name for profile in matched], ["K歌模式"])
|
||||
self.assertTrue(Path(built["mode_profile"]["source"]).is_file())
|
||||
self.assertTrue(Path(matched[0].source).is_file())
|
||||
|
||||
def test_build_named_mode_is_always_a_dry_run_configuration_workflow(self) -> None:
|
||||
mode_root = Path(self.temporary_directory.name) / "hardware-modes"
|
||||
@@ -916,7 +955,208 @@ operations:
|
||||
self.assertEqual(built["status"], "succeeded")
|
||||
self.assertEqual(built["execution_mode"], "dry_run")
|
||||
self.assertFalse(built["hardware_execution_allowed"])
|
||||
self.assertEqual(built["mode_profile"]["name"], "K歌模式")
|
||||
self.assertIsNone(built["mode_profile"])
|
||||
self.assertEqual(built["mode_profile_draft"]["name"], "K歌模式")
|
||||
self.assertEqual(
|
||||
built["orchestration_route"], "mode_profile_create_preview"
|
||||
)
|
||||
self.assertNotIn(
|
||||
"K歌模式",
|
||||
{
|
||||
mode["name"]
|
||||
for mode in self.service.runtime_info()["modes"]
|
||||
},
|
||||
)
|
||||
|
||||
def test_build_named_mode_uses_the_user_supplied_scene_requirements(self) -> None:
|
||||
mode_root = Path(self.temporary_directory.name) / "described-modes"
|
||||
self.service.mode_profiles = ModeProfileRegistry(mode_root)
|
||||
model_output = {
|
||||
"detected_domains": ["climate"],
|
||||
"requests": [
|
||||
{
|
||||
"domain": "climate",
|
||||
"capability": "climate.set_temperature",
|
||||
"target_scope": "vehicle",
|
||||
"operation": "set",
|
||||
"parameters": [{"name": "temperature_c", "value": 24}],
|
||||
"reason": "保持助眠温度。",
|
||||
}
|
||||
],
|
||||
"ambiguities": [],
|
||||
"decision_summary": ["已按用户描述构建助眠场景。"],
|
||||
"confidence": 0.95,
|
||||
}
|
||||
|
||||
with patch.object(
|
||||
self.service.openai,
|
||||
"interpret",
|
||||
return_value=model_output,
|
||||
) as interpret:
|
||||
built = self.service.process(
|
||||
"创建宝宝睡眠模式:营造安静的助眠氛围",
|
||||
"openai",
|
||||
"conv-build-described-mode",
|
||||
vehicle_id="denza_n7",
|
||||
)
|
||||
|
||||
self.assertEqual(built["status"], "succeeded")
|
||||
self.assertIsNone(built["mode_profile"])
|
||||
self.assertEqual(built["mode_profile_draft"]["name"], "宝宝睡眠模式")
|
||||
self.assertEqual(
|
||||
built["mode_profile_draft"]["instruction"], "营造安静的助眠氛围"
|
||||
)
|
||||
self.assertEqual(
|
||||
built["orchestration_route"], "mode_profile_create_preview"
|
||||
)
|
||||
self.assertEqual(interpret.call_args.args[0], "营造安静的助眠氛围")
|
||||
|
||||
def test_movie_mode_preview_and_confirmed_save_keep_all_nine_actions(
|
||||
self,
|
||||
) -> None:
|
||||
user_root = Path(self.temporary_directory.name) / "movie-mode-user"
|
||||
self.service.mode_profiles = ModeProfileRegistry(
|
||||
REPOSITORY_ROOT / "config" / "modes",
|
||||
user_root=user_root,
|
||||
)
|
||||
utterance = (
|
||||
"构建一个观影模式,遮阳帘关闭,车窗关闭,"
|
||||
"副驾座椅调整为零重力模式,灯光全部关闭"
|
||||
)
|
||||
|
||||
built = self.service.preview_native_can(
|
||||
utterance,
|
||||
provider="local",
|
||||
conversation_id="conv-build-movie-mode",
|
||||
vehicle_id="denza_n7",
|
||||
)
|
||||
|
||||
self.assertEqual(built["status"], "succeeded")
|
||||
self.assertEqual(
|
||||
built["orchestration_route"], "mode_profile_create_preview"
|
||||
)
|
||||
self.assertEqual(built["unresolved_clauses"], [])
|
||||
self.assertIsNone(built["mode_profile"])
|
||||
draft = built["mode_profile_draft"]
|
||||
self.assertEqual(draft["name"], "观影模式")
|
||||
self.assertEqual(draft["operation_count"], 9)
|
||||
self.assertEqual(
|
||||
set(draft["domains"]),
|
||||
{"seat", "roof", "window", "interior_light", "ambient_light"},
|
||||
)
|
||||
self.assertNotIn(
|
||||
"观影模式",
|
||||
{mode["name"] for mode in self.service.runtime_info()["modes"]},
|
||||
)
|
||||
|
||||
intent_actions = built["intent_ir"]["spec"]["actions"]
|
||||
capabilities = [action["capability"] for action in intent_actions]
|
||||
self.assertEqual(len(capabilities), 9)
|
||||
self.assertEqual(
|
||||
set(capabilities),
|
||||
{
|
||||
"seat.set_longitudinal_position",
|
||||
"seat.set_cushion_tilt",
|
||||
"seat.set_backrest_recline",
|
||||
"seat.set_legrest_height",
|
||||
"roof.set_shade_motion",
|
||||
"window.set_motion",
|
||||
"interior_light.set_indoor_power",
|
||||
"interior_light.set_reading_power",
|
||||
"ambient_light.set_power",
|
||||
},
|
||||
)
|
||||
window_action = next(
|
||||
action
|
||||
for action in intent_actions
|
||||
if action["capability"] == "window.set_motion"
|
||||
)
|
||||
self.assertEqual(window_action["target"]["zone"], "front_left")
|
||||
|
||||
saved = self.service.create_mode_profile_card(
|
||||
draft["name"],
|
||||
draft["instruction"],
|
||||
draft["source_request_id"],
|
||||
draft["vehicle_id"],
|
||||
)
|
||||
|
||||
self.assertEqual(saved["status"], "succeeded")
|
||||
profile = saved["mode_profile"]
|
||||
self.assertEqual(profile["name"], "观影模式")
|
||||
self.assertEqual(profile["origin"], "user")
|
||||
self.assertEqual(profile["operation_count"], 9)
|
||||
self.assertEqual(
|
||||
sum(
|
||||
len(config["spec"]["operations"])
|
||||
for config in profile["command_flow"]["function_configs"].values()
|
||||
),
|
||||
9,
|
||||
)
|
||||
self.assertEqual(
|
||||
set(profile["command_flow"]["plans"]),
|
||||
set(profile["command_flow"]["function_configs"]),
|
||||
)
|
||||
persisted = self.service.mode_profiles.get_profile(
|
||||
profile["id"], "denza_n7"
|
||||
)
|
||||
self.assertTrue(Path(persisted.source).is_file())
|
||||
|
||||
executed = self.service.execute_mode_profile_card(
|
||||
profile["id"],
|
||||
conversation_id="conv-execute-movie-mode",
|
||||
execution_mode="dry_run",
|
||||
vehicle_id="denza_n7",
|
||||
)
|
||||
self.assertEqual(executed["status"], "succeeded")
|
||||
self.assertEqual(executed["orchestration_route"], "saved_mode_command_flow")
|
||||
self.assertEqual(len(executed["execution_report"]["spec"]["results"]), 9)
|
||||
|
||||
def test_movie_mode_assistant_route_uses_real_builder_without_codex(self) -> None:
|
||||
user_root = Path(self.temporary_directory.name) / "assistant-movie-mode-user"
|
||||
self.service.mode_profiles = ModeProfileRegistry(
|
||||
REPOSITORY_ROOT / "config" / "modes",
|
||||
user_root=user_root,
|
||||
)
|
||||
utterance = (
|
||||
"构建一个观影模式,遮阳帘关闭,车窗关闭,"
|
||||
"副驾座椅调整为零重力模式,灯光全部关闭"
|
||||
)
|
||||
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 self.service.preview_native_can(
|
||||
instruction,
|
||||
provider="local",
|
||||
conversation_id="conv-assistant-movie-mode",
|
||||
execution_mode="hardware",
|
||||
vehicle_id="denza_n7",
|
||||
on_commands_validated=callback,
|
||||
)
|
||||
|
||||
result = assistant.stream(
|
||||
utterance,
|
||||
conversation_id="conv-assistant-movie-mode",
|
||||
principal="integration-test",
|
||||
history=[],
|
||||
emit=lambda event: events.append(event) or True,
|
||||
execute_control=execute_control,
|
||||
)
|
||||
|
||||
self.assertEqual(result["route"], "vehicle_control")
|
||||
self.assertTrue(result["success"])
|
||||
self.assertEqual(result["result"]["execution_mode"], "dry_run")
|
||||
self.assertEqual(result["result"]["mode_profile_draft"]["name"], "观影模式")
|
||||
self.assertEqual(result["result"]["mode_profile_draft"]["operation_count"], 9)
|
||||
self.assertEqual(len(result["result"]["actions"]), 9)
|
||||
self.assertEqual(events[0]["engine"], "n7web-mode-builder")
|
||||
self.assertEqual(events[-1]["route"], "vehicle_control")
|
||||
self.assertIn("观影模式", str(events[-1]["answer"]))
|
||||
self.assertFalse(any(user_root.glob("*.yaml")))
|
||||
codex_client.start_turn.assert_not_called()
|
||||
|
||||
def test_create_mode_language_does_not_match_ordinary_mode_execution(self) -> None:
|
||||
self.assertEqual(
|
||||
@@ -927,6 +1167,18 @@ operations:
|
||||
self.service._parse_mode_profile_create_name("新增冥想空间模式"),
|
||||
"冥想空间模式",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.service._parse_mode_profile_create_name(
|
||||
"创建宝宝睡眠模式:副驾半躺、空调24度、关闭阅读灯"
|
||||
),
|
||||
"宝宝睡眠模式",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.service._parse_mode_profile_create_name(
|
||||
"请帮我创建一个 宝宝睡眠模式:副驾半躺"
|
||||
),
|
||||
"宝宝睡眠模式",
|
||||
)
|
||||
self.assertIsNone(
|
||||
self.service._parse_mode_profile_create_name("进入K歌模式")
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user