feat: integrate learned peripherals into mode creation
Expose safe NFC-bound infrared actions to semantic scene composition, require flashing-light actions for romantic and Mid-Autumn modes, and preserve mixed CAN/IR mode flows. Fix NFC device reassignment and retry extension/toy MQTT publication when an active card's binding catalog changes.
This commit is contained in:
@@ -11,6 +11,8 @@ from cognitive_agent.application.assistant_capabilities import AssistantCapabili
|
||||
from cognitive_agent.application.scene_composer import (
|
||||
build_scene_output_schema,
|
||||
normalize_scene_output,
|
||||
scene_required_peripheral_refs,
|
||||
scene_semantic_guidance,
|
||||
)
|
||||
from cognitive_agent.application.seat_service import SeatDemoService
|
||||
|
||||
@@ -352,3 +354,150 @@ def test_production_scene_schema_remains_below_app_server_limit() -> None:
|
||||
encoded = json.dumps(schema, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
|
||||
|
||||
assert len(encoded) < 64 * 1024
|
||||
|
||||
|
||||
def test_schema_and_normalizer_accept_only_opaque_peripheral_refs() -> None:
|
||||
context = _context()
|
||||
context["peripheral_actions"] = [
|
||||
{
|
||||
"ref": "ir:0123456789abcdef01234567",
|
||||
"device_label": "氛围灯带",
|
||||
"action_label": "设置为红色",
|
||||
}
|
||||
]
|
||||
schema = build_scene_output_schema(context)
|
||||
raw = {
|
||||
"requests": [{"capability_ref": "ir:0123456789abcdef01234567"}],
|
||||
"ambiguities": [],
|
||||
"confidence": 0.91,
|
||||
}
|
||||
|
||||
assert Draft202012Validator(schema).is_valid(raw)
|
||||
assert not Draft202012Validator(schema).is_valid(
|
||||
{
|
||||
**raw,
|
||||
"requests": [
|
||||
{
|
||||
"capability_ref": "ir:invented",
|
||||
"slot": 7,
|
||||
"card_uid": "2CC8B103",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
normalized = normalize_scene_output(raw, context)
|
||||
assert normalized["requests"] == [
|
||||
{
|
||||
"domain": "self_extension",
|
||||
"capability": "infrared.emit",
|
||||
"target_scope": "ir:0123456789abcdef01234567",
|
||||
"operation": "emit",
|
||||
"duration_ms": None,
|
||||
"parameters": [
|
||||
{
|
||||
"name": "capability_ref",
|
||||
"value": "ir:0123456789abcdef01234567",
|
||||
}
|
||||
],
|
||||
"reason": (
|
||||
"Scene Composer selected a server-issued learned-device reference; "
|
||||
"runtime binding resolution is still required."
|
||||
),
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_schema_remains_bounded_with_full_peripheral_catalog() -> None:
|
||||
context = _context()
|
||||
context["peripheral_actions"] = [
|
||||
{"ref": f"ir:{index:024x}"}
|
||||
for index in range(248)
|
||||
]
|
||||
|
||||
schema = build_scene_output_schema(context)
|
||||
encoded = json.dumps(schema, ensure_ascii=False, separators=(",", ":")).encode()
|
||||
|
||||
assert len(encoded) < 64 * 1024
|
||||
peripheral_branch = schema["properties"]["requests"]["items"]["anyOf"][-1]
|
||||
assert len(
|
||||
peripheral_branch["properties"]["capability_ref"]["enum"]
|
||||
) == 248
|
||||
|
||||
|
||||
def test_learning_mode_is_grounded_as_free_semantic_composition() -> None:
|
||||
context = _context()
|
||||
guidance = scene_semantic_guidance("学习模式")
|
||||
context["scene_semantics"] = guidance
|
||||
schema = build_scene_output_schema(context)
|
||||
validator = Draft202012Validator(schema)
|
||||
valid = _output(
|
||||
_request(
|
||||
"climate.set_temperature",
|
||||
"vehicle",
|
||||
"temperature_c",
|
||||
24,
|
||||
)
|
||||
)
|
||||
|
||||
assert guidance["grounded"] is True
|
||||
assert guidance["family"] == "focused_learning"
|
||||
assert scene_semantic_guidance("灯光主题模式")["family"] == (
|
||||
"lighting_ambience"
|
||||
)
|
||||
assert "interior_light.set_reading_power" in guidance[
|
||||
"preferred_capability_ids"
|
||||
]
|
||||
assert schema["properties"]["requests"]["minItems"] == 1
|
||||
assert schema["properties"]["ambiguities"]["maxItems"] == 0
|
||||
assert validator.is_valid(valid)
|
||||
assert not validator.is_valid(
|
||||
{"requests": [], "ambiguities": [], "confidence": 0.4}
|
||||
)
|
||||
assert not validator.is_valid(
|
||||
{**valid, "ambiguities": ["不知道学习指什么"]}
|
||||
)
|
||||
with pytest.raises(ValueError, match="at least one request"):
|
||||
normalize_scene_output(
|
||||
{"requests": [], "ambiguities": [], "confidence": 0.4},
|
||||
context,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode_name", ("浪漫模式", "中秋欢快模式"))
|
||||
def test_romantic_and_mid_autumn_require_flashing_light_actions(
|
||||
mode_name: str,
|
||||
) -> None:
|
||||
context = _context()
|
||||
context["peripheral_actions"] = [
|
||||
{
|
||||
"ref": "ir:flash-open",
|
||||
"device_label": "闪烁灯",
|
||||
"object_names": ["闪烁灯", "闪光灯"],
|
||||
"action_label": "打开",
|
||||
"action_names": ["打开", "开启"],
|
||||
},
|
||||
{
|
||||
"ref": "ir:flash-red",
|
||||
"device_label": "闪烁灯",
|
||||
"object_names": ["闪烁灯", "闪光灯"],
|
||||
"action_label": "切换为红色",
|
||||
"action_names": ["切换为红色", "设置为红色"],
|
||||
},
|
||||
{
|
||||
"ref": "ir:flash-close",
|
||||
"device_label": "闪烁灯",
|
||||
"object_names": ["闪烁灯", "闪光灯"],
|
||||
"action_label": "关闭",
|
||||
"action_names": ["关闭", "熄灭"],
|
||||
},
|
||||
]
|
||||
required = scene_required_peripheral_refs(mode_name, context)
|
||||
semantics = scene_semantic_guidance(mode_name)
|
||||
semantics["peripheral_policy"] = {"required_refs": required}
|
||||
context["scene_semantics"] = semantics
|
||||
schema = build_scene_output_schema(context)
|
||||
|
||||
assert required == ["ir:flash-open", "ir:flash-red"]
|
||||
assert schema["properties"]["requests"]["maxItems"] == 10
|
||||
if "中秋" in mode_name:
|
||||
assert semantics["family"] == "festival_celebration"
|
||||
|
||||
Reference in New Issue
Block a user