完善模式卡片、NFC及多设备控制流程

This commit is contained in:
minghanchen
2026-09-01 16:18:57 +08:00
parent ca26259e63
commit cbf230182d
15 changed files with 1092 additions and 191 deletions
+152 -47
View File
@@ -608,6 +608,59 @@ class SeatDemoService:
"vehicle_id": resolved_vehicle_id,
}
def get_mode_profile_card(
self,
mode_id: str,
vehicle_id: str | None = None,
) -> ModeProfile:
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
return self.mode_profiles.get_profile(mode_id, resolved_vehicle_id)
def execute_mode_profile_card(
self,
mode_id: str,
*,
conversation_id: str | None = None,
execution_mode: str = "dry_run",
approval_token: str | None = None,
vehicle_id: str | None = None,
simulation_fault: str = "normal",
hardware_client_id: str | None = None,
hardware_access_bypass: bool = True,
control_transport: str = WIRED_CAN_TRANSPORT,
) -> dict[str, Any]:
"""Execute a persisted mode by ID without interpreting natural language."""
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
profile = self.mode_profiles.get_profile(mode_id, resolved_vehicle_id)
if not profile.command_flow or not profile.command_flow.get("plans"):
raise ValueError("该模式尚未生成可直接执行的控制指令流。")
result = self.process(
profile.instruction or f"进入{profile.name}",
provider="local",
conversation_id=conversation_id,
execution_mode=execution_mode,
approval_token=approval_token,
vehicle_id=resolved_vehicle_id,
simulation_fault=simulation_fault,
hardware_client_id=hardware_client_id,
hardware_access_bypass=hardware_access_bypass,
control_transport=control_transport,
_direct_mode_profile_id=profile.id,
)
public_profile = profile.public_dict()
result["provider_used"] = "saved_mode_command_flow"
result["utterance"] = profile.name
result["matched_modes"] = [public_profile]
result["mode_profile"] = public_profile
result["orchestration_route"] = "saved_mode_command_flow"
result["decision_summary"] = [
f"直接载入已保存的模式“{profile.name}”,未再次解析自然语言。",
*list(result.get("decision_summary") or []),
]
self._persist(result)
return result
def save_mode_profile_card(
self,
mode_id: str,
@@ -616,7 +669,7 @@ class SeatDemoService:
source_request_id: str,
vehicle_id: str | None = None,
) -> dict[str, Any]:
"""Persist one edited mode from a server-generated CAN command flow."""
"""Persist one edited mode from a server-generated control flow."""
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
clean_mode_id = str(mode_id).strip()
@@ -632,7 +685,7 @@ class SeatDemoService:
if len(clean_instruction) > 1000:
raise ValueError("自然语言指令不能超过 1000 个字符。")
if not MODE_EDITOR_REQUEST_ID_PATTERN.fullmatch(clean_request_id):
raise ValueError("CAN 指令流来源请求编号无效。")
raise ValueError("控制指令流来源请求编号无效。")
visible_profiles = self.mode_profiles._profiles_for_vehicle( # noqa: SLF001
resolved_vehicle_id
@@ -653,15 +706,15 @@ class SeatDemoService:
result_path = self.artifact_root / clean_request_id / "result.json"
if not result_path.is_file():
raise ValueError("未找到执行按钮生成的 CAN 指令流,请重新执行。")
raise ValueError("未找到“生成指令”按钮产生的控制指令流,请重新生成。")
try:
source_result = json.loads(result_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise ValueError("CAN 指令流来源结果无法读取,请重新执行。") from exc
raise ValueError("控制指令流来源结果无法读取,请重新生成。") from exc
if not isinstance(source_result, dict) or source_result.get(
"request_id"
) != clean_request_id:
raise ValueError("CAN 指令流来源结果不完整,请重新执行。")
raise ValueError("控制指令流来源结果不完整,请重新生成。")
recorded_instruction = str(source_result.get("utterance", "")).strip()
if not recorded_instruction:
source_spans = {
@@ -676,12 +729,12 @@ class SeatDemoService:
if len(source_spans) == 1:
recorded_instruction = next(iter(source_spans))
if recorded_instruction and recorded_instruction != clean_instruction:
raise ValueError("自然语言指令已修改,请重新点击执行生成 CAN 指令流。")
raise ValueError("自然语言指令已修改,请重新点击“生成指令”。")
plans = source_result.get("command_plans") or {}
configs = source_result.get("function_configs") or {}
if not isinstance(plans, dict) or not isinstance(configs, dict):
raise ValueError("执行结果中没有可永久化的 CAN 指令流。")
raise ValueError("解析结果中没有可永久化的控制指令流。")
executable_plan_keys = {
key
for key, plan in plans.items()
@@ -691,7 +744,7 @@ class SeatDemoService:
and plan["spec"]["commands"]
}
if not executable_plan_keys:
raise ValueError("请先点击执行并成功生成 CAN 控制指令流。")
raise ValueError("请先点击“生成指令”并成功生成控制指令流。")
operations: list[dict[str, Any]] = []
signatures: set[tuple[str, str, str, str]] = set()
@@ -707,7 +760,12 @@ class SeatDemoService:
if not isinstance(operation, dict):
continue
capability = str(operation.get("capability", "")).strip()
target = str((operation.get("target") or {}).get("zone", "")).strip()
operation_target = operation.get("target") or {}
target = str(
operation_target.get("instance")
or operation_target.get("zone")
or ""
).strip()
parameters = operation.get("parameters") or {}
if not domain or not capability or not target or not isinstance(
parameters, dict
@@ -733,37 +791,8 @@ class SeatDemoService:
}
)
if not operations:
raise ValueError("CAN 指令流没有关联到可复用的模式操作。")
raise ValueError("控制指令流没有关联到可复用的模式操作。")
execution_steps = []
for item in (
(source_result.get("execution_report") or {}).get("spec", {}).get(
"results", []
)
):
if not isinstance(item, dict):
continue
telemetry = item.get("telemetry") or {}
command_evidence = {
key: deepcopy(telemetry[key])
for key in (
"command_frame",
"sent_frames",
"mqtt_messages",
"response_frame",
"requested_parameters",
)
if key in telemetry
}
if command_evidence:
execution_steps.append(
{
"ref": item.get("ref"),
"domain": item.get("domain"),
"status": item.get("status"),
**command_evidence,
}
)
command_flow = {
"schema": "cognitive-agent-mode-command-flow/v1",
"source_request_id": clean_request_id,
@@ -776,7 +805,10 @@ class SeatDemoService:
if key in executable_plan_keys and key in configs
},
"plans": {key: deepcopy(plans[key]) for key in plans if key in executable_plan_keys},
"execution_steps": execution_steps,
# A mode card stores only the validated, deterministic plan. Live
# delivery evidence belongs to each later execution, not to the
# generation workflow.
"execution_steps": [],
}
saved = self.mode_profiles.save_mode(
clean_name,
@@ -1677,6 +1709,7 @@ class SeatDemoService:
_allow_empty_control_result: bool = False,
_on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
_direct_can_batch: tuple[list[str], dict[str, Any]] | None = None,
_direct_mode_profile_id: str | None = None,
) -> dict[str, Any]:
control_transport = self._validate_control_transport(control_transport)
# Creating a reusable scene is a configuration workflow. Synthesize
@@ -1702,6 +1735,7 @@ class SeatDemoService:
_allow_empty_control_result,
_on_commands_validated,
_direct_can_batch,
_direct_mode_profile_id,
)
if control_transport == WIRELESS_MQTT_TRANSPORT:
# The head unit owns both physical CAN channels in wireless mode.
@@ -1723,6 +1757,7 @@ class SeatDemoService:
_allow_empty_control_result,
_on_commands_validated,
_direct_can_batch,
_direct_mode_profile_id,
)
if hardware_access_bypass or self._driver_only_vehicle_execution_active(
control_transport
@@ -1744,6 +1779,7 @@ class SeatDemoService:
_allow_empty_control_result,
_on_commands_validated,
_direct_can_batch,
_direct_mode_profile_id,
)
authorized_client_id = self.hardware_access.ensure_authorized(hardware_client_id)
with self.hardware_execution.reserve():
@@ -1764,6 +1800,7 @@ class SeatDemoService:
_allow_empty_control_result,
_on_commands_validated,
_direct_can_batch,
_direct_mode_profile_id,
)
def _process_once(
@@ -1783,6 +1820,7 @@ class SeatDemoService:
allow_empty_control_result: bool = False,
on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
direct_can_batch: tuple[list[str], dict[str, Any]] | None = None,
direct_mode_profile_id: str | None = None,
) -> dict[str, Any]:
control_transport = self._validate_control_transport(control_transport)
utterance = utterance.strip()
@@ -1831,6 +1869,14 @@ class SeatDemoService:
if not CONVERSATION_ID_PATTERN.fullmatch(conversation_id):
raise ValueError("conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens")
vehicle_profile = self.vehicle_profiles.get(vehicle_id)
direct_mode_profile = (
self.mode_profiles.get_profile(
direct_mode_profile_id,
vehicle_profile.id,
)
if direct_mode_profile_id
else None
)
if mode_create_name is not None:
return self._process_mode_profile_create(
@@ -1852,7 +1898,9 @@ class SeatDemoService:
)
context["mode_profiles"] = self.mode_profiles.public_modes(vehicle_profile.id)
matched_modes = (
[]
[direct_mode_profile]
if direct_mode_profile is not None
else []
if direct_can_batch is not None
else self.mode_profiles.match_text(
utterance,
@@ -1941,7 +1989,56 @@ class SeatDemoService:
result,
traces,
)
if direct_can_batch is not None:
if direct_mode_profile is not None:
mode_requests = self.mode_profiles.requests_for_profiles(
[direct_mode_profile]
)
detected_domains = list(
dict.fromkeys(
str(item.get("domain") or "")
for item in mode_requests
if str(item.get("domain") or "")
)
)
parsed = self.model_plan_adapter.adapt(
{
"detected_domains": detected_domains or ["seat"],
"requests": mode_requests,
"ambiguities": [],
"decision_summary": [
f"已载入模式“{direct_mode_profile.name}”保存的结构化操作。"
],
"confidence": 1.0,
},
context,
vehicle_profile,
)
saved_native_actions: list[dict[str, Any]] = []
for request in mode_requests:
parameters = {
str(item.get("name")): deepcopy(item.get("value"))
for item in request.get("parameters", [])
if isinstance(item, dict) and str(item.get("name") or "")
}
action = {
"domain": str(request.get("domain") or ""),
"capability": str(request.get("capability") or ""),
"target": str(request.get("target_scope") or "vehicle"),
"parameters": parameters,
"operation": str(request.get("operation") or "set"),
"reason": str(request.get("reason") or ""),
"execution_backend": "native_can",
"parse_source": "saved_mode_command_flow",
}
if self.native_can_runtime.supports(action):
saved_native_actions.append(action)
parsed["native_can_actions"] = [
*list(parsed.get("native_can_actions", [])),
*saved_native_actions,
]
provider_used = "saved_mode_command_flow"
provider_notice = None
elif direct_can_batch is not None:
parsed = deepcopy(direct_can_batch[1])
provider_used = "direct_can_control"
provider_notice = None
@@ -1980,6 +2077,11 @@ class SeatDemoService:
relative_resolution.claimed_controls if relative_resolution else None
),
)
if direct_mode_profile is not None:
parsed = self._mark_control_parse_source(
parsed,
"saved_mode_command_flow",
)
if local_preview:
parsed["ambiguities"] = []
parsed["unresolved_spans"] = []
@@ -1993,11 +2095,14 @@ class SeatDemoService:
parsed = self._partition_native_actions(
parsed,
promote_supported=(
hardware_request
and (
self.vehicle_mqtt_settings.enabled
if control_transport == WIRELESS_MQTT_TRANSPORT
else self.vehicle_can_settings.enabled
direct_mode_profile is not None
or (
hardware_request
and (
self.vehicle_mqtt_settings.enabled
if control_transport == WIRELESS_MQTT_TRANSPORT
else self.vehicle_can_settings.enabled
)
)
),
)