优化语音输入/普通文本输入

This commit is contained in:
minghanchen
2026-09-03 13:36:40 +08:00
parent 009d8ca73e
commit 32d179143f
26 changed files with 2318 additions and 53 deletions
+85 -3
View File
@@ -532,6 +532,8 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
speech_utterance_id = str(
payload.get("speech_utterance_id", "")
).strip()
speech_understanding: dict[str, object] | None = None
speech_peripheral_route = False
if speech_utterance_id:
if path != "/api/requests":
raise ValueError(
@@ -540,13 +542,28 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
speech_understanding = self.speech.cached_result(
speech_utterance_id
)
if str(speech_understanding.get("clean_text", "")).strip() != utterance:
clean_text = str(
speech_understanding.get("clean_text", "")
).strip()
raw_text = str(
speech_understanding.get("raw_text", "")
).strip()
peripheral_routing = self._speech_peripheral_routing(raw_text)
peripheral_transcript = bool(
peripheral_routing.get("execution_eligible") and raw_text
)
expected_text = raw_text if peripheral_transcript else clean_text
if utterance != expected_text:
raise SpeechRecognitionError(
"speech_text_changed",
"语音文本已被修改,请确认修改后的内容再执行。",
status=409,
)
if speech_understanding.get("execution_eligible") is not True:
speech_peripheral_route = peripheral_transcript
if (
speech_understanding.get("execution_eligible") is not True
and not speech_peripheral_route
):
raise SpeechRecognitionError(
"speech_not_executable",
"语音意图尚未通过能力或参数校验,已拒绝执行。",
@@ -554,7 +571,8 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
)
provider = "local"
common_arguments["provider"] = provider
common_arguments["_speech_understanding"] = speech_understanding
if not speech_peripheral_route:
common_arguments["_speech_understanding"] = speech_understanding
if (
path != "/api/can/commands"
and "application/x-ndjson" in self.headers.get("Accept", "")
@@ -620,6 +638,14 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
)
else:
result = self.service.process(**common_arguments)
if speech_peripheral_route and speech_understanding is not None:
result["speech_understanding"] = {
"utterance_id": speech_utterance_id,
"raw_text": speech_understanding.get("raw_text"),
"clean_text": speech_understanding.get("clean_text"),
"semantic_status": speech_understanding.get("semantic_status"),
"routing": "validated_peripheral_text",
}
utterance = str(result.get("utterance") or utterance)
self.vehicle_display_sync.update_task(
status=str(result.get("status", "succeeded")),
@@ -815,6 +841,54 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
)
return result
def _speech_peripheral_routing(self, utterance: str) -> dict[str, object]:
"""Validate whether an ASR transcript may enter peripheral routing.
Vehicle speech keeps using its cached structured semantic payload. A
learned peripheral cannot be represented by the static vehicle
capability catalog, so it may instead reuse the exact server-owned ASR
transcript only after the live NFC binding and learned action resolve.
"""
text = str(utterance or "").strip()
if not text:
return {"execution_eligible": False, "action_count": 0}
try:
catalog = self.extension.list_peripheral_capabilities()
nfc_snapshot = self.nfc.snapshot()
partition = partition_active_peripheral_intent(
text,
catalog,
nfc_snapshot,
)
entries = list(partition.get("peripheral_actions") or [])
unresolved = list(
partition.get("unresolved_peripheral_clauses") or []
)
denied = list(partition.get("denied_peripheral_actions") or [])
plan_catalog = active_peripheral_plan_catalog(
text,
catalog,
nfc_snapshot,
)
direct = None
if not entries and not plan_catalog and not unresolved and not denied:
direct = resolve_active_peripheral_command(
text,
catalog,
nfc_snapshot,
)
except (ExtensionIntegrationError, GatewayError, ValueError):
return {"execution_eligible": False, "action_count": 0}
action_count = len(entries) or (1 if direct is not None else 0)
if plan_catalog:
action_count = max(action_count, 1)
return {
"execution_eligible": bool(action_count and not unresolved and not denied),
"action_count": action_count,
"has_ordered_plan": bool(plan_catalog),
}
@staticmethod
def _residual_has_vehicle_language(utterance: str) -> bool:
"""Recognize only obvious vehicle-domain text in a claimed mixed request."""
@@ -2206,6 +2280,14 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
self.rfile.read(content_length),
utterance_id=self.headers.get("X-Speech-Utterance-Id"),
)
raw_text = str(result.get("raw_text") or "").strip()
peripheral_routing = self._speech_peripheral_routing(raw_text)
if peripheral_routing.get("execution_eligible"):
result = {
**result,
"routing_text": raw_text,
"peripheral_routing": peripheral_routing,
}
self._json(result)
except SpeechRecognitionError as exc:
self._json(