"""Application service for the stage-one seat single-domain closed loop.""" from __future__ import annotations import json import os import re import sys import time import uuid from copy import deepcopy from datetime import UTC, datetime from pathlib import Path from typing import Any import yaml REPOSITORY_ROOT = Path(__file__).resolve().parents[3] SEAT_SOURCE = REPOSITORY_ROOT / "domains" / "seat" / "src" if str(SEAT_SOURCE) not in sys.path: sys.path.insert(0, str(SEAT_SOURCE)) from seat_domain import ( # noqa: E402 HardwareSeatExecutor, SIMULATED_HARDWARE_FAULTS, SeatCanMatrix, SeatCompiler, T2GSeatCompiler, SimulatedHardwareSeatExecutor, VirtualSeatExecutor, build_function_config, build_function_preset, build_intent_ir, parse_local_intent, validate_semantics, ) from cognitive_agent.application.capability_registry import CapabilityRegistry # noqa: E402 from cognitive_agent.application.conversation_state import ConversationStateStore # noqa: E402 from cognitive_agent.application.domain_runtime import ConfiguredDomainRuntime # noqa: E402 from cognitive_agent.application.mode_profile import ( # noqa: E402 ModeProfile, ModeProfileRegistry, ) from cognitive_agent.application.model_plan_adapter import ( # noqa: E402 ModelPlanAdapter, APPLY_PRESET_CAPABILITY, SAVE_PRESET_CAPABILITY, SEAT_CAPABILITIES, SEAT_MOTION_CAPABILITIES, ) from cognitive_agent.application.seat_calibration import position_pct_to_angle # noqa: E402 from cognitive_agent.application.vehicle_profile import ( # noqa: E402 VehicleProfile, VehicleProfileRegistry, ) from cognitive_agent.infrastructure.llm.openai_responses import ( # noqa: E402 OpenAIIntentError, OpenAIIntentInterpreter, ) from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402 HardwareSafetyError, SeatHardwareSettings, ) from cognitive_agent.runtime.contracts import ContractValidator # noqa: E402 def _now() -> str: return datetime.now(UTC).isoformat().replace("+00:00", "Z") def _clean_vehicle_number(value: float | None) -> int | float | None: if value is None: return None return int(value) if float(value).is_integer() else round(float(value), 1) CONVERSATION_ID_PATTERN = re.compile(r"^conv-[a-z0-9-]{6,80}$") SAVE_MODE_PROFILE_NAME = re.compile( r"(?:固化为|保存为长期|保存成长期|长期保存为|记为长期|命名为)" r"(?P[^,。,.!!\s]{1,64}?模式)" ) UPDATE_MODE_PROFILE_PREFIX = re.compile( r"^(?:请(?:帮我)?|帮我)?(?:修改一下|修改|调整|更改|更新|改一下)" r"(?P[^,。,.!!\s]{1,64}?模式)" r"(?:[,。,.!!\s]*(?P.*))?$" ) UPDATE_MODE_PROFILE_BA = re.compile( r"^(?:请(?:帮我)?|帮我)?(?:把|将)" r"(?P[^,。,.!!\s]{1,64}?模式)" r"(?:的)?(?P.+)$" ) RESTORE_MODE_PROFILE_PREFIX = re.compile( r"^(?:请(?:帮我)?|帮我)?(?:恢复|还原|重置|初始化)" r"(?P[^,。,.!!\s]{1,64}?模式)" r"(?:的)?(?:初始配置|初始参数|默认配置|原始配置|出厂配置)?$" ) RESTORE_MODE_PROFILE_SUFFIX = re.compile( r"^(?:请(?:帮我)?|帮我)?(?:把|将)?" r"(?P[^,。,.!!\s]{1,64}?模式)" r"(?:恢复|还原|重置|初始化)(?:到|为)?" r"(?:初始配置|初始参数|默认配置|原始配置|出厂配置)?$" ) SEAT_AXIS_TO_CAPABILITY = { axis: capability for capability, axis in SEAT_MOTION_CAPABILITIES.items() } SEAT_FUNCTION_TO_CAPABILITY = { function: capability for capability, function in SEAT_CAPABILITIES.items() } class SeatDemoService: def __init__(self, repository_root: Path = REPOSITORY_ROOT): self.root = repository_root self.domain_root = repository_root / "domains" / "seat" self.contracts = ContractValidator(repository_root) self.matrix = SeatCanMatrix( [ self.domain_root / "can" / "seat-thermal-matrix.yaml", self.domain_root / "can" / "seat-motion-virtual-matrix.yaml", ] ) self.compiler = SeatCompiler(self.domain_root / "can" / "signal-mapping.yaml") self.executor = VirtualSeatExecutor(self.matrix) self.simulated_hardware_executor = SimulatedHardwareSeatExecutor(self.matrix) self.simulated_hardware_enabled = os.getenv( "COGNITIVE_AGENT_SIMULATED_HARDWARE_ENABLED", "false" ).strip().lower() in {"1", "true", "yes", "on"} self.hardware_matrix = SeatCanMatrix( [ self.domain_root / "can" / "seat-thermal-hardware-matrix.yaml", self.domain_root / "can" / "seat-motion-hardware-matrix.yaml", ] ) self.hardware_compiler = T2GSeatCompiler( self.domain_root / "can" / "signal-mapping.hardware.yaml", repository_root / "platforms" / "t2g_seat_bench" / "calibration" / "seat.yaml", ) self.hardware_settings = SeatHardwareSettings.from_env(repository_root) # Shared by the short-lived hardware executor instances. A process must # establish a fresh physical four-axis reference before trusting stored # Hall counts, and a firmware replacement invalidates it again. self._seat_position_memory_state = {"synchronized": False} self.openai = OpenAIIntentInterpreter() self.domain_runtimes = { domain: ConfiguredDomainRuntime(repository_root, domain) for domain in ("climate", "roof", "ambient_light") } self.capability_registry = CapabilityRegistry( repository_root, {"seat", *self.domain_runtimes} ) self.capability_context = self.capability_registry.snapshot() self.model_plan_adapter = ModelPlanAdapter(self.capability_context) self.vehicle_profiles = VehicleProfileRegistry(repository_root / "config" / "vehicles") self.mode_profiles = ModeProfileRegistry(repository_root / "config" / "modes") self._artifact_root = repository_root / "artifacts" / "runs" self.conversations = ConversationStateStore( preset_root=repository_root / "artifacts" / "user-presets" ) @property def artifact_root(self) -> Path: return self._artifact_root @artifact_root.setter def artifact_root(self, value: Path) -> None: self._artifact_root = Path(value) if hasattr(self, "conversations"): self.conversations.preset_root = self._artifact_root / "user-presets" def runtime_info(self) -> dict[str, Any]: catalog = yaml.safe_load( (self.domain_root / "config" / "capabilities.yaml").read_text(encoding="utf-8") ) domain_runtime_info = [ runtime.runtime_info() for runtime in self.domain_runtimes.values() ] default_vehicle = self.vehicle_profiles.default() return { "service": "cabin-cognitive-orchestrator", "domain": "cabin", "platform": "reference_vehicle", "execution_mode": "dry_run", "execution_modes": ["dry_run", "simulated_hardware", "hardware"], "hardware_execution_allowed": self.hardware_settings.configured, "hardware": self.hardware_settings.public_info(), "seat_position_memory_synchronized": bool( self._seat_position_memory_state.get("synchronized", False) ), "simulated_hardware": { "enabled": self.simulated_hardware_enabled, "default_fault": "normal", "fault_scenarios": list(SIMULATED_HARDWARE_FAULTS), "physical_can_opened": False, }, "openai_available": self.openai.available, "openai_model": self.openai.model, "openai_responses_endpoint": self.openai.responses_endpoint, "conversation_memory": True, "default_vehicle_id": default_vehicle.id, "vehicle": default_vehicle.public_dict(), "vehicles": self.vehicle_profiles.public_profiles(), "modes": self.mode_profiles.public_modes(default_vehicle.id), "preset_storage": "conversation_durable", "inferred_level_range": [1, 3], "motion_position_range_pct": [0, 100], "capability_domains": [ { "domain": item["domain"], "availability": item["availability"], } for item in self.capability_context["domains"] ], "capabilities": [ capability for domain in self.capability_context["domains"] for capability in domain["capabilities"] ], "matrix": { "messages": len(self.matrix.data["messages"]) + sum(item["matrix"]["messages"] for item in domain_runtime_info), "signals": sum(len(item["signals"]) for item in self.matrix.data["messages"]) + sum(item["matrix"]["signals"] for item in domain_runtime_info), "source": { "seat": self.matrix.data["source"], **{ item["domain"]: item["matrix"]["source"] for item in domain_runtime_info }, }, "limitations": self.matrix.data["limitations"] + [ limitation for item in domain_runtime_info for limitation in item["matrix"]["limitations"] ], }, "domain_runtimes": domain_runtime_info, } def synchronize_hardware_state( self, conversation_id: str, vehicle_id: str | None = None, ) -> dict[str, Any]: """Read the bench and project it into the current passenger conversation.""" if not CONVERSATION_ID_PATTERN.fullmatch(conversation_id): raise ValueError( "conversation_id must start with conv- and contain only lowercase letters, numbers, or hyphens" ) if not self.hardware_settings.configured: raise HardwareSafetyError("the T2G seat hardware runtime is not configured") vehicle_profile = self.vehicle_profiles.get(vehicle_id) physical_state = self._new_hardware_executor().read_physical_state() context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) context = self._context_for_vehicle(context, vehicle_profile) return { "status": "succeeded", "conversation_id": conversation_id, "conversation_turn": context["turn_count"], "source": "physical_can", "captured_at": physical_state.get("captured_at"), "complete": bool(physical_state.get("complete")), "missing": list(physical_state.get("missing", [])), "physical_state": physical_state, "actuator_state": context["devices"], } def _new_hardware_executor(self) -> HardwareSeatExecutor: return HardwareSeatExecutor( self.hardware_matrix, self.hardware_settings.bus_factory(), thermal_timeout_ms=self.hardware_settings.thermal_timeout_ms, ack_timeout_ms=self.hardware_settings.ack_timeout_ms, axis_maximum_x100={ capability: int( self.hardware_compiler.protocol["normalized_position_max"] ) for capability in self.hardware_compiler.axes }, motor_max_hall={ int(axis["motor_selector"]): int(axis["default_max_hall_count"]) for axis in self.hardware_compiler.axes.values() }, stop_all_motor_selector=int( self.hardware_compiler.protocol["stop_all_motor_selector"] ), auto_home=self.hardware_settings.auto_home_enabled, home_timeout_s=self.hardware_settings.home_timeout_s, firmware_guard=self.hardware_settings.firmware_guard(), synchronize_position_before_first_motion=( self.hardware_settings.position_sync_enabled ), position_sync_state=self._seat_position_memory_state, ) def process( self, utterance: str, provider: str = "auto", conversation_id: str | None = None, execution_mode: str = "dry_run", approval_token: str | None = None, vehicle_id: str | None = None, simulation_fault: str = "normal", ) -> dict[str, Any]: utterance = utterance.strip() if not utterance: raise ValueError("utterance must not be empty") if len(utterance) > 1000: raise ValueError("utterance must be at most 1000 characters") if provider not in {"auto", "openai", "local"}: raise ValueError("provider must be auto, openai, or local") if execution_mode not in {"dry_run", "simulated_hardware", "hardware"}: raise ValueError( "execution_mode must be dry_run, simulated_hardware, or hardware" ) simulated_hardware_request = execution_mode == "simulated_hardware" if simulated_hardware_request and not self.simulated_hardware_enabled: raise HardwareSafetyError( "simulated hardware is disabled; set " "COGNITIVE_AGENT_SIMULATED_HARDWARE_ENABLED=true" ) if simulation_fault not in SIMULATED_HARDWARE_FAULTS: raise ValueError( "simulation_fault must be one of " + ", ".join(SIMULATED_HARDWARE_FAULTS) ) mode_save_name = self._parse_mode_profile_save_name(utterance) mode_restore_name = self._parse_mode_profile_restore_name(utterance) mode_update = self._parse_mode_profile_update(utterance) control_plane_request = ( mode_save_name is not None or mode_restore_name is not None or mode_update is not None ) hardware_request = execution_mode == "hardware" if hardware_request and not control_plane_request: self.hardware_settings.authorize(approval_token) conversation_id = conversation_id or f"conv-{uuid.uuid4().hex}" 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) request_id = f"req-{datetime.now(UTC).strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:6]}" context = self._context_for_vehicle( self.conversations.snapshot(conversation_id), vehicle_profile, ) context["mode_profiles"] = self.mode_profiles.public_modes(vehicle_profile.id) matched_modes = self.mode_profiles.match_text( utterance, vehicle_id=vehicle_profile.id, ) traces: list[dict[str, Any]] = [] result: dict[str, Any] = { "request_id": request_id, "conversation_id": conversation_id, "conversation_turn": context["turn_count"], "status": "processing", "provider_requested": provider, "execution_mode": execution_mode, "hardware_execution_allowed": ( hardware_request and not control_plane_request ), "simulated_hardware": simulated_hardware_request, "simulation_fault": simulation_fault if simulated_hardware_request else None, "vehicle": vehicle_profile.public_dict(), "matched_modes": [ profile.public_dict() for profile in matched_modes ], "mode_profile": None, "decision_summary": [], "deferred_requests": [], "model_requests": [], "intent_ir": None, "function_config": None, "function_configs": {}, "scene_config": None, "preset_config": None, "validation": [], "command_plan": None, "command_plans": {}, "execution_report": None, "actuator_state": context["devices"], "domain_state": context["domain_devices"], "saved_presets": [ {"name": item["name"], "target": item["target"]} for item in context.get("presets", []) ], "trace_events": traces, } started = time.perf_counter() if mode_save_name is not None: return self._process_mode_profile_save( utterance, request_id, conversation_id, context, mode_save_name, vehicle_profile, result, traces, ) if mode_restore_name is not None: return self._process_mode_profile_restore( utterance, request_id, conversation_id, mode_restore_name, vehicle_profile, result, traces, ) if mode_update is not None: mode_name, change_text = mode_update return self._process_mode_profile_update( utterance, request_id, provider, conversation_id, context, mode_name, change_text, vehicle_profile, result, traces, ) parsed, provider_used, provider_notice = self._interpret( utterance, provider, context, vehicle_profile, ) parsed = self._apply_mode_profiles(parsed, matched_modes, context, vehicle_profile) self._trace( traces, request_id, "intent_parse", "succeeded", f"使用 {provider_used} 完成结构化意图抽取。", started, { "provider": provider_used, "confidence": parsed["confidence"], "conversation_turn": context["turn_count"], }, ) if provider_notice: parsed["decision_summary"].append(provider_notice) result["provider_used"] = provider_used result["decision_summary"] = parsed["decision_summary"] result["deferred_requests"] = parsed.get("deferred_requests", []) result["model_requests"] = parsed.get("model_requests", []) context_started = time.perf_counter() self._trace( traces, request_id, "context_resolve", "succeeded", ( f"已将会话 {conversation_id} 的第 {context['turn_count']} 轮执行状态" "、域能力注册表和长期模式配置作为模型上下文;" "本地层仅注入已命中的长期模式动作。" ), context_started, { "conversation_id": conversation_id, "turn_count": context["turn_count"], "last_target": context["last_target"], "state_before": context["devices"], "capability_domains": [ item["domain"] for item in self.capability_context["domains"] ], "matched_modes": [profile.id for profile in matched_modes], }, ) route_started = time.perf_counter() detected_domains = parsed.get("detected_domains", ["seat"]) seat_only = detected_domains == ["seat"] self._trace( traces, request_id, "domain_route", "succeeded", ( "仅识别到 seat 领域,使用已验证的座椅单域闭环。" if seat_only else ( f"识别到 {','.join(detected_domains)} 领域," "按领域边界生成独立 FunctionConfig。" ) ), route_started, { "domains": detected_domains, "mode": "single_domain" if len(detected_domains) == 1 else "cross_domain", }, ) if parsed.get("domain_actions") or any( domain != "seat" for domain in detected_domains ): if hardware_request and not parsed.get("actions"): raise HardwareSafetyError( "hardware mode requires at least one passenger seat operation; " "non-seat-only requests stay dry-run" ) return self._process_orchestrated( utterance, request_id, provider_used, conversation_id, context, parsed, result, traces, hardware_request=hardware_request, simulated_hardware_request=simulated_hardware_request, simulation_fault=simulation_fault, vehicle_profile=vehicle_profile, ) intent_ir = build_intent_ir(parsed, utterance, request_id, provider_used) result["intent_ir"] = intent_ir generation_started = time.perf_counter() function_config = build_function_config(parsed, request_id, provider_used) preset_config = None if parsed.get("preset_actions"): preset_action = parsed["preset_actions"][0] preset_config = build_function_preset( preset_action["target"], preset_action["preset_name"], context["devices"][preset_action["target"]], request_id, provider_used, ) result["function_config"] = function_config result["function_configs"] = {"seat": function_config} if function_config else {} result["preset_config"] = preset_config generated_document = function_config or preset_config self._trace( traces, request_id, "function_generate", "succeeded" if generated_document else "waiting_confirmation", ( "已生成座椅 FunctionConfig。" if function_config else ( "已从当前姿态生成 FunctionPreset。" if preset_config else "没有足够信息生成可执行配置。" ) ), generation_started, { "operation_count": len(parsed["actions"]), "preset_operation_count": ( len(preset_config["spec"]["operations"]) if preset_config else 0 ), }, ) schema_started = time.perf_counter() validation = self.contracts.validate(intent_ir) if function_config is not None: validation.extend(self.contracts.validate(function_config)) if preset_config is not None: validation.extend(self.contracts.validate(preset_config)) schema_errors = [issue for issue in validation if issue["severity"] == "error"] self._trace( traces, request_id, "schema_validate", "failed" if schema_errors else "succeeded", f"公共与领域 Schema 校验{'发现错误' if schema_errors else '通过'}。", schema_started, {"error_count": len(schema_errors)}, ) semantic_started = time.perf_counter() semantic_issues = validate_semantics(parsed, function_config) validation.extend(semantic_issues) if hardware_request: self._adapt_validation_for_hardware(validation) semantic_errors = [issue for issue in semantic_issues if issue["severity"] == "error"] self._trace( traces, request_id, "domain_validate", "failed" if semantic_errors else "succeeded", "座椅语义校验阻止了不受支持的请求。" if semantic_errors else "座椅能力和目标映射校验通过。", semantic_started, {"error_count": len(semantic_errors), "warning_count": len(semantic_issues) - len(semantic_errors)}, ) result["validation"] = validation all_errors = [issue for issue in validation if issue["severity"] == "error"] if all_errors: if any(issue["code"] == "AMBIGUOUS_INTENT" for issue in all_errors): result["status"] = "needs_confirmation" else: result["status"] = "validation_failed" self._persist(result) return result if parsed.get("deferred_requests") and not parsed.get("actions"): result["status"] = "capability_unavailable" self._persist(result) return result if preset_config is not None and not parsed.get("actions"): preset_action = parsed["preset_actions"][0] compile_started = time.perf_counter() self._trace( traces, request_id, "compile", "succeeded", "FunctionPreset 属于控制平面数据,不生成 CAN 命令。", compile_started, { "preset_name": preset_action["preset_name"], "command_count": 0, }, ) safety_started = time.perf_counter() self._trace( traces, request_id, "safety_check", "succeeded", "预设仅保存已验证的五轴目标,不改变硬件发送边界。", safety_started, {"hardware_execution_allowed": False}, ) execute_started = time.perf_counter() execution_report = { "apiVersion": "cognitive-agent/v1alpha1", "kind": "ExecutionReport", "metadata": { "id": f"report-{request_id}", "name": "Seat posture preset save report", "version": "1.0.0", "request_id": request_id, "labels": {"control_plane": "preset_store"}, }, "spec": { "mode": "dry_run", "status": "succeeded", "started_at": _now(), "finished_at": _now(), "results": [ { "ref": f"save-{preset_action['target']}-posture-preset", "domain": "seat", "status": "succeeded", "message": ( f"已将 {preset_action['target']} 当前五轴姿态保存为" f"“{preset_action['preset_name']}”。" ), "telemetry": { "operation": "save_posture_preset", "target": preset_action["target"], "preset_name": preset_action["preset_name"], "captured_axis_count": len( preset_config["spec"]["operations"] ), "storage_scope": "conversation_durable", "can_command_emitted": False, }, } ], }, } report_issues = self.contracts.validate(execution_report) validation.extend(report_issues) result["execution_report"] = execution_report if report_issues: result["status"] = "execution_report_invalid" self._persist(result) return result updated_context = self.conversations.save_preset( conversation_id, utterance, preset_action["target"], preset_action["preset_name"], preset_config, parsed["decision_summary"], ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["status"] = "succeeded" result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["saved_presets"] = [ {"name": item["name"], "target": item["target"]} for item in updated_context["presets"] ] self._trace( traces, request_id, "execute", "succeeded", "当前姿态已写入会话预设仓库;未生成 CAN 帧。", execute_started, { "mode": "control_plane", "preset_name": preset_action["preset_name"], }, ) self._trace( traces, request_id, "state_update", "succeeded", f"预设“{preset_action['preset_name']}”已持久化,可在后续指令中直接调用。", time.perf_counter(), { "preset_count": len(updated_context["presets"]), "storage_scope": "conversation_durable", }, ) self._persist(result) return result compile_started = time.perf_counter() try: if hardware_request: self.hardware_settings.validate_function_config(function_config) command_plan = self.hardware_compiler.compile( function_config, context["devices"] ) else: command_plan = self.compiler.compile(function_config) except ValueError as exc: compile_issue = { "severity": "error", "code": "CAN_COMPILE_REJECTED", "path": "spec.operations", "message": str(exc), } validation.append(compile_issue) result["validation"] = validation result["status"] = "compile_failed" self._trace( traces, request_id, "compile", "failed", f"座椅 CAN 编译被安全约束阻止:{exc}", compile_started, {"error": str(exc), "hardware_request": hardware_request}, ) self._persist(result) return result plan_issues = self.contracts.validate(command_plan) validation.extend(plan_issues) result["command_plan"] = command_plan result["command_plans"] = {"seat": command_plan} self._trace( traces, request_id, "compile", "failed" if plan_issues else "succeeded", "配置已确定性编译为座椅 CAN 语义信号计划。", compile_started, { "messages": [ command["message"] for command in command_plan["spec"]["commands"] ], "command_count": len(command_plan["spec"]["commands"]), "signal_count": sum( len(command["signals"]) for command in command_plan["spec"]["commands"] ), }, ) if plan_issues: result["status"] = "compile_failed" self._persist(result) return result safety_started = time.perf_counter() self._trace( traces, request_id, "safety_check", "succeeded", ( ( "硬件启用、授权令牌、固件 profile 与目标座椅配置门禁已通过;" if self.hardware_settings.approval_required else "硬件启用、调试免令牌、固件 profile 与目标座椅配置门禁已通过;" ) + "板端 Position_Valid 将在发送绝对移动命令前实时检查。" if hardware_request else ( "模拟台架隔离门禁已通过;仅生成故障注入反馈,物理 CAN、ZLG 与 IAR 路径均未打开。" if simulated_hardware_request else "平台、矩阵和策略均限制为 dry-run;硬件发送路径未启用。" ) ), safety_started, { "hardware_execution_allowed": hardware_request, "simulated_hardware": simulated_hardware_request, "simulation_fault": ( simulation_fault if simulated_hardware_request else None ), "position_calibration_gate": ( "runtime_can_preflight" if hardware_request else "not_applicable" ), "mapping_status": ( "protocol_hil_verified_combined_firmware_hil_pending" if hardware_request else "draft" ), "firmware_profiles": ( sorted(self.hardware_settings.profiles) if hardware_request else [] ), }, ) execute_started = time.perf_counter() if hardware_request: hardware_executor = self._new_hardware_executor() execution_report = hardware_executor.execute( command_plan, function_config, context["devices"] ) elif simulated_hardware_request: execution_report = self.simulated_hardware_executor.execute( command_plan, function_config, context["devices"], fault_scenario=simulation_fault, ) else: execution_report = self.executor.execute( command_plan, function_config, context["devices"] ) report_issues = self.contracts.validate(execution_report) validation.extend(report_issues) result["execution_report"] = execution_report execution_succeeded = ( not report_issues and execution_report["spec"]["status"] == "succeeded" ) if report_issues: result["status"] = "execution_report_invalid" elif not execution_succeeded: result["status"] = "execution_failed" elif parsed.get("deferred_requests"): result["status"] = "partially_succeeded" else: result["status"] = "succeeded" execution_results = execution_report["spec"]["results"] stop_all_attempted = any( item.get("telemetry", {}).get("stop_all_attempted") for item in execution_results ) firmware_gate_failed = any( item.get("telemetry", {}).get("error_type") == "FirmwareProgrammingError" for item in execution_results ) failed_execution = next( (item for item in execution_results if item.get("status") == "failed"), None, ) failed_telemetry = ( failed_execution.get("telemetry", {}) if failed_execution else {} ) hardware_failure_summary = ( f"真实 CAN 执行失败:{failed_execution['message']}" if failed_execution else "真实 CAN 执行失败,未获得满足目标的物理反馈。" ) if stop_all_attempted: hardware_failure_summary += " 移动路径已尝试发送 STOP_ALL。" self._trace( traces, request_id, "execute", "succeeded" if execution_succeeded else "failed", ( "真实 CAN 命令已发送,并收到 IAR 固件的实际状态回读。" if hardware_request and execution_succeeded else ( ( "固件身份验证或自动烧录失败;未发送座椅控制帧。" if firmware_gate_failed else hardware_failure_summary ) if hardware_request else ( "模拟台架已生成隔离的 HIL 反馈,未访问物理 CAN。" if simulated_hardware_request and execution_succeeded else ( f"模拟台架故障已触发:{failed_execution['message']}" if simulated_hardware_request and failed_execution else "虚拟 CAN 已编码命令帧并生成模拟回读。" ) ) ) ), execute_started, { "mode": execution_report["spec"]["mode"], "result_count": len(execution_report["spec"]["results"]), "firmware_gate_failed": firmware_gate_failed, "stop_all_attempted": stop_all_attempted, "motor_result": failed_telemetry.get("motor_result_name"), "failure_category": failed_telemetry.get("failure_category"), "hall_before": failed_telemetry.get("hall_before"), "hall_after": failed_telemetry.get("hall_after"), }, ) if execution_succeeded: execution_notices = [ str(item.get("telemetry", {}).get("user_notice")) for item in execution_results if item.get("telemetry", {}).get("user_notice") ] for notice in execution_notices: if notice not in parsed["decision_summary"]: parsed["decision_summary"].append(notice) state_actions = ( self._hardware_feedback_actions( parsed["actions"], function_config, execution_report ) if hardware_request else parsed["actions"] ) updated_context = self.conversations.apply_successful_actions( conversation_id, utterance, state_actions, parsed["decision_summary"], ) physical_state = execution_report.get("spec", {}).get( "physical_state" ) if hardware_request and isinstance(physical_state, dict): updated_context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "state_update", "succeeded", f"会话状态已更新为第 {updated_context['turn_count']} 轮,可用于下一条指令。", time.perf_counter(), {"state_after": updated_context["devices"]}, ) elif hardware_request: physical_state = execution_report.get("spec", {}).get( "physical_state" ) if isinstance(physical_state, dict) and ( physical_state.get("position") or physical_state.get("ventilation") or physical_state.get("heating") ): updated_context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "state_sync", "succeeded", "真实执行未完成,但已按 CAN 物理回读同步副驾状态。", time.perf_counter(), {"state_after": updated_context["devices"]}, ) telemetry = next( ( item.get("telemetry", {}) for item in execution_report["spec"]["results"] if item.get("telemetry", {}).get("feedback_frame") ), execution_report["spec"]["results"][0].get("telemetry", {}), ) self._trace( traces, request_id, "telemetry", "succeeded" if execution_succeeded else "failed", ( "IAR 固件反馈帧中的实际状态与请求目标一致。" if hardware_request and execution_succeeded else ( ( f"未获得满足目标的真实硬件反馈:" f"{failed_execution['message']}" if failed_execution else "未获得满足目标的真实硬件反馈。" ) if hardware_request else ( "模拟台架反馈与请求目标一致,物理 CAN 始终关闭。" if simulated_hardware_request and execution_succeeded else ( f"模拟台架故障反馈:{failed_execution['message']}" if simulated_hardware_request and failed_execution else "虚构反馈帧中的最终状态与请求目标一致。" ) ) ) ), time.perf_counter(), { "command_frame": telemetry.get("command_frame", {}).get("data_hex"), "feedback_frame": telemetry.get("feedback_frame", {}).get("data_hex"), "simulated": not hardware_request, "simulation_fault": ( simulation_fault if simulated_hardware_request else None ), }, ) self._persist(result) return result @staticmethod def _adapt_validation_for_hardware( validation: list[dict[str, Any]], ) -> None: """Replace reference-vehicle dry-run warnings with HIL bench semantics.""" hardware_messages = { "LEVEL_COMMAND_SIMULATED": ( "LEVEL_COMMAND_T2G_MAPPED", "目标档位已映射到统一 T2G 固件的 0x29E 档位信号;" "执行结果以 0x4FA 物理 CAN 回读为准。", ), "SYNTHETIC_MOTION_CAN": ( "T2G_MOTION_PROTOCOL_MAPPED", "参考车型的虚拟运动矩阵未用于本次 HIL;座椅移动通过统一 T2G 固件的 " "0x300 命令和 0x301/0x302 物理反馈闭环执行。", ), "DRIVER_MOTION_INTERLOCK_NOT_IMPLEMENTED": ( "BENCH_ONLY_NO_VEHICLE_INTERLOCKS", "当前真实移动路径仅限独立 T2G 台架;尚未接入车速、挡位、乘员和防夹信号," "不得直接用于车载座椅系统。", ), "COMBINED_THERMAL_SIMULATED": ( "COMBINED_THERMAL_T2G_MAPPED", "加热与通风组合目标由统一 T2G 固件执行并以 0x4FA 物理反馈确认;" "接入完整座椅总成前仍需补充热安全互锁验证。", ), "VIRTUAL_EXECUTION_ONLY": ( "REFERENCE_MATRIX_OVERRIDDEN_BY_T2G_BENCH", "参考车型矩阵元数据仍不完整;本次 HIL 仅使用仓库内已验证的 " "T2G 统一固件协议、身份门禁和物理反馈闭环。", ), } for issue in validation: replacement = hardware_messages.get(str(issue.get("code"))) if replacement is not None: issue["code"], issue["message"] = replacement @staticmethod def _hardware_feedback_actions( actions: list[dict[str, Any]], function_config: dict[str, Any], execution_report: dict[str, Any], ) -> list[dict[str, Any]]: """Project authoritative physical feedback into conversation/UI state.""" feedback_by_ref = { item["ref"]: item.get("telemetry", {}) for item in execution_report["spec"]["results"] if item.get("status") == "succeeded" } updated = deepcopy(actions) operations = function_config["spec"]["operations"] for action, operation in zip(updated, operations, strict=True): telemetry = feedback_by_ref.get(operation["id"], {}) if action.get("action_type") == "position": if telemetry.get("effective_position_pct") is not None: action["position_pct"] = int( telemetry["effective_position_pct"] ) if telemetry.get("position_valid") is not None: action["position_valid"] = bool( telemetry["position_valid"] ) if telemetry.get("motion_mode") in { "relative_timed_duty", "timed_user_request", }: action["position_estimated"] = True action["feedback_basis"] = ( "timed_endpoint_or_hall_confirmed_open_loop" if telemetry.get("motion_mode") == "timed_user_request" else "relative_hall_confirmed_open_loop" ) continue raw = telemetry.get("feedback_value") if raw is not None: raw_value = int(raw) action["state"] = "off" if raw_value <= 1 else "on" action["requested_level"] = max(0, raw_value - 1) physical_state = execution_report.get("spec", {}).get("physical_state") if isinstance(physical_state, dict): positions = physical_state.get("position", {}) by_axis = { action.get("axis"): action for action in updated if action.get("action_type") == "position" } for axis, feedback in positions.items(): if not isinstance(feedback, dict) or feedback.get("position_pct") is None: continue action = by_axis.get(axis) if action is None: action = { "domain": "seat", "target": "passenger", "action_type": "position", "axis": axis, } updated.append(action) by_axis[axis] = action action["position_pct"] = int(feedback["position_pct"]) if feedback.get("position_valid") is not None: action["position_valid"] = bool(feedback["position_valid"]) action["feedback_basis"] = "physical_can_snapshot" by_function = { action.get("function"): action for action in updated if action.get("function") in {"ventilation", "heating"} } for feature in ("ventilation", "heating"): feedback = physical_state.get(feature) if not isinstance(feedback, dict): continue action = by_function.get(feature) if action is None: action = { "domain": "seat", "target": "passenger", "function": feature, } updated.append(action) by_function[feature] = action action["state"] = ( "on" if feedback.get("state") == "on" else "off" ) action["requested_level"] = int(feedback.get("level", 0)) for action in updated: action["physical_target_alias"] = "t2g_primary_seat" return updated @staticmethod def _parse_mode_profile_save_name(utterance: str) -> str | None: explicit = SAVE_MODE_PROFILE_NAME.search(utterance) if explicit is not None: return explicit.group("name").strip() save_like = any(word in utterance for word in ("保存", "记住", "记为")) mode_context = any( word in utterance for word in ("这个模式", "这个配置", "当前模式", "长期", "固化") ) if not save_like or not mode_context: return None generic = re.search( r"(?:保存为|保存成|记为|命名为)(?P[^,。,.!!\s]{1,64}?模式)", utterance, ) return generic.group("name").strip() if generic is not None else None @staticmethod def _parse_mode_profile_update(utterance: str) -> tuple[str, str] | None: explicit = UPDATE_MODE_PROFILE_PREFIX.search(utterance) if explicit is not None: change_text = str(explicit.group("change") or "").strip() return explicit.group("name").strip(), change_text inline = UPDATE_MODE_PROFILE_BA.search(utterance) if inline is None: return None change_text = inline.group("change").strip("的").strip() if not any(word in change_text for word in ("改", "调", "设", "更新", "变")): return None return inline.group("name").strip(), change_text @staticmethod def _parse_mode_profile_restore_name(utterance: str) -> str | None: explicit = RESTORE_MODE_PROFILE_PREFIX.search(utterance) if explicit is not None: return explicit.group("name").strip() suffix = RESTORE_MODE_PROFILE_SUFFIX.search(utterance) return suffix.group("name").strip() if suffix is not None else None def _apply_mode_profiles( self, parsed: dict[str, Any], matched_modes: list[ModeProfile], context: dict[str, Any], vehicle_profile: VehicleProfile, ) -> dict[str, Any]: if not matched_modes: return parsed if self._contains_seat_preset_request(parsed): return parsed if len(matched_modes) > 1: rejected = deepcopy(parsed) for key in ( "actions", "domain_actions", "preset_actions", "preset_events", "deferred_requests", "model_requests", ): rejected[key] = [] rejected["ambiguities"] = [ { "field": "mode_profile", "question": "一条请求只能执行一个座椅功能模式,请确认要执行的模式。", } ] rejected["decision_summary"] = [ "同一条请求命中多个功能模式,已停止生成控制指令以避免模式冲突。" ] return rejected safety_ambiguities = [ item for item in parsed.get("ambiguities", []) if item.get("field") != "capability" ] if safety_ambiguities: return parsed mode_requests = self.mode_profiles.requests_for_profiles(matched_modes) detected_domains = [] for request in mode_requests: if request["domain"] not in detected_domains: detected_domains.append(request["domain"]) mode_plan = { "detected_domains": detected_domains or ["seat"], "requests": mode_requests, "ambiguities": [], "decision_summary": [ "命中长期模式配置:" + "、".join(profile.name for profile in matched_modes) + "。" ], "confidence": max(float(parsed.get("confidence", 0)), 0.97), } mode_parsed = self.model_plan_adapter.adapt( mode_plan, context, vehicle_profile, ) parsed_has_explicit_request = bool( parsed.get("actions") or parsed.get("domain_actions") or parsed.get("preset_actions") or parsed.get("preset_events") or parsed.get("model_requests") ) merged = deepcopy(parsed) merged["actions"] = self._merge_control_actions( mode_parsed.get("actions", []), parsed.get("actions", []), ) merged["domain_actions"] = self._merge_control_actions( mode_parsed.get("domain_actions", []), parsed.get("domain_actions", []), ) for key in ( "preset_actions", "preset_events", "deferred_requests", "ambiguities", "model_requests", ): parsed_values = parsed.get(key, []) if key == "ambiguities" and not parsed_has_explicit_request: # A local parser does not know arbitrary named modes and emits # a generic unsupported-capability ambiguity. A unique, # repository-backed mode match is authoritative for that case. parsed_values = [] merged[key] = self._merge_unique( mode_parsed.get(key, []), parsed_values, ) merged["decision_summary"] = [ *mode_parsed.get("decision_summary", []), *parsed.get("decision_summary", []), ] merged["detected_domains"] = self._merge_domains( mode_parsed.get("detected_domains", []), parsed.get("detected_domains", []), ) merged["confidence"] = max( float(mode_parsed.get("confidence", 0)), float(parsed.get("confidence", 0)), ) return merged @staticmethod def _contains_seat_preset_request(parsed: dict[str, Any]) -> bool: capabilities = { request.get("capability") for request in parsed.get("model_requests", []) if isinstance(request, dict) } return bool( parsed.get("preset_actions") or parsed.get("preset_events") or SAVE_PRESET_CAPABILITY in capabilities or APPLY_PRESET_CAPABILITY in capabilities ) @classmethod def _merge_control_actions( cls, mode_items: list[dict[str, Any]], parsed_items: list[dict[str, Any]], ) -> list[dict[str, Any]]: parsed_controls = { cls._control_signature(item) for item in parsed_items } candidates = [ *parsed_items, *( item for item in mode_items if cls._control_signature(item) not in parsed_controls ), ] return cls._merge_unique([], candidates) @staticmethod def _control_signature(action: dict[str, Any]) -> tuple[Any, ...]: if action.get("domain"): return ( action.get("domain"), action.get("capability"), action.get("target"), ) if action.get("action_type") == "position": return ( "seat", "position", action.get("target"), action.get("axis"), ) return ( "seat", "function", action.get("target"), action.get("function"), ) @staticmethod def _merge_unique( first: list[dict[str, Any]], second: list[dict[str, Any]], ) -> list[dict[str, Any]]: merged = [] seen: set[str] = set() for item in [*first, *second]: signature = json.dumps(item, ensure_ascii=False, sort_keys=True) if signature in seen: continue seen.add(signature) merged.append(item) return merged @staticmethod def _merge_domains( first: list[str], second: list[str], ) -> list[str]: domains = [] for domain in [*first, *second]: if domain not in domains: domains.append(domain) return domains or ["seat"] def _process_mode_profile_update( self, utterance: str, request_id: str, provider: str, conversation_id: str, context: dict[str, Any], mode_name: str, change_text: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], ) -> dict[str, Any]: started = time.perf_counter() result["provider_used"] = "mode_profile_registry" result["decision_summary"] = [ f"识别为长期模式修改请求,目标模式为“{mode_name}”。" ] self._trace( traces, request_id, "intent_parse", "succeeded", "识别到长期模式配置修改请求;本次不调用 CAN 编译路径。", started, {"mode_name": mode_name, "change_text": change_text}, domain="mode", ) profile = self._resolve_single_mode_profile( mode_name, vehicle_profile, result, traces, request_id, ) if profile is None: self._persist(result) return result if not change_text: result["status"] = "needs_confirmation" result["validation"] = [ { "severity": "error", "code": "MODE_PROFILE_UPDATE_MISSING_CHANGE", "path": "utterance", "message": "请说明要把长期模式中的哪些参数改成什么目标值。", } ] self._persist(result) return result parsed, provider_used, provider_notice = self._interpret( change_text, provider, context, vehicle_profile, ) result["provider_used"] = f"mode_profile_registry+{provider_used}" result["decision_summary"].extend(parsed.get("decision_summary", [])) if provider_notice: result["decision_summary"].append(provider_notice) result["deferred_requests"] = parsed.get("deferred_requests", []) result["model_requests"] = parsed.get("model_requests", []) source_actions = [ *parsed.get("actions", []), *parsed.get("domain_actions", []), ] mode_operations = self._mode_operations_from_actions(source_actions) if not mode_operations: validation = [ { "severity": "error", "code": "NO_MODE_PROFILE_UPDATE_OPERATIONS", "path": "spec.operations", "message": "没有从修改说明中得到可写入长期模式库的可执行配置。", } ] validation.extend( { "severity": "error", "code": "AMBIGUOUS_INTENT", "path": ambiguity.get("field", "utterance"), "message": ambiguity.get("question", "修改说明不完整。"), } for ambiguity in parsed.get("ambiguities", []) ) result["validation"] = validation result["status"] = ( "capability_unavailable" if parsed.get("deferred_requests") and not parsed.get("ambiguities") else "needs_confirmation" ) self._persist(result) return result updated_profile = self.mode_profiles.update_mode(profile, mode_operations) profile_public = updated_profile.public_dict() source_path = self._display_path(Path(updated_profile.source)) backup_path = self._display_path( Path(updated_profile.source).with_suffix(".yaml.initial.bak") ) result["mode_profile"] = { **profile_public, "source": source_path, "initial_backup_source": backup_path, } result["matched_modes"] = [profile_public] result["model_requests"] = self.mode_profiles.requests_for_profiles( [updated_profile] ) result["decision_summary"].append( f"已更新长期模式“{updated_profile.name}”,原始配置备份保留为初始化来源。" ) self._trace( traces, request_id, "function_generate", "succeeded", "已将修改后的可执行配置写回长期模式 YAML。", time.perf_counter(), { "mode_profile_id": updated_profile.id, "operation_count": len(updated_profile.operations), "updated_operation_count": len(mode_operations), "source": source_path, "initial_backup_source": backup_path, }, domain="mode", ) execution_report = self._mode_profile_control_report( request_id, "update_mode_profile", updated_profile, f"已更新长期模式“{updated_profile.name}”。", { "updated_operation_count": len(mode_operations), "source": source_path, "initial_backup_source": backup_path, }, ) validation = self.contracts.validate(execution_report) result["validation"] = validation result["execution_report"] = execution_report if validation: result["status"] = "execution_report_invalid" self._persist(result) return result updated_context = self.conversations.record_mode_profile_event( conversation_id, utterance, result["mode_profile"], source_actions, result["decision_summary"], "mode_profile_update", ) result["status"] = ( "partially_succeeded" if parsed.get("deferred_requests") else "succeeded" ) result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "execute", "succeeded", "长期模式修改属于控制平面操作,未生成或发送 CAN 帧。", time.perf_counter(), { "mode": "control_plane", "mode_profile_id": updated_profile.id, "can_command_emitted": False, }, domain="mode", ) self._trace( traces, request_id, "state_update", "succeeded", f"长期模式“{updated_profile.name}”已更新,可被后续自然语言请求命中。", time.perf_counter(), { "turn_count": updated_context["turn_count"], "mode_profile_id": updated_profile.id, }, domain="mode", ) self._persist(result) return result def _process_mode_profile_restore( self, utterance: str, request_id: str, conversation_id: str, mode_name: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], ) -> dict[str, Any]: started = time.perf_counter() result["provider_used"] = "mode_profile_registry" result["decision_summary"] = [ f"识别为长期模式初始化请求,目标模式为“{mode_name}”。" ] self._trace( traces, request_id, "intent_parse", "succeeded", "识别到长期模式初始化请求;本次不调用模型或 CAN 编译路径。", started, {"mode_name": mode_name}, domain="mode", ) profile = self._resolve_single_mode_profile( mode_name, vehicle_profile, result, traces, request_id, ) if profile is None: self._persist(result) return result try: restored_profile = self.mode_profiles.restore_initial_mode(profile) except ValueError as exc: result["status"] = "needs_confirmation" result["validation"] = [ { "severity": "error", "code": "MODE_PROFILE_INITIAL_BACKUP_MISSING", "path": "mode_profile.initial_backup", "message": str(exc), } ] self._persist(result) return result profile_public = restored_profile.public_dict() source_path = self._display_path(Path(restored_profile.source)) backup_path = self._display_path( Path(restored_profile.source).with_suffix(".yaml.initial.bak") ) result["mode_profile"] = { **profile_public, "source": source_path, "initial_backup_source": backup_path, } result["matched_modes"] = [profile_public] result["model_requests"] = self.mode_profiles.requests_for_profiles( [restored_profile] ) result["decision_summary"].append( f"已将长期模式“{restored_profile.name}”恢复为初始配置。" ) self._trace( traces, request_id, "function_generate", "succeeded", "已从初始备份恢复长期模式 YAML。", time.perf_counter(), { "mode_profile_id": restored_profile.id, "operation_count": len(restored_profile.operations), "source": source_path, "initial_backup_source": backup_path, }, domain="mode", ) execution_report = self._mode_profile_control_report( request_id, "restore_mode_profile", restored_profile, f"已恢复长期模式“{restored_profile.name}”。", { "source": source_path, "initial_backup_source": backup_path, }, ) validation = self.contracts.validate(execution_report) result["validation"] = validation result["execution_report"] = execution_report if validation: result["status"] = "execution_report_invalid" self._persist(result) return result updated_context = self.conversations.record_mode_profile_event( conversation_id, utterance, result["mode_profile"], [], result["decision_summary"], "mode_profile_restore", ) result["status"] = "succeeded" result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "execute", "succeeded", "长期模式初始化属于控制平面操作,未生成或发送 CAN 帧。", time.perf_counter(), { "mode": "control_plane", "mode_profile_id": restored_profile.id, "can_command_emitted": False, }, domain="mode", ) self._trace( traces, request_id, "state_update", "succeeded", f"长期模式“{restored_profile.name}”已恢复,可被后续自然语言请求命中。", time.perf_counter(), { "turn_count": updated_context["turn_count"], "mode_profile_id": restored_profile.id, }, domain="mode", ) self._persist(result) return result def _resolve_single_mode_profile( self, mode_name: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], request_id: str, ) -> ModeProfile | None: matches = self.mode_profiles.match_text( mode_name, vehicle_id=vehicle_profile.id, ) result["matched_modes"] = [profile.public_dict() for profile in matches] if len(matches) == 1: return matches[0] result["status"] = "needs_confirmation" result["validation"] = [ { "severity": "error", "code": ( "MODE_PROFILE_NOT_FOUND" if not matches else "MODE_PROFILE_AMBIGUOUS" ), "path": "utterance.mode_profile", "message": ( f"未在车型 {vehicle_profile.id} 的长期模式库中找到“{mode_name}”。" if not matches else f"“{mode_name}”命中了多个长期模式,请指定更完整名称。" ), } ] self._trace( traces, request_id, "context_resolve", "failed", result["validation"][0]["message"], time.perf_counter(), { "mode_name": mode_name, "vehicle_id": vehicle_profile.id, "match_count": len(matches), }, domain="mode", ) return None def _mode_profile_control_report( self, request_id: str, operation: str, profile: ModeProfile, message: str, telemetry: dict[str, Any], ) -> dict[str, Any]: return { "apiVersion": "cognitive-agent/v1alpha1", "kind": "ExecutionReport", "metadata": { "id": f"report-{request_id}", "name": "Mode profile control report", "version": "1.0.0", "request_id": request_id, "labels": {"control_plane": "mode_profile_store"}, }, "spec": { "mode": "dry_run", "status": "succeeded", "started_at": _now(), "finished_at": _now(), "results": [ { "ref": f"{operation}-{profile.id}", "domain": "mode", "status": "succeeded", "message": message, "telemetry": { "operation": operation, "mode_profile_id": profile.id, "operation_count": len(profile.operations), "storage_scope": "repository_config", "can_command_emitted": False, **telemetry, }, } ], }, } def _display_path(self, path: Path) -> str: try: return str(path.relative_to(self.root)) except ValueError: return str(path) def _process_mode_profile_save( self, utterance: str, request_id: str, conversation_id: str, context: dict[str, Any], mode_name: str, vehicle_profile: VehicleProfile, result: dict[str, Any], traces: list[dict[str, Any]], ) -> dict[str, Any]: started = time.perf_counter() result["provider_used"] = "mode_profile_registry" result["decision_summary"] = [ f"识别为长期模式固化请求,目标名称为“{mode_name}”。" ] self._trace( traces, request_id, "intent_parse", "succeeded", "识别到长期模式配置保存请求;不调用 CAN 编译路径。", started, {"mode_name": mode_name}, domain="mode", ) source_actions, mode_operations = self._last_mode_source(context) if not mode_operations: result["status"] = "needs_confirmation" result["validation"] = [ { "severity": "error", "code": "NO_RECENT_MODE_SOURCE", "path": "conversation.recent_turns", "message": "当前会话没有可固化为长期模式的最近成功动作。", } ] self._persist(result) return result profile = self.mode_profiles.save_mode( mode_name, mode_operations, vehicle_id=vehicle_profile.id, ) profile_public = profile.public_dict() profile_path = Path(profile.source) try: source_path = str(profile_path.relative_to(self.root)) except ValueError: source_path = str(profile_path) result["mode_profile"] = { **profile_public, "source": source_path, } result["matched_modes"] = [profile_public] result["decision_summary"].append( f"已将最近一次成功动作固化为长期模式“{profile.name}”。" ) result["model_requests"] = self.mode_profiles.requests_for_profiles([profile]) generation_started = time.perf_counter() self._trace( traces, request_id, "function_generate", "succeeded", "已写入长期模式 YAML;该配置将在后续请求中重新生成 FunctionConfig。", generation_started, { "mode_profile_id": profile.id, "operation_count": len(profile.operations), "source": source_path, }, domain="mode", ) execution_report = { "apiVersion": "cognitive-agent/v1alpha1", "kind": "ExecutionReport", "metadata": { "id": f"report-{request_id}", "name": "Mode profile save report", "version": "1.0.0", "request_id": request_id, "labels": {"control_plane": "mode_profile_store"}, }, "spec": { "mode": "dry_run", "status": "succeeded", "started_at": _now(), "finished_at": _now(), "results": [ { "ref": f"save-{profile.id}", "domain": "mode", "status": "succeeded", "message": f"已保存长期模式“{profile.name}”。", "telemetry": { "operation": "save_mode_profile", "mode_profile_id": profile.id, "operation_count": len(profile.operations), "storage_scope": "repository_config", "source": source_path, "can_command_emitted": False, }, } ], }, } validation = self.contracts.validate(execution_report) result["validation"] = validation result["execution_report"] = execution_report if validation: result["status"] = "execution_report_invalid" self._persist(result) return result updated_context = self.conversations.save_mode_profile( conversation_id, utterance, result["mode_profile"], source_actions, result["decision_summary"], ) result["status"] = "succeeded" result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "execute", "succeeded", "长期模式保存属于控制平面操作,未生成或发送 CAN 帧。", time.perf_counter(), { "mode": "control_plane", "mode_profile_id": profile.id, "can_command_emitted": False, }, domain="mode", ) self._trace( traces, request_id, "state_update", "succeeded", f"长期模式“{profile.name}”已登记,可被后续自然语言请求命中。", time.perf_counter(), { "turn_count": updated_context["turn_count"], "mode_profile_id": profile.id, }, domain="mode", ) self._persist(result) return result def _last_mode_source( self, context: dict[str, Any], ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: for turn in reversed(context.get("recent_turns", [])): actions = turn.get("actions", []) if not isinstance(actions, list): continue operations = self._mode_operations_from_actions(actions) if operations: return deepcopy(actions), operations return [], [] def _mode_operations_from_actions( self, actions: list[dict[str, Any]], ) -> list[dict[str, Any]]: operations = [] for action in actions: operation = self._mode_operation_from_action(action) if operation is not None: operations.append(operation) return operations def _mode_operation_from_action( self, action: dict[str, Any], ) -> dict[str, Any] | None: domain = action.get("domain") if domain in self.domain_runtimes: return { "domain": domain, "capability": action["capability"], "target_scope": action["target"], "operation": action.get("operation", "set"), "parameters": dict(action.get("parameters", {})), "reason": action.get("reason", "用户固化的跨域模式动作。"), } if action.get("action_type") == "position": capability = SEAT_AXIS_TO_CAPABILITY.get(str(action.get("axis"))) if capability is None: return None parameters = {"position_pct": action["position_pct"]} if action.get("angle_deg") is not None: parameters["angle_deg"] = action["angle_deg"] return { "domain": "seat", "capability": capability, "target_scope": action["target"], "operation": "set", "parameters": parameters, "reason": "用户固化的座椅位置模式动作。", } function = action.get("function") capability = SEAT_FUNCTION_TO_CAPABILITY.get(str(function)) if capability is None: return None parameters = {"state": action.get("state")} if action.get("requested_level") is not None: parameters["level"] = action["requested_level"] return { "domain": "seat", "capability": capability, "target_scope": action["target"], "operation": "set", "parameters": parameters, "reason": "用户固化的座椅舒适功能模式动作。", } def _process_orchestrated( self, utterance: str, request_id: str, provider_used: str, conversation_id: str, context: dict[str, Any], parsed: dict[str, Any], result: dict[str, Any], traces: list[dict[str, Any]], *, hardware_request: bool = False, simulated_hardware_request: bool = False, simulation_fault: str = "normal", vehicle_profile: VehicleProfile | None = None, ) -> dict[str, Any]: """Run isolated adapters, with only the passenger seat eligible for HIL.""" vehicle_profile = vehicle_profile or self.vehicle_profiles.default() generation_started = time.perf_counter() intent_ir = build_intent_ir(parsed, utterance, request_id, provider_used) for action in parsed.get("domain_actions", []): capability = self.domain_runtimes[action["domain"]].capabilities[ action["capability"] ] intent_ir["spec"]["requests"].append( { "domain": action["domain"], "goal": ( f"{action['operation']} {action['capability']} " f"for {action['target']}" ), "target": { "device": capability["target_devices"][0], "zone": action["target"], }, "parameters": { "capability": action["capability"], **action["parameters"], }, "source_span": utterance, } ) result["intent_ir"] = intent_ir configs: dict[str, dict[str, Any]] = {} if parsed.get("actions"): seat_config = build_function_config(parsed, request_id, provider_used) if seat_config is not None: configs["seat"] = seat_config domain_actions: dict[str, list[dict[str, Any]]] = {} for action in parsed.get("domain_actions", []): domain_actions.setdefault(action["domain"], []).append(action) for domain in ("climate", "roof", "ambient_light"): if domain in domain_actions: configs[domain] = self.domain_runtimes[domain].build_function_config( domain_actions[domain], request_id, provider_used, ) result["function_configs"] = configs result["function_config"] = ( configs.get("seat") or next(iter(configs.values()), None) ) self._trace( traces, request_id, "function_generate", "succeeded" if configs else "waiting_confirmation", ( f"已按领域边界生成 {len(configs)} 份 FunctionConfig。" if configs else "没有足够的绝对目标生成可执行配置。" ), generation_started, { "domains": list(configs), "operation_count": sum( len(config["spec"]["operations"]) for config in configs.values() ), }, domain="orchestration", ) validation_started = time.perf_counter() validation = self.contracts.validate(intent_ir) for config in configs.values(): validation.extend(self.contracts.validate(config)) if parsed.get("actions"): validation.extend(validate_semantics(parsed, configs.get("seat"))) else: for ambiguity in parsed.get("ambiguities", []): validation.append( { "severity": "error", "code": "AMBIGUOUS_INTENT", "path": ambiguity["field"], "message": ambiguity["question"], } ) validation.append( { "severity": "warning", "code": "VIRTUAL_EXECUTION_ONLY", "path": "platform.reference_vehicle", "message": "当前三域矩阵仅允许虚拟执行,禁止实车下发。", } ) if not configs and not parsed.get("ambiguities") and not parsed.get( "deferred_requests" ): validation.append( { "severity": "error", "code": "NO_EXECUTABLE_OPERATION", "path": "spec.operations", "message": "模型没有生成任何可执行领域操作。", } ) if hardware_request: self._adapt_validation_for_hardware(validation) result["validation"] = validation errors = [issue for issue in validation if issue["severity"] == "error"] self._trace( traces, request_id, "schema_validate", "failed" if errors else "succeeded", f"公共和领域 Schema 校验{'发现错误' if errors else '通过'}。", validation_started, {"error_count": len(errors), "document_count": len(configs) + 1}, domain="orchestration", ) if errors: result["status"] = ( "needs_confirmation" if any(issue["code"] == "AMBIGUOUS_INTENT" for issue in errors) else "validation_failed" ) self._persist(result) return result if parsed.get("deferred_requests") and not configs: result["status"] = "capability_unavailable" self._persist(result) return result compile_started = time.perf_counter() plans: dict[str, dict[str, Any]] = {} for domain, config in configs.items(): if domain == "seat": if hardware_request: self.hardware_settings.validate_function_config(config) plans[domain] = self.hardware_compiler.compile( config, context["devices"], ) else: plans[domain] = self.compiler.compile(config) else: runtime = self.domain_runtimes[domain] plans[domain] = runtime.compile(config) validation.extend(self.contracts.validate(plans[domain])) result["command_plans"] = plans result["command_plan"] = plans.get("seat") or next( iter(plans.values()), None ) scene_config = ( self._build_scene_config(request_id, configs, plans) if len(configs) > 1 else None ) result["scene_config"] = scene_config if scene_config is not None: validation.extend(self.contracts.validate(scene_config)) compile_errors = [ issue for issue in validation if issue["severity"] == "error" ] self._trace( traces, request_id, "compile", "failed" if compile_errors else "succeeded", ( f"{len(configs)} 份配置已编译为相互隔离的 CAN 命令计划" + (",并生成 SceneConfig。" if scene_config else "。") ), compile_started, { "domains": list(plans), "command_count": sum( len(plan["spec"]["commands"]) for plan in plans.values() ), "scene_generated": scene_config is not None, }, domain="orchestration", ) if compile_errors: result["status"] = "compile_failed" self._persist(result) return result self._trace( traces, request_id, "safety_check", "succeeded", ( "副驾座椅已通过 T2G HIL 安全门,其他座舱领域保持虚拟 CAN。" if hardware_request else ( "模拟台架仅对座椅结果注入故障;全部领域的物理发送路径保持关闭。" if simulated_hardware_request else "所有领域均为 dry-run;矩阵缺少实车完整性信息,硬件发送保持禁用。" ) ), time.perf_counter(), { "hardware_execution_allowed": hardware_request, "simulated_hardware": simulated_hardware_request, "simulation_fault": ( simulation_fault if simulated_hardware_request else None ), "hardware_domains": ["seat"] if hardware_request else [], "virtual_domains": [ domain for domain in configs if not hardware_request or domain != "seat" ], "domains": list(configs), }, domain="orchestration", ) execute_started = time.perf_counter() execution_started_at = _now() reports: dict[str, dict[str, Any]] = {} for domain, config in configs.items(): if domain == "seat": if hardware_request: reports[domain] = self._new_hardware_executor().execute( plans[domain], config, context["devices"] ) elif simulated_hardware_request: reports[domain] = self.simulated_hardware_executor.execute( plans[domain], config, context["devices"], fault_scenario=simulation_fault, ) else: reports[domain] = self.executor.execute( plans[domain], config, context["devices"] ) else: reports[domain] = self.domain_runtimes[domain].execute( plans[domain], config ) report_results = [ item for domain_report in reports.values() for item in domain_report["spec"]["results"] ] execution_succeeded = all( domain_report["spec"]["status"] == "succeeded" for domain_report in reports.values() ) and all(item.get("status") == "succeeded" for item in report_results) physical_state = ( reports.get("seat", {}).get("spec", {}).get("physical_state") if hardware_request else None ) simulated_seat = simulated_hardware_request and "seat" in reports report_spec: dict[str, Any] = { "mode": "hil" if hardware_request or simulated_seat else "dry_run", "status": "succeeded" if execution_succeeded else "failed", "started_at": execution_started_at, "finished_at": _now(), "results": report_results, } if isinstance(physical_state, dict): report_spec["physical_state"] = physical_state execution_report = { "apiVersion": "cognitive-agent/v1alpha1", "kind": "ExecutionReport", "metadata": { "id": f"report-{request_id}", "name": ( "Cabin orchestration mixed HIL execution report" if hardware_request else ( "Cabin orchestration simulated hardware execution report" if simulated_seat else "Cabin orchestration virtual execution report" ) ), "version": "1.0.0", "request_id": request_id, "labels": { "simulation": "mixed" if hardware_request else "true", "seat_feedback": ( "physical_can" if hardware_request else ( "simulated_hardware" if simulated_seat else "simulated" ) ), "fault_scenario": ( simulation_fault if simulated_seat else "none" ), "orchestration": "cross_domain" if len(configs) > 1 else "single_domain", }, }, "spec": report_spec, } report_issues = self.contracts.validate(execution_report) validation.extend(report_issues) result["execution_report"] = execution_report result["validation"] = validation if report_issues: result["status"] = "execution_report_invalid" elif not execution_succeeded: result["status"] = "execution_failed" elif parsed.get("deferred_requests"): result["status"] = "partially_succeeded" else: result["status"] = "succeeded" failed_execution = next( (item for item in report_results if item.get("status") == "failed"), None, ) self._trace( traces, request_id, "execute", "succeeded" if execution_succeeded and not report_issues else "failed", ( "副驾座椅已按物理 CAN 回读闭环执行,其他领域已完成虚拟 CAN 执行。" if hardware_request and execution_succeeded else ( f"副驾座椅真实执行失败:{failed_execution['message']}" if hardware_request and failed_execution else ( f"模拟台架已执行 {len(report_results)} 个领域操作,物理 CAN 未打开。" if simulated_seat and execution_succeeded else ( f"模拟台架故障已触发:{failed_execution['message']}" if simulated_seat and failed_execution else ( "本次没有座椅动作,故障注入未应用;" f"已虚拟执行 {len(report_results)} 个领域操作。" if simulated_hardware_request else f"已虚拟执行 {len(report_results)} 个领域操作并汇总反馈。" ) ) ) ) ), execute_started, { "mode": execution_report["spec"]["mode"], "simulation": execution_report["metadata"]["labels"]["simulation"], "domains": list(configs), "result_count": len(report_results), "failed_domain": ( failed_execution.get("domain") if failed_execution else None ), }, domain="orchestration", ) if execution_succeeded and not report_issues: seat_actions = parsed.get("actions", []) if hardware_request: seat_actions = self._hardware_feedback_actions( seat_actions, configs["seat"], reports["seat"], ) all_actions = [*seat_actions, *parsed.get("domain_actions", [])] updated_context = self.conversations.apply_successful_actions( conversation_id, utterance, all_actions, parsed["decision_summary"], ) if hardware_request and isinstance(physical_state, dict): updated_context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["conversation_turn"] = updated_context["turn_count"] result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "state_update", "succeeded", f"跨域会话状态已更新为第 {updated_context['turn_count']} 轮。", time.perf_counter(), { "seat_state": updated_context["devices"], "domain_state": updated_context["domain_devices"], }, domain="orchestration", ) elif hardware_request and isinstance(physical_state, dict) and ( physical_state.get("position") or physical_state.get("ventilation") or physical_state.get("heating") ): updated_context = self.conversations.synchronize_physical_passenger( conversation_id, physical_state, ) updated_context = self._context_for_vehicle( updated_context, vehicle_profile, ) result["actuator_state"] = updated_context["devices"] result["domain_state"] = updated_context["domain_devices"] self._trace( traces, request_id, "state_sync", "succeeded", "跨域真实执行未完成,但已按 CAN 回读同步副驾物理状态。", time.perf_counter(), {"seat_state": updated_context["devices"].get("passenger", {})}, domain="orchestration", ) self._persist(result) return result @staticmethod def _build_scene_config( request_id: str, configs: dict[str, dict[str, Any]], plans: dict[str, dict[str, Any]], ) -> dict[str, Any]: function_refs = [] steps = [] for domain in configs: reference_id = f"{domain}_function" function_refs.append( { "id": reference_id, "domain": domain, "config_ref": f"functions/{domain}.yaml", "sha256": plans[domain]["spec"]["source"]["config_sha256"], } ) steps.append( { "id": f"execute_{domain}", "function_ref": reference_id, "start_offset_ms": 0, "failure_policy": "abort" if domain == "seat" else "continue", } ) return { "apiVersion": "cognitive-agent/v1alpha1", "kind": "SceneConfig", "metadata": { "id": f"scene-{request_id}", "name": "Generated cabin scene", "version": "1.0.0", "request_id": request_id, "generated_at": _now(), "labels": {"execution_scope": "virtual_only"}, }, "spec": { "function_refs": function_refs, "steps": steps, "execution": { "mode": "parallel", "default_failure_policy": "continue", "max_parallelism": min(4, len(configs)), "scene_timeout_ms": 30000, }, }, } def _interpret( self, utterance: str, provider: str, context: dict[str, Any], vehicle_profile: VehicleProfile | None = None, ) -> tuple[dict[str, Any], str, str | None]: vehicle_profile = vehicle_profile or self.vehicle_profiles.default() if provider == "local": return parse_local_intent(utterance, context), "local_rules", None if provider == "openai": if not self.openai.available: raise ValueError("GPT 模式需要设置 OPENAI_API_KEY") try: plan = self.openai.interpret(utterance, context, self.capability_context) return ( self.model_plan_adapter.adapt(plan, context, vehicle_profile), f"openai:{self.openai.model}", None, ) except OpenAIIntentError: return ( parse_local_intent(utterance, context), "local_rules_fallback", "GPT 调用失败,已降级到本地规则解析;CAN 编译和执行路径不变。", ) if self.openai.available: try: plan = self.openai.interpret(utterance, context, self.capability_context) return ( self.model_plan_adapter.adapt(plan, context, vehicle_profile), f"openai:{self.openai.model}", None, ) except OpenAIIntentError: return ( parse_local_intent(utterance, context), "local_rules_fallback", "GPT 调用失败,已降级到本地规则解析;CAN 编译和执行路径不变。", ) return ( parse_local_intent(utterance, context), "local_rules", "未配置 OPENAI_API_KEY,本次使用可重复的本地规则解析。", ) @staticmethod def _context_for_vehicle( context: dict[str, Any], vehicle_profile: VehicleProfile, ) -> dict[str, Any]: projected = deepcopy(context) projected["vehicle"] = vehicle_profile.public_dict() for target_state in projected.get("devices", {}).values(): positions = target_state.get("position", {}) for axis, state in positions.items(): if not isinstance(state, dict): continue motor = vehicle_profile.seat_motors.get(str(axis)) if motor is None or motor.display_angle is None: continue position_pct = state.get("position_pct") if ( not isinstance(position_pct, (int, float)) or isinstance(position_pct, bool) ): continue current_angle = state.get("angle_deg") legacy_angle = position_pct_to_angle(str(axis), float(position_pct)) should_project = current_angle is None or ( legacy_angle is not None and isinstance(current_angle, (int, float)) and not isinstance(current_angle, bool) and abs(float(current_angle) - float(legacy_angle)) < 0.0001 ) if should_project: state["angle_deg"] = _clean_vehicle_number( motor.display_angle_for_pct(int(position_pct)) ) return projected @staticmethod def _trace( traces: list[dict[str, Any]], request_id: str, stage: str, status: str, summary: str, started: float, details: dict[str, Any], domain: str = "seat", ) -> None: traces.append( { "apiVersion": "cognitive-agent/v1alpha1", "kind": "TraceEvent", "event_id": f"evt-{len(traces) + 1:02d}-{request_id}", "request_id": request_id, "timestamp": _now(), "stage": stage, "status": status, "domain": domain, "summary": summary, "details": details, "duration_ms": max(0, int((time.perf_counter() - started) * 1000)), } ) def _persist(self, result: dict[str, Any]) -> None: request_dir = self.artifact_root / result["request_id"] (request_dir / "functions").mkdir(parents=True, exist_ok=True) (request_dir / "presets").mkdir(parents=True, exist_ok=True) (request_dir / "command-plans").mkdir(parents=True, exist_ok=True) try: artifact_path = request_dir.relative_to(self.root) except ValueError: artifact_path = request_dir result["artifact_path"] = str(artifact_path) self._write_yaml(request_dir / "intent.yaml", result.get("intent_ir")) function_configs = result.get("function_configs") or {} if function_configs: for domain, document in function_configs.items(): self._write_yaml( request_dir / "functions" / f"{domain}.yaml", document ) else: self._write_yaml( request_dir / "functions" / "seat.yaml", result.get("function_config"), ) self._write_yaml(request_dir / "scene.yaml", result.get("scene_config")) self._write_yaml( request_dir / "presets" / "seat.yaml", result.get("preset_config") ) command_plans = result.get("command_plans") or {} if command_plans: for domain, document in command_plans.items(): self._write_yaml( request_dir / "command-plans" / f"{domain}.yaml", document ) else: self._write_yaml( request_dir / "command-plans" / "seat.yaml", result.get("command_plan"), ) self._write_yaml( request_dir / "execution-report.yaml", result.get("execution_report") ) trace_text = "".join( json.dumps(event, ensure_ascii=False) + "\n" for event in result["trace_events"] ) self._atomic_write(request_dir / "trace.jsonl", trace_text) self._atomic_write( request_dir / "result.json", json.dumps(result, ensure_ascii=False, indent=2) + "\n", ) def _write_yaml(self, path: Path, document: dict[str, Any] | None) -> None: if document is None: return text = yaml.safe_dump(document, allow_unicode=True, sort_keys=False) self._atomic_write(path, text) @staticmethod def _atomic_write(path: Path, content: str) -> None: temporary = path.with_suffix(path.suffix + ".tmp") temporary.write_text(content, encoding="utf-8") temporary.replace(path)