mqtt切换为远程服务器部署,优化简单指令解析时长

This commit is contained in:
Miayui
2026-09-11 10:22:24 +08:00
parent 51ca12b624
commit 3fc6f2c2a4
34 changed files with 4290 additions and 484 deletions
@@ -145,8 +145,14 @@ _DOMAIN_PRESENTATION: dict[str, dict[str, object]] = {
},
"window": {
"label": "车窗",
"aliases": ["车窗", "窗户", "玻璃窗", "左前窗", "右前窗", "左后窗", "右后窗"],
"control_aliases": ["车窗", "窗户", "玻璃窗", "左前窗", "右前窗", "左后窗", "右后窗"],
"aliases": [
"车窗", "窗户", "玻璃窗", "左前窗", "右前窗", "左后窗", "右后窗",
"左前排窗", "右前排窗", "左后排窗", "右后排窗",
],
"control_aliases": [
"车窗", "窗户", "玻璃窗", "左前窗", "右前窗", "左后窗", "右后窗",
"左前排窗", "右前排窗", "左后排窗", "右后排窗",
],
},
"door": {
"label": "后排车门锁",
@@ -158,11 +158,13 @@ _EXPLICIT_EVIDENCE: dict[str, _EvidenceRule] = {
r"智能天幕|天幕(?:玻璃)?|顶棚玻璃|\broof\b", r"自动调光|自动变色|automatic\s+dimming"
),
"window.set_motion": _rule(
r"车窗|窗户|左前窗|右前窗|左后窗|右后窗|\bwindow\b",
r"车窗|窗户|左前窗|右前窗|左后窗|右后窗|"
r"左前排窗|右前排窗|左后排窗|右后排窗|\bwindow\b",
r"打开|开启|关闭|关上|升起|降下|停止|\bopen\b|\bclose\b|\bstop\b",
),
"window.set_position": _rule(
r"车窗|窗户|左前窗|右前窗|左后窗|右后窗|\bwindow\b",
r"车窗|窗户|左前窗|右前窗|左后窗|右后窗|"
r"左前排窗|右前排窗|左后排窗|右后排窗|\bwindow\b",
r"位置|开到|关到|升到|降到|小缝|一半|半开|\d+\s*[%%]|\bposition\b",
),
"door.set_rear_lock": _rule(
@@ -828,6 +828,8 @@ class CodexAssistantService:
history: Sequence[dict[str, str]],
emit: EmitEvent,
execute_control: ControlExecutor,
preview_control: ControlExecutor | None = None,
supplement_control: ControlExecutor | None = None,
capability_snapshot: Mapping[str, object] | None = None,
capability_context: Mapping[str, object] | None = None,
scene_capability_context: Mapping[str, object] | None = None,
@@ -894,6 +896,14 @@ class CodexAssistantService:
authorization.reason == "explicit_registered_control"
and _normalize_instruction(message) in normalized_quick_controls
)
explicit_local_control = (
authorization.reason == "explicit_registered_control"
)
local_first_control = bool(
explicit_local_control
and preview_control is not None
and supplement_control is not None
)
mode_profile_preview = authorization.reason in {
"explicit_mode_profile_create",
"explicit_mode_profile_draft_update",
@@ -942,6 +952,7 @@ class CodexAssistantService:
direct_vehicle_control = (
fast_comfort_control
or exact_quick_control
or local_first_control
or saved_mode_execution_id is not None
or direct_mode_profile_preview
or authorization.reason == "explicit_mode_profile_draft_update"
@@ -978,7 +989,7 @@ class CodexAssistantService:
else "codex-app-server-scene-composer"
if scene_composition_preview
else "n7web-control-router"
if fast_comfort_control or exact_quick_control
if fast_comfort_control or exact_quick_control or local_first_control
else "codex-app-server"
)
@@ -1024,6 +1035,8 @@ class CodexAssistantService:
if fast_comfort_control
else "正在执行快捷车控……"
if exact_quick_control
else "正在本地解析并下发明确控制指令……"
if local_first_control
else "小迪正在理解并处理……"
),
}
@@ -1092,6 +1105,25 @@ class CodexAssistantService:
),
}
if (
local_first_control
and preview_control is not None
and supplement_control is not None
):
return self._stream_local_first_vehicle_control(
authorization,
message_id=message_id,
emit=emit,
preview_control=preview_control,
supplement_control=supplement_control,
conversation_id=conversation_id,
principal=principal,
capability_snapshot=catalog,
capability_context=model_context,
control_terms=control_terms,
query_capabilities=catalog_query,
)
if direct_vehicle_control:
return self._stream_direct_vehicle_control(
(
@@ -1535,7 +1567,16 @@ class CodexAssistantService:
active = start_structured_turn(
thread_id,
message,
application_context=scene_context,
application_context={
**dict(scene_context),
"natural_language_routing": {
"original_utterance": message,
"parsed_clauses": [],
"resolved_clauses": [],
"unresolved_clauses": [message],
"cloud_input_utterance": message,
},
},
output_schema=output_schema,
timeout=self._remaining_scene_time(deadline),
)
@@ -1730,6 +1771,285 @@ class CodexAssistantService:
except (RuntimeError, TypeError) as exc:
LOGGER.debug("Scene Composer thread cleanup failed", exc_info=exc)
def _stream_local_first_vehicle_control(
self,
authorization: _VehicleAuthorization,
*,
message_id: str,
emit: EmitEvent,
preview_control: ControlExecutor,
supplement_control: ControlExecutor,
conversation_id: str,
principal: str,
capability_snapshot: Mapping[str, object],
capability_context: Mapping[str, object],
control_terms: Sequence[str],
query_capabilities: CapabilityQuery,
) -> dict[str, object]:
"""Publish deterministic controls, then delegate only unresolved text."""
instruction = authorization.instruction
if instruction is None:
raise CodexAssistantError("local-first vehicle control was not authorized")
if (
emit(
{
"type": "action.started",
"message_id": message_id,
"display_text": "正在通过本地规则解析明确控制指令……",
}
)
is False
):
return {"cancelled": True}
stream_open = True
def on_progress(payload: dict[str, object]) -> None:
nonlocal stream_open
if not stream_open:
return
validated_payload = self._validated_control_payload(payload)
if validated_payload is not None:
stream_open = (
emit(
{
"type": "validated_control_commands",
"message_id": message_id,
"payload": validated_payload,
}
)
is not False
)
if not stream_open:
return
stream_open = (
emit(
{
"type": "action.progress",
"message_id": message_id,
"display_text": "本地控制指令已解析并进入下发链路……",
"payload": self._safe_vehicle_result(payload),
}
)
is not False
)
try:
preview = preview_control(instruction, on_progress)
if not isinstance(preview, dict):
raise TypeError("vehicle preview returned an invalid result")
except Exception: # noqa: BLE001 - stable browser-facing failure
answer = "车辆控制未完成,本地安全执行链没有返回有效结果。"
for event in (
{
"type": "action.completed",
"message_id": message_id,
"success": False,
"state": "failed",
"display_text": "本地控制未完成",
},
{
"type": "answer.delta",
"message_id": message_id,
"delta": answer,
},
{
"type": "work.completed",
"message_id": message_id,
"success": False,
"display_text": "车辆控制未完成",
},
{
"type": "answer.completed",
"message_id": message_id,
"route": "vehicle_control",
"answer": answer,
},
):
if emit(event) is False:
return {"cancelled": True, "answer": answer}
return {
"route": "vehicle_control",
"answer": answer,
"success": False,
}
if not stream_open:
return {"cancelled": True}
routing_value = preview.get("natural_language_routing")
routing = dict(routing_value) if isinstance(routing_value, Mapping) else {}
raw_unresolved = routing.get("unresolved_clauses")
if not isinstance(raw_unresolved, Sequence) or isinstance(
raw_unresolved,
(str, bytes, bytearray),
):
raw_unresolved = preview.get("unresolved_clauses")
unresolved_clauses = list(
dict.fromkeys(
text
for item in (
list(raw_unresolved)[:64]
if isinstance(raw_unresolved, Sequence)
and not isinstance(raw_unresolved, (str, bytes, bytearray))
else []
)
for text in [str(item).strip()[:1000]]
if text
)
)
safe_preview = self._safe_vehicle_result(preview)
if not unresolved_clauses:
action_state, successful, completion_text = _vehicle_action_state(preview)
answer = self._fast_comfort_answer(
"explicit_registered_control",
preview,
successful,
)
if not stream_open:
return {"cancelled": True, "answer": answer}
for event in (
{
"type": "action.completed",
"message_id": message_id,
"success": successful,
"state": action_state,
"display_text": completion_text,
"payload": safe_preview,
},
{
"type": "answer.delta",
"message_id": message_id,
"delta": answer,
},
{
"type": "work.completed",
"message_id": message_id,
"success": successful,
"state": action_state,
"display_text": completion_text,
},
{
"type": "answer.completed",
"message_id": message_id,
"route": "vehicle_control",
"answer": answer,
},
):
if emit(event) is False:
return {"cancelled": True, "answer": answer}
return {
"route": "vehicle_control",
"answer": answer,
"success": successful,
"result": safe_preview,
}
local_has_commands = bool(
preview.get("function_configs") and preview.get("command_plans")
)
local_status = str(preview.get("status") or "")
local_succeeded = local_has_commands and local_status in {
"succeeded",
"partially_succeeded",
"no_action",
}
if stream_open and emit(
{
"type": "action.completed",
"message_id": message_id,
"success": local_succeeded,
"state": "submitted" if local_succeeded else "failed",
"display_text": (
"本地明确控制已先行处理"
if local_succeeded
else "本地阶段未生成控制指令"
),
"payload": safe_preview,
}
) is False:
return {"cancelled": True}
if emit(
{
"type": "work.progress",
"message_id": message_id,
"display_text": "本地明确子句已处理,正在补充未解析子句……",
}
) is False:
return {"cancelled": True}
cloud_message = ",".join(unresolved_clauses)
routing.update(
{
"original_utterance": str(
routing.get("original_utterance") or instruction
)[:1000],
"parsed_clauses": list(routing.get("parsed_clauses") or []),
"resolved_clauses": list(routing.get("resolved_clauses") or []),
"unresolved_clauses": unresolved_clauses,
"cloud_input_utterance": cloud_message,
}
)
if not self.client.available:
answer = (
"已先行处理本地可解析的控制;其余子句暂无法连接云端模型,"
"未生成额外控制。"
)
emit(
{
"type": "answer.delta",
"message_id": message_id,
"delta": answer,
}
)
emit(
{
"type": "work.completed",
"message_id": message_id,
"success": local_succeeded,
"display_text": "本地控制已处理,云端补充不可用",
}
)
emit(
{
"type": "answer.completed",
"message_id": message_id,
"route": "vehicle_control",
"answer": answer,
}
)
return {
"route": "vehicle_control",
"answer": answer,
"success": local_succeeded,
"result": safe_preview,
}
conversation, conversation_key = self._conversation(
principal,
conversation_id,
)
try:
with conversation.lock:
return self._stream_locked(
conversation,
cloud_message,
message_id=message_id,
emit=emit,
execute_control=supplement_control,
capability_snapshot=capability_snapshot,
capability_context={
**dict(capability_context),
"natural_language_routing": routing,
},
control_terms=control_terms,
query_capabilities=query_capabilities,
)
finally:
self._release_conversation(conversation_key, conversation)
def _stream_direct_vehicle_control(
self,
authorization: _VehicleAuthorization,
@@ -1753,6 +2073,10 @@ class CodexAssistantService:
authorization.reason == "explicit_mode_profile_draft_update"
)
exact_quick_control = authorization.reason == "exact_quick_control"
explicit_local_control = authorization.reason in {
"exact_quick_control",
"explicit_registered_control",
}
saved_mode_execution = authorization.reason == "explicit_saved_mode_execute"
mode_profile_management_route = {
"explicit_mode_profile_update": "mode_profile_update",
@@ -1776,6 +2100,8 @@ class CodexAssistantService:
if saved_mode_execution
else "正在通过车辆安全链执行快捷控制……"
if exact_quick_control
else "正在通过本地规则解析并下发控制……"
if explicit_local_control
else "正在通过车辆安全链执行舒适性调节……"
),
}
@@ -1790,6 +2116,20 @@ class CodexAssistantService:
nonlocal stream_open
if not stream_open:
return
validated_payload = self._validated_control_payload(payload)
if validated_payload is not None:
stream_open = (
emit(
{
"type": "validated_control_commands",
"message_id": message_id,
"payload": validated_payload,
}
)
is not False
)
if not stream_open:
return
stream_open = (
emit(
{
@@ -1802,6 +2142,8 @@ class CodexAssistantService:
if saved_mode_execution
else "功能模式存储请求已解析,正在校验……"
if mode_profile_management_route is not None
else "本地控制指令已通过校验并进入下发链路……"
if explicit_local_control
else "控制指令已通过校验,正在执行……"
),
"payload": self._safe_vehicle_result(payload),
@@ -1909,6 +2251,14 @@ class CodexAssistantService:
if exact_quick_control and successful
else "快捷控制未完成"
if exact_quick_control
else "明确控制已提交,等待车辆状态确认"
if explicit_local_control and action_state == "submitted"
else "明确控制模拟编排已完成"
if explicit_local_control and action_state == "simulated"
else "明确控制已完成"
if explicit_local_control and successful
else "明确控制未完成"
if explicit_local_control
else "舒适性调节已提交,等待车辆状态确认"
if action_state == "submitted"
else "舒适性调节模拟编排已完成"
@@ -2251,7 +2601,11 @@ class CodexAssistantService:
result: Mapping[str, object],
successful: bool,
) -> str:
if reason in {"exact_quick_control", "explicit_saved_mode_execute"}:
if reason in {
"exact_quick_control",
"explicit_registered_control",
"explicit_saved_mode_execute",
}:
instruction = re.sub(
r"\s+",
" ",
@@ -2260,7 +2614,7 @@ class CodexAssistantService:
or (
"功能模式"
if reason == "explicit_saved_mode_execute"
else "快捷控制指令"
else "控制指令"
)
),
).strip()[:160]
@@ -2332,12 +2686,24 @@ class CodexAssistantService:
answer_redactor = _StreamingSecretRedactor(request_token)
cancelled = False
deadline = time.monotonic() + self._turn_timeout
application_context = dict(capability_context)
if not isinstance(
application_context.get("natural_language_routing"),
Mapping,
):
application_context["natural_language_routing"] = {
"original_utterance": message,
"parsed_clauses": [],
"resolved_clauses": [],
"unresolved_clauses": [message],
"cloud_input_utterance": message,
}
try:
active = self.client.start_turn(
conversation.thread_id,
message,
request_token,
application_context=capability_context,
application_context=application_context,
)
terminal: dict[str, object] | None = None
while terminal is None:
@@ -2642,6 +3008,17 @@ class CodexAssistantService:
)
def on_progress(payload: dict[str, object]) -> None:
validated_payload = self._validated_control_payload(
payload,
secret=request_token,
)
if validated_payload is not None:
context.events.put(
{
"type": "validated_control_commands",
"payload": validated_payload,
}
)
context.events.put(
{
"type": "action.progress",
@@ -2960,8 +3337,9 @@ class CodexAssistantService:
for raw_key, raw_value in list(value.items())[:24]:
key = str(raw_key)[:80]
if re.search(
r"token|secret|password|authorization|approval|raw.?can|"
r"can.?frame|data.?hex|payload.?hex",
r"token|secret|password|authorization|approval|api.?key|"
r"credential|cookie|bearer|private.?key|access.?key|"
r"raw.?can|can.?frame|data.?hex|payload.?hex",
key,
re.IGNORECASE,
):
@@ -2984,7 +3362,290 @@ class CodexAssistantService:
)
for item in list(value)[:24]
]
return str(value).replace(secret, "[内部令牌已隐藏]")[:240]
text = str(value)
if secret:
text = text.replace(secret, "[内部令牌已隐藏]")
return text[:240]
@staticmethod
def _validated_control_payload(
result: Mapping[str, object],
*,
secret: str = "",
) -> dict[str, object] | None:
"""Build a browser-only allowlist projection of compiled controls.
MCP tool results intentionally exclude command plans and raw CAN
material. The web console still needs a deterministic view after
validation, so copy only semantic fields, canonical debug commands,
reconstructed IR payloads, display-safe CAN bytes, and stage timings.
"""
raw_configurations = result.get("function_configs")
configuration_entries: list[tuple[str, Mapping[str, object]]] = []
if isinstance(raw_configurations, Mapping):
configuration_entries.extend(
(str(key), value)
for key, value in raw_configurations.items()
if isinstance(value, Mapping)
)
if not configuration_entries and isinstance(
result.get("function_config"), Mapping
):
configuration_entries.append(("default", result["function_config"]))
raw_plans = result.get("command_plans")
plan_entries: list[tuple[str, Mapping[str, object]]] = []
if isinstance(raw_plans, Mapping):
plan_entries.extend(
(str(key), value)
for key, value in raw_plans.items()
if isinstance(value, Mapping)
)
if not plan_entries and isinstance(result.get("command_plan"), Mapping):
plan_entries.append(("default", result["command_plan"]))
if not configuration_entries or not plan_entries:
return None
def safe_text(value: object, limit: int) -> str:
"""Return bounded display text without copying the request secret."""
text = str(value or "")
if secret and secret in text:
return ""
return text[:limit]
def command_refs(command: Mapping[str, object]) -> tuple[str, ...]:
raw_refs = command.get("operation_ref")
if isinstance(raw_refs, Sequence) and not isinstance(
raw_refs, (str, bytes, bytearray)
):
values = raw_refs
else:
values = str(raw_refs or "").split(",")
return tuple(str(value).strip() for value in values if str(value).strip())
def matching_commands(
entries: Sequence[tuple[str, Mapping[str, object]]],
operation_id: str,
) -> list[tuple[Mapping[str, object], Mapping[str, object]]]:
matches: list[tuple[Mapping[str, object], Mapping[str, object]]] = []
for _plan_key, plan in entries:
spec = plan.get("spec")
if not isinstance(spec, Mapping):
continue
commands = spec.get("commands")
if not isinstance(commands, Sequence) or isinstance(
commands, (str, bytes, bytearray)
):
continue
matches.extend(
(command, plan)
for command in commands
if isinstance(command, Mapping)
and operation_id in command_refs(command)
)
return matches
presentations: list[dict[str, object]] = []
for config_key, configuration in configuration_entries[:16]:
spec = configuration.get("spec")
if not isinstance(spec, Mapping):
continue
operations = spec.get("operations")
if not isinstance(operations, Sequence) or isinstance(
operations, (str, bytes, bytearray)
):
continue
raw_domain = str(spec.get("domain") or "")
domain = safe_text(raw_domain, 80)
preferred_plans = [
entry for entry in plan_entries if entry[0] == config_key
]
for operation in operations[:24]:
if not isinstance(operation, Mapping):
continue
raw_operation_id = str(operation.get("id") or "").strip()[:160]
operation_id = safe_text(raw_operation_id, 160).strip()
capability = safe_text(operation.get("capability"), 160).strip()
if not raw_operation_id or not operation_id or not capability:
continue
compiled = matching_commands(preferred_plans, raw_operation_id)
if (
not preferred_plans
and len(configuration_entries) == 1
and len(plan_entries) == 1
):
# Keep the legacy function_config/command_plan pair working,
# but never search globally across multi-config results. A
# repeated operation id must fail closed instead of borrowing
# a compiled command from another domain.
compiled = matching_commands(plan_entries, raw_operation_id)
if not compiled:
continue
channel = "IR" if raw_domain == "self_extension" else "CAN1"
actual_commands: list[str] = []
frames: list[dict[str, str]] = []
for command, plan in compiled[:16]:
signals = command.get("signals")
signals = signals if isinstance(signals, Mapping) else {}
plan_spec = plan.get("spec")
plan_domain = (
str(plan_spec.get("domain") or "")
if isinstance(plan_spec, Mapping)
else ""
)
message = str(command.get("message") or "")
bus = str(command.get("bus") or "").lower()
if message == "infrared.emit" or plan_domain == "self_extension":
command_channel = "IR"
elif "seat" in bus or "can0" in bus:
command_channel = "CAN0"
else:
command_channel = "CAN1"
channel = command_channel
debug_command = " ".join(
safe_text(signals.get("debug_command"), 256).split()
)
if re.fullmatch(r"[A-Za-z0-9_.:+% -]{1,256}", debug_command):
if debug_command not in actual_commands:
actual_commands.append(debug_command)
slot_value = signals.get("slot")
slot = (
int(slot_value)
if isinstance(slot_value, int)
and not isinstance(slot_value, bool)
and 1 <= slot_value <= 248
else None
)
if message == "infrared.emit" and slot is not None:
actual = f"infrared emit {slot}"
if actual not in actual_commands:
actual_commands.append(actual)
frame = {
"label": "GSCU1B TCP",
"value": json.dumps(
{
"action": "emit",
"data": {"no": slot},
"type": "infrared",
},
ensure_ascii=False,
separators=(",", ":"),
),
}
if frame not in frames:
frames.append(frame)
continue
frame_id = str(signals.get("frame_id") or "").strip()
data_bytes = " ".join(
str(signals.get("data_hex") or "").strip().split()
).upper()
if re.fullmatch(
r"0[xX][0-9A-Fa-f]{1,8}", frame_id
) and re.fullmatch(
r"[0-9A-F]{2}(?: [0-9A-F]{2}){0,63}", data_bytes
):
frame = {
"label": f"{command_channel} 帧",
"value": f"0x{frame_id[2:].upper()} · {data_bytes}",
}
if frame not in frames:
frames.append(frame)
safe_target = CodexAssistantService._safe_result_value(
operation.get("target") or {}, secret=secret
)
safe_parameters = CodexAssistantService._safe_result_value(
operation.get("parameters") or {}, secret=secret
)
presentations.append(
{
"operation": {
"id": operation_id,
"capability": capability,
"target": safe_target if isinstance(safe_target, dict) else {},
"parameters": (
safe_parameters
if isinstance(safe_parameters, dict)
else {}
),
**(
{
"parse_source": safe_text(
operation.get("parse_source"), 80
)
}
if safe_text(operation.get("parse_source"), 80)
else {}
),
},
"domain": domain,
"channel": channel,
"actual_command": ";".join(actual_commands),
"frames": frames,
}
)
if not presentations:
return None
allowed_trace_stages = {
"intent_parse",
"context_resolve",
"domain_route",
"function_generate",
"schema_validate",
"domain_validate",
"compile",
"nfc_peripheral_route",
"local_peripheral_plan",
"nfc_peripheral_sequence",
}
safe_trace: list[dict[str, object]] = []
raw_trace = result.get("trace_events")
if isinstance(raw_trace, Sequence) and not isinstance(
raw_trace, (str, bytes, bytearray)
):
for item in raw_trace[:32]:
if not isinstance(item, Mapping):
continue
stage = str(item.get("stage") or "")
duration = item.get("duration_ms")
if (
stage in allowed_trace_stages
and isinstance(duration, (int, float))
and not isinstance(duration, bool)
and duration >= 0
):
safe_trace.append({"stage": stage, "duration_ms": duration})
payload: dict[str, object] = {
"validated_control_commands": presentations,
"trace_events": safe_trace,
}
for key in (
"request_id",
"preview_request_id",
"supplement_of",
"utterance",
"provider_used",
"execution_mode",
"control_transport",
"local_control_budget_ms",
"local_control_elapsed_ms",
"local_control_within_budget",
"natural_language_routing",
):
if result.get(key) is not None:
payload[key] = CodexAssistantService._safe_result_value(
result[key], secret=secret
)
return payload
@staticmethod
def _safe_vehicle_result(
@@ -20,6 +20,10 @@ WINDOW_TARGETS = {
"所有": "all",
"四个": "all",
"全车": "all",
"左前排": "front_left",
"右前排": "front_right",
"左后排": "rear_left",
"右后排": "rear_right",
"前排": "front",
"前座": "front",
"后排": "rear",
@@ -88,7 +92,8 @@ LIGHT_TARGETS = {
_EXPLICIT_LOCATION_HINT = re.compile(
r"全部|所有|四个|全车|整车|前排|前座|后排|后座|二排|第二排|"
r"中排|第三排|主驾|主驾驶|驾驶位|驾驶员|司机位|副驾|副驾驶|"
r"乘客位|乘员|左前|前左|右前|前右|左后|后左|右后|后右|"
r"乘客位|乘员|左前排|右前排|左后排|右后排|"
r"左前|前左|右前|前右|左后|后左|右后|后右|"
r"左侧|右侧|左边|右边"
)
_WHOLE_VEHICLE_HINT = re.compile(r"全部|所有|四个|全车|整车")
@@ -339,6 +344,7 @@ CH1_DEFAULT_TARGETS = {
_EXPLICIT_TARGET_PATTERNS = {
"window": re.compile(
r"(?:全部|所有|四个|全车|前排|前座|后排|后座|二排|第二排|"
r"左前排|右前排|左后排|右后排|"
r"左前|前左|右前|前右|左后|后左|右后|后右|左侧|右侧|"
r"主驾驶|驾驶位|驾驶员|司机位|主驾|副驾驶|乘客位|乘员|副驾)"
r"(?:的)?(?:车窗|窗)|window\s+(?:all|front|rear|front-left|front-right|rear-left|rear-right)",
@@ -1094,11 +1100,16 @@ def _parse_chinese_window_commands(compact: str) -> list[dict[str, Any]]:
clauses = [clause for clause in CLAUSE_SEPARATOR.split(compact) if clause]
exclusion_pattern = re.compile(
r"^(?:主驾|主驾驶|驾驶位|司机位|副驾|副驾驶|乘客位|"
r"左前排|右前排|左后排|右后排|"
r"左前|前左|右前|前右|左后|后左|右后|后右)"
r"(?:车窗)?(?:除外|除去|不包括|不含)$"
r"(?:车窗|窗)?(?:除外|除去|不包括|不含)$"
)
for index, clause in enumerate(clauses):
if "车窗" not in clause:
if (
"车窗" not in clause
and "窗户" not in clause
and not _EXPLICIT_TARGET_PATTERNS["window"].search(clause)
):
continue
window_text = clause
if index + 1 < len(clauses) and exclusion_pattern.search(
@@ -2007,7 +2018,8 @@ def merge_native_can_intent(
]
constraints = list(result.get("semantic_constraints", []))
for match in re.finditer(
r"(?P<target>左前|前左|右前|前右|左后|后左|右后|后右)(?:车窗)?"
r"(?P<target>左前排|右前排|左后排|右后排|"
r"左前|前左|右前|前右|左后|后左|右后|后右)(?:车窗|窗)?"
r"(?:除外|除去|不包括|不含)",
utterance,
):
@@ -34,6 +34,8 @@ CHINESE_DIGITS = {
"六": 6, "七": 7, "八": 8, "九": 9, "十": 10,
}
WINDOW_TARGET_LABELS = {
"左前排": ("front_left",), "右前排": ("front_right",),
"左后排": ("rear_left",), "右后排": ("rear_right",),
"后排左侧": ("rear_left",), "后座左侧": ("rear_left",),
"二排左侧": ("rear_left",), "第二排左侧": ("rear_left",),
"后排右侧": ("rear_right",), "后座右侧": ("rear_right",),
@@ -57,6 +59,7 @@ WINDOW_TARGET_LABELS = {
NARROW_LOCATION = re.compile(
r"主驾|主驾驶|驾驶位|驾驶员|司机位|副驾|副驾驶|乘客位|乘员|"
r"前排|前座|后排|后座|二排|第二排|中排|第三排|三排|前舱|后舱|"
r"左前排|右前排|左后排|右后排|"
r"左前|前左|右前|前右|左后|后左|右后|后右|左侧|右侧|左边|右边"
)
UNSUPPORTED_SINGLE_HVAC_ZONE = re.compile(
+176 -7
View File
@@ -171,6 +171,7 @@ MODE_PROFILE_UPDATE_EXPLICIT_TARGET_PATTERN = re.compile(
)
WIRED_CAN_TRANSPORT = "wired_can"
WIRELESS_MQTT_TRANSPORT = "wireless_mqtt"
LOCAL_CONTROL_BUDGET_MS = 1000
VEHICLE_CONTROL_TRANSPORTS = frozenset(
{WIRED_CAN_TRANSPORT, WIRELESS_MQTT_TRANSPORT}
)
@@ -292,6 +293,7 @@ CONTROL_CLAUSE_SUBJECT_PATTERN = re.compile(
r"座椅|座位|靠背|椅背|坐盆|坐垫|腿托|脚托|滑轨|"
r"通风|加热|按摩|零重力|半躺|女王副驾|休闲躺椅|"
r"遮阳帘|天幕帘|天幕|车窗|窗户|"
r"(?:左前|前左|右前|前右|左后|后左|右后|后右)(?:排)?窗|"
r"灯光|室内灯|车内灯|阅读灯|氛围灯|"
r"空调|压缩机|温度|风量|风速|内循环|外循环|"
r"车门|门锁|后备箱|前大灯|近光灯|远光灯|雾灯|转向灯|双闪|"
@@ -302,10 +304,17 @@ CONTROL_CLAUSE_PREDICATE_PATTERN = re.compile(
r"调整|调节|调到|调成|调为|调至|设置|设为|设到|切换|"
r"移动|移到|移至|前移|后移|上移|下移|升高|降低|抬起|放倒|"
r"调直|立直|竖直|归零|复位|锁上|上锁|落锁|解锁|"
r"零重力|半躺|最大|最小|最高|最低|最前|最后|到底|"
r"零重力|半躺|一半|半开|半关|最大|最小|最高|最低|最前|最后|到底|"
r"(?:\d+(?:\.\d+)?|[零一二三四五六七八九十百两半]+)"
r"(?:%|%|度|℃|档|级|秒|毫秒)"
)
# These verbs identify an independent request that may be outside the local
# vehicle registry. They are used only to split mixed sentences so an
# already deterministic clause never has to be repeated to a hosted model.
CLOUD_REQUEST_CLAUSE_PATTERN = re.compile(
r"播放|暂停播放|停止播放|导航|搜索|查找|查询|拨打|呼叫|发送|"
r"提醒|推荐|讲解|介绍|告诉|规划"
)
RESTORE_MODE_PROFILE_PREFIX = re.compile(
r"^(?:请(?:帮我)?|帮我)?(?:恢复|还原|重置|初始化)"
r"(?P<name>[^,。,.!!\s]{1,64}?模式)"
@@ -1613,9 +1622,11 @@ class SeatDemoService:
on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
assistant_plan: dict[str, Any] | None = None,
mode_profile_draft_context: dict[str, Any] | None = None,
_cloud_instruction_context: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Validate and execute deterministic controls before hosted parsing."""
local_stage_started = time.perf_counter()
utterance = utterance.strip()
if not utterance:
raise ValueError("utterance must not be empty")
@@ -1644,6 +1655,7 @@ class SeatDemoService:
_on_commands_validated=on_commands_validated,
_assistant_plan=assistant_plan,
_mode_profile_draft_context=mode_profile_draft_context,
_cloud_instruction_context=_cloud_instruction_context,
)
result.update(
{
@@ -1662,14 +1674,71 @@ class SeatDemoService:
vehicle_profile,
)
clause_results = self._local_clause_results(utterance, context)
unresolved_clauses = [
locally_parsed_clauses = [
clause
for clause, _, status in clause_results
if status != "unparsed"
]
locally_resolved_clauses = [
clause
for clause, _, status in clause_results
if status == "complete"
]
locally_unresolved_clauses = [
clause
for clause, _, status in clause_results
if status != "complete"
]
started = time.perf_counter()
supplied_routing = (
_cloud_instruction_context
if isinstance(_cloud_instruction_context, dict)
else {}
)
def routing_clauses(key: str) -> list[str]:
raw = supplied_routing.get(key)
if not isinstance(raw, list):
return []
return [
text
for item in raw[:64]
for text in [str(item).strip()[:1000]]
if text
]
def unique_clauses(*groups: list[str]) -> list[str]:
return list(dict.fromkeys(item for group in groups for item in group))
parsed_clauses = unique_clauses(
routing_clauses("parsed_clauses"),
locally_parsed_clauses,
)
resolved_clauses = unique_clauses(
routing_clauses("resolved_clauses"),
locally_resolved_clauses,
)
unresolved_clauses = locally_unresolved_clauses
routing_unresolved_clauses = unique_clauses(
routing_clauses("unresolved_clauses"),
locally_unresolved_clauses,
)
original_utterance = str(
supplied_routing.get("original_utterance") or utterance
).strip()[:1000]
natural_language_routing = {
"original_utterance": original_utterance,
"parsed_clauses": parsed_clauses,
"resolved_clauses": resolved_clauses,
"unresolved_clauses": routing_unresolved_clauses,
}
local_dispatch_elapsed_ms: float | None = None
def publish_local_commands(validated: dict[str, Any]) -> None:
nonlocal local_dispatch_elapsed_ms
local_dispatch_elapsed_ms = round(
(time.perf_counter() - local_stage_started) * 1000,
3,
)
if on_commands_validated is None:
return
event = deepcopy(validated)
@@ -1681,6 +1750,14 @@ class SeatDemoService:
"supplement_required": bool(
provider != "local" and unresolved_clauses
),
"natural_language_routing": deepcopy(
natural_language_routing
),
"local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS,
"local_control_elapsed_ms": local_dispatch_elapsed_ms,
"local_control_within_budget": (
local_dispatch_elapsed_ms < LOCAL_CONTROL_BUDGET_MS
),
}
)
on_commands_validated(event)
@@ -1702,8 +1779,17 @@ class SeatDemoService:
_on_commands_validated=publish_local_commands,
_assistant_plan=assistant_plan,
_mode_profile_draft_context=mode_profile_draft_context,
_cloud_instruction_context=natural_language_routing,
)
elapsed_ms = round(
(time.perf_counter() - local_stage_started) * 1000,
3,
)
local_control_elapsed_ms = (
local_dispatch_elapsed_ms
if local_dispatch_elapsed_ms is not None
else elapsed_ms
)
elapsed_ms = round((time.perf_counter() - started) * 1000, 3)
supplement_required = bool(
provider != "local" and unresolved_clauses
)
@@ -1713,7 +1799,17 @@ class SeatDemoService:
"provider_requested": provider,
"preview_request_id": result["request_id"],
"preview_elapsed_ms": elapsed_ms,
"local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS,
"local_control_elapsed_ms": local_control_elapsed_ms,
"local_control_within_budget": (
local_control_elapsed_ms < LOCAL_CONTROL_BUDGET_MS
),
"parsed_clauses": parsed_clauses,
"resolved_clauses": resolved_clauses,
"unresolved_clauses": unresolved_clauses,
"natural_language_routing": deepcopy(
natural_language_routing
),
"supplement_required": supplement_required,
}
)
@@ -1737,7 +1833,10 @@ class SeatDemoService:
"hardware_client_id": hardware_client_id,
"reasoning_effort": reasoning_effort,
"control_transport": control_transport,
"parsed_clauses": parsed_clauses,
"resolved_clauses": resolved_clauses,
"unresolved_clauses": unresolved_clauses,
"natural_language_routing": natural_language_routing,
"preview_control_signatures": result.get(
"parsed_control_signatures", []
),
@@ -1802,6 +1901,34 @@ class SeatDemoService:
for item in manifest.get("preview_control_signatures", [])
if isinstance(item, list)
}
manifest_routing = manifest.get("natural_language_routing")
natural_language_routing = (
deepcopy(manifest_routing)
if isinstance(manifest_routing, dict)
else {
"original_utterance": str(manifest.get("utterance") or ""),
"parsed_clauses": list(manifest.get("parsed_clauses") or []),
"resolved_clauses": list(manifest.get("resolved_clauses") or []),
"unresolved_clauses": unresolved_clauses,
}
)
routing_unresolved = natural_language_routing.get("unresolved_clauses")
natural_language_routing["unresolved_clauses"] = list(
dict.fromkeys(
[
str(item).strip()
for item in (
routing_unresolved
if isinstance(routing_unresolved, list)
else []
)
if str(item).strip()
]
+ unresolved_clauses
)
)
cloud_utterance = ",".join(unresolved_clauses)
natural_language_routing["cloud_input_utterance"] = cloud_utterance
def publish_cloud_commands(validated: dict[str, Any]) -> None:
if on_commands_validated is None:
@@ -1818,7 +1945,7 @@ class SeatDemoService:
on_commands_validated(event)
supplement = self.process(
",".join(unresolved_clauses),
cloud_utterance,
provider=selected_provider,
conversation_id=str(manifest["conversation_id"]),
execution_mode=str(manifest.get("execution_mode") or "dry_run"),
@@ -1837,6 +1964,7 @@ class SeatDemoService:
_model_parse_source="cloud_supplement",
_allow_empty_control_result=True,
_on_commands_validated=publish_cloud_commands,
_cloud_instruction_context=natural_language_routing,
)
merged = self._merge_native_can_phases(preview, supplement)
merged.update(
@@ -1853,6 +1981,7 @@ class SeatDemoService:
"supplement_request_id": supplement["request_id"],
"supplement_required": False,
"unresolved_clauses": unresolved_clauses,
"natural_language_routing": natural_language_routing,
}
)
self._persist(merged)
@@ -2149,6 +2278,7 @@ class SeatDemoService:
_speech_understanding: dict[str, Any] | None = None,
_assistant_plan: dict[str, Any] | None = None,
_mode_profile_draft_context: dict[str, Any] | None = None,
_cloud_instruction_context: 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
@@ -2197,6 +2327,7 @@ class SeatDemoService:
_speech_understanding,
assistant_plan=_assistant_plan,
mode_profile_draft_context=_mode_profile_draft_context,
cloud_instruction_context=_cloud_instruction_context,
)
if control_transport == WIRELESS_MQTT_TRANSPORT:
# The head unit owns both physical CAN channels in wireless mode.
@@ -2222,6 +2353,7 @@ class SeatDemoService:
_speech_understanding,
assistant_plan=_assistant_plan,
mode_profile_draft_context=_mode_profile_draft_context,
cloud_instruction_context=_cloud_instruction_context,
)
if hardware_access_bypass or self._driver_only_vehicle_execution_active(
control_transport
@@ -2247,6 +2379,7 @@ class SeatDemoService:
_speech_understanding,
assistant_plan=_assistant_plan,
mode_profile_draft_context=_mode_profile_draft_context,
cloud_instruction_context=_cloud_instruction_context,
)
authorized_client_id = self.hardware_access.ensure_authorized(hardware_client_id)
with self.hardware_execution.reserve():
@@ -2271,6 +2404,7 @@ class SeatDemoService:
_speech_understanding,
assistant_plan=_assistant_plan,
mode_profile_draft_context=_mode_profile_draft_context,
cloud_instruction_context=_cloud_instruction_context,
)
def _process_once(
@@ -2296,6 +2430,7 @@ class SeatDemoService:
allow_fuzzy_mode_profile_match: bool = True,
allow_scene_composition: bool = False,
mode_profile_draft_context: dict[str, Any] | None = None,
cloud_instruction_context: dict[str, Any] | None = None,
) -> dict[str, Any]:
control_transport = self._validate_control_transport(control_transport)
utterance = utterance.strip()
@@ -2626,6 +2761,7 @@ class SeatDemoService:
model_parse_source=model_parse_source,
assistant_plan=assistant_plan,
allow_scene_composition=allow_scene_composition,
cloud_instruction_context=cloud_instruction_context,
)
dynamic_mode_stop = self._is_dynamic_mode_stop_request(utterance)
if dynamic_mode_stop:
@@ -7280,6 +7416,11 @@ class SeatDemoService:
and CONTROL_CLAUSE_PREDICATE_PATTERN.search(value)
)
def looks_like_independent_request(value: str) -> bool:
return looks_like_control(value) or bool(
CLOUD_REQUEST_CLAUSE_PATTERN.search(value)
)
def split_independent_controls(
value: str,
separator_pattern: str,
@@ -7290,7 +7431,14 @@ class SeatDemoService:
for index in range(1, len(tokens), 2):
separator = tokens[index]
next_item = tokens[index + 1].strip()
if looks_like_control(current) and looks_like_control(next_item):
if (
looks_like_independent_request(current)
and looks_like_independent_request(next_item)
and (
looks_like_control(current)
or looks_like_control(next_item)
)
):
if current:
separated.append(current)
current = next_item
@@ -7600,6 +7748,7 @@ class SeatDemoService:
model_parse_source: str = "cloud_model",
assistant_plan: dict[str, Any] | None = None,
allow_scene_composition: bool = False,
cloud_instruction_context: dict[str, Any] | None = None,
) -> tuple[dict[str, Any], str, str | None]:
vehicle_profile = vehicle_profile or self.vehicle_profiles.default()
if assistant_plan is not None and allow_scene_composition:
@@ -7764,9 +7913,29 @@ class SeatDemoService:
interpret_options = {}
if reasoning_effort is not None:
interpret_options["reasoning_effort"] = reasoning_effort
hosted_context = deepcopy(context)
if isinstance(cloud_instruction_context, dict):
routing_context = deepcopy(cloud_instruction_context)
else:
routing_context = {
"original_utterance": utterance,
"parsed_clauses": [
clause
for clause, _, status in clause_results
if status != "unparsed"
],
"resolved_clauses": [
clause
for clause, _, status in clause_results
if status == "complete"
],
"unresolved_clauses": fallback_clauses,
}
routing_context["cloud_input_utterance"] = fallback_utterance
hosted_context["natural_language_routing"] = routing_context
plan = self.openai.interpret(
fallback_utterance,
context,
hosted_context,
self.capability_context,
**interpret_options,
)
+179 -64
View File
@@ -15,7 +15,7 @@ import uuid
from contextlib import closing
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from typing import Any, Callable
from urllib.error import HTTPError, URLError
from urllib.parse import urlparse
from urllib.request import Request, urlopen
@@ -2387,6 +2387,7 @@ class SelfExtensionService:
conversation_turn: int = 0,
execution_mode: str = "dry_run",
provider_requested: str = "auto",
on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
) -> dict[str, Any]:
"""Execute a live-NFC peripheral match through the main request API."""
@@ -2398,41 +2399,8 @@ class SelfExtensionService:
request_id = f"req-extension-{uuid.uuid4().hex}"
conversation_id = conversation_id or f"conv-{uuid.uuid4().hex}"
hardware_request = execution_mode == "hardware"
if hardware_request:
gateway_result = self.execute_gateway("emit", {"slot": slot})
transport = str(gateway_result.get("transport") or self.gateway.transport)
gateway_acknowledged = gateway_result.get("gateway_acknowledged") is True
command = gateway_result.get("request")
operation_message = (
f"{capability.get('device_name')} · {capability.get('action')} 已发送至 "
"GSCU1B;网关已确认接收,外设物理效果待独立确认。"
)
delivery_status = "published"
else:
command = build_gateway_command("emit", {"slot": slot})
transport = "dry_run"
gateway_acknowledged = False
operation_message = (
f"{capability.get('device_name')} · {capability.get('action')} 已完成红外路由校验;"
"当前为 dry-run,未向 GSCU1B 发射。"
)
delivery_status = "planned"
transport = self.gateway.transport if hardware_request else "dry_run"
operation_ref = str(capability.get("id") or f"ir-slot-{slot}")
operation = {
"ref": operation_ref,
"domain": "self_extension",
"status": "succeeded",
"message": operation_message,
"telemetry": {
"transport": transport,
"slot": slot,
"delivery_status": delivery_status,
"gateway_acknowledged": gateway_acknowledged,
"physical_effect_confirmed": False,
"command": command,
},
}
function_config = {
"apiVersion": "cognitive-agent.io/v1alpha1",
"kind": "FunctionConfig",
@@ -2455,7 +2423,9 @@ class SelfExtensionService:
"action": capability.get("action"),
"slot": slot,
},
"parse_source": capability.get("parse_source", "local_rule"),
"parse_source": capability.get(
"parse_source", "local_rule"
),
"timeout_ms": int(self.command_timeout * 1000),
"failure_policy": "abort",
}
@@ -2479,6 +2449,78 @@ class SelfExtensionService:
],
},
}
if on_commands_validated is not None:
on_commands_validated(
{
"request_id": request_id,
"utterance": utterance,
"conversation_id": conversation_id,
"conversation_turn": conversation_turn,
"status": "processing",
"phase": "commands_validated",
"orchestration_route": "nfc_peripheral",
"provider_requested": provider_requested,
"provider_used": "nfc_peripheral_router",
"execution_mode": execution_mode,
"control_transport": (
"wireless_mqtt"
if transport == "mqtt"
else f"gscu_{transport}"
),
"trace_events": [
{
"stage": "nfc_peripheral_route",
"status": "succeeded",
"summary": "本地规则已完成红外能力路由与指令编译",
"duration_ms": 0,
}
],
"validation": [],
"function_config": function_config,
"function_configs": {"self_extension": function_config},
"command_plan": command_plan,
"command_plans": {"self_extension": command_plan},
"execution_report": None,
"operation_results": [],
"deferred_requests": [],
"domain_state": {},
"actuator_state": {},
}
)
if hardware_request:
gateway_result = self.execute_gateway("emit", {"slot": slot})
transport = str(gateway_result.get("transport") or self.gateway.transport)
gateway_acknowledged = gateway_result.get("gateway_acknowledged") is True
command = gateway_result.get("request")
operation_message = (
f"{capability.get('device_name')} · {capability.get('action')} 已发送至 "
"GSCU1B;网关已确认接收,外设物理效果待独立确认。"
)
delivery_status = "published"
else:
command = build_gateway_command("emit", {"slot": slot})
transport = "dry_run"
gateway_acknowledged = False
operation_message = (
f"{capability.get('device_name')} · {capability.get('action')} 已完成红外路由校验;"
"当前为 dry-run,未向 GSCU1B 发射。"
)
delivery_status = "planned"
operation = {
"ref": operation_ref,
"domain": "self_extension",
"status": "succeeded",
"message": operation_message,
"telemetry": {
"transport": transport,
"slot": slot,
"delivery_status": delivery_status,
"gateway_acknowledged": gateway_acknowledged,
"physical_effect_confirmed": False,
"command": command,
},
}
control_transport = (
"wireless_mqtt" if transport == "mqtt" else f"gscu_{transport}"
)
@@ -2548,6 +2590,7 @@ class SelfExtensionService:
conversation_turn: int = 0,
execution_mode: str = "dry_run",
provider_requested: str = "auto",
on_commands_validated: Callable[[dict[str, Any]], None] | None = None,
) -> dict[str, Any]:
"""Execute a validated ordered peripheral/delay plan for the live NFC."""
@@ -2642,6 +2685,76 @@ class SelfExtensionService:
}
)
function_config = {
"apiVersion": "cognitive-agent.io/v1alpha1",
"kind": "FunctionConfig",
"metadata": {
"name": f"{request_id}-sequence",
"labels": {"execution_scope": "nfc_bound_ir_peripheral"},
},
"spec": {
"domain": "self_extension",
"operations": function_operations,
},
}
command_plan = {
"apiVersion": "cognitive-agent.io/v1alpha1",
"kind": "CommandPlan",
"metadata": {"name": f"{request_id}-gscu-sequence"},
"spec": {
"domain": "self_extension",
"platform": "gscu1b",
"commands": commands,
},
}
if on_commands_validated is not None:
on_commands_validated(
{
"request_id": request_id,
"utterance": utterance,
"conversation_id": conversation_id,
"conversation_turn": conversation_turn,
"status": "processing",
"phase": "commands_validated",
"orchestration_route": "nfc_peripheral_sequence",
"provider_requested": provider_requested,
"provider_used": str(
plan_result.get("provider_used")
or f"gpt:{plan_result.get('model') or self.openai_model}"
),
"execution_mode": execution_mode,
"control_transport": (
"wireless_mqtt"
if transport == "mqtt"
else f"gscu_{transport}"
),
"trace_events": [
{
"stage": (
"local_peripheral_plan"
if str(plan_result.get("provider_used") or "").startswith(
"infrared_local"
)
else "gpt_peripheral_plan"
),
"status": "succeeded",
"summary": f"已编译并校验 {len(steps)} 个有序步骤",
"duration_ms": 0,
}
],
"validation": [],
"function_config": function_config,
"function_configs": {"self_extension": function_config},
"command_plan": command_plan,
"command_plans": {"self_extension": command_plan},
"execution_report": None,
"operation_results": [],
"deferred_requests": [],
"domain_state": {},
"actuator_state": {},
}
)
for index, step in enumerate(steps):
if step.get("kind") == "delay":
delay_ms = int(step.get("delay_ms", 0))
@@ -2781,28 +2894,6 @@ class SelfExtensionService:
)
report_status = "succeeded" if succeeded else "failed"
first_capability = next(iter(catalog_by_id.values()))
function_config = {
"apiVersion": "cognitive-agent.io/v1alpha1",
"kind": "FunctionConfig",
"metadata": {
"name": f"{request_id}-sequence",
"labels": {"execution_scope": "nfc_bound_ir_peripheral"},
},
"spec": {
"domain": "self_extension",
"operations": function_operations,
},
}
command_plan = {
"apiVersion": "cognitive-agent.io/v1alpha1",
"kind": "CommandPlan",
"metadata": {"name": f"{request_id}-gscu-sequence"},
"spec": {
"domain": "self_extension",
"platform": "gscu1b",
"commands": commands,
},
}
control_transport = (
"wireless_mqtt" if transport == "mqtt" else f"gscu_{transport}"
)
@@ -2988,12 +3079,22 @@ class SelfExtensionService:
item for item in vehicle_devices[:32] if isinstance(item, dict)
]
allowed_vehicle_commands = set(_vehicle_catalog_commands(vehicle_devices))
response_text = self._request_gpt_plan(
utterance,
planner_peripherals,
vehicle_devices,
api_key,
)
raw_routing = payload.get("natural_language_routing")
if isinstance(raw_routing, dict):
response_text = self._request_gpt_plan(
utterance,
planner_peripherals,
vehicle_devices,
api_key,
natural_language_routing=dict(raw_routing),
)
else:
response_text = self._request_gpt_plan(
utterance,
planner_peripherals,
vehicle_devices,
api_key,
)
try:
proposed = json.loads(response_text)
except json.JSONDecodeError as exc:
@@ -3120,6 +3221,8 @@ class SelfExtensionService:
peripherals: list[dict[str, Any]],
vehicle_devices: list[dict[str, Any]],
api_key: str,
*,
natural_language_routing: dict[str, Any] | None = None,
) -> str:
peripheral_ids = [
str(item.get("id"))
@@ -3172,6 +3275,9 @@ class SelfExtensionService:
"required": ["summary", "needs_confirmation", "question", "steps"],
}
system_prompt = f"""你是车载外设自扩展工作台的意图规划器。只输出 JSON 对象。
输入中的 natural_language_routing 是可信路由数据:original_utterance 为完整原句,
parsed_clauses/resolved_clauses 已由本地规则处理,只能为 unresolved_clauses 和当前 utterance
补充计划,禁止重复、替换或重新执行已解析子句。
peripheral 只能逐字引用目录中 status=learned 的短 capability_id,绝不能编造或改写 ID。
仅当同类存在多台可匹配的外设玩具且用户未说明目标时,设置 needs_confirmation=true 并追问;
此追问规则不适用于车窗、空调、座椅、电动风口等车端能力。
@@ -3218,6 +3324,15 @@ delay 步骤的 capability_id 和 command 必须为空字符串,不能位于
"text": json.dumps(
{
"utterance": utterance,
"natural_language_routing": (
natural_language_routing or {
"original_utterance": utterance,
"parsed_clauses": [],
"resolved_clauses": [],
"unresolved_clauses": [utterance],
"cloud_input_utterance": utterance,
}
),
"learned_peripheral_capabilities": peripherals,
"can_dev_vehicle_capabilities": vehicle_devices,
},