feat: update CAN integration and T2G seat firmware
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import threading
|
||||
import unittest
|
||||
@@ -9,9 +10,11 @@ import urllib.request
|
||||
from http.cookiejar import CookieJar
|
||||
from http.server import ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from cognitive_agent.auth import AuthConfig, AuthRuntime, hash_password
|
||||
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
|
||||
from cognitive_agent.infrastructure.llm.openai_responses import OpenAIIntentError
|
||||
from cognitive_agent.server import SeatDemoHandler
|
||||
|
||||
|
||||
@@ -22,8 +25,18 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.temporary_directory = tempfile.TemporaryDirectory()
|
||||
service = SeatDemoService(REPOSITORY_ROOT)
|
||||
# Web regression tests must never inherit a developer's live bench
|
||||
# enablement, especially when local debug mode intentionally skips the
|
||||
# per-request approval token.
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "false"},
|
||||
clear=False,
|
||||
):
|
||||
service = SeatDemoService(REPOSITORY_ROOT)
|
||||
service.artifact_root = Path(cls.temporary_directory.name)
|
||||
cls.service = service
|
||||
cls.original_service = SeatDemoHandler.service
|
||||
SeatDemoHandler.service = service
|
||||
cls.original_auth = SeatDemoHandler.auth
|
||||
SeatDemoHandler.auth = AuthRuntime(
|
||||
@@ -53,6 +66,7 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
cls.server.shutdown()
|
||||
cls.server.server_close()
|
||||
cls.thread.join(timeout=2)
|
||||
SeatDemoHandler.service = cls.original_service
|
||||
SeatDemoHandler.auth = cls.original_auth
|
||||
cls.temporary_directory.cleanup()
|
||||
|
||||
@@ -99,7 +113,7 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
self.assertIn("FBXLoader.js", html)
|
||||
self.assertIn('data-seat-layout="five-seat"', html)
|
||||
self.assertIn('data-active-seat="front-right"', html)
|
||||
self.assertIn('placeholder="请输入 123456"', html)
|
||||
self.assertIn('placeholder="请输入服务端配置令牌"', html)
|
||||
self.assertIn('data-callout="backrest_recline"', html)
|
||||
self.assertIn('data-callout="legrest_extension"', html)
|
||||
self.assertIn('data-callout="backrest_included_angle"', html)
|
||||
@@ -116,7 +130,14 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
self.assertIn("坐垫脚托夹角133度", html)
|
||||
self.assertNotIn('data-axis="height"', html)
|
||||
self.assertEqual(runtime["domain"], "cabin")
|
||||
self.assertFalse(runtime["hardware_execution_allowed"])
|
||||
self.assertEqual(
|
||||
runtime["hardware_execution_allowed"],
|
||||
runtime["hardware"]["configured"],
|
||||
)
|
||||
self.assertEqual(runtime["hardware"]["supported_targets"], ["front_right"])
|
||||
self.assertEqual(
|
||||
runtime["hardware"]["supported_seat_instances"], ["passenger"]
|
||||
)
|
||||
self.assertGreaterEqual(runtime["matrix"]["messages"], 25)
|
||||
self.assertGreaterEqual(runtime["matrix"]["signals"], 128)
|
||||
self.assertEqual(runtime["motion_position_range_pct"], [0, 100])
|
||||
@@ -148,6 +169,13 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
self.assertIn("climate.set_temperature", capability_names)
|
||||
self.assertIn("roof.set_transmittance", capability_names)
|
||||
self.assertIn("ambient_light.set_illumination", capability_names)
|
||||
seat_capabilities = [
|
||||
item for item in runtime["capabilities"] if item["name"].startswith("seat.")
|
||||
]
|
||||
self.assertTrue(seat_capabilities)
|
||||
self.assertTrue(
|
||||
all(item["target_scopes"] == ["passenger"] for item in seat_capabilities)
|
||||
)
|
||||
|
||||
def test_post_request_returns_complete_dry_run(self) -> None:
|
||||
request = urllib.request.Request(
|
||||
@@ -156,7 +184,7 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
headers={"Content-Type": "application/json"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"utterance": "主驾有点热,有点闷",
|
||||
"utterance": "副驾有点热,有点闷",
|
||||
"provider": "local",
|
||||
"conversation_id": "conv-e2e-browser",
|
||||
"vehicle_id": "denza_n7",
|
||||
@@ -172,7 +200,171 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
self.assertEqual(result["execution_report"]["spec"]["mode"], "dry_run")
|
||||
self.assertEqual(result["conversation_id"], "conv-e2e-browser")
|
||||
self.assertEqual(result["vehicle"]["id"], "denza_n7")
|
||||
self.assertEqual(result["actuator_state"]["driver"]["ventilation"]["level"], 1)
|
||||
self.assertEqual(result["actuator_state"]["passenger"]["ventilation"]["level"], 1)
|
||||
|
||||
def test_hardware_state_endpoint_returns_physical_passenger_snapshot(self) -> None:
|
||||
synchronized = {
|
||||
"status": "succeeded",
|
||||
"conversation_id": "conv-e2e-physical-state",
|
||||
"source": "physical_can",
|
||||
"complete": True,
|
||||
"missing": [],
|
||||
"actuator_state": {
|
||||
"passenger": {
|
||||
"position": {
|
||||
"longitudinal": {"position_pct": 0},
|
||||
"cushion_tilt": {"position_pct": 0},
|
||||
"backrest_recline": {"position_pct": 0},
|
||||
"legrest_height": {"position_pct": 0},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
with patch.object(
|
||||
self.service,
|
||||
"synchronize_hardware_state",
|
||||
return_value=synchronized,
|
||||
) as synchronize:
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/api/hardware/seat-state?"
|
||||
"conversation_id=conv-e2e-physical-state&vehicle_id=denza_n7",
|
||||
timeout=3,
|
||||
) as response:
|
||||
payload = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
self.assertEqual(payload["source"], "physical_can")
|
||||
self.assertEqual(
|
||||
payload["actuator_state"]["passenger"]["position"]["longitudinal"]["position_pct"],
|
||||
0,
|
||||
)
|
||||
synchronize.assert_called_once_with(
|
||||
"conv-e2e-physical-state",
|
||||
"denza_n7",
|
||||
)
|
||||
|
||||
def test_gpt_http_failure_falls_back_without_returning_502(self) -> None:
|
||||
original_api_key = self.service.openai.api_key
|
||||
self.service.openai.api_key = "test-key"
|
||||
request = urllib.request.Request(
|
||||
f"{self.base_url}/api/requests",
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"utterance": "打开副驾座椅通风",
|
||||
"provider": "openai",
|
||||
"conversation_id": "conv-e2e-gpt-fallback",
|
||||
"execution_mode": "dry_run",
|
||||
}
|
||||
).encode("utf-8"),
|
||||
)
|
||||
try:
|
||||
with patch.object(
|
||||
self.service.openai,
|
||||
"interpret",
|
||||
side_effect=OpenAIIntentError("invalid_api_key"),
|
||||
):
|
||||
with self.opener.open(request, timeout=3) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
self.assertEqual(response.status, 201)
|
||||
finally:
|
||||
self.service.openai.api_key = original_api_key
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(result["provider_used"], "local_rules_fallback")
|
||||
self.assertEqual(result["execution_report"]["spec"]["mode"], "dry_run")
|
||||
|
||||
def test_frontend_suite_covers_every_physical_seat_capability(self) -> None:
|
||||
suite_path = (
|
||||
REPOSITORY_ROOT
|
||||
/ "tests"
|
||||
/ "fixtures"
|
||||
/ "seat_frontend_capabilities.json"
|
||||
)
|
||||
suite = json.loads(suite_path.read_text(encoding="utf-8"))
|
||||
required = set(suite["required_hardware_capabilities"])
|
||||
covered: set[str] = set()
|
||||
original_api_key = self.service.openai.api_key
|
||||
self.service.openai.api_key = "frontend-suite-test-key"
|
||||
try:
|
||||
for case in suite["cases"]:
|
||||
with self.subTest(case=case["id"]):
|
||||
requests = []
|
||||
for expected in case["operations"]:
|
||||
covered.add(expected["capability"])
|
||||
requests.append(
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": expected["capability"],
|
||||
"target_scope": expected["target"],
|
||||
"operation": "set",
|
||||
"parameters": [
|
||||
{"name": name, "value": value}
|
||||
for name, value in expected["parameters"].items()
|
||||
],
|
||||
"reason": f"frontend test case {case['id']}",
|
||||
}
|
||||
)
|
||||
model_plan = {
|
||||
"detected_domains": ["seat"],
|
||||
"requests": requests,
|
||||
"ambiguities": [],
|
||||
"decision_summary": [f"执行前端测试用例 {case['id']}。"],
|
||||
"confidence": 0.99,
|
||||
}
|
||||
request = urllib.request.Request(
|
||||
f"{self.base_url}/api/requests",
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"utterance": case["utterance"],
|
||||
"provider": "openai",
|
||||
"conversation_id": f"conv-frontend-suite-{case['id'].lower()}",
|
||||
"execution_mode": "dry_run",
|
||||
"vehicle_id": "denza_n7",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
).encode("utf-8"),
|
||||
)
|
||||
with patch.object(
|
||||
self.service.openai,
|
||||
"interpret",
|
||||
return_value=model_plan,
|
||||
):
|
||||
try:
|
||||
with self.opener.open(request, timeout=3) as response:
|
||||
result = json.loads(response.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", errors="replace")
|
||||
self.fail(f"frontend case {case['id']} returned {exc.code}: {detail}")
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
operations = result["function_config"]["spec"]["operations"]
|
||||
self.assertEqual(len(operations), len(case["operations"]))
|
||||
self.assertEqual(
|
||||
result["execution_report"]["spec"]["mode"], "dry_run"
|
||||
)
|
||||
self.assertEqual(
|
||||
len(result["execution_report"]["spec"]["results"]),
|
||||
len(case["operations"]),
|
||||
)
|
||||
for expected, actual in zip(case["operations"], operations):
|
||||
self.assertEqual(actual["capability"], expected["capability"])
|
||||
self.assertEqual(
|
||||
actual["target"]["instance"], expected["target"]
|
||||
)
|
||||
for name, value in expected["parameters"].items():
|
||||
self.assertEqual(actual["parameters"][name], value)
|
||||
finally:
|
||||
self.service.openai.api_key = original_api_key
|
||||
|
||||
self.assertEqual(covered, required)
|
||||
safety_ids = {item["id"] for item in suite["safety_cases"]}
|
||||
self.assertEqual(
|
||||
safety_ids,
|
||||
{"SAFE-01", "SAFE-02", "SAFE-03", "RECOVERY-01"},
|
||||
)
|
||||
|
||||
def test_hardware_request_is_rejected_when_server_gate_is_locked(self) -> None:
|
||||
request = urllib.request.Request(
|
||||
@@ -181,7 +373,7 @@ class SeatWebApiTests(unittest.TestCase):
|
||||
headers={"Content-Type": "application/json"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"utterance": "主驾座椅加热开到一档",
|
||||
"utterance": "副驾座椅加热开到一档",
|
||||
"provider": "local",
|
||||
"conversation_id": "conv-e2e-hardware-locked",
|
||||
"execution_mode": "hardware",
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"suite": "t2g-seat-frontend-capabilities",
|
||||
"execution_policy": {
|
||||
"automated_mode": "dry_run",
|
||||
"physical_mode": "hardware",
|
||||
"physical_test_requires_operator": true,
|
||||
"auto_home_during_suite": false
|
||||
},
|
||||
"required_hardware_capabilities": [
|
||||
"seat.set_heating",
|
||||
"seat.set_ventilation",
|
||||
"seat.set_longitudinal_position",
|
||||
"seat.set_cushion_tilt",
|
||||
"seat.set_backrest_recline",
|
||||
"seat.set_legrest_height"
|
||||
],
|
||||
"cases": [
|
||||
{
|
||||
"id": "THERM-01",
|
||||
"utterance": "副驾驶座椅加热开到3档",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_heating", "parameters": {"state": "on", "level": 3}}
|
||||
],
|
||||
"bench_check": "0x4FA 加热档位回读为3,靠背加热电流诊断合理。"
|
||||
},
|
||||
{
|
||||
"id": "THERM-02",
|
||||
"utterance": "关闭副驾驶座椅加热",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_heating", "parameters": {"state": "off"}}
|
||||
],
|
||||
"bench_check": "0x4FA 加热档位回读为0,输出关闭。"
|
||||
},
|
||||
{
|
||||
"id": "THERM-03",
|
||||
"utterance": "副驾驶座椅通风开到2档",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_ventilation", "parameters": {"state": "on", "level": 2}}
|
||||
],
|
||||
"bench_check": "0x4FA 通风档位回读为2,风机运行。"
|
||||
},
|
||||
{
|
||||
"id": "THERM-04",
|
||||
"utterance": "关闭副驾驶座椅通风",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_ventilation", "parameters": {"state": "off"}}
|
||||
],
|
||||
"bench_check": "0x4FA 通风档位回读为0,风机关闭。"
|
||||
},
|
||||
{
|
||||
"id": "MOTION-01",
|
||||
"utterance": "把副驾驶座椅水平位置调到25%",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_longitudinal_position", "parameters": {"position_pct": 25}}
|
||||
],
|
||||
"bench_check": "M0动作,0x302归一化位置约2500,最终COMPLETE/OK。"
|
||||
},
|
||||
{
|
||||
"id": "MOTION-02",
|
||||
"utterance": "把副驾驶坐盆倾角调到35%",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_cushion_tilt", "parameters": {"position_pct": 35}}
|
||||
],
|
||||
"bench_check": "M1动作,0x302归一化位置约3500,最终COMPLETE/OK。"
|
||||
},
|
||||
{
|
||||
"id": "MOTION-03",
|
||||
"utterance": "把副驾驶靠背调到130度",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_backrest_recline", "parameters": {"angle_deg": 130}}
|
||||
],
|
||||
"bench_check": "M2向目标角度动作,显示角度约130度,最终COMPLETE/OK。"
|
||||
},
|
||||
{
|
||||
"id": "MOTION-04",
|
||||
"utterance": "把副驾驶腿托抬到145度",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_legrest_height", "parameters": {"angle_deg": 145}}
|
||||
],
|
||||
"bench_check": "M3向目标角度动作,显示角度约145度,最终COMPLETE/OK。"
|
||||
},
|
||||
{
|
||||
"id": "COMBINED-01",
|
||||
"utterance": "副驾驶加热1档、通风1档,座椅水平到30%,坐盆到25%,靠背到120度,腿托到125度",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_heating", "parameters": {"state": "on", "level": 1}},
|
||||
{"target": "passenger", "capability": "seat.set_ventilation", "parameters": {"state": "on", "level": 1}},
|
||||
{"target": "passenger", "capability": "seat.set_longitudinal_position", "parameters": {"position_pct": 30}},
|
||||
{"target": "passenger", "capability": "seat.set_cushion_tilt", "parameters": {"position_pct": 25}},
|
||||
{"target": "passenger", "capability": "seat.set_backrest_recline", "parameters": {"angle_deg": 120}},
|
||||
{"target": "passenger", "capability": "seat.set_legrest_height", "parameters": {"angle_deg": 125}}
|
||||
],
|
||||
"bench_check": "六项操作逐项成功,每项均有对应ACK/状态回读;任一失败后不继续宣称整句成功。"
|
||||
},
|
||||
{
|
||||
"id": "DEFAULT-01",
|
||||
"utterance": "打开座椅通风",
|
||||
"operations": [
|
||||
{"target": "passenger", "capability": "seat.set_ventilation", "parameters": {"state": "on"}}
|
||||
],
|
||||
"bench_check": "未指定座位时生成且仅生成副驾通风操作。"
|
||||
}
|
||||
],
|
||||
"safety_cases": [
|
||||
{
|
||||
"id": "SAFE-01",
|
||||
"utterance": "打开主驾驶座椅通风",
|
||||
"expected": "needs_confirmation",
|
||||
"reason": "当前仅支持副驾;显式主驾请求不得重定向到副驾。"
|
||||
},
|
||||
{
|
||||
"id": "SAFE-02",
|
||||
"utterance": "把副驾驶腿托伸长到50%",
|
||||
"expected": "hardware_safety_gate",
|
||||
"reason": "当前台架没有独立腿托伸缩轴,必须在打开CAN前拒绝。"
|
||||
},
|
||||
{
|
||||
"id": "SAFE-03",
|
||||
"utterance": "副驾驶座椅加热开到1档",
|
||||
"expected": "hardware_safety_gate",
|
||||
"reason": "错误或缺失硬件授权令牌必须返回403。"
|
||||
},
|
||||
{
|
||||
"id": "RECOVERY-01",
|
||||
"utterance": "副驾驶座椅加热开到1档",
|
||||
"expected": "program_then_execute",
|
||||
"reason": "无匹配周期反馈时自动烧写仓库固件,复验通过后才允许动作。"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from cognitive_agent.application.domain_runtime import ConfiguredDomainRuntime
|
||||
from cognitive_agent.application.mode_profile import ModeProfileRegistry
|
||||
@@ -254,7 +254,24 @@ class MultiDomainServiceTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(
|
||||
by_capability["seat.set_legrest_height"]["parameters"],
|
||||
{"position_pct": 87, "angle_deg": 163},
|
||||
{"position_pct": 94, "angle_deg": 163},
|
||||
)
|
||||
|
||||
def test_named_mode_profile_suppresses_generic_local_parser_ambiguity(self) -> None:
|
||||
result = self.service.process(
|
||||
"副驾进入零重力模式",
|
||||
"local",
|
||||
"conv-zero-gravity-local-mode",
|
||||
vehicle_id="denza_n7",
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(result["matched_modes"][0]["id"], "zero_gravity")
|
||||
self.assertFalse(
|
||||
any(
|
||||
issue.get("code") == "AMBIGUOUS_INTENT"
|
||||
for issue in result["validation"]
|
||||
)
|
||||
)
|
||||
|
||||
def test_named_mode_profile_combines_with_temporary_roof_request(self) -> None:
|
||||
@@ -294,6 +311,252 @@ class MultiDomainServiceTests(unittest.TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_hardware_orchestration_uses_physical_passenger_seat_and_virtual_roof(
|
||||
self,
|
||||
) -> None:
|
||||
model_output = {
|
||||
"detected_domains": ["seat", "roof"],
|
||||
"requests": [
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_backrest_recline",
|
||||
"target_scope": "passenger",
|
||||
"operation": "set",
|
||||
"parameters": [{"name": "position_pct", "value": 55}],
|
||||
"reason": "调整副驾靠背。",
|
||||
},
|
||||
{
|
||||
"domain": "roof",
|
||||
"capability": "roof.set_transmittance",
|
||||
"target_scope": "vehicle",
|
||||
"operation": "set",
|
||||
"parameters": [{"name": "transmittance_pct", "value": 20}],
|
||||
"reason": "降低天幕透光率。",
|
||||
},
|
||||
],
|
||||
"ambiguities": [],
|
||||
"decision_summary": ["执行副驾与天幕联合模式。"],
|
||||
"confidence": 0.96,
|
||||
}
|
||||
physical_state = {
|
||||
"position": {
|
||||
"longitudinal": {"position_pct": 51, "position_valid": True},
|
||||
"cushion_tilt": {"position_pct": 11, "position_valid": True},
|
||||
"backrest_recline": {"position_pct": 54, "position_valid": True},
|
||||
"legrest_height": {"position_pct": 0, "position_valid": True},
|
||||
},
|
||||
"ventilation": {"state": "off", "level": 0},
|
||||
"heating": {"state": "off", "level": 0},
|
||||
"captured_at": "2026-07-31T04:00:00Z",
|
||||
"complete": True,
|
||||
"missing": [],
|
||||
}
|
||||
|
||||
def execute_hardware(plan, config, devices):
|
||||
del plan, devices
|
||||
operation = config["spec"]["operations"][0]
|
||||
return {
|
||||
"apiVersion": "cognitive-agent/v1alpha1",
|
||||
"kind": "ExecutionReport",
|
||||
"metadata": {
|
||||
"id": "report-hardware-test",
|
||||
"name": "T2G seat hardware execution report",
|
||||
"version": "1.0.0",
|
||||
"request_id": config["metadata"]["request_id"],
|
||||
"labels": {"simulation": "false"},
|
||||
},
|
||||
"spec": {
|
||||
"mode": "hil",
|
||||
"status": "succeeded",
|
||||
"started_at": "2026-07-31T04:00:00Z",
|
||||
"finished_at": "2026-07-31T04:00:01Z",
|
||||
"physical_state": physical_state,
|
||||
"results": [
|
||||
{
|
||||
"ref": operation["id"],
|
||||
"domain": "seat",
|
||||
"status": "succeeded",
|
||||
"message": "physical target reached",
|
||||
"telemetry": {
|
||||
"motion_axis": "backrest_recline",
|
||||
"effective_position_pct": 54,
|
||||
"position_valid": True,
|
||||
"simulated": False,
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
hardware_executor = MagicMock()
|
||||
hardware_executor.execute.side_effect = execute_hardware
|
||||
initial_driver_backrest = self.service.conversations.snapshot(
|
||||
"conv-mixed-hardware-success"
|
||||
)["devices"]["driver"]["position"]["backrest_recline"]["position_pct"]
|
||||
with (
|
||||
patch.object(self.service.openai, "interpret", return_value=model_output),
|
||||
patch.object(type(self.service.hardware_settings), "authorize"),
|
||||
patch.object(
|
||||
type(self.service.hardware_settings),
|
||||
"validate_function_config",
|
||||
),
|
||||
patch.object(
|
||||
self.service,
|
||||
"_new_hardware_executor",
|
||||
return_value=hardware_executor,
|
||||
) as executor_factory,
|
||||
):
|
||||
result = self.service.process(
|
||||
"副驾靠背调到55%,天幕透光率调到20%",
|
||||
"openai",
|
||||
"conv-mixed-hardware-success",
|
||||
execution_mode="hardware",
|
||||
approval_token="test-token",
|
||||
vehicle_id="denza_n7",
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(result["execution_report"]["spec"]["mode"], "hil")
|
||||
self.assertEqual(
|
||||
result["execution_report"]["metadata"]["labels"]["simulation"],
|
||||
"mixed",
|
||||
)
|
||||
executor_factory.assert_called_once_with()
|
||||
hardware_executor.execute.assert_called_once()
|
||||
by_domain = {
|
||||
item["domain"]: item
|
||||
for item in result["execution_report"]["spec"]["results"]
|
||||
}
|
||||
self.assertFalse(by_domain["seat"]["telemetry"]["simulated"])
|
||||
self.assertTrue(by_domain["roof"]["telemetry"]["simulated"])
|
||||
self.assertEqual(
|
||||
result["actuator_state"]["passenger"]["position"][
|
||||
"backrest_recline"
|
||||
]["position_pct"],
|
||||
54,
|
||||
)
|
||||
self.assertEqual(
|
||||
result["actuator_state"]["driver"]["position"]["backrest_recline"][
|
||||
"position_pct"
|
||||
],
|
||||
initial_driver_backrest,
|
||||
)
|
||||
self.assertEqual(result["domain_state"]["roof"]["transmittance_pct"], 20)
|
||||
|
||||
def test_hardware_orchestration_failure_still_synchronizes_physical_seat(
|
||||
self,
|
||||
) -> None:
|
||||
model_output = {
|
||||
"detected_domains": ["seat", "roof"],
|
||||
"requests": [
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_longitudinal_position",
|
||||
"target_scope": "passenger",
|
||||
"operation": "set",
|
||||
"parameters": [{"name": "position_pct", "value": 100}],
|
||||
"reason": "移动副驾。",
|
||||
},
|
||||
{
|
||||
"domain": "roof",
|
||||
"capability": "roof.set_transmittance",
|
||||
"target_scope": "vehicle",
|
||||
"operation": "set",
|
||||
"parameters": [{"name": "transmittance_pct", "value": 20}],
|
||||
"reason": "降低天幕透光率。",
|
||||
},
|
||||
],
|
||||
"ambiguities": [],
|
||||
"decision_summary": ["执行副驾与天幕联合模式。"],
|
||||
"confidence": 0.96,
|
||||
}
|
||||
hardware_executor = MagicMock()
|
||||
|
||||
def execute_hardware(plan, config, devices):
|
||||
del plan, devices
|
||||
return {
|
||||
"apiVersion": "cognitive-agent/v1alpha1",
|
||||
"kind": "ExecutionReport",
|
||||
"metadata": {
|
||||
"id": "report-hardware-failure-test",
|
||||
"name": "T2G seat hardware execution report",
|
||||
"version": "1.0.0",
|
||||
"request_id": config["metadata"]["request_id"],
|
||||
"labels": {"simulation": "false"},
|
||||
},
|
||||
"spec": {
|
||||
"mode": "hil",
|
||||
"status": "failed",
|
||||
"started_at": "2026-07-31T04:00:00Z",
|
||||
"finished_at": "2026-07-31T04:00:01Z",
|
||||
"physical_state": {
|
||||
"position": {
|
||||
"longitudinal": {
|
||||
"position_pct": 63,
|
||||
"position_valid": True,
|
||||
}
|
||||
},
|
||||
"captured_at": "2026-07-31T04:00:01Z",
|
||||
"complete": False,
|
||||
"missing": ["cushion_tilt", "backrest_recline", "legrest_height"],
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"ref": config["spec"]["operations"][0]["id"],
|
||||
"domain": "seat",
|
||||
"status": "failed",
|
||||
"message": "Motor0 TIMEOUT",
|
||||
"telemetry": {"simulated": False},
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
hardware_executor.execute.side_effect = execute_hardware
|
||||
initial_roof = self.service.conversations.snapshot(
|
||||
"conv-mixed-hardware-failure"
|
||||
)["domain_devices"]["roof"]["transmittance_pct"]
|
||||
with (
|
||||
patch.object(self.service.openai, "interpret", return_value=model_output),
|
||||
patch.object(type(self.service.hardware_settings), "authorize"),
|
||||
patch.object(
|
||||
type(self.service.hardware_settings),
|
||||
"validate_function_config",
|
||||
),
|
||||
patch.object(
|
||||
self.service,
|
||||
"_new_hardware_executor",
|
||||
return_value=hardware_executor,
|
||||
),
|
||||
):
|
||||
result = self.service.process(
|
||||
"副驾水平移到100%,天幕透光率调到20%",
|
||||
"openai",
|
||||
"conv-mixed-hardware-failure",
|
||||
execution_mode="hardware",
|
||||
approval_token="test-token",
|
||||
vehicle_id="denza_n7",
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "execution_failed")
|
||||
self.assertEqual(result["execution_report"]["spec"]["status"], "failed")
|
||||
self.assertEqual(
|
||||
result["actuator_state"]["passenger"]["position"]["longitudinal"][
|
||||
"position_pct"
|
||||
],
|
||||
63,
|
||||
)
|
||||
self.assertEqual(
|
||||
result["actuator_state"]["passenger"]["position"]["longitudinal"][
|
||||
"feedback_source"
|
||||
],
|
||||
"physical_can",
|
||||
)
|
||||
self.assertEqual(
|
||||
result["domain_state"]["roof"]["transmittance_pct"],
|
||||
initial_roof,
|
||||
)
|
||||
|
||||
def test_reference_mode_library_modes_execute_for_denza(self) -> None:
|
||||
model_output = {
|
||||
"detected_domains": ["seat"],
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -42,7 +42,7 @@ class ModeProfileRegistryTests(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
by_capability["seat.set_legrest_height"]["parameters"],
|
||||
[
|
||||
{"name": "position_pct", "value": 87},
|
||||
{"name": "position_pct", "value": 94},
|
||||
{"name": "angle_deg", "value": 163},
|
||||
],
|
||||
)
|
||||
@@ -91,17 +91,17 @@ class ModeProfileRegistryTests(unittest.TestCase):
|
||||
def test_denza_mode_positions_use_corrected_n7_zero_points(self) -> None:
|
||||
registry = ModeProfileRegistry(REPOSITORY_ROOT / "config" / "modes")
|
||||
expected_positions = {
|
||||
"baby_nap": (81, 30),
|
||||
"calf_decompression_float": (51, 75),
|
||||
"cruise_sofa": (51, 30),
|
||||
"easy_entry_exit": (100, 0),
|
||||
"electric_beach_chair": (100, 0),
|
||||
"leisure_lounger": (51, 30),
|
||||
"meditation_space": (100, 44),
|
||||
"nap": (0, 0),
|
||||
"office": (72, 22),
|
||||
"queen_passenger_driving": (72, 38),
|
||||
"zero_gravity": (100, 100),
|
||||
"baby_nap": (81, 30, 41, 0),
|
||||
"calf_decompression_float": (51, 75, 0, 100),
|
||||
"cruise_sofa": (51, 30, 44, 53),
|
||||
"easy_entry_exit": (100, 0, 0, 0),
|
||||
"electric_beach_chair": (100, 0, 100, 60),
|
||||
"leisure_lounger": (51, 30, 66, 76),
|
||||
"meditation_space": (100, 44, 44, 58),
|
||||
"nap": (100, 100, 81, 94),
|
||||
"office": (72, 22, 22, 21),
|
||||
"queen_passenger_driving": (72, 38, 44, 31),
|
||||
"zero_gravity": (100, 100, 81, 94),
|
||||
}
|
||||
|
||||
profiles = {
|
||||
@@ -124,6 +124,8 @@ class ModeProfileRegistryTests(unittest.TestCase):
|
||||
for capability in (
|
||||
"seat.set_longitudinal_position",
|
||||
"seat.set_cushion_tilt",
|
||||
"seat.set_backrest_recline",
|
||||
"seat.set_legrest_height",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -15,17 +15,17 @@ INTENT = {
|
||||
{
|
||||
"domain": "seat",
|
||||
"capability": "seat.set_ventilation",
|
||||
"target_scope": "driver",
|
||||
"target_scope": "passenger",
|
||||
"operation": "activate",
|
||||
"parameters": [
|
||||
{"name": "state", "value": "on"},
|
||||
{"name": "level", "value": 1},
|
||||
],
|
||||
"reason": "Driver requested seat ventilation.",
|
||||
"reason": "Passenger requested seat ventilation.",
|
||||
}
|
||||
],
|
||||
"ambiguities": [],
|
||||
"decision_summary": ["打开主驾座椅通风"],
|
||||
"decision_summary": ["打开副驾座椅通风"],
|
||||
"confidence": 0.99,
|
||||
}
|
||||
|
||||
@@ -59,11 +59,11 @@ class OpenAIResponsesTests(unittest.TestCase):
|
||||
return_value=FakeResponse(response_body),
|
||||
) as urlopen:
|
||||
result = interpreter.interpret(
|
||||
"主驾还有点闷",
|
||||
"副驾还有点闷",
|
||||
{
|
||||
"last_target": "driver",
|
||||
"last_target": "passenger",
|
||||
"devices": {
|
||||
"driver": {
|
||||
"passenger": {
|
||||
"ventilation": {"state": "on", "level": 1}
|
||||
}
|
||||
},
|
||||
@@ -100,7 +100,7 @@ class OpenAIResponsesTests(unittest.TestCase):
|
||||
"cognitive_agent.infrastructure.llm.openai_responses.urllib.request.urlopen",
|
||||
return_value=FakeResponse(stream_body),
|
||||
) as urlopen:
|
||||
result = interpreter.interpret("打开主驾座椅通风")
|
||||
result = interpreter.interpret("打开副驾座椅通风")
|
||||
|
||||
request_payload = json.loads(urlopen.call_args.args[0].data.decode("utf-8"))
|
||||
self.assertEqual(result, INTENT)
|
||||
@@ -122,7 +122,7 @@ class OpenAIResponsesTests(unittest.TestCase):
|
||||
) as urlopen, patch(
|
||||
"cognitive_agent.infrastructure.llm.openai_responses.time.sleep"
|
||||
) as sleep:
|
||||
result = interpreter.interpret("打开主驾座椅通风")
|
||||
result = interpreter.interpret("打开副驾座椅通风")
|
||||
|
||||
self.assertEqual(result, INTENT)
|
||||
self.assertEqual(urlopen.call_count, 2)
|
||||
@@ -158,7 +158,7 @@ class OpenAIResponsesTests(unittest.TestCase):
|
||||
) as urlopen, patch(
|
||||
"cognitive_agent.infrastructure.llm.openai_responses.time.sleep"
|
||||
) as sleep:
|
||||
result = interpreter.interpret("打开主驾座椅通风")
|
||||
result = interpreter.interpret("打开副驾座椅通风")
|
||||
|
||||
self.assertEqual(result, INTENT)
|
||||
self.assertEqual(urlopen.call_count, 2)
|
||||
|
||||
@@ -32,7 +32,7 @@ class VehicleProfileTests(unittest.TestCase):
|
||||
self.assertEqual(profile.seat_motors["cushion_tilt"].positive_direction, "down")
|
||||
self.assertEqual(profile.seat_motors["backrest_recline"].range, 68)
|
||||
self.assertEqual(profile.seat_motors["backrest_recline"].hall_total_pulses, 917)
|
||||
self.assertEqual(profile.seat_motors["legrest_height"].range, 78)
|
||||
self.assertEqual(profile.seat_motors["legrest_height"].range, 72)
|
||||
self.assertEqual(profile.seat_motors["legrest_height"].hall_total_pulses, 647)
|
||||
|
||||
def test_converts_relative_physical_delta_to_absolute_percent(self) -> None:
|
||||
@@ -61,13 +61,13 @@ class VehicleProfileTests(unittest.TestCase):
|
||||
"kind": "included_angle",
|
||||
"zero_deg": 95,
|
||||
"max_deg": 163,
|
||||
"provisional": True,
|
||||
"provisional": False,
|
||||
})
|
||||
self.assertEqual(legrest["display_angle"], {
|
||||
"kind": "included_angle",
|
||||
"zero_deg": 95,
|
||||
"max_deg": 173,
|
||||
"provisional": True,
|
||||
"max_deg": 167,
|
||||
"provisional": False,
|
||||
})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user