This commit is contained in:
orin
2026-07-30 08:45:38 +08:00
commit 25b24ac4cd
1333 changed files with 170092 additions and 0 deletions
+532
View File
@@ -0,0 +1,532 @@
"""Translate a generic model plan into domain adapter inputs without semantic rewriting."""
from __future__ import annotations
from copy import deepcopy
import re
from typing import Any
from cognitive_agent.application.seat_calibration import (
ANGLE_CALIBRATIONS,
angle_to_position_pct,
)
SEAT_CAPABILITIES = {
"seat.set_ventilation": "ventilation",
"seat.set_heating": "heating",
}
SEAT_MOTION_CAPABILITIES = {
"seat.set_longitudinal_position": "longitudinal",
"seat.set_cushion_tilt": "cushion_tilt",
"seat.set_backrest_recline": "backrest_recline",
"seat.set_legrest_height": "legrest_height",
"seat.set_legrest_extension": "legrest_extension",
}
SAVE_PRESET_CAPABILITY = "seat.save_posture_preset"
APPLY_PRESET_CAPABILITY = "seat.apply_posture_preset"
HEX_COLOR = re.compile(r"^#[0-9A-Fa-f]{6}$")
class ModelPlanAdapter:
"""Perform structural capability routing; user-intent decisions remain model-owned."""
def __init__(self, capability_context: dict[str, Any]) -> None:
self.capabilities: dict[str, tuple[str, str, dict[str, Any]]] = {}
for domain in capability_context.get("domains", []):
for capability in domain.get("capabilities", []):
self.capabilities[capability["name"]] = (
domain["domain"],
domain["availability"],
capability,
)
def adapt(
self,
plan: dict[str, Any],
context: dict[str, Any] | None = None,
) -> dict[str, Any]:
actions: list[dict[str, Any]] = []
domain_actions: list[dict[str, Any]] = []
deferred: list[dict[str, Any]] = []
preset_actions: list[dict[str, Any]] = []
preset_events: list[dict[str, Any]] = []
ambiguities = list(plan.get("ambiguities", []))
context = context or {}
for index, request in enumerate(plan.get("requests", [])):
capability_name = str(request.get("capability"))
registered = self.capabilities.get(capability_name)
if registered is None or registered[0] != request.get("domain"):
ambiguities.append(
{
"field": f"requests[{index}].capability",
"question": "模型请求的领域与能力注册表不一致。",
}
)
continue
domain, availability, capability = registered
parameters = {
str(item["name"]): item.get("value")
for item in request.get("parameters", [])
}
allowed_parameters = {item["name"] for item in capability.get("parameters", [])}
unknown_parameters = set(parameters) - allowed_parameters
if unknown_parameters:
ambiguities.append(
{
"field": f"requests[{index}].parameters",
"question": (
f"能力 {capability_name} 不支持参数 "
f"{', '.join(sorted(unknown_parameters))}。"
),
}
)
continue
if availability != "executable":
values = [value for value in parameters.values() if value is not None]
deferred.append(
{
"domain": domain,
"capability": capability_name,
"target_scope": request["target_scope"],
"desired_change": request["operation"],
"requested_value": values[0] if len(values) == 1 else None,
"parameters": parameters,
"reason": request["reason"],
}
)
continue
if domain != "seat":
generic_action = self._generic_action(
index,
request,
parameters,
capability,
ambiguities,
)
if generic_action is not None:
domain_actions.append(generic_action)
continue
if capability_name == SAVE_PRESET_CAPABILITY:
preset_action = self._preset_action(
index, request, parameters, ambiguities
)
if preset_action is not None:
preset_actions.append(preset_action)
continue
if capability_name == APPLY_PRESET_CAPABILITY:
preset_action = self._preset_action(
index, request, parameters, ambiguities
)
if preset_action is None:
continue
preset = self._find_preset(
context.get("presets", []),
preset_action["target"],
preset_action["preset_name"],
)
if preset is None:
available = [
str(item.get("name"))
for item in context.get("presets", [])
if item.get("target") == preset_action["target"]
]
suffix = (
f"当前可用预设:{', '.join(available)}。"
if available
else "当前座位尚未保存任何预设。"
)
ambiguities.append(
{
"field": f"requests[{index}].parameters.preset_name",
"question": (
f"未找到 {preset_action['target']} 的预设"
f"“{preset_action['preset_name']}”。{suffix}"
),
}
)
continue
for operation in preset["function_preset"]["spec"]["operations"]:
operation_parameters = dict(operation["parameters"])
action = self._seat_action(
index,
{
"capability": operation["capability"],
"target_scope": preset_action["target"],
"operation": "set",
},
operation_parameters,
ambiguities,
context,
)
if action is not None:
actions.append(action)
preset_events.append(
{
"action": "apply",
"target": preset_action["target"],
"preset_name": preset_action["preset_name"],
}
)
continue
action = self._seat_action(
index, request, parameters, ambiguities, context
)
if action is not None:
actions.append(action)
return {
"detected_domains": list(plan.get("detected_domains", ["seat"])),
"actions": actions,
"domain_actions": domain_actions,
"preset_actions": preset_actions,
"preset_events": preset_events,
"deferred_requests": deferred,
"ambiguities": ambiguities,
"decision_summary": list(plan.get("decision_summary", [])),
"confidence": float(plan.get("confidence", 0)),
"model_requests": deepcopy(plan.get("requests", [])),
}
@staticmethod
def _generic_action(
index: int,
request: dict[str, Any],
parameters: dict[str, Any],
capability: dict[str, Any],
ambiguities: list[dict[str, str]],
) -> dict[str, Any] | None:
target = request.get("target_scope")
if target not in capability.get("target_scopes", []):
ambiguities.append(
{
"field": f"requests[{index}].target_scope",
"question": (
f"{request['capability']} 不支持目标范围 {target!r}。"
),
}
)
return None
parameter_specs = {
item["name"]: item for item in capability.get("parameters", [])
}
state_spec = parameter_specs.get("state")
if state_spec is not None and parameters.get("state") is None:
inferred_state = {
"activate": "on",
"deactivate": "off",
}.get(request.get("operation"))
if inferred_state is not None:
parameters["state"] = inferred_state
for name, spec in parameter_specs.items():
value = parameters.get(name)
if spec.get("required") and value is None:
ambiguities.append(
{
"field": f"requests[{index}].parameters.{name}",
"question": (
f"{request['capability']} 需要模型给出参数 {name} 的绝对目标值。"
),
}
)
return None
if value is None:
continue
expected_type = spec.get("type")
numeric = (
isinstance(value, (int, float))
and not isinstance(value, bool)
)
type_valid = {
"number": numeric,
"integer": numeric and float(value).is_integer(),
"string": isinstance(value, str),
"boolean": isinstance(value, bool),
"object": isinstance(value, dict),
"array": isinstance(value, list),
}.get(str(expected_type), False)
if not type_valid:
ambiguities.append(
{
"field": f"requests[{index}].parameters.{name}",
"question": f"参数 {name} 的类型必须是 {expected_type}。",
}
)
return None
if numeric and (
("minimum" in spec and float(value) < float(spec["minimum"]))
or ("maximum" in spec and float(value) > float(spec["maximum"]))
):
ambiguities.append(
{
"field": f"requests[{index}].parameters.{name}",
"question": (
f"参数 {name} 必须位于 "
f"{spec.get('minimum', '-∞')} 到 {spec.get('maximum', '+∞')} 之间。"
),
}
)
return None
if spec.get("allowed_values") and value not in spec["allowed_values"]:
ambiguities.append(
{
"field": f"requests[{index}].parameters.{name}",
"question": (
f"参数 {name} 必须是 "
f"{', '.join(map(str, spec['allowed_values']))} 之一。"
),
}
)
return None
if name == "color_hex" and not HEX_COLOR.fullmatch(str(value)):
ambiguities.append(
{
"field": f"requests[{index}].parameters.color_hex",
"question": "氛围灯颜色必须是 #RRGGBB 格式。",
}
)
return None
normalized = {}
for name, value in parameters.items():
spec = parameter_specs[name]
normalized[name] = (
int(value)
if spec.get("type") == "integer"
else value.upper()
if name == "color_hex"
else value
)
return {
"domain": request["domain"],
"capability": request["capability"],
"target": str(target),
"parameters": normalized,
"operation": request.get("operation", "set"),
"reason": request.get("reason", ""),
}
@staticmethod
def _preset_action(
index: int,
request: dict[str, Any],
parameters: dict[str, Any],
ambiguities: list[dict[str, str]],
) -> dict[str, str] | None:
target = request.get("target_scope")
if target not in {"driver", "passenger"}:
ambiguities.append(
{
"field": f"requests[{index}].target_scope",
"question": "座椅姿态预设需要明确主驾或副驾。",
}
)
return None
preset_name = parameters.get("preset_name")
if (
not isinstance(preset_name, str)
or not preset_name.strip()
or len(preset_name.strip()) > 64
):
ambiguities.append(
{
"field": f"requests[{index}].parameters.preset_name",
"question": "保存或调用座椅姿态预设需要提供 1-64 个字符的名称。",
}
)
return None
return {"target": target, "preset_name": preset_name.strip()}
@staticmethod
def _find_preset(
presets: list[dict[str, Any]],
target: str,
preset_name: str,
) -> dict[str, Any] | None:
expected = preset_name.casefold()
return next(
(
preset
for preset in presets
if preset.get("target") == target
and str(preset.get("name", "")).casefold() == expected
),
None,
)
@staticmethod
def _seat_action(
index: int,
request: dict[str, Any],
parameters: dict[str, Any],
ambiguities: list[dict[str, str]],
context: dict[str, Any],
) -> dict[str, Any] | None:
target = request.get("target_scope")
if target not in {"driver", "passenger"}:
ambiguities.append(
{
"field": f"requests[{index}].target_scope",
"question": "座椅执行适配器需要 driver 或 passenger 目标。",
}
)
return None
capability = request["capability"]
if capability in SEAT_MOTION_CAPABILITIES:
axis = SEAT_MOTION_CAPABILITIES[capability]
angle = parameters.get("angle_deg")
if angle is not None and capability in ANGLE_CALIBRATIONS:
calibration = ANGLE_CALIBRATIONS[capability]
if (
not isinstance(angle, (int, float))
or isinstance(angle, bool)
or not calibration.minimum_deg <= float(angle) <= calibration.maximum_deg
):
ambiguities.append(
{
"field": f"requests[{index}].parameters.angle_deg",
"question": (
f"{capability} 的夹角必须在 "
f"{calibration.minimum_deg:g}-{calibration.maximum_deg:g} 度之间。"
),
}
)
return None
exact_angle: int | float = (
int(angle) if float(angle).is_integer() else float(angle)
)
action = {
"action_type": "position",
"target": target,
"axis": axis,
"position_pct": angle_to_position_pct(capability, float(angle)),
"angle_deg": exact_angle,
}
return ModelPlanAdapter._preserve_relative_motion(
index, request, action, context, ambiguities
)
position = parameters.get("position_pct")
if (
not isinstance(position, (int, float))
or isinstance(position, bool)
or not float(position).is_integer()
or not 0 <= float(position) <= 100
):
ambiguities.append(
{
"field": f"requests[{index}].parameters.position_pct",
"question": "座椅位置动作需要模型给出 0-100 的绝对整数目标。",
}
)
return None
action = {
"action_type": "position",
"target": target,
"axis": axis,
"position_pct": int(position),
}
return ModelPlanAdapter._preserve_relative_motion(
index, request, action, context, ambiguities
)
state = parameters.get("state")
if state is None:
state = {"activate": "on", "deactivate": "off"}.get(request.get("operation"))
if state not in {"on", "off"}:
ambiguities.append(
{
"field": f"requests[{index}].parameters.state",
"question": "座椅动作需要明确的 on/off 目标状态。",
}
)
return None
level = parameters.get("level")
return {
"target": target,
"function": SEAT_CAPABILITIES[capability],
"state": state,
"requested_level": (
int(level)
if isinstance(level, (int, float)) and not isinstance(level, bool)
else None
),
}
@staticmethod
def _preserve_relative_motion(
index: int,
request: dict[str, Any],
action: dict[str, Any],
context: dict[str, Any],
ambiguities: list[dict[str, str]],
) -> dict[str, Any] | None:
"""Keep the model's relative intent after it resolves an absolute target.
The absolute target remains the public capability value. This context is
also needed by the hardware compiler to choose the bounded timed/Hall path
when the controller has not yet acquired an absolute endpoint calibration.
"""
requested_operation = str(request.get("operation", "set"))
if requested_operation not in {"increase", "decrease"}:
return action
target = str(action["target"])
axis = str(action["axis"])
previous = (
context.get("devices", {})
.get(target, {})
.get("position", {})
.get(axis)
)
if not isinstance(previous, dict):
return action
start_position = previous.get("position_pct")
if not isinstance(start_position, (int, float)) or isinstance(
start_position, bool
):
return action
target_position = int(action["position_pct"])
signed_delta = target_position - int(start_position)
expected_sign = 1 if requested_operation == "increase" else -1
if signed_delta * expected_sign < 0:
ambiguities.append(
{
"field": f"requests[{index}].operation",
"question": (
"模型给出的相对移动方向与其解析出的绝对目标相互矛盾,"
"无法安全生成电机方向。"
),
}
)
return None
motion_intent: dict[str, Any] = {
"mode": "relative",
"direction": requested_operation,
"start_position_pct": int(start_position),
"delta_position_pct": abs(signed_delta),
}
start_angle = previous.get("angle_deg")
target_angle = action.get("angle_deg")
if (
isinstance(start_angle, (int, float))
and not isinstance(start_angle, bool)
and isinstance(target_angle, (int, float))
and not isinstance(target_angle, bool)
):
motion_intent["start_angle_deg"] = float(start_angle)
motion_intent["delta_angle_deg"] = abs(
float(target_angle) - float(start_angle)
)
action["motion_intent"] = motion_intent
return action