fix: compose named cockpit modes from capabilities

This commit is contained in:
vee
2026-09-07 20:52:19 +08:00
parent cf9b89508c
commit 6b2b644ee6
23 changed files with 2917 additions and 124 deletions
+410 -18
View File
@@ -8,6 +8,7 @@ import re
import sys
import time
import uuid
from collections import Counter
from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor
from copy import deepcopy
@@ -728,6 +729,90 @@ class SeatDemoService:
vehicle_id,
)
def trial_mode_profile_draft(
self,
name: str,
instruction: str,
source_request_id: str,
vehicle_id: str | None = None,
) -> dict[str, Any]:
"""Replay the server-owned dry-run evidence for one assistant draft."""
clean_name = str(name).strip()
clean_instruction = str(instruction).strip()
clean_request_id = str(source_request_id).strip()
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
if not clean_name or not clean_instruction:
raise ValueError("功能模式草稿名称和指令不能为空。")
if not MODE_EDITOR_REQUEST_ID_PATTERN.fullmatch(clean_request_id):
raise ValueError("功能模式草稿来源请求编号无效。")
result_path = self.artifact_root / clean_request_id / "result.json"
if not result_path.is_file():
raise ValueError("未找到功能模式草稿,请重新生成。")
try:
source_result = json.loads(result_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise ValueError("功能模式草稿结果无法读取,请重新生成。") from exc
if not isinstance(source_result, dict) or source_result.get(
"request_id"
) != clean_request_id:
raise ValueError("功能模式草稿来源结果不完整,请重新生成。")
if (
source_result.get("status") != "succeeded"
or source_result.get("execution_mode") != "dry_run"
):
raise ValueError("功能模式草稿没有通过完整的模拟校验。")
draft = source_result.get("mode_profile_draft")
if not isinstance(draft, dict):
raise ValueError("来源请求不是可试运行的功能模式草稿。")
if str(draft.get("source_request_id") or "").strip() != clean_request_id:
raise ValueError("功能模式草稿来源请求编号不匹配,请重新生成。")
if str(draft.get("name") or "").strip() != clean_name:
raise ValueError("功能模式草稿名称不匹配,请重新生成。")
if str(draft.get("instruction") or "").strip() != clean_instruction:
raise ValueError("功能模式草稿指令不匹配,请重新生成。")
if str(draft.get("vehicle_id") or "").strip() != resolved_vehicle_id:
raise ValueError("功能模式草稿不属于当前车型。")
intent_ir = source_result.get("intent_ir")
spec = intent_ir.get("spec") if isinstance(intent_ir, dict) else None
actions = spec.get("actions") if isinstance(spec, dict) else None
function_configs = source_result.get("function_configs")
operation_count = draft.get("operation_count")
if (
not isinstance(actions, list)
or not actions
or not isinstance(function_configs, dict)
or not function_configs
or not isinstance(operation_count, int)
or isinstance(operation_count, bool)
or len(actions) != operation_count
):
raise ValueError("功能模式草稿动作证据不完整,请重新生成。")
validation = source_result.get("validation", [])
validation = validation if isinstance(validation, list) else []
if any(
isinstance(issue, dict) and issue.get("severity") == "error"
for issue in validation
):
raise ValueError("功能模式草稿未通过安全校验,请重新生成。")
return {
"request_id": clean_request_id,
"status": "succeeded",
"execution_mode": "dry_run",
"hardware_execution_allowed": False,
"orchestration_route": "mode_profile_draft_trial",
"mode_profile_draft": deepcopy(draft),
"intent_ir": {"spec": {"actions": deepcopy(actions)}},
"function_configs": deepcopy(function_configs),
"execution_report": deepcopy(source_result.get("execution_report")),
"validation": deepcopy(validation),
"trial_source": "server_validated_preview_artifact",
}
def _persist_mode_profile_card(
self,
mode_id: str | None,
@@ -797,11 +882,18 @@ class SeatDemoService:
if source_vehicle_id and source_vehicle_id != resolved_vehicle_id:
raise ValueError("控制指令流不属于当前车型,请重新生成。")
source_draft = source_result.get("mode_profile_draft")
draft_existing_mode_revision = ""
if isinstance(source_draft, dict):
draft_request_id = str(source_draft.get("source_request_id") or "").strip()
draft_vehicle_id = str(source_draft.get("vehicle_id") or "").strip()
draft_instruction = str(source_draft.get("instruction") or "").strip()
draft_name = str(source_draft.get("name") or "").strip()
draft_existing_mode_id = str(
source_draft.get("existing_mode_id") or ""
).strip()
draft_existing_mode_revision = str(
source_draft.get("existing_mode_revision") or ""
).strip()
if draft_request_id != clean_request_id:
raise ValueError("功能模式草稿与控制指令流不匹配,请重新生成。")
if draft_vehicle_id and draft_vehicle_id != resolved_vehicle_id:
@@ -810,6 +902,19 @@ class SeatDemoService:
raise ValueError("自然语言指令已修改,请重新生成模式预览。")
if draft_name and draft_name != clean_name:
raise ValueError("功能模式名称已修改,请重新生成模式预览。")
if clean_mode_id and not draft_existing_mode_id:
raise ValueError("新功能模式预览不能通过已有模式的更新接口保存。")
if draft_existing_mode_id and draft_existing_mode_id != clean_mode_id:
raise ValueError("该预览属于已有功能模式,请通过对应模式的更新接口保存。")
if draft_existing_mode_id and not draft_existing_mode_revision:
raise ValueError("功能模式预览版本过旧,请重新生成后再更新。")
if (
draft_existing_mode_revision
and profile is not None
and draft_existing_mode_revision
!= self.mode_profiles.profile_revision(profile)
):
raise ValueError("功能模式已被其他会话更新,请重新生成预览后再保存。")
recorded_instruction = str(source_result.get("utterance", "")).strip()
if not recorded_instruction:
source_spans = {
@@ -920,6 +1025,8 @@ class SeatDemoService:
instruction=clean_instruction,
command_flow=command_flow,
timeline=profile.timeline if profile is not None else None,
expected_revision=draft_existing_mode_revision or None,
create_only=not bool(clean_mode_id),
)
public_profile = next(
(
@@ -1422,15 +1529,7 @@ class SeatDemoService:
"conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens"
)
vehicle_profile = self.vehicle_profiles.get(vehicle_id)
control_plane_request = any(
matcher(utterance) is not None
for matcher in (
self._parse_mode_profile_save_name,
self._parse_mode_profile_create_name,
self._parse_mode_profile_restore_name,
self._parse_mode_profile_update,
)
)
control_plane_request = self.is_mode_profile_configuration_request(utterance)
if control_plane_request:
result = self.process(
utterance,
@@ -2070,6 +2169,8 @@ class SeatDemoService:
direct_mode_profile_id: str | None = None,
speech_understanding: dict[str, Any] | None = None,
assistant_plan: dict[str, Any] | None = None,
allow_fuzzy_mode_profile_match: bool = True,
allow_scene_composition: bool = False,
) -> dict[str, Any]:
control_transport = self._validate_control_transport(control_transport)
utterance = utterance.strip()
@@ -2151,7 +2252,11 @@ class SeatDemoService:
[direct_mode_profile]
if direct_mode_profile is not None
else []
if direct_can_batch is not None or speech_understanding is not None
if (
direct_can_batch is not None
or speech_understanding is not None
or not allow_fuzzy_mode_profile_match
)
else self.mode_profiles.match_text(
utterance,
vehicle_id=vehicle_profile.id,
@@ -2325,6 +2430,7 @@ class SeatDemoService:
local_preview=local_preview,
model_parse_source=model_parse_source,
assistant_plan=assistant_plan,
allow_scene_composition=allow_scene_composition,
)
dynamic_mode_stop = self._is_dynamic_mode_stop_request(utterance)
if dynamic_mode_stop:
@@ -3422,6 +3528,21 @@ class SeatDemoService:
)
return generic.group("name").strip() if generic is not None else None
@classmethod
def is_mode_profile_configuration_request(cls, utterance: str) -> bool:
"""Identify mode configuration text that must never reach live executors."""
clean_utterance = str(utterance).strip()
return any(
matcher(clean_utterance) is not None
for matcher in (
cls._parse_mode_profile_save_name,
cls._parse_mode_profile_create_name,
cls._parse_mode_profile_restore_name,
cls._parse_mode_profile_update,
)
)
@staticmethod
def _parse_mode_profile_create_name(utterance: str) -> str | None:
explicit = CREATE_MODE_PROFILE_NAME.search(utterance)
@@ -4378,6 +4499,109 @@ class SeatDemoService:
"issues": issues,
}
@staticmethod
def _scene_plan_signatures(
assistant_plan: dict[str, Any],
) -> Counter[tuple[str, str, str]]:
signatures: Counter[tuple[str, str, str]] = Counter()
requests = assistant_plan.get("requests", [])
if not isinstance(requests, list):
return signatures
for request in requests:
if not isinstance(request, dict):
continue
domain = str(request.get("domain") or "")
capability = str(request.get("capability") or "")
target = str(request.get("target_scope") or "")
if domain == "seat" and target == "front_right":
target = "passenger"
if domain and capability and target:
signatures[(domain, capability, target)] += 1
return signatures
@staticmethod
def _scene_action_signatures(
actions: list[dict[str, Any]],
) -> Counter[tuple[str, str, str]]:
signatures: Counter[tuple[str, str, str]] = Counter()
for action in actions:
domain = str(action.get("domain") or "")
capability = str(action.get("capability") or "")
raw_target = action.get("target")
target = (
str(raw_target.get("instance") or raw_target.get("zone") or "")
if isinstance(raw_target, dict)
else str(action.get("target_scope") or raw_target or "")
)
if domain == "seat" and target == "front_right":
target = "passenger"
if domain and capability and target:
signatures[(domain, capability, target)] += 1
return signatures
@staticmethod
def _scene_parameters_preserved(
assistant_plan: dict[str, Any],
actions: list[dict[str, Any]],
) -> bool:
actual_parameters: dict[tuple[str, str, str], dict[str, Any]] = {}
for action in actions:
domain = str(action.get("domain") or "")
capability = str(action.get("capability") or "")
raw_target = action.get("target")
target = (
str(raw_target.get("instance") or raw_target.get("zone") or "")
if isinstance(raw_target, dict)
else str(action.get("target_scope") or raw_target or "")
)
if domain == "seat" and target == "front_right":
target = "passenger"
parameters = action.get("parameters")
if domain and capability and target and isinstance(parameters, dict):
actual_parameters[(domain, capability, target)] = parameters
requests = assistant_plan.get("requests", [])
if not isinstance(requests, list):
return False
for request in requests:
if not isinstance(request, dict):
return False
domain = str(request.get("domain") or "")
capability = str(request.get("capability") or "")
target = str(request.get("target_scope") or "")
if domain == "seat" and target == "front_right":
target = "passenger"
parameters = request.get("parameters")
if not isinstance(parameters, list):
return False
expected = {
str(parameter.get("name") or ""): parameter.get("value")
for parameter in parameters
if isinstance(parameter, dict)
}
actual = actual_parameters.get((domain, capability, target))
if actual is None or any(actual.get(name) != value for name, value in expected.items()):
return False
return True
@staticmethod
def _scrub_failed_scene_preview(result: dict[str, Any]) -> None:
failed_intent = result.get("intent_ir")
failed_spec = (
failed_intent.get("spec") if isinstance(failed_intent, dict) else None
)
if isinstance(failed_spec, dict):
failed_spec["actions"] = []
failed_spec["requests"] = []
result["deferred_requests"] = []
result["model_requests"] = []
result["function_config"] = None
result["function_configs"] = {}
result["command_plan"] = None
result["command_plans"] = {}
result["scene_config"] = None
result["execution_report"] = None
def _process_mode_profile_create(
self,
utterance: str,
@@ -4405,9 +4629,25 @@ class SeatDemoService:
if create_match is not None
else ""
)
source_utterance = requested_instruction or (
f"进入{mode_name},请根据该场景名称使用当前可控原子能力构建并执行场景"
existing_profile = self.mode_profiles.resolve_exact_name(
mode_name,
vehicle_id=vehicle_profile.id,
)
source_profile = existing_profile if not requested_instruction else None
draft_mode_name = existing_profile.name if existing_profile is not None else mode_name
generation_source = (
"registered_mode_profile"
if source_profile is not None
else "codex_scene_composition"
if assistant_plan is not None and not requested_instruction
else "explicit_scene_requirements"
)
# A name-only request may already describe a repository-backed scene.
# Reuse that vehicle-scoped profile as trusted structured input instead
# of appending a model prompt to the user's utterance. The latter was
# being parsed as a second control clause on the assistant's local fast
# path, turning a valid nine-action mode into an incomplete draft.
source_utterance = requested_instruction or f"进入{mode_name}"
source_result = self._process_once(
source_utterance,
provider,
@@ -4417,7 +4657,20 @@ class SeatDemoService:
vehicle_profile.id,
simulation_fault,
control_transport=control_transport,
direct_mode_profile_id=(
source_profile.id if source_profile is not None else None
),
assistant_plan=assistant_plan,
# ``source_utterance`` is synthesized for a name-only, unknown
# scene. It must not fall back to substring matching (for example
# ``我的小憩模式`` -> ``小憩模式``), otherwise the exact resolver's
# ambiguity and collision checks can be bypassed.
allow_fuzzy_mode_profile_match=bool(requested_instruction),
allow_scene_composition=(
assistant_plan is not None
and source_profile is None
and not requested_instruction
),
)
draft_context = self._context_for_vehicle(
self.conversations.snapshot(conversation_id),
@@ -4434,9 +4687,12 @@ class SeatDemoService:
clause
for clause, _parsed, status in local_clause_results
if status != "complete"
and not self.mode_profiles.match_text(
clause,
vehicle_id=vehicle_profile.id,
and not (
(requested_instruction or source_profile is not None)
and self.mode_profiles.match_text(
clause,
vehicle_id=vehicle_profile.id,
)
)
]
if provider == "local" and assistant_plan is None and unresolved_draft_clauses:
@@ -4468,7 +4724,49 @@ class SeatDemoService:
]
self._persist(source_result)
return source_result
source_intent = source_result.get("intent_ir")
source_spec = (
source_intent.get("spec") if isinstance(source_intent, dict) else None
)
source_actions = (
source_spec.get("actions", []) if isinstance(source_spec, dict) else []
)
typed_source_actions = [
action for action in source_actions if isinstance(action, dict)
] if isinstance(source_actions, list) else []
if (
source_result.get("status") == "succeeded"
and generation_source == "codex_scene_composition"
and assistant_plan is not None
and (
self._scene_plan_signatures(assistant_plan)
!= self._scene_action_signatures(typed_source_actions)
or not self._scene_parameters_preserved(
assistant_plan,
typed_source_actions,
)
)
):
source_result["status"] = "validation_failed"
source_result["validation"] = [
*list(source_result.get("validation", [])),
{
"severity": "error",
"code": "MODE_PROFILE_SCENE_PLAN_INCOMPLETE",
"path": "intent_ir.spec.actions",
"message": (
"Codex 场景候选动作未全部进入当前车型的可复用执行链,"
"已拒绝生成部分功能模式。"
),
},
]
if source_result.get("status") != "succeeded":
if generation_source == "codex_scene_composition":
# Final fail-closed barrier for errors discovered after the
# plan guard (adapter, schema, compiler, or safety validation).
# A failed reusable scene must not expose a trial-able subset.
self._scrub_failed_scene_preview(source_result)
source_result["mode_creation_failure"] = {
"code": "MODE_PROFILE_ACTION_VALIDATION_FAILED",
"reason": "场景动作未通过车辆能力或安全校验",
@@ -4528,12 +4826,23 @@ class SeatDemoService:
)
source_request_id = str(source_result.get("request_id") or "")
source_result["mode_profile_draft"] = {
"name": mode_name,
"name": draft_mode_name,
"instruction": source_utterance,
"source_request_id": source_request_id,
"vehicle_id": vehicle_profile.id,
"domains": domains,
"operation_count": len(reusable_actions),
**(
{
"existing_mode_id": existing_profile.id,
"existing_mode_revision": (
self.mode_profiles.profile_revision(existing_profile)
),
}
if existing_profile is not None
else {}
),
"generation_source": generation_source,
}
source_result["mode_creation"] = {
"requested_utterance": utterance,
@@ -4542,12 +4851,34 @@ class SeatDemoService:
"source_status": source_result.get("status"),
"source_provider": source_result.get("provider_used"),
"operation_count": len(reusable_actions),
**(
{
"existing_mode_id": existing_profile.id,
"existing_mode_revision": (
self.mode_profiles.profile_revision(existing_profile)
),
}
if existing_profile is not None
else {}
),
"generation_source": generation_source,
}
source_result["orchestration_route"] = "mode_profile_create_preview"
source_result["decision_summary"] = [
f"已构建并验证“{mode_name}”场景。",
(
f"已从当前车型的工程模式目录载入并验证“{source_profile.name}”的"
f" {len(reusable_actions)} 项设备动作。"
if source_profile is not None
else f"Codex 已根据场景语义组合并验证“{draft_mode_name}”的工程能力。"
if generation_source == "codex_scene_composition"
else f"已构建并验证“{mode_name}”场景。"
),
*list(source_result.get("decision_summary", [])),
"请确认动作后保存为功能模式,或先进行一次模拟试运行。",
(
"可先进行一次模拟试运行,确认动作后更新现有功能模式。"
if existing_profile is not None
else "请确认动作后保存为功能模式,或先进行一次模拟试运行。"
),
]
# Overwrite the persisted source result with the draft metadata. The
# mode-card endpoint later re-reads this server-owned artifact and
@@ -6236,6 +6567,7 @@ class SeatDemoService:
local_preview: bool = False,
model_parse_source: str = "cloud_model",
assistant_plan: dict[str, Any] | None = None,
allow_scene_composition: bool = False,
) -> tuple[dict[str, Any], str, str | None]:
vehicle_profile = vehicle_profile or self.vehicle_profiles.default()
clause_results = self._local_clause_results(utterance, context)
@@ -6282,6 +6614,7 @@ class SeatDemoService:
if status != "complete"
],
trusted_local=trusted_local,
allow_scene_composition=allow_scene_composition,
)
model = self.model_plan_adapter.adapt(
guarded.plan,
@@ -6289,6 +6622,65 @@ class SeatDemoService:
vehicle_profile,
utterance=fallback_utterance,
)
if allow_scene_composition:
promoted_native: list[dict[str, Any]] = []
remaining_domain_actions: list[dict[str, Any]] = []
for action in model.get("domain_actions", []):
if self.native_can_runtime.supports(action):
promoted = dict(action)
promoted["execution_backend"] = "native_can"
promoted_native.append(promoted)
else:
remaining_domain_actions.append(action)
remaining_deferred: list[dict[str, Any]] = []
for deferred in model.get("deferred_requests", []):
candidate = {
"domain": str(deferred.get("domain") or ""),
"capability": str(deferred.get("capability") or ""),
"target": str(
deferred.get("target_scope")
or deferred.get("target")
or ""
),
"operation": str(deferred.get("desired_change") or "set"),
"parameters": dict(deferred.get("parameters") or {}),
"reason": "Codex 场景候选动作已通过工程能力契约校验。",
"execution_backend": "native_can",
}
if self.native_can_runtime.supports(candidate):
promoted_native.append(candidate)
else:
remaining_deferred.append(deferred)
model.setdefault("ambiguities", []).append(
{
"field": "assistant_plan",
"question": (
f"能力 {candidate['capability']} 无法进入当前车型的"
"可复用执行链。"
),
}
)
model["native_can_actions"] = [
*model.get("native_can_actions", []),
*promoted_native,
]
model["domain_actions"] = remaining_domain_actions
model["deferred_requests"] = remaining_deferred
if allow_scene_composition and model.get("ambiguities"):
# Adapter validation is the second half of the scene gate: it
# owns targets, required parameters, types and ranges. If any
# candidate fails there, discard every sibling action so the
# reusable draft can never become a silently partial scene.
for key in (
"actions",
"domain_actions",
"native_can_actions",
"deferred_requests",
"preset_actions",
"preset_events",
"model_requests",
):
model[key] = []
model = self._mark_control_parse_source(
model,
model_parse_source,