Files
Cognitive_Agent/tests/integration/test_assistant_capability_execution_matrix.py

1069 lines
34 KiB
Python

from __future__ import annotations
import os
import queue
from collections.abc import Callable, Iterator, Mapping
from dataclasses import dataclass
from pathlib import Path
import pytest
from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog
from cognitive_agent.application.assistant_plan_guard import guard_assistant_plan
from cognitive_agent.application.codex_assistant import (
ActiveCodexTurn,
CodexAssistantService,
CodexVehicleToolError,
)
from cognitive_agent.application.mode_profile import ModeProfileRegistry
from cognitive_agent.application.seat_service import SeatDemoService
ROOT = Path(__file__).resolve().parents[2]
REPOSITORY_MODE_IDS = frozenset(
path.stem
for path in (ROOT / "config" / "modes" / "vehicles" / "denza_n7").glob(
"*.yaml"
)
)
def _repository_catalog_modes(modes: object) -> list[Mapping[str, object]]:
assert REPOSITORY_MODE_IDS
assert isinstance(modes, list)
mode_ids = {
str(mode.get("id") or "")
for mode in modes
if isinstance(mode, Mapping)
}
assert "" not in mode_ids
assert len(mode_ids) == len(modes), "catalog mode ids must remain unique"
assert REPOSITORY_MODE_IDS <= mode_ids
repository_modes = [
mode
for mode in modes
if isinstance(mode, Mapping) and str(mode.get("id") or "") in REPOSITORY_MODE_IDS
]
assert {str(mode["id"]) for mode in repository_modes} == REPOSITORY_MODE_IDS
return repository_modes
@dataclass(frozen=True)
class _CapabilityCase:
capability: str
message: str
target: str
parameters: Mapping[str, object]
operation: str = "set"
expected_effect: str = "action"
@property
def domain(self) -> str:
return self.capability.split(".", 1)[0]
def assistant_plan(self) -> dict[str, object]:
return {
"detected_domains": [self.domain],
"requests": [
{
"domain": self.domain,
"capability": self.capability,
"target_scope": self.target,
"operation": self.operation,
"duration_ms": None,
"parameters": [
{"name": name, "value": value} for name, value in self.parameters.items()
],
"reason": "将用户明确表达的控制目标规范化为登记能力。",
}
],
"ambiguities": [],
"decision_summary": ["仅规范化原始请求明确涉及的能力。"],
"confidence": 0.95,
}
CAPABILITY_CASES = (
_CapabilityCase(
"ambient_light.set_brightness_level",
"氛围灯亮度调到3档",
"vehicle",
{"brightness_level": 3},
),
_CapabilityCase(
"ambient_light.set_color_index",
"设置氛围灯颜色编号32",
"vehicle",
{"color_index": 32},
),
_CapabilityCase(
"ambient_light.set_illumination",
"氛围灯调成蓝色,亮度50%",
"vehicle",
{"color_hex": "#0000FF", "brightness_pct": 50},
),
_CapabilityCase(
"ambient_light.set_mode",
"氛围灯设置为影院模式",
"vehicle",
{"mode": "cinema"},
),
_CapabilityCase(
"ambient_light.set_power",
"打开氛围灯",
"vehicle",
{"state": "on"},
"activate",
),
_CapabilityCase(
"climate.set_ac",
"空调压缩机设为开启状态",
"vehicle",
{"state": "on"},
"activate",
),
_CapabilityCase(
"climate.set_control_mode",
"空调设置为自动控制模式",
"vehicle",
{"mode": "auto"},
),
_CapabilityCase(
"climate.set_cycle_mode",
"空调设置为内循环",
"vehicle",
{"mode": "internal"},
),
_CapabilityCase(
"climate.set_fan_level",
"空调风量调到3档",
"vehicle",
{"level": 3},
),
_CapabilityCase(
"climate.set_front_defrost",
"打开前挡风玻璃除雾",
"vehicle",
{"state": "on"},
"activate",
),
_CapabilityCase(
"climate.set_outlet_mode",
"空调出风模式设置为吹面吹脚",
"vehicle",
{"mode": "face_feet"},
),
_CapabilityCase(
"climate.set_power",
"打开空调",
"vehicle",
{"state": "on"},
"activate",
),
_CapabilityCase(
"climate.set_temperature",
"空调温度调到23度",
"vehicle",
{"temperature_c": 23},
),
_CapabilityCase(
"door.set_rear_lock",
"后门锁上锁",
"rear",
{"state": "lock"},
),
_CapabilityCase(
"interior_light.set_indoor_power",
"打开车内灯",
"vehicle",
{"state": "on"},
"activate",
),
_CapabilityCase(
"interior_light.set_reading_power",
"打开所有阅读灯",
"all",
{"state": "on"},
"activate",
),
_CapabilityCase(
"outlet.set_calibrated_position",
"主驾左风口标定坐标水平-20垂直30",
"driver_left",
{"horizontal_value": -20, "vertical_value": 30},
),
_CapabilityCase(
"outlet.set_position_percent",
"主驾左风口水平25%垂直75%",
"driver_left",
{"horizontal_pct": 25, "vertical_pct": 75},
),
_CapabilityCase(
"outlet.set_switch_state",
"打开主驾左出风口",
"driver_left",
{"state": "open"},
),
_CapabilityCase(
"roof.set_automatic_dimming",
"开启智能天幕自动调光",
"vehicle",
{"state": "on"},
"activate",
),
_CapabilityCase(
"roof.set_shade_motion",
"打开遮阳帘",
"vehicle",
{"motion": "open"},
),
_CapabilityCase(
"roof.set_shade_position",
"遮阳帘打开30%",
"vehicle",
{"position_pct": 30},
),
_CapabilityCase(
"roof.set_transmittance",
"智能天幕透光率调到30%",
"vehicle",
{"transmittance_pct": 30},
),
_CapabilityCase(
"seat.apply_posture_preset",
"调用座椅预设矩阵姿态",
"passenger",
{"preset_name": "矩阵姿态"},
"apply",
"preset_apply",
),
_CapabilityCase(
"seat.recall_driver_memory_native",
"召回主驾座椅2号记忆位置",
"driver",
{"slot": 2},
"apply",
),
_CapabilityCase(
"seat.save_posture_preset",
"保存当前座椅姿态为矩阵姿态",
"passenger",
{"preset_name": "矩阵姿态"},
"save",
"preset_save",
),
_CapabilityCase(
"seat.set_backrest_recline",
"副驾靠背调到120度",
"passenger",
{"angle_deg": 120},
),
_CapabilityCase(
"seat.set_cushion_tilt",
"副驾座椅坐垫高度调到40%",
"passenger",
{"position_pct": 40},
),
_CapabilityCase(
"seat.set_driver_axis_native",
"主驾座椅水平向前移动500毫秒",
"driver",
{"axis": "horizontal", "direction": "forward", "duration_ms": 500},
),
_CapabilityCase(
"seat.set_driver_heating_native",
"打开主驾座椅加热低档",
"driver",
{"state": "on", "level": 2},
"activate",
),
_CapabilityCase(
"seat.set_driver_massage_native",
"主驾座椅按摩强度开到3档",
"driver",
{"action": "strength", "strength": 3},
),
_CapabilityCase(
"seat.set_driver_position_native",
"主驾座椅记忆位置调到2号",
"driver",
{"slot": 2},
"apply",
),
_CapabilityCase(
"seat.set_driver_ventilation_native",
"打开主驾座椅通风低档",
"driver",
{"state": "on", "level": 2},
"activate",
),
_CapabilityCase(
"seat.set_heating",
"打开副驾座椅加热3档",
"passenger",
{"state": "on", "level": 3},
"activate",
),
_CapabilityCase(
"seat.set_legrest_extension",
"副驾腿托伸出到40%",
"passenger",
{"position_pct": 40},
),
_CapabilityCase(
"seat.set_legrest_height",
"副驾腿托升高到40%",
"passenger",
{"position_pct": 40},
),
_CapabilityCase(
"seat.set_longitudinal_position",
"副驾座椅纵向调到40%",
"passenger",
{"position_pct": 40},
),
_CapabilityCase(
"seat.set_ventilation",
"打开副驾座椅通风3档",
"passenger",
{"state": "on", "level": 3},
"activate",
),
_CapabilityCase(
"seat.start_dynamic_mode",
"启动副驾木马模式",
"passenger",
{"mode_id": 1},
"activate",
),
_CapabilityCase(
"seat.stop_dynamic_mode",
"停止副驾木马模式",
"passenger",
{},
"deactivate",
),
_CapabilityCase(
"window.set_motion",
"打开左前车窗",
"front_left",
{"motion": "open"},
),
_CapabilityCase(
"window.set_position",
"把左前车窗开到50%",
"front_left",
{"position_pct": 50},
),
)
def _complete_turn(turn: ActiveCodexTurn, answer: str = "已按要求完成。") -> None:
item_id = f"answer-{turn.turn_id}"
turn.events.put(
{
"method": "item/started",
"params": {
"item": {
"type": "agentMessage",
"id": item_id,
"text": "",
"phase": "final_answer",
}
},
}
)
turn.events.put(
{
"method": "item/agentMessage/delta",
"params": {"itemId": item_id, "delta": answer},
}
)
turn.events.put(
{
"method": "item/completed",
"params": {
"item": {
"type": "agentMessage",
"id": item_id,
"text": answer,
"phase": "final_answer",
}
},
}
)
turn.events.put(
{
"method": "turn/completed",
"params": {
"turn": {
"id": turn.turn_id,
"status": "completed",
"items": [],
"error": None,
}
},
}
)
class _ToolCallingCodexClient:
def __init__(self, *, available: bool = True) -> None:
self.available = available
self.on_turn: Callable[[str, ActiveCodexTurn], None] | None = None
self.thread_calls = 0
self.turn_calls = 0
self.request_tokens: list[str] = []
@property
def status(self) -> str:
return "ready" if self.available else "failed"
def start(self, _callback_url: str, _callback_token: str) -> None:
return None
def close(self) -> None:
return None
def start_thread(self, *, developer_instructions: str, ephemeral: bool = True) -> str:
assert "get_n7web_capabilities" in developer_instructions
assert "execute_cockpit_request" in developer_instructions
assert ephemeral is True
self.thread_calls += 1
return f"thread-matrix-{self.thread_calls}"
def start_turn(
self,
thread_id: str,
text: str,
request_token: str,
*,
application_context: dict[str, object] | None = None,
) -> ActiveCodexTurn:
del text
assert thread_id.startswith("thread-matrix-")
assert isinstance(application_context, dict)
self.turn_calls += 1
self.request_tokens.append(request_token)
turn = ActiveCodexTurn(
thread_id=thread_id,
turn_id=f"turn-matrix-{self.turn_calls}",
events=queue.Queue(),
)
if self.on_turn is not None:
self.on_turn(request_token, turn)
_complete_turn(turn)
return turn
def finish_turn(self, _turn: ActiveCodexTurn) -> None:
return None
def interrupt(self, _turn: ActiveCodexTurn) -> None:
return None
def unsubscribe_thread(self, _thread_id: str) -> None:
return None
@pytest.fixture(scope="module")
def vehicle_service(
tmp_path_factory: pytest.TempPathFactory,
) -> Iterator[SeatDemoService]:
memory_path = ROOT / "tests" / "fixtures" / "driver_seat_memory.json"
previous_memory_path = os.environ.get("COGNITIVE_AGENT_DRIVER_SEAT_MEMORY_PATH")
os.environ["COGNITIVE_AGENT_DRIVER_SEAT_MEMORY_PATH"] = str(memory_path)
service = SeatDemoService(ROOT)
service.artifact_root = Path(tmp_path_factory.mktemp("assistant-matrix-runs"))
service.mode_profiles = ModeProfileRegistry(
ROOT / "config" / "modes",
user_root=Path(tmp_path_factory.mktemp("assistant-matrix-modes")),
)
yield service
service.close()
if previous_memory_path is None:
os.environ.pop("COGNITIVE_AGENT_DRIVER_SEAT_MEMORY_PATH", None)
else:
os.environ["COGNITIVE_AGENT_DRIVER_SEAT_MEMORY_PATH"] = previous_memory_path
@pytest.fixture(scope="module")
def catalog(vehicle_service: SeatDemoService) -> AssistantCapabilityCatalog:
return AssistantCapabilityCatalog(
vehicle_service,
peripheral_provider=list,
nfc_provider=lambda: {"reader": {"state": "ready"}, "card": None},
)
def _stream_with_catalog(
assistant: CodexAssistantService,
catalog: AssistantCapabilityCatalog,
message: str,
*,
execute_control: Callable[..., dict[str, object]],
conversation_id: str,
authoritative_answer: str | None = None,
) -> tuple[dict[str, object], list[dict[str, object]]]:
snapshot = catalog.snapshot()
events: list[dict[str, object]] = []
result = assistant.stream(
message,
conversation_id=conversation_id,
principal="assistant-matrix-user",
history=[],
emit=lambda event: events.append(event),
execute_control=execute_control,
capability_snapshot=snapshot,
capability_context=catalog.compact_context(snapshot),
control_terms=catalog.control_terms(snapshot),
query_capabilities=catalog.query,
authoritative_answer=authoritative_answer,
)
return result, events
def test_execution_matrix_exactly_matches_every_assistant_callable_capability(
catalog: AssistantCapabilityCatalog,
) -> None:
callable_capabilities = {
capability["id"]
for capability in catalog.snapshot()["capabilities"]
if capability["assistant_callable"] is True
}
matrix_capabilities = {case.capability for case in CAPABILITY_CASES}
assert len(CAPABILITY_CASES) == len(matrix_capabilities), (
"the execution matrix must contain exactly one case per atomic capability"
)
assert len(callable_capabilities) == 42
assert matrix_capabilities == callable_capabilities
@pytest.mark.parametrize(
"case",
CAPABILITY_CASES,
ids=lambda case: case.capability,
)
def test_every_assistant_callable_capability_reaches_the_real_tool_boundary(
vehicle_service: SeatDemoService,
catalog: AssistantCapabilityCatalog,
case: _CapabilityCase,
) -> None:
proposed_plan = case.assistant_plan()
guarded = guard_assistant_plan(
proposed_plan,
fallback_text=case.message,
capability_context=vehicle_service.capability_context,
executable_capabilities={item.capability for item in CAPABILITY_CASES},
fallback_intents=(),
trusted_local={},
)
assert guarded.rejected_requests == ()
assert len(guarded.plan["requests"]) == 1
guarded_request = guarded.plan["requests"][0]
assert guarded_request["capability"] == case.capability
assert guarded_request["target_scope"] == case.target
assert guarded_request["operation"] == case.operation
assert guarded_request["parameters"] == [
{"name": name, "value": value} for name, value in case.parameters.items()
]
capability_slug = case.capability.replace(".", "-").replace("_", "-")
vehicle_conversation_id = f"conv-matrix-{capability_slug}"
client = _ToolCallingCodexClient()
assistant = CodexAssistantService(client)
tool_results: list[dict[str, object]] = []
raw_results: list[dict[str, object]] = []
if case.expected_effect == "preset_apply":
setup_case = _CapabilityCase(
"seat.save_posture_preset",
"保存当前座椅姿态为矩阵姿态",
"passenger",
{"preset_name": "矩阵姿态"},
"save",
)
setup_result = vehicle_service.preview_native_can(
setup_case.message,
provider="local",
conversation_id=vehicle_conversation_id,
execution_mode="dry_run",
assistant_plan=setup_case.assistant_plan(),
)
assert setup_result["status"] == "succeeded"
def invoke_tool(request_token: str, _turn: ActiveCodexTurn) -> None:
tool_results.append(
assistant.execute_cockpit_request(
request_token,
proposed_plan,
)
)
def execute_control(
instruction: str,
progress: Callable[[dict[str, object]], None],
assistant_plan: Mapping[str, object],
) -> dict[str, object]:
assert instruction == case.message
result = vehicle_service.preview_native_can(
instruction,
provider="local",
conversation_id=vehicle_conversation_id,
execution_mode="dry_run",
on_commands_validated=progress,
assistant_plan=dict(assistant_plan),
)
raw_results.append(result)
return result
client.on_turn = invoke_tool
result, events = _stream_with_catalog(
assistant,
catalog,
case.message,
execute_control=execute_control,
conversation_id=f"conv-assistant-{capability_slug}",
)
assert result["route"] == "agent"
assert raw_results[0]["status"] == "succeeded"
assert tool_results[0]["verified"] is False
assert tool_results[0]["verification_status"] == "simulated"
safe_actions = tool_results[0]["actions"]
raw_actions = raw_results[0]["intent_ir"]["spec"]["actions"]
if case.expected_effect == "action":
assert case.capability in {action["capability"] for action in safe_actions}
elif case.expected_effect == "preset_save":
assert raw_actions == []
assert raw_results[0]["saved_presets"] == [{"name": "矩阵姿态", "target": "passenger"}]
else:
assert case.expected_effect == "preset_apply"
assert raw_results[0]["saved_presets"] == [{"name": "矩阵姿态", "target": "passenger"}]
assert {action["capability"] for action in raw_actions} == {
"seat.set_longitudinal_position",
"seat.set_cushion_tilt",
"seat.set_backrest_recline",
"seat.set_legrest_height",
"seat.set_legrest_extension",
}
action_event = next(event for event in events if event["type"] == "action.completed")
assert action_event["success"] is True
def test_every_catalog_mode_executes_through_the_deterministic_assistant_route(
vehicle_service: SeatDemoService,
catalog: AssistantCapabilityCatalog,
) -> None:
snapshot = catalog.snapshot()
modes = _repository_catalog_modes(snapshot["modes"])
for index, mode in enumerate(modes):
assert isinstance(mode, Mapping)
assert mode["assistant_callable"] is True
message = f"进入{mode['name']}"
client = _ToolCallingCodexClient()
assistant = CodexAssistantService(client)
raw_results: list[dict[str, object]] = []
tool_results: list[dict[str, object]] = []
def invoke_tool(
request_token: str,
_turn: ActiveCodexTurn,
*,
current_results: list[dict[str, object]] = tool_results,
current_assistant: CodexAssistantService = assistant,
) -> None:
current_results.append(current_assistant.execute_cockpit_request(request_token))
def execute_control(
instruction: str,
progress: Callable[[dict[str, object]], None],
*,
current_message: str = message,
current_index: int = index,
current_results: list[dict[str, object]] = raw_results,
) -> dict[str, object]:
assert instruction == current_message
result = vehicle_service.preview_native_can(
instruction,
provider="local",
conversation_id=f"conv-mode-matrix-{current_index:02d}",
execution_mode="dry_run",
on_commands_validated=progress,
)
current_results.append(result)
return result
client.on_turn = invoke_tool
result, events = _stream_with_catalog(
assistant,
catalog,
message,
execute_control=execute_control,
conversation_id=f"conv-assistant-mode-{index:02d}",
)
assert raw_results[0]["status"] == "succeeded", mode["id"]
assert raw_results[0]["matched_modes"][0]["id"] == mode["id"]
assert tool_results == []
assert client.turn_calls == 0
assert result["route"] == "mode_profile_execute"
assert result["success"] is True
assert events[0]["engine"] == "n7web-mode-profile-runner"
def test_every_catalog_mode_rejects_incompatible_explicit_seat_scopes(
vehicle_service: SeatDemoService,
catalog: AssistantCapabilityCatalog,
) -> None:
modes = _repository_catalog_modes(catalog.snapshot()["modes"])
for mode_index, mode in enumerate(modes):
assert isinstance(mode, Mapping)
profile = vehicle_service.mode_profiles.get_profile(str(mode["id"]), "denza_n7")
has_driver = any(
operation.domain == "seat" and operation.target_scope in {"driver", "front_left"}
for operation in profile.operations
)
for scope_index, scope in enumerate(
("主驾", "驾驶员", "后排", "二排", "前排", "所有座椅", "全车座椅")
):
if has_driver and scope in {"主驾", "驾驶员"}:
continue # Valid dual-seat subsets have a separate exact-action matrix.
message = f"请让{scope}进入{mode['name']}"
result = vehicle_service.preview_native_can(
message,
provider="local",
conversation_id=(
f"conv-mode-target-reject-{mode_index:02d}-{scope_index:02d}"
),
execution_mode="dry_run",
)
assert result["status"] in {"no_action", "needs_confirmation"}, (
mode["id"],
scope,
)
assert result["intent_ir"]["spec"]["actions"] == [], (
mode["id"],
scope,
)
assert any(
item.get("reason") == "mode_target_scope_mismatch"
for item in result["intent_ir"]["spec"].get("ambiguities", [])
), (mode["id"], scope)
passenger = vehicle_service.preview_native_can(
f"请让副驾进入{mode['name']}",
provider="local",
conversation_id=f"conv-mode-target-pass-{mode_index:02d}",
execution_mode="dry_run",
)
assert passenger["status"] == "succeeded", mode["id"]
assert passenger["intent_ir"]["spec"]["actions"], mode["id"]
def test_preview_and_process_share_mode_target_safety_gate(
vehicle_service: SeatDemoService,
) -> None:
message = "主驾开启零重力模式"
direct = vehicle_service.process(
message,
provider="local",
conversation_id="conv-mode-target-direct",
execution_mode="dry_run",
)
preview = vehicle_service.preview_native_can(
message,
provider="local",
conversation_id="conv-mode-target-preview",
execution_mode="dry_run",
)
assert direct["status"] in {"no_action", "needs_confirmation"}
assert preview["status"] in {"no_action", "needs_confirmation"}
assert direct["intent_ir"]["spec"]["actions"] == []
assert preview["intent_ir"]["spec"]["actions"] == []
assert {
item.get("reason")
for item in preview["intent_ir"]["spec"].get("ambiguities", [])
} == {"mode_target_scope_mismatch"}
def test_durable_nfc_peripheral_can_execute_after_bound_card_is_removed(
vehicle_service: SeatDemoService,
) -> None:
learned = {
"id": "star-projector-on",
"status": "learned",
"enabled": True,
"device_id": "star-projector",
"device_name": "星空投影仪",
"object_names": ["星空投影"],
"action": "开启",
"action_names": ["打开", "开启"],
"card_uid": "A1B2C3D4",
"slot": 7,
}
active_catalog = AssistantCapabilityCatalog(
vehicle_service,
peripheral_provider=lambda: [learned],
nfc_provider=lambda: {"card": {"uid": "a1b2c3d4"}},
)
detached_catalog = AssistantCapabilityCatalog(
vehicle_service,
peripheral_provider=lambda: [learned],
nfc_provider=lambda: {"card": None},
)
active_client = _ToolCallingCodexClient()
active_assistant = CodexAssistantService(active_client)
executed: list[str] = []
active_client.on_turn = lambda token, _turn: active_assistant.execute_cockpit_request(token)
_stream_with_catalog(
active_assistant,
active_catalog,
"打开星空投影仪",
execute_control=lambda instruction, _progress: (
executed.append(instruction)
or {
"status": "succeeded",
"execution_mode": "dry_run",
"utterance": instruction,
"infrared_actions": [
{
"capability_id": "star-projector-on",
"device_id": "star-projector",
"device_name": "星空投影仪",
"action": "开启",
"slot": 7,
"transport": "infrared",
}
],
}
),
conversation_id="conv-active-peripheral-01",
)
assert executed == ["打开星空投影仪"]
detached_client = _ToolCallingCodexClient()
detached_assistant = CodexAssistantService(detached_client)
detached_executed: list[str] = []
detached_client.on_turn = (
lambda token, _turn: detached_assistant.execute_cockpit_request(token)
)
_stream_with_catalog(
detached_assistant,
detached_catalog,
"打开星空投影仪",
execute_control=lambda instruction, _progress: (
detached_executed.append(instruction)
or {
"status": "succeeded",
"execution_mode": "dry_run",
"utterance": instruction,
"infrared_actions": [
{
"capability_id": "star-projector-on",
"device_id": "star-projector",
"device_name": "星空投影仪",
"action": "开启",
"slot": 7,
"transport": "infrared",
}
],
}
),
conversation_id="conv-detached-peripheral-01",
)
assert detached_executed == ["打开星空投影仪"]
@pytest.mark.parametrize(
"message",
[
"整车锁车",
"打开车门",
"打开壁灯",
"打开物理天窗",
"打开前大灯",
"支持遮阳帘吗",
"请把“打开空调”翻译成英文,不要执行",
"帮我修改网页里的空调图标",
],
)
def test_unsupported_or_non_control_language_cannot_cross_the_execution_boundary(
catalog: AssistantCapabilityCatalog,
message: str,
) -> None:
client = _ToolCallingCodexClient()
assistant = CodexAssistantService(client)
errors: list[str] = []
def attempt_control(request_token: str, _turn: ActiveCodexTurn) -> None:
try:
assistant.execute_cockpit_request(request_token)
except CodexVehicleToolError as exc:
errors.append(str(exc))
client.on_turn = attempt_control
_stream_with_catalog(
assistant,
catalog,
message,
execute_control=lambda _instruction, _progress: pytest.fail(
"unsupported or informational request must not execute"
),
conversation_id=f"conv-rejection-{abs(hash(message)):x}",
)
assert errors == ["vehicle control was not authorized by the original user request"]
def test_capability_fast_route_works_when_codex_is_unavailable(
catalog: AssistantCapabilityCatalog,
) -> None:
client = _ToolCallingCodexClient(available=False)
assistant = CodexAssistantService(client)
answer = catalog.fast_answer("车端有哪些可控设备", catalog.snapshot())
assert answer is not None
result, events = _stream_with_catalog(
assistant,
catalog,
"车端有哪些可控设备",
execute_control=lambda _instruction, _progress: pytest.fail(
"capability fast route must never execute controls"
),
conversation_id="conv-fast-capability-route",
authoritative_answer=answer.answer,
)
assert result == {"route": "capability_catalog", "answer": answer.answer}
assert client.thread_calls == 0
assert client.turn_calls == 0
assert [event["type"] for event in events] == [
"message.accepted",
"work.started",
"answer.delta",
"work.completed",
"answer.completed",
]
assert events[0]["engine"] == "n7web-capability-registry"
assert events[-1]["route"] == "capability_catalog"
def test_hot_comfort_fast_route_reaches_real_climate_service(
vehicle_service: SeatDemoService,
catalog: AssistantCapabilityCatalog,
) -> None:
client = _ToolCallingCodexClient(available=False)
assistant = CodexAssistantService(client, fast_implicit_controls=True)
raw_results: list[dict[str, object]] = []
def execute_control(
instruction: str,
progress: Callable[[dict[str, object]], None],
) -> dict[str, object]:
result = vehicle_service.preview_native_can(
instruction,
provider="local",
conversation_id="conv-real-hot-comfort",
execution_mode="dry_run",
on_commands_validated=progress,
)
raw_results.append(result)
return result
result, events = _stream_with_catalog(
assistant,
catalog,
"车上有点热",
execute_control=execute_control,
conversation_id="conv-fast-hot-real-service",
)
assert client.thread_calls == 0
assert client.turn_calls == 0
assert result["route"] == "vehicle_control"
assert result["success"] is True
assert len(raw_results) == 1
assert raw_results[0]["status"] == "succeeded"
actions = raw_results[0]["intent_ir"]["spec"]["actions"]
capabilities = [action["capability"] for action in actions]
assert capabilities.count("climate.set_power") == 1
assert capabilities.count("climate.set_ac") == 0
assert capabilities.count("climate.set_temperature") == 3
assert capabilities.count("climate.set_fan_level") == 1
assert {
action["target"]["zone"]
for action in actions
if action["capability"] == "climate.set_temperature"
} == {"driver", "passenger", "rear"}
assert events[-1]["type"] == "answer.completed"
assert "23℃" in events[-1]["answer"]
def test_turn_bound_capability_query_has_token_and_per_turn_rate_limits(
catalog: AssistantCapabilityCatalog,
) -> None:
client = _ToolCallingCodexClient()
assistant = CodexAssistantService(client)
queries: list[str] = []
results: list[dict[str, object]] = []
limit_errors: list[str] = []
control_calls: list[str] = []
def query_capabilities(snapshot: Mapping[str, object], query: str) -> dict[str, object]:
queries.append(query)
return catalog.query(snapshot, query)
def use_tools(request_token: str, _turn: ActiveCodexTurn) -> None:
with pytest.raises(ValueError, match="160 characters"):
assistant.get_n7web_capabilities(request_token, "x" * 161)
for query in ("遮阳帘", "空调", "办公模式", "不存在的设备"):
results.append(assistant.get_n7web_capabilities(request_token, query))
try:
assistant.get_n7web_capabilities(request_token, "第五次查询")
except CodexVehicleToolError as exc:
limit_errors.append(str(exc))
assistant.execute_cockpit_request(request_token)
client.on_turn = use_tools
snapshot = catalog.snapshot()
events: list[dict[str, object]] = []
assistant.stream(
"打开空调",
conversation_id="conv-capability-query-limits",
principal="assistant-matrix-user",
history=[],
emit=lambda event: events.append(event),
execute_control=lambda instruction, _progress: (
control_calls.append(instruction)
or {
"status": "succeeded",
"execution_mode": "dry_run",
"utterance": instruction,
"intent_ir": {"spec": {"actions": []}},
}
),
capability_snapshot=snapshot,
capability_context=catalog.compact_context(snapshot),
control_terms=catalog.control_terms(snapshot),
query_capabilities=query_capabilities,
)
assert queries == ["遮阳帘", "空调", "办公模式", "不存在的设备"]
assert all(result["ok"] is True for result in results)
assert all(result["source"] == "n7web_runtime_registry" for result in results)
assert results[0]["catalog"]["matched"] is True
assert results[-1]["catalog"]["matched"] is False
assert limit_errors == ["capability query limit exceeded for this turn"]
assert control_calls == ["打开空调"]
expired_token = client.request_tokens[0]
with pytest.raises(CodexVehicleToolError, match="missing or expired"):
assistant.get_n7web_capabilities(expired_token, "空调")
with pytest.raises(CodexVehicleToolError, match="missing or expired"):
assistant.get_n7web_capabilities("unknown-token", "空调")