2742 lines
106 KiB
Python
Executable File
2742 lines
106 KiB
Python
Executable File
"""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<name>[^,。,.!!\s]{1,64}?模式)"
|
||
)
|
||
UPDATE_MODE_PROFILE_PREFIX = re.compile(
|
||
r"^(?:请(?:帮我)?|帮我)?(?:修改一下|修改|调整|更改|更新|改一下)"
|
||
r"(?P<name>[^,。,.!!\s]{1,64}?模式)"
|
||
r"(?:[,。,.!!\s]*(?P<change>.*))?$"
|
||
)
|
||
UPDATE_MODE_PROFILE_BA = re.compile(
|
||
r"^(?:请(?:帮我)?|帮我)?(?:把|将)"
|
||
r"(?P<name>[^,。,.!!\s]{1,64}?模式)"
|
||
r"(?:的)?(?P<change>.+)$"
|
||
)
|
||
RESTORE_MODE_PROFILE_PREFIX = re.compile(
|
||
r"^(?:请(?:帮我)?|帮我)?(?:恢复|还原|重置|初始化)"
|
||
r"(?P<name>[^,。,.!!\s]{1,64}?模式)"
|
||
r"(?:的)?(?:初始配置|初始参数|默认配置|原始配置|出厂配置)?$"
|
||
)
|
||
RESTORE_MODE_PROFILE_SUFFIX = re.compile(
|
||
r"^(?:请(?:帮我)?|帮我)?(?:把|将)?"
|
||
r"(?P<name>[^,。,.!!\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<name>[^,。,.!!\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)
|