feat: integrate Chinese speech understanding and capability routing
This commit is contained in:
@@ -1445,6 +1445,139 @@ class SeatDemoService:
|
||||
_direct_can_batch=direct_can_batch,
|
||||
)
|
||||
|
||||
def _adapt_speech_understanding(
|
||||
self,
|
||||
understanding: dict[str, Any],
|
||||
context: dict[str, Any],
|
||||
vehicle_profile: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""Revalidate a server-owned speech result into the normal control IR.
|
||||
|
||||
Speech is allowed to skip a second natural-language parse, but it never
|
||||
skips the authoritative capability catalog or ModelPlanAdapter. Native
|
||||
CAN actions and configured-domain/seat actions are routed through the
|
||||
same structures used by the rest of one-sentence orchestration.
|
||||
"""
|
||||
|
||||
if understanding.get("execution_eligible") is not True:
|
||||
raise ValueError("speech understanding is not execution eligible")
|
||||
commands = understanding.get("commands")
|
||||
if not isinstance(commands, list) or not commands:
|
||||
raise ValueError("speech understanding has no executable commands")
|
||||
|
||||
native_actions: list[dict[str, Any]] = []
|
||||
model_requests: list[dict[str, Any]] = []
|
||||
detected_domains: list[str] = []
|
||||
|
||||
def add_payload(payload: object, source_text: str) -> None:
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError("speech command has no validated executor payload")
|
||||
domain = str(payload.get("domain", ""))
|
||||
capability = str(payload.get("capability", ""))
|
||||
target = str(payload.get("target", ""))
|
||||
operation = str(payload.get("operation", "set"))
|
||||
parameters = payload.get("parameters")
|
||||
if not domain or not capability or not target or not isinstance(parameters, dict):
|
||||
raise ValueError("speech executor payload is incomplete")
|
||||
|
||||
definitions = tuple(
|
||||
definition
|
||||
for definition in self.capability_registry.find(capability)
|
||||
if definition.domain == domain
|
||||
)
|
||||
if len(definitions) != 1:
|
||||
raise ValueError(
|
||||
f"speech capability is not uniquely registered: {domain}.{capability}"
|
||||
)
|
||||
if domain not in detected_domains:
|
||||
detected_domains.append(domain)
|
||||
# Catalog availability describes where a capability is declared,
|
||||
# not necessarily which executor owns it. Roof, climate and
|
||||
# ambient capabilities are declared in domain catalogs but some
|
||||
# are byte-exact NativeCanRuntime operations. Keep this routing in
|
||||
# lockstep with CapabilityResolver._executor_binding().
|
||||
if capability in self.native_can_runtime.supported_capabilities:
|
||||
native_actions.append(
|
||||
{
|
||||
"domain": domain,
|
||||
"capability": capability,
|
||||
"target": target,
|
||||
"operation": operation,
|
||||
"parameters": deepcopy(parameters),
|
||||
"parse_source": "speech_semantic",
|
||||
"source_span": source_text,
|
||||
"reason": "语音语义层已按真实能力目录解析并由服务端重新校验。",
|
||||
}
|
||||
)
|
||||
return
|
||||
model_requests.append(
|
||||
{
|
||||
"domain": domain,
|
||||
"capability": capability,
|
||||
"target_scope": target,
|
||||
"operation": operation,
|
||||
"parameters": [
|
||||
{"name": name, "value": value}
|
||||
for name, value in parameters.items()
|
||||
],
|
||||
"reason": "语音语义层已按真实能力目录解析并由服务端重新校验。",
|
||||
}
|
||||
)
|
||||
|
||||
for command in commands:
|
||||
if not isinstance(command, dict):
|
||||
raise ValueError("speech command must be an object")
|
||||
if (
|
||||
command.get("status") != "active"
|
||||
or command.get("negated") is True
|
||||
or command.get("resolution_status") != "executable"
|
||||
or command.get("validation_issues")
|
||||
):
|
||||
raise ValueError("speech command failed execution invariants")
|
||||
source_text = str(command.get("source_text") or understanding.get("raw_text") or "")
|
||||
payload = command.get("executor_payload")
|
||||
if command.get("mode_profile_id"):
|
||||
if not isinstance(payload, dict) or not isinstance(payload.get("operations"), list):
|
||||
raise ValueError("speech mode command has no validated operations")
|
||||
for operation_payload in payload["operations"]:
|
||||
add_payload(operation_payload, source_text)
|
||||
else:
|
||||
add_payload(payload, source_text)
|
||||
|
||||
plan = {
|
||||
"detected_domains": detected_domains,
|
||||
"requests": model_requests,
|
||||
"ambiguities": [],
|
||||
"decision_summary": [
|
||||
"复用服务端语音理解结果;未对 cleanText 进行第二次自然语言解析。"
|
||||
],
|
||||
"confidence": min(
|
||||
float(command.get("confidence", 0.0))
|
||||
for command in commands
|
||||
if isinstance(command, dict)
|
||||
),
|
||||
}
|
||||
parsed = self.model_plan_adapter.adapt(
|
||||
plan,
|
||||
context=context,
|
||||
vehicle_profile=vehicle_profile,
|
||||
utterance=str(understanding.get("raw_text") or ""),
|
||||
)
|
||||
parsed["native_can_actions"] = native_actions
|
||||
parsed.setdefault("semantic_constraints", [])
|
||||
parsed.setdefault("unresolved_spans", [])
|
||||
if parsed.get("ambiguities") or parsed.get("deferred_requests"):
|
||||
raise ValueError("speech command no longer passes capability resolution")
|
||||
mapped_count = (
|
||||
len(parsed.get("actions", ()))
|
||||
+ len(parsed.get("domain_actions", ()))
|
||||
+ len(parsed.get("native_can_actions", ()))
|
||||
)
|
||||
expected_count = len(model_requests) + len(native_actions)
|
||||
if mapped_count != expected_count:
|
||||
raise ValueError("speech command mapping lost a validated capability mutation")
|
||||
return parsed
|
||||
|
||||
def process(
|
||||
self,
|
||||
utterance: str,
|
||||
@@ -1465,6 +1598,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result: bool = False,
|
||||
_on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
|
||||
_direct_can_batch: tuple[list[str], dict[str, Any]] | None = None,
|
||||
_speech_understanding: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
control_transport = self._validate_control_transport(control_transport)
|
||||
# Creating a reusable scene is a configuration workflow. Synthesize
|
||||
@@ -1490,6 +1624,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result,
|
||||
_on_commands_validated,
|
||||
_direct_can_batch,
|
||||
_speech_understanding,
|
||||
)
|
||||
if control_transport == WIRELESS_MQTT_TRANSPORT:
|
||||
# The head unit owns both physical CAN channels in wireless mode.
|
||||
@@ -1511,6 +1646,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result,
|
||||
_on_commands_validated,
|
||||
_direct_can_batch,
|
||||
_speech_understanding,
|
||||
)
|
||||
if hardware_access_bypass or self._driver_only_vehicle_execution_active(
|
||||
control_transport
|
||||
@@ -1532,6 +1668,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result,
|
||||
_on_commands_validated,
|
||||
_direct_can_batch,
|
||||
_speech_understanding,
|
||||
)
|
||||
authorized_client_id = self.hardware_access.ensure_authorized(hardware_client_id)
|
||||
with self.hardware_execution.reserve():
|
||||
@@ -1552,6 +1689,7 @@ class SeatDemoService:
|
||||
_allow_empty_control_result,
|
||||
_on_commands_validated,
|
||||
_direct_can_batch,
|
||||
_speech_understanding,
|
||||
)
|
||||
|
||||
def _process_once(
|
||||
@@ -1571,6 +1709,7 @@ class SeatDemoService:
|
||||
allow_empty_control_result: bool = False,
|
||||
on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
|
||||
direct_can_batch: tuple[list[str], dict[str, Any]] | None = None,
|
||||
speech_understanding: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
control_transport = self._validate_control_transport(control_transport)
|
||||
utterance = utterance.strip()
|
||||
@@ -1641,7 +1780,7 @@ class SeatDemoService:
|
||||
context["mode_profiles"] = self.mode_profiles.public_modes(vehicle_profile.id)
|
||||
matched_modes = (
|
||||
[]
|
||||
if direct_can_batch is not None
|
||||
if direct_can_batch is not None or speech_understanding is not None
|
||||
else self.mode_profiles.match_text(
|
||||
utterance,
|
||||
vehicle_id=vehicle_profile.id,
|
||||
@@ -1691,6 +1830,16 @@ class SeatDemoService:
|
||||
"can_commands": (
|
||||
list(direct_can_batch[0]) if direct_can_batch is not None else []
|
||||
),
|
||||
"speech_understanding": (
|
||||
{
|
||||
"utterance_id": speech_understanding.get("utterance_id"),
|
||||
"raw_text": speech_understanding.get("raw_text"),
|
||||
"clean_text": speech_understanding.get("clean_text"),
|
||||
"semantic_status": speech_understanding.get("semantic_status"),
|
||||
}
|
||||
if speech_understanding is not None
|
||||
else None
|
||||
),
|
||||
}
|
||||
|
||||
started = time.perf_counter()
|
||||
@@ -1733,6 +1882,19 @@ class SeatDemoService:
|
||||
parsed = deepcopy(direct_can_batch[1])
|
||||
provider_used = "direct_can_control"
|
||||
provider_notice = None
|
||||
elif speech_understanding is not None:
|
||||
parsed = self._adapt_speech_understanding(
|
||||
speech_understanding,
|
||||
context,
|
||||
vehicle_profile,
|
||||
)
|
||||
parsed = self._mark_control_parse_source(
|
||||
parsed,
|
||||
"speech_semantic",
|
||||
overwrite=True,
|
||||
)
|
||||
provider_used = "speech_semantic"
|
||||
provider_notice = "使用服务端缓存并重新校验的语音结构化意图。"
|
||||
else:
|
||||
parsed, provider_used, provider_notice = self._interpret(
|
||||
utterance,
|
||||
|
||||
Reference in New Issue
Block a user