feat: improve mode and peripheral orchestration
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from cognitive_agent.server import SeatDemoHandler
|
||||
|
||||
|
||||
def _result(mode: str, *, status: str = "succeeded") -> dict[str, object]:
|
||||
return {
|
||||
"request_id": f"req-{mode}-{status}",
|
||||
"status": status,
|
||||
"success": status == "succeeded",
|
||||
"execution_mode": mode,
|
||||
"decision_summary": [],
|
||||
"function_configs": {},
|
||||
"command_plans": {},
|
||||
"trace_events": [],
|
||||
"operation_results": [],
|
||||
"execution_report": None,
|
||||
"control_transport": "test",
|
||||
}
|
||||
|
||||
|
||||
def _entries() -> list[dict[str, object]]:
|
||||
return [
|
||||
{
|
||||
"index": 0,
|
||||
"clause": "打开闪烁灯",
|
||||
"capability": {
|
||||
"id": "ir-slot:flash:2",
|
||||
"device_id": "flash",
|
||||
"device_name": "闪烁灯",
|
||||
"action": "打开",
|
||||
"slot": 2,
|
||||
"parse_source": "local_rule",
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_hardware_branches_preflight_then_start_together() -> None:
|
||||
handler = object.__new__(SeatDemoHandler)
|
||||
lock = threading.Lock()
|
||||
preflight_completed: set[str] = set()
|
||||
hardware_started: dict[str, float] = {}
|
||||
|
||||
def mark_preflight(name: str) -> dict[str, object]:
|
||||
time.sleep(0.03)
|
||||
with lock:
|
||||
preflight_completed.add(name)
|
||||
return _result("dry_run")
|
||||
|
||||
def execute_can() -> dict[str, object]:
|
||||
with lock:
|
||||
assert preflight_completed == {"can", "peripheral"}
|
||||
hardware_started["can"] = time.perf_counter()
|
||||
time.sleep(0.12)
|
||||
return _result("hardware")
|
||||
|
||||
def execute_peripheral(
|
||||
_utterance: str,
|
||||
_entries_value: list[dict[str, object]],
|
||||
*,
|
||||
execution_mode: str,
|
||||
**_kwargs: object,
|
||||
) -> dict[str, object]:
|
||||
if execution_mode == "dry_run":
|
||||
return mark_preflight("peripheral")
|
||||
with lock:
|
||||
assert preflight_completed == {"can", "peripheral"}
|
||||
hardware_started["peripheral"] = time.perf_counter()
|
||||
time.sleep(0.12)
|
||||
return _result("hardware")
|
||||
|
||||
with patch.object(
|
||||
handler,
|
||||
"_execute_local_peripheral_actions",
|
||||
side_effect=execute_peripheral,
|
||||
):
|
||||
result = handler._execute_can_and_peripheral_branches(
|
||||
utterance="打开空调和闪烁灯",
|
||||
entries=_entries(),
|
||||
execution_mode="hardware",
|
||||
provider="local",
|
||||
conversation_id="conv-parallel-unit",
|
||||
can_execute=execute_can,
|
||||
can_preflight=lambda: mark_preflight("can"),
|
||||
parallel_eligible=True,
|
||||
)
|
||||
|
||||
assert result["status"] == "succeeded"
|
||||
assert result["parallel_dispatch"]["enabled"] is True
|
||||
assert result["parallel_dispatch"]["preflight_status"] == "succeeded"
|
||||
assert abs(hardware_started["can"] - hardware_started["peripheral"]) < 0.05
|
||||
dispatch = result["parallel_dispatch"]["dispatch"]
|
||||
branch_durations = [
|
||||
item["duration_ms"] for item in dispatch["branches"].values()
|
||||
]
|
||||
assert dispatch["total_ms"] < sum(branch_durations)
|
||||
|
||||
|
||||
def test_failed_preflight_starts_neither_hardware_branch() -> None:
|
||||
handler = object.__new__(SeatDemoHandler)
|
||||
hardware_calls: list[str] = []
|
||||
|
||||
def execute_peripheral(
|
||||
_utterance: str,
|
||||
_entries_value: list[dict[str, object]],
|
||||
*,
|
||||
execution_mode: str,
|
||||
**_kwargs: object,
|
||||
) -> dict[str, object]:
|
||||
if execution_mode == "hardware":
|
||||
hardware_calls.append("peripheral")
|
||||
return _result(execution_mode)
|
||||
|
||||
with patch.object(
|
||||
handler,
|
||||
"_execute_local_peripheral_actions",
|
||||
side_effect=execute_peripheral,
|
||||
):
|
||||
result = handler._execute_can_and_peripheral_branches(
|
||||
utterance="打开空调和闪烁灯",
|
||||
entries=_entries(),
|
||||
execution_mode="hardware",
|
||||
provider="local",
|
||||
conversation_id="conv-parallel-preflight-failure",
|
||||
can_execute=lambda: hardware_calls.append("can") or _result("hardware"),
|
||||
can_preflight=lambda: _result(
|
||||
"dry_run",
|
||||
status="validation_failed",
|
||||
),
|
||||
parallel_eligible=True,
|
||||
)
|
||||
|
||||
assert hardware_calls == []
|
||||
assert result["status"] == "validation_failed"
|
||||
assert result["execution_mode"] == "dry_run"
|
||||
assert result["hardware_execution_allowed"] is False
|
||||
assert result["parallel_dispatch"]["enabled"] is False
|
||||
assert result["parallel_dispatch"]["preflight_status"] == "failed"
|
||||
|
||||
|
||||
def test_wireless_parallel_dispatch_does_not_require_local_bench_lease() -> None:
|
||||
handler = object.__new__(SeatDemoHandler)
|
||||
authorization_calls: list[str | None] = []
|
||||
handler.service = SimpleNamespace(
|
||||
hardware_access=SimpleNamespace(
|
||||
ensure_authorized=lambda client_id: authorization_calls.append(client_id)
|
||||
)
|
||||
)
|
||||
|
||||
handler._ensure_parallel_hardware_authorized(
|
||||
execution_mode="hardware",
|
||||
control_transport="wireless_mqtt",
|
||||
hardware_access_bypass=False,
|
||||
hardware_client_id=None,
|
||||
)
|
||||
|
||||
assert authorization_calls == []
|
||||
|
||||
|
||||
def test_wired_parallel_dispatch_keeps_local_bench_lease_gate() -> None:
|
||||
handler = object.__new__(SeatDemoHandler)
|
||||
authorization_calls: list[str | None] = []
|
||||
handler.service = SimpleNamespace(
|
||||
hardware_access=SimpleNamespace(
|
||||
ensure_authorized=lambda client_id: authorization_calls.append(client_id)
|
||||
)
|
||||
)
|
||||
|
||||
handler._ensure_parallel_hardware_authorized(
|
||||
execution_mode="hardware",
|
||||
control_transport="wired_can",
|
||||
hardware_access_bypass=False,
|
||||
hardware_client_id="hwc-wired-parallel-test",
|
||||
)
|
||||
|
||||
assert authorization_calls == ["hwc-wired-parallel-test"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"utterance",
|
||||
(
|
||||
"先打开空调,再打开闪烁灯",
|
||||
"打开空调,然后打开闪烁灯",
|
||||
"打开空调,2秒后打开闪烁灯",
|
||||
),
|
||||
)
|
||||
def test_explicit_cross_transport_order_disables_parallelism(
|
||||
utterance: str,
|
||||
) -> None:
|
||||
assert SeatDemoHandler._mixed_request_requires_transport_order(utterance)
|
||||
|
||||
|
||||
def test_ineligible_flow_keeps_existing_can_then_peripheral_order() -> None:
|
||||
handler = object.__new__(SeatDemoHandler)
|
||||
calls: list[str] = []
|
||||
|
||||
def execute_peripheral(
|
||||
_utterance: str,
|
||||
_entries_value: list[dict[str, object]],
|
||||
*,
|
||||
execution_mode: str,
|
||||
**_kwargs: object,
|
||||
) -> dict[str, object]:
|
||||
calls.append(f"peripheral:{execution_mode}")
|
||||
return _result(execution_mode)
|
||||
|
||||
def unexpected_preflight() -> dict[str, object]:
|
||||
raise AssertionError("ordered flow must not run a duplicate preflight")
|
||||
|
||||
with patch.object(
|
||||
handler,
|
||||
"_execute_local_peripheral_actions",
|
||||
side_effect=execute_peripheral,
|
||||
):
|
||||
result = handler._execute_can_and_peripheral_branches(
|
||||
utterance="先打开空调,再打开闪烁灯",
|
||||
entries=_entries(),
|
||||
execution_mode="hardware",
|
||||
provider="local",
|
||||
conversation_id="conv-ordered-unit",
|
||||
can_execute=lambda: calls.append("can:hardware") or _result("hardware"),
|
||||
can_preflight=unexpected_preflight,
|
||||
parallel_eligible=False,
|
||||
)
|
||||
|
||||
assert calls == ["can:hardware", "peripheral:hardware"]
|
||||
assert result["status"] == "succeeded"
|
||||
assert "parallel_dispatch" not in result
|
||||
|
||||
|
||||
def test_saved_static_mode_uses_parallel_can_and_peripheral_path() -> None:
|
||||
handler = object.__new__(SeatDemoHandler)
|
||||
can_modes: list[str] = []
|
||||
peripheral_modes: list[str] = []
|
||||
persisted: list[dict[str, object]] = []
|
||||
profile = SimpleNamespace(
|
||||
id="mode-parallel",
|
||||
schedule=None,
|
||||
command_flow={"execution_steps": []},
|
||||
public_dict=lambda: {"id": "mode-parallel", "name": "并行模式"},
|
||||
)
|
||||
|
||||
def execute_mode_profile_card(
|
||||
_mode_id: str,
|
||||
**kwargs: object,
|
||||
) -> dict[str, object]:
|
||||
mode = str(kwargs.get("execution_mode") or "")
|
||||
can_modes.append(mode)
|
||||
return _result(mode)
|
||||
|
||||
handler.service = SimpleNamespace(
|
||||
execute_mode_profile_card=execute_mode_profile_card,
|
||||
persist_composed_result=lambda result: persisted.append(result),
|
||||
)
|
||||
|
||||
def execute_peripheral(
|
||||
_utterance: str,
|
||||
_entries_value: list[dict[str, object]],
|
||||
*,
|
||||
execution_mode: str,
|
||||
**_kwargs: object,
|
||||
) -> dict[str, object]:
|
||||
peripheral_modes.append(execution_mode)
|
||||
return _result(execution_mode)
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
handler,
|
||||
"_exact_local_peripheral_mode_invocation",
|
||||
return_value=profile,
|
||||
),
|
||||
patch.object(handler, "_exact_n7_mode_invocation", return_value=None),
|
||||
patch.object(handler, "_saved_mode_infrared_entries", return_value=_entries()),
|
||||
patch.object(
|
||||
handler,
|
||||
"_execute_local_peripheral_actions",
|
||||
side_effect=execute_peripheral,
|
||||
),
|
||||
):
|
||||
result = handler._execute_n7_named_mode_request(
|
||||
"/api/requests",
|
||||
{
|
||||
"utterance": "进入并行模式",
|
||||
"execution_mode": "hardware",
|
||||
"provider": "local",
|
||||
"conversation_id": "conv-saved-parallel",
|
||||
"vehicle_id": "denza_n7",
|
||||
"control_transport": "wired_can",
|
||||
},
|
||||
)
|
||||
|
||||
assert result is not None
|
||||
assert can_modes == ["dry_run", "hardware"]
|
||||
assert peripheral_modes == ["dry_run", "hardware"]
|
||||
assert result["parallel_dispatch"]["enabled"] is True
|
||||
assert result["provider_used"] == "saved_mode_command_flow"
|
||||
assert persisted == [result]
|
||||
Reference in New Issue
Block a user