Files
Cognitive_Agent/src/cognitive_agent/application/seat_service.py
T

2742 lines
106 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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)