from __future__ import annotations import tempfile import unittest from pathlib import Path from unittest.mock import patch from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService from cognitive_agent.infrastructure.llm.openai_responses import OpenAIIntentError class SeatServiceIntegrationTests(unittest.TestCase): def setUp(self) -> None: self.service = SeatDemoService(REPOSITORY_ROOT) self.temporary_directory = tempfile.TemporaryDirectory() self.addCleanup(self.temporary_directory.cleanup) self.service.artifact_root = Path(self.temporary_directory.name) def test_supported_request_completes_full_dry_run(self) -> None: result = self.service.process( "打开副驾座椅通风,同时关闭副驾座椅加热", provider="local" ) self.assertEqual(result["status"], "succeeded") self.assertEqual(len(result["function_config"]["spec"]["operations"]), 2) telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["command_frame"]["data_hex"], "00 90") self.assertTrue(Path(result["artifact_path"]).is_absolute()) self.assertTrue( (self.service.artifact_root / result["request_id"] / "result.json").is_file() ) for event in result["trace_events"]: self.assertEqual(self.service.contracts.validate(event), []) def test_gear_request_is_preserved_and_simulated(self) -> None: result = self.service.process("副驾座椅加热开到三档", provider="local") self.assertEqual(result["status"], "succeeded") operation = result["function_config"]["spec"]["operations"][0] self.assertEqual(operation["parameters"]["level"], 3) telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["effective_level"], 3) self.assertTrue(telemetry["level_command_simulated"]) self.assertIn( "LEVEL_COMMAND_SIMULATED", {issue["code"] for issue in result["validation"]} ) def test_consecutive_feedback_uses_previous_successful_state(self) -> None: conversation_id = "conv-test-context" first = self.service.process( "副驾有点热,有点闷", provider="local", conversation_id=conversation_id, ) second = self.service.process( "副驾还有点闷", provider="local", conversation_id=conversation_id, ) self.assertEqual(first["status"], "succeeded") self.assertEqual(first["actuator_state"]["passenger"]["ventilation"]["level"], 1) self.assertEqual(second["status"], "succeeded") self.assertEqual(second["conversation_turn"], 2) self.assertEqual(second["actuator_state"]["passenger"]["ventilation"]["level"], 2) operation = second["function_config"]["spec"]["operations"][0] self.assertEqual(operation["parameters"], {"state": "on", "level": 2}) self.assertIn("提升到2档", "".join(second["decision_summary"])) def test_four_turn_causal_feedback_sequence(self) -> None: conversation_id = "conv-causal-sequence" utterances = [ "我有点热", "我有点冷", "我还有点冷", "副驾吹的我有点冷", ] first, second, third, fourth = [ self.service.process(text, "local", conversation_id) for text in utterances ] self.assertEqual( first["function_config"]["spec"]["operations"][0]["parameters"], {"state": "on", "level": 1}, ) second_operation = second["function_config"]["spec"]["operations"][0] self.assertEqual(second_operation["capability"], "seat.set_ventilation") self.assertEqual(second_operation["parameters"], {"state": "off"}) third_operation = third["function_config"]["spec"]["operations"][0] self.assertEqual(third_operation["capability"], "seat.set_heating") self.assertEqual(third_operation["parameters"], {"state": "on", "level": 1}) fourth_operation = fourth["function_config"]["spec"]["operations"][0] self.assertEqual(fourth_operation["target"]["instance"], "passenger") self.assertEqual(fourth_operation["capability"], "seat.set_ventilation") self.assertEqual(fourth_operation["parameters"], {"state": "off"}) self.assertEqual( fourth["actuator_state"]["passenger"]["heating"], {"state": "on", "level": 1}, ) self.assertEqual(fourth["conversation_turn"], 4) def test_compound_cold_and_stuffy_feedback_generates_balanced_dry_run(self) -> None: result = self.service.process( "副驾有点冷、有点闷", provider="local", conversation_id="conv-compound-comfort", ) self.assertEqual(result["status"], "succeeded") operations = result["function_config"]["spec"]["operations"] self.assertEqual( {operation["capability"] for operation in operations}, {"seat.set_heating", "seat.set_ventilation"}, ) command = result["command_plan"]["spec"]["commands"][0] self.assertEqual( command["signals"], { "Front_Passenger_Seat_Heating_S": 1, "Front_Passenger_Seat_Ventilation_S": 1, }, ) self.assertIn( "COMBINED_THERMAL_SIMULATED", {issue["code"] for issue in result["validation"]}, ) def test_vehicle_scope_is_routed_away_from_seat_domain(self) -> None: result = self.service.process( "车上太闷了", provider="local", conversation_id="conv-vehicle-scope", ) self.assertEqual(result["status"], "needs_confirmation") self.assertEqual(result["intent_ir"]["spec"]["detected_domains"], ["climate"]) self.assertIsNone(result["function_config"]) self.assertIsNone(result["command_plan"]) self.assertEqual(result["actuator_state"]["passenger"]["ventilation"]["state"], "off") route = next(event for event in result["trace_events"] if event["stage"] == "domain_route") self.assertEqual(route["details"]["domains"], ["climate"]) def test_openai_output_is_not_rewritten_by_local_semantic_planner(self) -> None: model_output = { "detected_domains": ["seat"], "requests": [ { "domain": "seat", "capability": "seat.set_ventilation", "target_scope": "passenger", "operation": "deactivate", "parameters": [{"name": "state", "value": "off"}], "reason": "model-owned-plan", } ], "ambiguities": [], "decision_summary": ["model-owned-plan"], "confidence": 0.91, } context = self.service.conversations.snapshot("conv-model-owned-plan") self.service.openai.api_key = "test-key" with patch.object(self.service.openai, "interpret", return_value=model_output) as interpret: parsed, provider, notice = self.service._interpret( "车上太闷了", "openai", context ) self.assertEqual(parsed["decision_summary"], ["model-owned-plan"]) self.assertEqual(parsed["model_requests"], model_output["requests"]) self.assertEqual( parsed["actions"], [ { "target": "passenger", "function": "ventilation", "state": "off", "requested_level": None, } ], ) self.assertEqual(provider, f"openai:{self.service.openai.model}") self.assertIsNone(notice) interpret.assert_called_once_with( "车上太闷了", context, self.service.capability_context ) def test_explicit_gpt_failure_falls_back_to_local_rules(self) -> None: self.service.openai.api_key = "test-key" with patch.object( self.service.openai, "interpret", side_effect=OpenAIIntentError("invalid_api_key"), ): result = self.service.process( "打开副驾座椅通风", "openai", "conv-explicit-gpt-fallback", ) self.assertEqual(result["status"], "succeeded") self.assertEqual(result["provider_used"], "local_rules_fallback") self.assertEqual( result["function_config"]["spec"]["operations"][0]["target"]["instance"], "passenger", ) self.assertTrue( any("已降级到本地规则解析" in item for item in result["decision_summary"]) ) def test_model_climate_request_completes_domain_loop(self) -> None: model_output = { "detected_domains": ["climate"], "requests": [ { "domain": "climate", "capability": "climate.set_fan_level", "target_scope": "vehicle", "operation": "increase", "parameters": [{"name": "level", "value": 3}], "reason": "Increase cabin airflow.", } ], "ambiguities": [], "decision_summary": ["整车舱风量增加需求。"], "confidence": 0.94, } self.service.openai.api_key = "test-key" with patch.object(self.service.openai, "interpret", return_value=model_output): result = self.service.process( "车上太闷了", "openai", "conv-model-deferred", ) self.assertEqual(result["status"], "succeeded") self.assertEqual(result["model_requests"], model_output["requests"]) self.assertEqual(result["deferred_requests"], []) self.assertIn("climate", result["function_configs"]) request = result["intent_ir"]["spec"]["requests"][0] self.assertEqual(request["domain"], "climate") self.assertEqual(request["parameters"]["capability"], "climate.set_fan_level") def test_climate_and_seat_requests_generate_scene(self) -> None: model_output = { "detected_domains": ["seat", "climate"], "requests": [ { "domain": "seat", "capability": "seat.set_heating", "target_scope": "passenger", "operation": "activate", "parameters": [ {"name": "state", "value": "on"}, {"name": "level", "value": 1}, ], "reason": "Warm the passenger seat.", }, { "domain": "climate", "capability": "climate.set_fan_level", "target_scope": "vehicle", "operation": "set", "parameters": [{"name": "level", "value": 2}], "reason": "Improve cabin airflow.", }, ], "ambiguities": [], "decision_summary": ["座椅加热与整车空调风量并行执行。"], "confidence": 0.9, } self.service.openai.api_key = "test-key" with patch.object(self.service.openai, "interpret", return_value=model_output): result = self.service.process( "副驾有点冷、有点闷", "openai", "conv-model-partial", ) self.assertEqual(result["status"], "succeeded") self.assertIsNotNone(result["execution_report"]) self.assertEqual( result["actuator_state"]["passenger"]["heating"], {"state": "on", "level": 1}, ) self.assertEqual(result["domain_state"]["climate"]["fan_level"], 2) self.assertEqual(set(result["function_configs"]), {"seat", "climate"}) self.assertEqual(result["scene_config"]["kind"], "SceneConfig") def test_model_motion_requests_complete_control_and_feedback_loop(self) -> None: model_output = { "detected_domains": ["seat"], "requests": [ { "domain": "seat", "capability": "seat.set_longitudinal_position", "target_scope": "passenger", "operation": "set", "parameters": [{"name": "position_pct", "value": 35}], "reason": "Move the passenger seat backward from its current midpoint.", }, { "domain": "seat", "capability": "seat.set_backrest_recline", "target_scope": "passenger", "operation": "set", "parameters": [{"name": "position_pct", "value": 60}], "reason": "Recline the passenger backrest.", }, { "domain": "seat", "capability": "seat.set_legrest_height", "target_scope": "passenger", "operation": "set", "parameters": [{"name": "position_pct", "value": 65}], "reason": "Raise the passenger leg rest.", }, { "domain": "seat", "capability": "seat.set_legrest_extension", "target_scope": "passenger", "operation": "set", "parameters": [{"name": "position_pct", "value": 70}], "reason": "Extend the passenger leg rest.", }, ], "ambiguities": [], "decision_summary": ["四个电机轴使用绝对位置目标。"], "confidence": 0.93, } self.service.openai.api_key = "test-key" with patch.object(self.service.openai, "interpret", return_value=model_output): result = self.service.process( "副驾往后一点并把靠背躺下,腿托升起并伸长", "openai", "conv-model-motion", ) self.assertEqual(result["status"], "succeeded") self.assertEqual(len(result["function_config"]["spec"]["operations"]), 4) self.assertEqual(len(result["command_plan"]["spec"]["commands"]), 4) self.assertEqual(len(result["execution_report"]["spec"]["results"]), 4) passenger = result["actuator_state"]["passenger"]["position"] self.assertEqual(passenger["longitudinal"]["position_pct"], 35) self.assertEqual(passenger["backrest_recline"]["position_pct"], 60) self.assertEqual(passenger["legrest_height"]["position_pct"], 65) self.assertEqual(passenger["legrest_extension"]["position_pct"], 70) telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"] self.assertEqual(telemetry["start_position_pct"], 50) self.assertEqual(telemetry["effective_position_pct"], 35) self.assertEqual(telemetry["feedback_frame"]["frame_id"], "0x559") self.assertEqual(telemetry["fault_mask"], 0) codes = {issue["code"] for issue in result["validation"]} self.assertIn("SYNTHETIC_MOTION_CAN", codes) def test_motion_follow_up_state_is_supplied_to_model(self) -> None: def output(position: int) -> dict[str, object]: return { "detected_domains": ["seat"], "requests": [ { "domain": "seat", "capability": "seat.set_longitudinal_position", "target_scope": "passenger", "operation": "increase", "parameters": [{"name": "position_pct", "value": position}], "reason": "Model resolved the relative request.", } ], "ambiguities": [], "decision_summary": [f"Move to {position}%."], "confidence": 0.9, } self.service.openai.api_key = "test-key" with patch.object( self.service.openai, "interpret", side_effect=[output(60), output(70)], ) as interpret: first = self.service.process( "副驾往后一点", "openai", "conv-motion-follow-up" ) second = self.service.process( "还是靠前,再往后一点", "openai", "conv-motion-follow-up" ) self.assertEqual( first["actuator_state"]["passenger"]["position"]["longitudinal"][ "position_pct" ], 60, ) self.assertEqual( second["actuator_state"]["passenger"]["position"]["longitudinal"][ "position_pct" ], 70, ) second_context = interpret.call_args_list[1].args[1] self.assertEqual( second_context["devices"]["passenger"]["position"]["longitudinal"][ "position_pct" ], 60, ) def test_model_relative_physical_motion_uses_vehicle_profile(self) -> None: model_output = { "detected_domains": ["seat"], "requests": [ { "domain": "seat", "capability": "seat.set_longitudinal_position", "target_scope": "passenger", "operation": "decrease", "parameters": [{"name": "distance_mm", "value": 50}], "reason": "Move forward by a physical distance.", }, { "domain": "seat", "capability": "seat.set_backrest_recline", "target_scope": "passenger", "operation": "increase", "parameters": [{"name": "angle_delta_deg", "value": 10}], "reason": "Recline by a relative physical angle.", }, ], "ambiguities": [], "decision_summary": ["使用车型量程把相对物理量换算为绝对百分比。"], "confidence": 0.94, } self.service.openai.api_key = "test-key" with patch.object(self.service.openai, "interpret", return_value=model_output) as interpret: result = self.service.process( "副驾水平向前移动50毫米,靠背往后10度", "openai", "conv-vehicle-profile-relative", vehicle_id="denza_n7", ) self.assertEqual(result["status"], "succeeded") self.assertEqual(result["vehicle"]["id"], "denza_n7") operations = result["function_config"]["spec"]["operations"] self.assertEqual(operations[0]["parameters"], {"position_pct": 27}) self.assertEqual( operations[1]["parameters"], {"position_pct": 35, "angle_deg": 118.6}, ) backrest_motion = operations[1]["motion"] self.assertEqual(backrest_motion["mode"], "relative") self.assertEqual(backrest_motion["direction"], "increase") self.assertEqual(backrest_motion["start_position_pct"], 20) self.assertEqual(backrest_motion["delta_position_pct"], 15) self.assertEqual(backrest_motion["start_angle_deg"], 108.6) self.assertEqual(backrest_motion["delta_angle_deg"], 10) context_sent_to_model = interpret.call_args.args[1] self.assertEqual(context_sent_to_model["vehicle"]["id"], "denza_n7") self.assertEqual( context_sent_to_model["devices"]["passenger"]["position"][ "backrest_recline" ]["angle_deg"], 108.6, ) def test_vehicle_profile_angle_limits_are_accepted(self) -> None: model_output = { "detected_domains": ["seat"], "requests": [ { "domain": "seat", "capability": "seat.set_backrest_recline", "target_scope": "passenger", "operation": "set", "parameters": [{"name": "angle_deg", "value": 163}], "reason": "Use the vehicle-specific maximum backrest angle.", }, { "domain": "seat", "capability": "seat.set_legrest_height", "target_scope": "passenger", "operation": "set", "parameters": [{"name": "angle_deg", "value": 167}], "reason": "Use the vehicle-specific maximum legrest angle.", }, ], "ambiguities": [], "decision_summary": ["车型角度边界按腾势 N7 配置执行。"], "confidence": 0.95, } self.service.openai.api_key = "test-key" with patch.object(self.service.openai, "interpret", return_value=model_output): result = self.service.process( "副驾靠背到163度,腿托到167度", "openai", "conv-vehicle-angle-limits", vehicle_id="denza_n7", ) self.assertEqual(result["status"], "succeeded") operations = result["function_config"]["spec"]["operations"] self.assertEqual(operations[0]["parameters"], {"position_pct": 100, "angle_deg": 163}) self.assertEqual(operations[1]["parameters"], {"position_pct": 100, "angle_deg": 167}) def test_motion_adapter_does_not_resolve_missing_absolute_target(self) -> None: model_output = { "detected_domains": ["seat"], "requests": [ { "domain": "seat", "capability": "seat.set_longitudinal_position", "target_scope": "passenger", "operation": "decrease", "parameters": [], "reason": "No absolute target was produced.", } ], "ambiguities": [], "decision_summary": ["Unresolved relative request."], "confidence": 0.6, } self.service.openai.api_key = "test-key" with patch.object(self.service.openai, "interpret", return_value=model_output): result = self.service.process( "副驾往后一点", "openai", "conv-motion-missing-target" ) self.assertEqual(result["status"], "needs_confirmation") self.assertIsNone(result["function_config"]) self.assertIn( "AMBIGUOUS_INTENT", {issue["code"] for issue in result["validation"]} ) self.assertNotIn( "NO_EXECUTABLE_OPERATION", {issue["code"] for issue in result["validation"]}, ) self.assertNotIn( "PUBLIC_SCHEMA", {issue["code"] for issue in result["validation"]} ) def test_exact_angles_and_named_preset_complete_conversation_loop(self) -> None: def plan(requests: list[dict[str, object]], summary: str) -> dict[str, object]: return { "detected_domains": ["seat"], "requests": requests, "ambiguities": [], "decision_summary": [summary], "confidence": 0.96, } def request( capability: str, operation: str, parameters: list[dict[str, object]], ) -> dict[str, object]: return { "domain": "seat", "capability": capability, "target_scope": "passenger", "operation": operation, "parameters": parameters, "reason": "Model resolved the user's posture goal against runtime context.", } first_plan = plan( [ request( "seat.set_backrest_recline", "set", [{"name": "angle_deg", "value": 128}], ), request( "seat.set_legrest_height", "set", [{"name": "angle_deg", "value": 133}], ), ], "副驾靠背/坐垫夹角设为128度,坐垫/腿托夹角设为133度。", ) recline_plan = plan( [ request( "seat.set_backrest_recline", "increase", [{"name": "angle_deg", "value": 133}], ) ], "根据当前128度姿态,将副驾靠背继续后仰至133度。", ) save_plan = plan( [ request( "seat.save_posture_preset", "save", [{"name": "preset_name", "value": "零重力模式"}], ) ], "将副驾当前五轴姿态保存为零重力模式。", ) leave_preset_plan = plan( [ request( "seat.set_backrest_recline", "set", [{"name": "angle_deg", "value": 105}], ) ], "将副驾靠背恢复到105度用于验证预设回调。", ) apply_plan = plan( [ request( "seat.apply_posture_preset", "apply", [{"name": "preset_name", "value": "零重力模式"}], ) ], "调用已保存的副驾零重力模式。", ) self.service.openai.api_key = "test-key" conversation_id = "conv-angle-preset-loop" with patch.object( self.service.openai, "interpret", side_effect=[ first_plan, recline_plan, save_plan, leave_preset_plan, apply_plan, ], ) as interpret: first = self.service.process( "副驾调整为零重力模式,要求靠背座椅夹角128度,坐垫脚托夹角133度", "openai", conversation_id, ) second = self.service.process( "靠背再向后一点", "openai", conversation_id ) saved = self.service.process( "将当前状态保存为零重力模式", "openai", conversation_id ) changed = self.service.process( "副驾靠背恢复直立一些", "openai", conversation_id ) applied = self.service.process( "副驾恢复到零重力模式", "openai", conversation_id ) first_operations = first["function_config"]["spec"]["operations"] self.assertEqual( first_operations[0]["parameters"], {"position_pct": 49, "angle_deg": 128}, ) self.assertEqual( first_operations[1]["parameters"], {"position_pct": 53, "angle_deg": 133}, ) self.assertEqual( first["actuator_state"]["passenger"]["position"]["backrest_recline"][ "angle_deg" ], 128, ) first_telemetry = first["execution_report"]["spec"]["results"][0]["telemetry"] self.assertEqual(first_telemetry["requested_angle_deg"], 128) self.assertEqual(first_telemetry["effective_position_pct"], 49) self.assertEqual( second["actuator_state"]["passenger"]["position"]["backrest_recline"][ "angle_deg" ], 133, ) relative_motion = second["function_config"]["spec"]["operations"][0][ "motion" ] self.assertEqual(relative_motion["mode"], "relative") self.assertEqual(relative_motion["direction"], "increase") self.assertEqual(relative_motion["start_position_pct"], 49) self.assertEqual(relative_motion["delta_position_pct"], 7) self.assertEqual(relative_motion["delta_angle_deg"], 5) second_context = interpret.call_args_list[1].args[1] self.assertEqual( second_context["devices"]["passenger"]["position"]["backrest_recline"][ "angle_deg" ], 128, ) self.assertEqual(saved["status"], "succeeded") self.assertIsNone(saved["function_config"]) self.assertEqual(saved["preset_config"]["kind"], "FunctionPreset") self.assertEqual(saved["preset_config"]["metadata"]["name"], "零重力模式") self.assertFalse( saved["execution_report"]["spec"]["results"][0]["telemetry"][ "can_command_emitted" ] ) self.assertEqual( saved["saved_presets"], [{"name": "零重力模式", "target": "passenger"}], ) preset_path = ( self.service.artifact_root / "user-presets" / f"{conversation_id}.json" ) self.assertTrue(preset_path.is_file()) self.assertEqual( changed["actuator_state"]["passenger"]["position"]["backrest_recline"][ "angle_deg" ], 105, ) self.assertEqual(applied["status"], "succeeded") restored = applied["actuator_state"]["passenger"]["position"] self.assertEqual(restored["backrest_recline"]["angle_deg"], 133) self.assertEqual(restored["legrest_height"]["angle_deg"], 133) self.assertEqual(len(applied["function_config"]["spec"]["operations"]), 5) apply_context = interpret.call_args_list[4].args[1] self.assertEqual(apply_context["presets"][0]["name"], "零重力模式") def test_missing_target_defaults_to_passenger(self) -> None: result = self.service.process("打开座椅通风", provider="local") self.assertEqual(result["status"], "succeeded") operation = result["function_config"]["spec"]["operations"][0] self.assertEqual(operation["target"]["instance"], "passenger") def test_explicit_driver_target_is_rejected(self) -> None: result = self.service.process("打开主驾座椅通风", provider="local") self.assertEqual(result["status"], "needs_confirmation") self.assertIsNone(result["function_config"]) questions = [ item["question"] for item in result["intent_ir"]["spec"]["ambiguities"] ] self.assertTrue(any("仅控制副驾" in question for question in questions)) def test_model_driver_target_is_rejected(self) -> None: model_output = { "detected_domains": ["seat"], "requests": [ { "domain": "seat", "capability": "seat.set_ventilation", "target_scope": "driver", "operation": "activate", "parameters": [{"name": "state", "value": "on"}], "reason": "Ventilate the explicitly requested driver seat.", } ], "ambiguities": [], "decision_summary": ["Explicit driver-seat request."], "confidence": 0.95, } self.service.openai.api_key = "test-key" with patch.object(self.service.openai, "interpret", return_value=model_output): result = self.service.process( "打开主驾座椅通风", "openai", "conv-driver-rejected" ) self.assertEqual(result["status"], "needs_confirmation") self.assertIsNone(result["function_config"]) questions = [ item["question"] for item in result["intent_ir"]["spec"]["ambiguities"] ] self.assertTrue(any("仅控制副驾" in question for question in questions)) if __name__ == "__main__": unittest.main()