fix: compose named cockpit modes from capabilities
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user