Files
Cognitive_Agent/domains/seat/tests/integration/test_seat_service.py
T
2026-07-30 08:45:38 +08:00

611 lines
25 KiB
Python
Executable File

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
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"], "01 80")
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"]["driver"]["ventilation"]["level"], 1)
self.assertEqual(second["status"], "succeeded")
self.assertEqual(second["conversation_turn"], 2)
self.assertEqual(second["actuator_state"]["driver"]["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"]["driver"]["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"],
{"Driver_Seat_Heating_S": 1, "Driver_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_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": "driver",
"operation": "activate",
"parameters": [
{"name": "state", "value": "on"},
{"name": "level", "value": 1},
],
"reason": "Warm the driver 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"]["driver"]["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": "driver",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 35}],
"reason": "Move the driver seat backward from its current midpoint.",
},
{
"domain": "seat",
"capability": "seat.set_backrest_recline",
"target_scope": "driver",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 60}],
"reason": "Recline the driver 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)
driver = result["actuator_state"]["driver"]["position"]
passenger = result["actuator_state"]["passenger"]["position"]
self.assertEqual(driver["longitudinal"]["position_pct"], 35)
self.assertEqual(driver["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"], "0x558")
self.assertEqual(telemetry["fault_mask"], 0)
codes = {issue["code"] for issue in result["validation"]}
self.assertIn("SYNTHETIC_MOTION_CAN", codes)
self.assertIn("DRIVER_MOTION_INTERLOCK_NOT_IMPLEMENTED", 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": "driver",
"operation": "decrease",
"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(40), output(30)],
) 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"]["driver"]["position"]["longitudinal"][
"position_pct"
],
40,
)
self.assertEqual(
second["actuator_state"]["driver"]["position"]["longitudinal"][
"position_pct"
],
30,
)
second_context = interpret.call_args_list[1].args[1]
self.assertEqual(
second_context["devices"]["driver"]["position"]["longitudinal"]["position_pct"],
40,
)
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": "driver",
"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": 73, "angle_deg": 128},
)
self.assertEqual(
first_operations[1]["parameters"],
{"position_pct": 58, "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"], 73)
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"], 73)
self.assertEqual(relative_motion["delta_position_pct"], 11)
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_requests_confirmation(self) -> None:
result = self.service.process("打开座椅通风", provider="local")
self.assertEqual(result["status"], "needs_confirmation")
self.assertIsNone(result["function_config"])
if __name__ == "__main__":
unittest.main()