Files
Cognitive_Agent/domains/seat/tests/integration/test_seat_service.py
T
2026-08-24 14:15:53 +08:00

963 lines
39 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
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_dynamic_mode_runs_until_explicit_natural_language_stop(self) -> None:
conversation_id = "conv-dynamic-stop"
started = self.service.process(
"\u5f00\u542f\u52a8\u6001\u6447\u7bee\u6a21\u5f0f",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
)
stopped = self.service.process(
"\u5173\u95ed\u52a8\u6001\u6447\u7bee\u6a21\u5f0f",
provider="local",
conversation_id=conversation_id,
vehicle_id="denza_n7",
)
self.assertEqual(started["status"], "succeeded")
self.assertEqual(
started["actuator_state"]["passenger"]["dynamic_mode"],
{
"mode_id": 2,
"name": "\u52a8\u6001\u6447\u7bee\u6a21\u5f0f",
"state": "running",
"repeat": "until_stopped_or_timeout",
"max_runtime_ms": 600_000,
},
)
self.assertEqual(stopped["status"], "succeeded")
operation = stopped["function_config"]["spec"]["operations"][0]
self.assertEqual(operation["capability"], "seat.stop_dynamic_mode")
self.assertEqual(operation["parameters"], {})
self.assertEqual(
stopped["actuator_state"]["passenger"]["dynamic_mode"]["state"],
"idle",
)
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_local_rules_precede_openai_for_unscoped_driver_console_command(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.assertTrue(
any("默认选择主驾座椅" in item for item in parsed["decision_summary"])
)
self.assertEqual(len(parsed["model_requests"]), 1)
self.assertEqual(
parsed["model_requests"][0]["capability"],
"seat.set_driver_ventilation_native",
)
self.assertEqual(parsed["actions"], [])
self.assertEqual(len(parsed["deferred_requests"]), 1)
deferred = parsed["deferred_requests"][0]
self.assertEqual(deferred["capability"], "seat.set_driver_ventilation_native")
self.assertEqual(deferred["target_scope"], "driver")
self.assertEqual(deferred["parameters"], {"state": "off"})
self.assertEqual(deferred["execution_backend"], "native_can")
self.assertEqual(provider, "local_rules")
self.assertIsNone(notice)
interpret.assert_not_called()
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_local_first_calls_model_with_only_uncovered_clauses(self) -> None:
self.service.openai.api_key = "test-key"
model_output = {
"detected_domains": ["ambient_light"],
"requests": [
{
"domain": "ambient_light",
"capability": "ambient_light.set_power",
"target_scope": "vehicle",
"operation": "set",
"parameters": [{"name": "state", "value": "on"}],
"reason": "播放场景需要氛围灯。",
}
],
"ambiguities": [],
"decision_summary": ["仅补充本地规则未覆盖的播放场景。"],
"confidence": 0.9,
}
context = self.service.conversations.snapshot("conv-clause-fallback")
with patch.object(
self.service.openai, "interpret", return_value=model_output
) as interpret:
parsed, provider, notice = self.service._interpret(
"空调温度设置为25度,播放音乐",
"openai",
context,
)
interpret.assert_called_once_with(
"播放音乐", context, self.service.capability_context
)
self.assertEqual(
provider, f"local_rules+openai:{self.service.openai.model}"
)
self.assertIn("仅未覆盖", str(notice))
self.assertEqual(
[item["capability"] for item in parsed["native_can_actions"]],
["climate.set_temperature"],
)
self.assertEqual(
[item["capability"] for item in parsed["domain_actions"]],
["ambient_light.set_power"],
)
def test_local_first_compiles_memory_temperature_and_fan_without_model(self) -> None:
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret") as interpret:
result = self.service.process(
"主驾座椅调整到记忆位置3,空调温度设置为25度,风速设置为1档",
"openai",
"conv-local-memory-climate",
)
interpret.assert_not_called()
self.assertEqual(result["status"], "succeeded")
self.assertEqual(result["provider_used"], "local_rules")
self.assertEqual(
set(result["function_configs"]), {"native_seat", "native_climate"}
)
commands = [
command["signals"]["debug_command"]
for plan in result["command_plans"].values()
for command in plan["spec"]["commands"]
]
self.assertEqual(
commands,
["seat driver position 3", "ac temp driver 25", "ac fan 1"],
)
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(
result["provider_used"],
f"local_rules+openai:{self.service.openai.model}",
)
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_model_timed_motion_survives_endpoint_resolution_and_reaches_compiler(self) -> None:
self.service.process(
"副驾座椅水平位置调到100%",
provider="local",
conversation_id="conv-model-timed",
)
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_longitudinal_position",
"target_scope": "passenger",
"operation": "increase",
"duration_ms": 3_000,
"parameters": [{"name": "position_pct", "value": 100}],
"reason": "Continue moving the rail backward for three seconds.",
},
{
"domain": "seat",
"capability": "seat.set_legrest_height",
"target_scope": "passenger",
"operation": "increase",
"duration_ms": 3_000,
"parameters": [],
"reason": "Raise the leg rest for three seconds.",
},
],
"ambiguities": [],
"decision_summary": ["保留两个动作各自三秒的定时运动语义。"],
"confidence": 0.99,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"副驾座椅水平再后移动3秒,副驾腿托向上调3秒",
"openai",
"conv-model-timed",
vehicle_id="denza_n7",
)
self.assertEqual(result["status"], "succeeded")
operations = result["function_config"]["spec"]["operations"]
self.assertEqual(len(operations), 2)
by_capability = {item["capability"]: item for item in operations}
rail_motion = by_capability["seat.set_longitudinal_position"]["motion"]
self.assertEqual(rail_motion["mode"], "timed")
self.assertEqual(rail_motion["direction"], "increase")
self.assertEqual(rail_motion["start_position_pct"], 100)
self.assertEqual(rail_motion["delta_position_pct"], 0)
self.assertEqual(rail_motion["applied_duration_ms"], 3_000)
legrest_motion = by_capability["seat.set_legrest_height"]["motion"]
self.assertEqual(legrest_motion["mode"], "timed")
self.assertEqual(legrest_motion["direction"], "increase")
self.assertEqual(legrest_motion["applied_duration_ms"], 3_000)
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_driver_ch1(self) -> None:
result = self.service.process("打开座椅通风", provider="local")
self.assertEqual(result["status"], "succeeded")
operation = result["function_config"]["spec"]["operations"][0]
self.assertEqual(operation["target"]["zone"], "driver")
self.assertEqual(
operation["capability"], "seat.set_driver_ventilation_native"
)
self.assertEqual(
result["command_plan"]["spec"]["commands"][0]["bus"],
"body_can",
)
def test_explicit_driver_target_uses_ch1_native_can(self) -> None:
result = self.service.process("打开主驾座椅通风", provider="local")
self.assertEqual(result["status"], "succeeded")
operation = result["function_config"]["spec"]["operations"][0]
self.assertEqual(operation["target"]["zone"], "driver")
self.assertEqual(
operation["capability"], "seat.set_driver_ventilation_native"
)
command = result["command_plan"]["spec"]["commands"][0]
self.assertEqual(command["bus"], "body_can")
self.assertEqual(command["signals"]["frame_id"], "0x431")
self.assertEqual(command["signals"]["feedback_ids"], "0x4FA")
def test_model_driver_target_is_replaced_by_ch1_native_can(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"], "succeeded")
operation = result["function_config"]["spec"]["operations"][0]
self.assertEqual(operation["target"]["zone"], "driver")
self.assertEqual(
operation["capability"], "seat.set_driver_ventilation_native"
)
self.assertFalse(result["deferred_requests"])
if __name__ == "__main__":
unittest.main()