merge: integrate remote MQTT routing with seat-scoped dynamic modes

This commit is contained in:
vee
2026-09-12 15:10:16 +08:00
43 changed files with 5726 additions and 492 deletions
+276 -7
View File
@@ -66,6 +66,12 @@ from cognitive_agent.application.mode_profile_intent import ( # noqa: E402
parse_mode_profile_delete,
parse_mode_profile_update,
)
from cognitive_agent.application.mode_scene_catalog import ( # noqa: E402
DEFAULT_N7_SCENE_SERVER_ID,
DEFAULT_N7_SCENE_SERVER_NAME,
ModeSceneCatalogPublisher,
build_mode_scene_catalog,
)
from cognitive_agent.application.mode_schedule import ModeSchedule, ModeScheduleRunner # noqa: E402
from cognitive_agent.application.mode_schedule_service import ModeScheduleServiceMixin # noqa: E402
from cognitive_agent.application.model_plan_adapter import ( # noqa: E402
@@ -179,6 +185,7 @@ MODE_PROFILE_UPDATE_EXPLICIT_TARGET_PATTERN = re.compile(
)
WIRED_CAN_TRANSPORT = "wired_can"
WIRELESS_MQTT_TRANSPORT = "wireless_mqtt"
LOCAL_CONTROL_BUDGET_MS = 1000
VEHICLE_CONTROL_TRANSPORTS = frozenset(
{WIRED_CAN_TRANSPORT, WIRELESS_MQTT_TRANSPORT}
)
@@ -300,6 +307,7 @@ CONTROL_CLAUSE_SUBJECT_PATTERN = re.compile(
r"座椅|座位|靠背|椅背|坐盆|坐垫|腿托|脚托|滑轨|"
r"通风|加热|按摩|零重力|半躺|女王副驾|休闲躺椅|"
r"遮阳帘|天幕帘|天幕|车窗|窗户|"
r"(?:左前|前左|右前|前右|左后|后左|右后|后右)(?:排)?窗|"
r"灯光|室内灯|车内灯|阅读灯|氛围灯|"
r"空调|压缩机|温度|风量|风速|内循环|外循环|"
r"车门|门锁|后备箱|前大灯|近光灯|远光灯|雾灯|转向灯|双闪|"
@@ -310,10 +318,17 @@ CONTROL_CLAUSE_PREDICATE_PATTERN = re.compile(
r"调整|调节|调到|调成|调为|调至|设置|设为|设到|切换|"
r"移动|移到|移至|前移|后移|上移|下移|升高|降低|抬起|放倒|"
r"调直|立直|竖直|归零|复位|锁上|上锁|落锁|解锁|"
r"零重力|半躺|最大|最小|最高|最低|最前|最后|到底|"
r"零重力|半躺|一半|半开|半关|最大|最小|最高|最低|最前|最后|到底|"
r"(?:\d+(?:\.\d+)?|[零一二三四五六七八九十百两半]+)"
r"(?:%|%|度|℃|档|级|秒|毫秒)"
)
# These verbs identify an independent request that may be outside the local
# vehicle registry. They are used only to split mixed sentences so an
# already deterministic clause never has to be repeated to a hosted model.
CLOUD_REQUEST_CLAUSE_PATTERN = re.compile(
r"播放|暂停播放|停止播放|导航|搜索|查找|查询|拨打|呼叫|发送|"
r"提醒|推荐|讲解|介绍|告诉|规划"
)
RESTORE_MODE_PROFILE_PREFIX = re.compile(
r"^(?:请(?:帮我)?|帮我)?(?:恢复|还原|重置|初始化)"
r"(?P<name>[^,。,.!!\s]{1,64}?模式)"
@@ -429,6 +444,17 @@ class SeatDemoService(ModeScheduleServiceMixin):
repository_root / "config" / "modes",
user_root=self.mode_profile_user_root,
)
self._mode_scene_catalog_publisher: ModeSceneCatalogPublisher | None = None
self._mode_scene_catalog_status: dict[str, Any] = {
"enabled": False,
"state": "disabled",
"path": None,
"vehicle_id": self.vehicle_profiles.default().id,
"server_id": DEFAULT_N7_SCENE_SERVER_ID,
"catalog_revision": None,
"scene_count": 0,
"last_error": None,
}
self.scene_templates = SceneTemplateRegistry(
repository_root / "config" / "scene_templates"
)
@@ -447,6 +473,88 @@ class SeatDemoService(ModeScheduleServiceMixin):
if hasattr(self, "conversations"):
self.conversations.preset_root = self._artifact_root / "user-presets"
def configure_mode_scene_catalog(
self,
path: Path,
*,
server_id: str = DEFAULT_N7_SCENE_SERVER_ID,
name: str = DEFAULT_N7_SCENE_SERVER_NAME,
) -> dict[str, Any]:
"""Enable the derived JSON catalog without changing YAML persistence."""
catalog_path = Path(path).expanduser()
if not catalog_path.is_absolute():
catalog_path = self.root / catalog_path
vehicle_id = self.vehicle_profiles.default().id
self._mode_scene_catalog_publisher = ModeSceneCatalogPublisher(
catalog_path,
vehicle_id,
server_id=server_id,
name=name,
)
self._mode_scene_catalog_status = {
"enabled": True,
"state": "initializing",
"path": str(catalog_path),
"vehicle_id": vehicle_id,
"server_id": self._mode_scene_catalog_publisher.server_id,
"catalog_revision": None,
"scene_count": 0,
"last_error": None,
}
# The registry notifies after create, update, delete, restore, and any
# explicit reload. The callback only updates a derived projection; YAML
# remains the authoritative store used by every existing execution path.
self.mode_profiles.add_change_listener(self._synchronize_mode_scene_catalog)
return self.mode_scene_catalog_status()
def _synchronize_mode_scene_catalog(self, registry: ModeProfileRegistry) -> None:
publisher = self._mode_scene_catalog_publisher
if publisher is None:
return
try:
catalog = publisher.publish(registry)
except Exception as exc: # noqa: BLE001 - derived export must not block controls
self._mode_scene_catalog_status.update(
{
"state": "error",
"last_error": str(exc),
}
)
return
self._mode_scene_catalog_status.update(
{
"state": "ready",
"catalog_revision": catalog["catalog_revision"],
"scene_count": catalog["scene_count"],
"last_error": None,
}
)
def mode_scene_catalog(self, vehicle_id: str | None = None) -> dict[str, Any]:
"""Return the current read-only mode-card catalog for an N7 client."""
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
publisher = self._mode_scene_catalog_publisher
if publisher is not None and resolved_vehicle_id == publisher.vehicle_id:
self._synchronize_mode_scene_catalog(self.mode_profiles)
snapshot = publisher.snapshot()
if snapshot is not None and self._mode_scene_catalog_status["state"] == "ready":
return snapshot
return build_mode_scene_catalog(
self.mode_profiles,
resolved_vehicle_id,
server_id=(
publisher.server_id
if publisher is not None
else DEFAULT_N7_SCENE_SERVER_ID
),
name=(publisher.name if publisher is not None else DEFAULT_N7_SCENE_SERVER_NAME),
)
def mode_scene_catalog_status(self) -> dict[str, Any]:
return deepcopy(self._mode_scene_catalog_status)
@property
def driver_only_ch1_execution_active(self) -> bool:
"""Return whether deployment policy authorizes only the CH1 vehicle path."""
@@ -603,6 +711,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
"scene_templates": self.scene_templates.public_templates(),
"mode_profile_storage": "persistent_yaml",
"mode_profile_user_root": str(self.mode_profile_user_root),
"mode_scene_catalog": self.mode_scene_catalog_status(),
"preset_storage": "conversation_durable",
"inferred_level_range": [1, 3],
"motion_position_range_pct": [0, 100],
@@ -1545,9 +1654,11 @@ class SeatDemoService(ModeScheduleServiceMixin):
on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
assistant_plan: dict[str, Any] | None = None,
mode_profile_draft_context: dict[str, Any] | None = None,
_cloud_instruction_context: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Validate and execute deterministic controls before hosted parsing."""
local_stage_started = time.perf_counter()
utterance = utterance.strip()
if not utterance:
raise ValueError("utterance must not be empty")
@@ -1576,6 +1687,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
_on_commands_validated=on_commands_validated,
_assistant_plan=assistant_plan,
_mode_profile_draft_context=mode_profile_draft_context,
_cloud_instruction_context=_cloud_instruction_context,
)
result.update(
{
@@ -1594,14 +1706,71 @@ class SeatDemoService(ModeScheduleServiceMixin):
vehicle_profile,
)
clause_results = self._local_clause_results(utterance, context)
unresolved_clauses = [
locally_parsed_clauses = [
clause
for clause, _, status in clause_results
if status != "unparsed"
]
locally_resolved_clauses = [
clause
for clause, _, status in clause_results
if status == "complete"
]
locally_unresolved_clauses = [
clause
for clause, _, status in clause_results
if status != "complete"
]
started = time.perf_counter()
supplied_routing = (
_cloud_instruction_context
if isinstance(_cloud_instruction_context, dict)
else {}
)
def routing_clauses(key: str) -> list[str]:
raw = supplied_routing.get(key)
if not isinstance(raw, list):
return []
return [
text
for item in raw[:64]
for text in [str(item).strip()[:1000]]
if text
]
def unique_clauses(*groups: list[str]) -> list[str]:
return list(dict.fromkeys(item for group in groups for item in group))
parsed_clauses = unique_clauses(
routing_clauses("parsed_clauses"),
locally_parsed_clauses,
)
resolved_clauses = unique_clauses(
routing_clauses("resolved_clauses"),
locally_resolved_clauses,
)
unresolved_clauses = locally_unresolved_clauses
routing_unresolved_clauses = unique_clauses(
routing_clauses("unresolved_clauses"),
locally_unresolved_clauses,
)
original_utterance = str(
supplied_routing.get("original_utterance") or utterance
).strip()[:1000]
natural_language_routing = {
"original_utterance": original_utterance,
"parsed_clauses": parsed_clauses,
"resolved_clauses": resolved_clauses,
"unresolved_clauses": routing_unresolved_clauses,
}
local_dispatch_elapsed_ms: float | None = None
def publish_local_commands(validated: dict[str, Any]) -> None:
nonlocal local_dispatch_elapsed_ms
local_dispatch_elapsed_ms = round(
(time.perf_counter() - local_stage_started) * 1000,
3,
)
if on_commands_validated is None:
return
event = deepcopy(validated)
@@ -1613,6 +1782,14 @@ class SeatDemoService(ModeScheduleServiceMixin):
"supplement_required": bool(
provider != "local" and unresolved_clauses
),
"natural_language_routing": deepcopy(
natural_language_routing
),
"local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS,
"local_control_elapsed_ms": local_dispatch_elapsed_ms,
"local_control_within_budget": (
local_dispatch_elapsed_ms < LOCAL_CONTROL_BUDGET_MS
),
}
)
on_commands_validated(event)
@@ -1634,8 +1811,17 @@ class SeatDemoService(ModeScheduleServiceMixin):
_on_commands_validated=publish_local_commands,
_assistant_plan=assistant_plan,
_mode_profile_draft_context=mode_profile_draft_context,
_cloud_instruction_context=natural_language_routing,
)
elapsed_ms = round(
(time.perf_counter() - local_stage_started) * 1000,
3,
)
local_control_elapsed_ms = (
local_dispatch_elapsed_ms
if local_dispatch_elapsed_ms is not None
else elapsed_ms
)
elapsed_ms = round((time.perf_counter() - started) * 1000, 3)
supplement_required = bool(
provider != "local" and unresolved_clauses
)
@@ -1645,7 +1831,17 @@ class SeatDemoService(ModeScheduleServiceMixin):
"provider_requested": provider,
"preview_request_id": result["request_id"],
"preview_elapsed_ms": elapsed_ms,
"local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS,
"local_control_elapsed_ms": local_control_elapsed_ms,
"local_control_within_budget": (
local_control_elapsed_ms < LOCAL_CONTROL_BUDGET_MS
),
"parsed_clauses": parsed_clauses,
"resolved_clauses": resolved_clauses,
"unresolved_clauses": unresolved_clauses,
"natural_language_routing": deepcopy(
natural_language_routing
),
"supplement_required": supplement_required,
}
)
@@ -1669,7 +1865,10 @@ class SeatDemoService(ModeScheduleServiceMixin):
"hardware_client_id": hardware_client_id,
"reasoning_effort": reasoning_effort,
"control_transport": control_transport,
"parsed_clauses": parsed_clauses,
"resolved_clauses": resolved_clauses,
"unresolved_clauses": unresolved_clauses,
"natural_language_routing": natural_language_routing,
"preview_control_signatures": result.get(
"parsed_control_signatures", []
),
@@ -1734,6 +1933,34 @@ class SeatDemoService(ModeScheduleServiceMixin):
for item in manifest.get("preview_control_signatures", [])
if isinstance(item, list)
}
manifest_routing = manifest.get("natural_language_routing")
natural_language_routing = (
deepcopy(manifest_routing)
if isinstance(manifest_routing, dict)
else {
"original_utterance": str(manifest.get("utterance") or ""),
"parsed_clauses": list(manifest.get("parsed_clauses") or []),
"resolved_clauses": list(manifest.get("resolved_clauses") or []),
"unresolved_clauses": unresolved_clauses,
}
)
routing_unresolved = natural_language_routing.get("unresolved_clauses")
natural_language_routing["unresolved_clauses"] = list(
dict.fromkeys(
[
str(item).strip()
for item in (
routing_unresolved
if isinstance(routing_unresolved, list)
else []
)
if str(item).strip()
]
+ unresolved_clauses
)
)
cloud_utterance = ",".join(unresolved_clauses)
natural_language_routing["cloud_input_utterance"] = cloud_utterance
def publish_cloud_commands(validated: dict[str, Any]) -> None:
if on_commands_validated is None:
@@ -1750,7 +1977,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
on_commands_validated(event)
supplement = self.process(
",".join(unresolved_clauses),
cloud_utterance,
provider=selected_provider,
conversation_id=str(manifest["conversation_id"]),
execution_mode=str(manifest.get("execution_mode") or "dry_run"),
@@ -1769,6 +1996,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
_model_parse_source="cloud_supplement",
_allow_empty_control_result=True,
_on_commands_validated=publish_cloud_commands,
_cloud_instruction_context=natural_language_routing,
)
merged = self._merge_native_can_phases(preview, supplement)
merged.update(
@@ -1785,6 +2013,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
"supplement_request_id": supplement["request_id"],
"supplement_required": False,
"unresolved_clauses": unresolved_clauses,
"natural_language_routing": natural_language_routing,
}
)
self._persist(merged)
@@ -2081,6 +2310,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
_speech_understanding: dict[str, Any] | None = None,
_assistant_plan: dict[str, Any] | None = None,
_mode_profile_draft_context: dict[str, Any] | None = None,
_cloud_instruction_context: dict[str, Any] | None = None,
) -> dict[str, Any]:
control_transport = self._validate_control_transport(control_transport)
# Creating a reusable scene is a configuration workflow. Synthesize
@@ -2155,6 +2385,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
_speech_understanding,
assistant_plan=_assistant_plan,
mode_profile_draft_context=_mode_profile_draft_context,
cloud_instruction_context=_cloud_instruction_context,
)
if control_transport == WIRELESS_MQTT_TRANSPORT:
# The head unit owns both physical CAN channels in wireless mode.
@@ -2180,6 +2411,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
_speech_understanding,
assistant_plan=_assistant_plan,
mode_profile_draft_context=_mode_profile_draft_context,
cloud_instruction_context=_cloud_instruction_context,
)
if hardware_access_bypass or self._driver_only_vehicle_execution_active(
control_transport
@@ -2205,6 +2437,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
_speech_understanding,
assistant_plan=_assistant_plan,
mode_profile_draft_context=_mode_profile_draft_context,
cloud_instruction_context=_cloud_instruction_context,
)
authorized_client_id = self.hardware_access.ensure_authorized(hardware_client_id)
with self.hardware_execution.reserve():
@@ -2229,6 +2462,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
_speech_understanding,
assistant_plan=_assistant_plan,
mode_profile_draft_context=_mode_profile_draft_context,
cloud_instruction_context=_cloud_instruction_context,
)
def _process_once(
@@ -2256,6 +2490,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
scene_scope_utterance: str | None = None,
mode_creation_scopes: frozenset[str] | None = None,
mode_profile_draft_context: dict[str, Any] | None = None,
cloud_instruction_context: dict[str, Any] | None = None,
) -> dict[str, Any]:
control_transport = self._validate_control_transport(control_transport)
utterance = utterance.strip()
@@ -2589,6 +2824,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
assistant_plan=assistant_plan,
allow_scene_composition=allow_scene_composition,
scene_scope_utterance=scene_scope_utterance,
cloud_instruction_context=cloud_instruction_context,
)
scene_label_only = assistant_plan is not None and allow_scene_composition
dynamic_mode_stop = (
@@ -7450,6 +7686,11 @@ class SeatDemoService(ModeScheduleServiceMixin):
and CONTROL_CLAUSE_PREDICATE_PATTERN.search(value)
)
def looks_like_independent_request(value: str) -> bool:
return looks_like_control(value) or bool(
CLOUD_REQUEST_CLAUSE_PATTERN.search(value)
)
def split_independent_controls(
value: str,
separator_pattern: str,
@@ -7460,7 +7701,14 @@ class SeatDemoService(ModeScheduleServiceMixin):
for index in range(1, len(tokens), 2):
separator = tokens[index]
next_item = tokens[index + 1].strip()
if looks_like_control(current) and looks_like_control(next_item):
if (
looks_like_independent_request(current)
and looks_like_independent_request(next_item)
and (
looks_like_control(current)
or looks_like_control(next_item)
)
):
if current:
separated.append(current)
current = next_item
@@ -7771,6 +8019,7 @@ class SeatDemoService(ModeScheduleServiceMixin):
assistant_plan: dict[str, Any] | None = None,
allow_scene_composition: bool = False,
scene_scope_utterance: str | None = None,
cloud_instruction_context: dict[str, Any] | None = None,
) -> tuple[dict[str, Any], str, str | None]:
vehicle_profile = vehicle_profile or self.vehicle_profiles.default()
if assistant_plan is not None and allow_scene_composition:
@@ -7936,9 +8185,29 @@ class SeatDemoService(ModeScheduleServiceMixin):
interpret_options = {}
if reasoning_effort is not None:
interpret_options["reasoning_effort"] = reasoning_effort
hosted_context = deepcopy(context)
if isinstance(cloud_instruction_context, dict):
routing_context = deepcopy(cloud_instruction_context)
else:
routing_context = {
"original_utterance": utterance,
"parsed_clauses": [
clause
for clause, _, status in clause_results
if status != "unparsed"
],
"resolved_clauses": [
clause
for clause, _, status in clause_results
if status == "complete"
],
"unresolved_clauses": fallback_clauses,
}
routing_context["cloud_input_utterance"] = fallback_utterance
hosted_context["natural_language_routing"] = routing_context
plan = self.openai.interpret(
fallback_utterance,
context,
hosted_context,
self.capability_context,
**interpret_options,
)