feat: add seat-scoped static and dynamic assistant modes
This commit is contained in:
@@ -47,17 +47,27 @@ from cognitive_agent.application.direct_can_control import ( # noqa: E402
|
||||
build_direct_can_intent,
|
||||
)
|
||||
from cognitive_agent.application.domain_runtime import ConfiguredDomainRuntime # noqa: E402
|
||||
from cognitive_agent.application.mode_creation_seat_policy import ( # noqa: E402
|
||||
ModeCreationSeatPolicyError,
|
||||
creation_seat_scopes,
|
||||
project_seed_operations,
|
||||
project_seed_plan,
|
||||
project_seed_schedule,
|
||||
)
|
||||
from cognitive_agent.application.mode_profile import ( # noqa: E402
|
||||
ModeProfile,
|
||||
ModeProfileRegistry,
|
||||
)
|
||||
from cognitive_agent.application.mode_profile_intent import ( # noqa: E402
|
||||
is_mode_profile_query,
|
||||
mode_creation_source_name,
|
||||
normalize_mode_profile_name,
|
||||
parse_mode_profile_create,
|
||||
parse_mode_profile_delete,
|
||||
parse_mode_profile_update,
|
||||
)
|
||||
from cognitive_agent.application.mode_schedule import ModeSchedule, ModeScheduleRunner # noqa: E402
|
||||
from cognitive_agent.application.mode_schedule_service import ModeScheduleServiceMixin # noqa: E402
|
||||
from cognitive_agent.application.model_plan_adapter import ( # noqa: E402
|
||||
APPLY_PRESET_CAPABILITY,
|
||||
DYNAMIC_MODE_STOP_CAPABILITY,
|
||||
@@ -90,6 +100,10 @@ from cognitive_agent.application.native_can_runtime import ( # noqa: E402
|
||||
NativeCanRuntime,
|
||||
)
|
||||
from cognitive_agent.application.scene_templates import SceneTemplateRegistry # noqa: E402
|
||||
from cognitive_agent.application.schedule_intent import ( # noqa: E402
|
||||
is_schedule_stop,
|
||||
needs_mode_schedule,
|
||||
)
|
||||
from cognitive_agent.application.seat_calibration import position_pct_to_angle # noqa: E402
|
||||
from cognitive_agent.application.semantic_ir import ( # noqa: E402
|
||||
apply_language_safety_guards,
|
||||
@@ -331,8 +345,9 @@ DYNAMIC_MODE_WORDS = (
|
||||
)
|
||||
|
||||
|
||||
class SeatDemoService:
|
||||
class SeatDemoService(ModeScheduleServiceMixin):
|
||||
def __init__(self, repository_root: Path = REPOSITORY_ROOT):
|
||||
self.mode_schedule_runner = ModeScheduleRunner()
|
||||
self.root = repository_root
|
||||
self.domain_root = repository_root / "domains" / "seat"
|
||||
self.contracts = ContractValidator(repository_root)
|
||||
@@ -663,7 +678,7 @@ class SeatDemoService:
|
||||
|
||||
resolved_vehicle_id = vehicle_id or self.vehicle_profiles.default().id
|
||||
profile = self.mode_profiles.get_profile(mode_id, resolved_vehicle_id)
|
||||
if not profile.command_flow or not profile.command_flow.get("plans"):
|
||||
if profile.schedule is None and (not profile.command_flow or not profile.command_flow.get("plans")):
|
||||
raise ValueError("该模式尚未生成可直接执行的控制指令流。")
|
||||
result = self.process(
|
||||
profile.instruction or f"进入{profile.name}",
|
||||
@@ -678,6 +693,8 @@ class SeatDemoService:
|
||||
control_transport=control_transport,
|
||||
_direct_mode_profile_id=profile.id,
|
||||
)
|
||||
if profile.schedule is not None:
|
||||
return result
|
||||
public_profile = profile.public_dict()
|
||||
result["provider_used"] = "saved_mode_command_flow"
|
||||
result["utterance"] = profile.name
|
||||
@@ -796,7 +813,7 @@ class SeatDemoService:
|
||||
):
|
||||
raise ValueError("功能模式草稿未通过安全校验,请重新生成。")
|
||||
|
||||
return {
|
||||
trial = {
|
||||
"request_id": clean_request_id,
|
||||
"status": "succeeded",
|
||||
"execution_mode": "dry_run",
|
||||
@@ -809,6 +826,14 @@ class SeatDemoService:
|
||||
"validation": deepcopy(validation),
|
||||
"trial_source": "server_validated_preview_artifact",
|
||||
}
|
||||
schedule = self._validated_schedule_artifact(source_result)
|
||||
if schedule is not None:
|
||||
trial["schedule"] = schedule.to_dict()
|
||||
trial["schedule_execution"] = self.mode_schedule_runner.simulate(
|
||||
schedule, lambda phase: {"status": "succeeded", "phase_id": phase.id,
|
||||
"operations": deepcopy(list(phase.operations))},
|
||||
)
|
||||
return trial
|
||||
|
||||
def _persist_mode_profile_card(
|
||||
self,
|
||||
@@ -1007,6 +1032,11 @@ class SeatDemoService:
|
||||
# generation workflow.
|
||||
"execution_steps": [],
|
||||
}
|
||||
schedule = self._validated_schedule_artifact(source_result)
|
||||
if profile is not None and profile.schedule is not None and schedule is None:
|
||||
raise ValueError("动态模式更新必须保留完整时间轴,请按阶段重新生成预览。")
|
||||
if schedule is not None:
|
||||
operations = [deepcopy(op) for phase in schedule.phases for op in phase.operations]
|
||||
saved = self.mode_profiles.save_mode(
|
||||
clean_name,
|
||||
operations,
|
||||
@@ -1022,6 +1052,7 @@ class SeatDemoService:
|
||||
instruction=clean_instruction,
|
||||
command_flow=command_flow,
|
||||
timeline=profile.timeline if profile is not None else None,
|
||||
schedule=schedule,
|
||||
expected_revision=draft_existing_mode_revision or None,
|
||||
create_only=not bool(clean_mode_id),
|
||||
)
|
||||
@@ -1220,6 +1251,7 @@ class SeatDemoService:
|
||||
self._vehicle_mqtt_transport.start()
|
||||
|
||||
def close(self) -> None:
|
||||
self.mode_schedule_runner.close()
|
||||
if self._vehicle_state_monitor is not None:
|
||||
self._vehicle_state_monitor.close()
|
||||
self._vehicle_mqtt_transport.close()
|
||||
@@ -2076,6 +2108,32 @@ class SeatDemoService:
|
||||
or mode_profile_query
|
||||
):
|
||||
execution_mode = "dry_run"
|
||||
schedule_requested = "模式" in utterance or _direct_mode_profile_id or is_schedule_stop(utterance)
|
||||
schedule_vehicle_id = vehicle_id or (self.vehicle_profiles.default().id if schedule_requested else "")
|
||||
if execution_mode not in {"dry_run", "simulated_hardware", "hardware"}:
|
||||
raise ValueError(f"unsupported execution mode: {execution_mode}")
|
||||
if is_schedule_stop(utterance):
|
||||
return self._cancel_mode_schedule(schedule_vehicle_id, conversation_id, utterance, execution_mode)
|
||||
schedule_profile = (self.mode_profiles.get_profile(_direct_mode_profile_id, schedule_vehicle_id)
|
||||
if _direct_mode_profile_id else None)
|
||||
if schedule_requested and schedule_profile is None and not (draft_update or mode_delete_name or mode_profile_query or parse_mode_profile_create(utterance)):
|
||||
candidates = self.mode_profiles.match_text(utterance, vehicle_id=schedule_vehicle_id)
|
||||
if len(candidates) == 1 and candidates[0].schedule is not None:
|
||||
aliases = "|".join(re.escape(name) for name in (candidates[0].name, *candidates[0].aliases))
|
||||
if re.fullmatch(
|
||||
r"(?:(?:请|麻烦|帮我|帮忙|给我|我想|我要|想要)\s*){0,4}"
|
||||
r"(?:(?:主驾|副驾|驾驶位|副驾驶|主驾驶)(?:座椅)?\s*)?"
|
||||
r"(?:进入|启动|执行|开启|启用|运行|打开|调用|应用|使用|切换到)\s*"
|
||||
rf"(?:{aliases})(?:吧|一下)?[。!!\s]*", utterance.strip(),
|
||||
):
|
||||
schedule_profile = candidates[0]
|
||||
if schedule_profile is not None and schedule_profile.schedule is not None:
|
||||
return self._run_mode_schedule(
|
||||
schedule_profile, utterance=utterance, conversation_id=conversation_id,
|
||||
execution_mode=execution_mode, approval_token=approval_token, vehicle_id=schedule_vehicle_id,
|
||||
simulation_fault=simulation_fault, hardware_client_id=hardware_client_id,
|
||||
hardware_access_bypass=hardware_access_bypass, control_transport=control_transport,
|
||||
)
|
||||
if execution_mode != "hardware":
|
||||
return self._process_once(
|
||||
utterance,
|
||||
@@ -2196,6 +2254,7 @@ class SeatDemoService:
|
||||
allow_fuzzy_mode_profile_match: bool = True,
|
||||
allow_scene_composition: bool = False,
|
||||
scene_scope_utterance: str | None = None,
|
||||
mode_creation_scopes: frozenset[str] | None = None,
|
||||
mode_profile_draft_context: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
control_transport = self._validate_control_transport(control_transport)
|
||||
@@ -2454,6 +2513,8 @@ class SeatDemoService:
|
||||
mode_requests = self.mode_profiles.requests_for_profiles(
|
||||
[direct_mode_profile]
|
||||
)
|
||||
if mode_creation_scopes is not None:
|
||||
mode_requests = project_seed_operations(mode_requests, mode_creation_scopes)
|
||||
detected_domains = list(
|
||||
dict.fromkeys(
|
||||
str(item.get("domain") or "")
|
||||
@@ -3687,6 +3748,16 @@ class SeatDemoService:
|
||||
) -> dict[str, Any]:
|
||||
if not matched_modes:
|
||||
return parsed
|
||||
if any(mode.schedule is not None for mode in matched_modes):
|
||||
rejected = deepcopy(parsed)
|
||||
for key in ("actions", "domain_actions", "native_can_actions", "preset_actions",
|
||||
"preset_events", "deferred_requests", "model_requests"):
|
||||
rejected[key] = []
|
||||
rejected["ambiguities"] = [{
|
||||
"field": "mode_profile",
|
||||
"question": "动态模式必须通过时间轴执行,请独立输入“进入模式名称”;不会将各阶段压平执行。",
|
||||
}]
|
||||
return rejected
|
||||
if self._contains_seat_preset_request(parsed):
|
||||
return parsed
|
||||
if len(matched_modes) > 1:
|
||||
@@ -3767,6 +3838,19 @@ class SeatDemoService:
|
||||
return parsed
|
||||
|
||||
mode_requests = self.mode_profiles.requests_for_profiles(matched_modes)
|
||||
selected_seat_scope = self._explicit_mode_seat_selection(
|
||||
matched_modes[0], utterance,
|
||||
)
|
||||
if selected_seat_scope is not None:
|
||||
# A cabin profile can carry independent comfort operations for both
|
||||
# front seats. An explicit single-seat invocation selects only that
|
||||
# seat; it must not authorize its sibling or mutate the stored mode.
|
||||
mode_requests = [
|
||||
request for request in mode_requests
|
||||
if request["domain"] != "seat"
|
||||
or self._normalized_mode_seat_scope(request["target_scope"])
|
||||
in selected_seat_scope
|
||||
]
|
||||
detected_domains = []
|
||||
for request in mode_requests:
|
||||
if request["domain"] not in detected_domains:
|
||||
@@ -3787,6 +3871,24 @@ class SeatDemoService:
|
||||
context,
|
||||
vehicle_profile,
|
||||
)
|
||||
# Native-only comfort capabilities are represented as deferred items by
|
||||
# the generic adapter. A verified stored mode must materialize them too;
|
||||
# otherwise a successful named-mode run silently drops its driver seat.
|
||||
mode_native_actions = []
|
||||
for item in mode_parsed.get("deferred_requests", []):
|
||||
if item.get("execution_backend") != "native_can":
|
||||
continue
|
||||
action = {
|
||||
"domain": item["domain"],
|
||||
"capability": item["capability"],
|
||||
"target": item["target_scope"],
|
||||
"parameters": deepcopy(item.get("parameters", {})),
|
||||
"operation": item.get("desired_change", "set"),
|
||||
"reason": item.get("reason", ""),
|
||||
"execution_backend": "native_can",
|
||||
}
|
||||
if self.native_can_runtime.supports(action):
|
||||
mode_native_actions.append(action)
|
||||
parsed_has_explicit_request = bool(
|
||||
parsed.get("actions")
|
||||
or parsed.get("domain_actions")
|
||||
@@ -3803,6 +3905,10 @@ class SeatDemoService:
|
||||
mode_parsed.get("domain_actions", []),
|
||||
parsed.get("domain_actions", []),
|
||||
)
|
||||
merged["native_can_actions"] = self._merge_control_actions(
|
||||
mode_native_actions,
|
||||
parsed.get("native_can_actions", []),
|
||||
)
|
||||
for key in (
|
||||
"preset_actions",
|
||||
"preset_events",
|
||||
@@ -3834,13 +3940,35 @@ class SeatDemoService:
|
||||
)
|
||||
return merged
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def _explicit_mode_target_conflict(
|
||||
cls,
|
||||
profile: ModeProfile,
|
||||
utterance: str,
|
||||
) -> tuple[str, tuple[str, ...]] | None:
|
||||
"""Reject a named mode when its persisted seat scope contradicts text."""
|
||||
|
||||
requested, normalized_scopes = cls._explicit_mode_seat_scopes(profile, utterance)
|
||||
if requested is None or requested == normalized_scopes:
|
||||
return None
|
||||
if cls._explicit_mode_seat_selection(profile, utterance) is not None:
|
||||
return None
|
||||
return ",".join(sorted(requested)), tuple(sorted(normalized_scopes))
|
||||
|
||||
@staticmethod
|
||||
def _normalized_mode_seat_scope(scope: str) -> str:
|
||||
return (
|
||||
"driver" if scope == "front_left" else
|
||||
"passenger" if scope == "front_right" else
|
||||
"rear" if scope in {"rear", "rear_left", "rear_right"} else scope
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _explicit_mode_seat_scopes(
|
||||
cls,
|
||||
profile: ModeProfile,
|
||||
utterance: str,
|
||||
) -> tuple[frozenset[str] | None, set[str]]:
|
||||
aliases = (profile.id, profile.name, *profile.aliases)
|
||||
mode_clause = next(
|
||||
(
|
||||
@@ -3856,23 +3984,34 @@ class SeatDemoService:
|
||||
utterance,
|
||||
)
|
||||
requested = resolve_explicit_seat_scopes(
|
||||
mode_clause,
|
||||
mode_clause.replace("主副驾", "主驾和副驾"),
|
||||
assume_seat_context=True,
|
||||
)
|
||||
if requested is None:
|
||||
return None
|
||||
|
||||
normalized_scopes = {
|
||||
"driver" if operation.target_scope == "front_left" else
|
||||
"passenger" if operation.target_scope == "front_right" else
|
||||
"rear" if operation.target_scope in {"rear", "rear_left", "rear_right"} else
|
||||
operation.target_scope
|
||||
cls._normalized_mode_seat_scope(operation.target_scope)
|
||||
for operation in profile.operations
|
||||
if operation.domain == "seat"
|
||||
}
|
||||
if requested == normalized_scopes:
|
||||
return requested, normalized_scopes
|
||||
|
||||
@classmethod
|
||||
def _explicit_mode_seat_selection(
|
||||
cls,
|
||||
profile: ModeProfile,
|
||||
utterance: str,
|
||||
) -> frozenset[str] | None:
|
||||
"""Scope a dual-seat mode invocation, never mode creation or editing."""
|
||||
requested, supported = cls._explicit_mode_seat_scopes(profile, utterance)
|
||||
if (
|
||||
requested is None
|
||||
or len(requested) != 1
|
||||
or supported != {"driver", "passenger"}
|
||||
or not requested.issubset(supported)
|
||||
or cls.is_mode_profile_configuration_request(utterance)
|
||||
or not re.search(r"进入|开启|启动|执行|启用|运行|切换|打开|调用|应用|使用", utterance)
|
||||
):
|
||||
return None
|
||||
return ",".join(sorted(requested)), tuple(sorted(normalized_scopes))
|
||||
return requested
|
||||
|
||||
@staticmethod
|
||||
def _apply_implicit_mode_target_policy(
|
||||
@@ -4205,7 +4344,9 @@ class SeatDemoService:
|
||||
for issue in validation
|
||||
):
|
||||
raise ValueError("待保存功能模式草稿没有通过完整安全校验。")
|
||||
operations = self._preview_mode_operations(source_result)
|
||||
schedule = self._validated_schedule_artifact(source_result)
|
||||
operations = ([deepcopy(op) for phase in schedule.phases for op in phase.operations]
|
||||
if schedule is not None else self._preview_mode_operations(source_result))
|
||||
operation_count = draft.get("operation_count")
|
||||
if (
|
||||
not isinstance(operation_count, int)
|
||||
@@ -4335,6 +4476,16 @@ class SeatDemoService:
|
||||
vehicle_id=vehicle_profile.id,
|
||||
)
|
||||
)
|
||||
if source_draft.get("schedule") is not None:
|
||||
from cognitive_agent.application.mode_schedule_edit import revise_schedule
|
||||
|
||||
return revise_schedule(
|
||||
self, ModeSchedule.from_dict(source_draft["schedule"]),
|
||||
change_text=change_text, utterance=utterance, name=mode_name,
|
||||
conversation_id=conversation_id, vehicle_profile=vehicle_profile,
|
||||
context=context, control_transport=control_transport,
|
||||
source_draft=source_draft,
|
||||
)
|
||||
base_operations = self._absolute_mode_operations(source_operations)
|
||||
if not change_text:
|
||||
raise ValueError("请说明要调整的设备和明确目标值。")
|
||||
@@ -4684,6 +4835,14 @@ class SeatDemoService:
|
||||
if profile is None:
|
||||
self._persist(result)
|
||||
return result
|
||||
if profile.schedule is not None:
|
||||
from cognitive_agent.application.mode_schedule_edit import update_saved_schedule
|
||||
|
||||
return update_saved_schedule(
|
||||
self, profile, change_text=change_text, utterance=utterance,
|
||||
conversation_id=conversation_id, vehicle_profile=vehicle_profile,
|
||||
context=context, result=result,
|
||||
)
|
||||
if not change_text:
|
||||
result["status"] = "needs_confirmation"
|
||||
result["validation"] = [
|
||||
@@ -5351,6 +5510,25 @@ class SeatDemoService:
|
||||
result["scene_config"] = None
|
||||
result["execution_report"] = None
|
||||
|
||||
def _mode_creation_seat_failure(
|
||||
self, utterance: str, conversation_id: str, vehicle: VehicleProfile, reason: str,
|
||||
) -> dict[str, Any]:
|
||||
result = {
|
||||
"request_id": f"req-{datetime.now(UTC).strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex}",
|
||||
"utterance": utterance, "conversation_id": conversation_id,
|
||||
"vehicle": vehicle.public_dict(), "status": "validation_failed",
|
||||
"execution_mode": "dry_run", "hardware_execution_allowed": False,
|
||||
"orchestration_route": "mode_profile_create_preview", "trace_events": [],
|
||||
"validation": [{"severity": "error", "code": "MODE_SEAT_SCOPE_UNAVAILABLE",
|
||||
"path": "mode_profile_draft.seat_scope", "message": reason}],
|
||||
"mode_creation_failure": {"code": "MODE_SEAT_SCOPE_UNAVAILABLE", "reason": reason,
|
||||
"next_step": "请选择该模式实际支持的座位,或改用当前座位支持的舒适功能"},
|
||||
"decision_summary": [reason, "没有保存模式或下发控制。"],
|
||||
}
|
||||
self._scrub_failed_scene_preview(result)
|
||||
self._persist(result)
|
||||
return result
|
||||
|
||||
def _process_mode_profile_create(
|
||||
self,
|
||||
utterance: str,
|
||||
@@ -5385,6 +5563,14 @@ class SeatDemoService:
|
||||
):
|
||||
raise ValueError("功能模式名称与创建请求不一致。")
|
||||
requested_instruction = create_parts[1] if create_parts is not None else ""
|
||||
selected_seats = creation_seat_scopes(utterance) if source_draft is None else None
|
||||
if selected_seats is not None and "unsupported" in selected_seats and re.search(
|
||||
r"座椅|座位|靠背|椅背|腿托|脚托|坐垫|坐盆|按摩|零重力", utterance
|
||||
):
|
||||
return self._mode_creation_seat_failure(
|
||||
utterance, conversation_id, vehicle_profile,
|
||||
"当前模式只支持明确的主驾或副驾座椅,不会将未支持的座位替换为其他座位。",
|
||||
)
|
||||
raw_plan_metadata = (
|
||||
assistant_plan.get("metadata")
|
||||
if isinstance(assistant_plan, dict)
|
||||
@@ -5411,6 +5597,7 @@ class SeatDemoService:
|
||||
or requested_instruction
|
||||
or assistant_plan.get("requests")
|
||||
!= candidate_template.plan().get("requests")
|
||||
or assistant_plan.get("schedule") != candidate_template.plan().get("schedule")
|
||||
):
|
||||
raise ValueError("内置常用模式模板已更新,请重新提交。")
|
||||
scene_template = candidate_template
|
||||
@@ -5441,6 +5628,10 @@ class SeatDemoService:
|
||||
if not requested_instruction and source_draft is None
|
||||
else None
|
||||
)
|
||||
if source_profile is None and source_draft is None and not requested_instruction and scene_template is None:
|
||||
source_name = mode_creation_source_name(mode_name)
|
||||
if source_name != mode_name:
|
||||
source_profile = self.mode_profiles.resolve_exact_name(source_name, vehicle_id=vehicle_profile.id)
|
||||
draft_mode_name = (
|
||||
str(source_draft.get("name") or mode_name).strip()
|
||||
if isinstance(source_draft, dict)
|
||||
@@ -5456,7 +5647,7 @@ class SeatDemoService:
|
||||
else "registered_mode_profile"
|
||||
if source_profile is not None
|
||||
else "codex_scene_composition"
|
||||
if assistant_plan is not None and not requested_instruction
|
||||
if assistant_plan is not None and (not requested_instruction or needs_mode_schedule(utterance))
|
||||
else "explicit_scene_requirements"
|
||||
)
|
||||
# A name-only request may already describe a repository-backed scene.
|
||||
@@ -5465,7 +5656,63 @@ class SeatDemoService:
|
||||
# being parsed as a second control clause on the assistant's local fast
|
||||
# path, turning a valid nine-action mode into an incomplete draft.
|
||||
source_utterance = requested_instruction or f"进入{draft_mode_name}"
|
||||
if requested_instruction and selected_seats is not None and len(selected_seats) == 1:
|
||||
# Qualify only explicit seat-device clauses, never an HVAC/window
|
||||
# zone or unrelated control. General one-shot controls keep their
|
||||
# existing target policy; this is scoped to new mode construction.
|
||||
label = "主驾" if selected_seats == {"driver"} else "副驾"
|
||||
instruction_clauses = self._split_intent_clauses(requested_instruction)
|
||||
qualified_clauses = [
|
||||
f"{label}{clause}" if (
|
||||
re.search(r"座椅|座位|靠背|椅背|腿托|脚托|坐垫|坐盆|按摩", clause)
|
||||
and resolve_explicit_seat_scopes(clause, assume_seat_context=True) is None
|
||||
) else clause
|
||||
for clause in instruction_clauses
|
||||
]
|
||||
# Preserve the user's punctuation and wording unless a seat really
|
||||
# needs qualification; explicit instructions need no rewriting.
|
||||
if qualified_clauses != instruction_clauses:
|
||||
source_utterance = ",".join(qualified_clauses)
|
||||
raw_schedule = (assistant_plan.get("schedule") if isinstance(assistant_plan, dict) else None)
|
||||
if source_profile is not None and source_profile.schedule is not None:
|
||||
raw_schedule = source_profile.schedule.to_dict()
|
||||
if raw_schedule is not None:
|
||||
if isinstance(assistant_plan, dict) and assistant_plan.get("ambiguities"):
|
||||
raise ValueError("动态需求仍有未解决的问题,不能生成部分成功的时间轴,请补充或调整需求。")
|
||||
if source_profile is None and source_draft is None and scene_template is None and not needs_mode_schedule(utterance):
|
||||
raise ValueError("用户未请求动态或分阶段模式,不能擅自生成定时任务。")
|
||||
schedule = ModeSchedule.from_dict(raw_schedule)
|
||||
if selected_seats is not None and (source_profile is not None or scene_template is not None):
|
||||
try:
|
||||
schedule = project_seed_schedule(schedule, selected_seats)
|
||||
except ModeCreationSeatPolicyError as exc:
|
||||
return self._mode_creation_seat_failure(utterance, conversation_id, vehicle_profile, str(exc))
|
||||
return self._preview_mode_schedule(
|
||||
schedule, name=draft_mode_name, utterance=utterance,
|
||||
conversation_id=conversation_id, vehicle_profile=vehicle_profile,
|
||||
control_transport=control_transport, generation_source=generation_source,
|
||||
source_profile=existing_profile, source_template=scene_template, source_draft=source_draft,
|
||||
scene_scope_utterance="" if source_draft is not None else utterance,
|
||||
)
|
||||
if needs_mode_schedule(utterance) and scene_template is None and not (source_profile is not None and source_profile.timeline):
|
||||
result = {
|
||||
"request_id": f"req-{datetime.now(UTC).strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex}",
|
||||
"utterance": utterance, "conversation_id": conversation_id,
|
||||
"vehicle": vehicle_profile.public_dict(), "validation": [], "trace_events": [],
|
||||
"status": "validation_failed", "execution_mode": "dry_run", "hardware_execution_allowed": False,
|
||||
"orchestration_route": "mode_profile_create_preview",
|
||||
}
|
||||
result["mode_creation_failure"] = {
|
||||
"code": "MODE_SCHEDULE_REQUIRED",
|
||||
"reason": "该需求包含时间变化,但没有生成完整的分阶段时间轴",
|
||||
"next_step": "请重试,或补充每个阶段的设备目标和持续时间;未保存或执行静态替代动作",
|
||||
}
|
||||
self._scrub_failed_scene_preview(result)
|
||||
self._persist(result)
|
||||
return result
|
||||
try:
|
||||
if scene_template is not None and selected_seats is not None:
|
||||
assistant_plan = project_seed_plan(assistant_plan, selected_seats)
|
||||
source_result = self._process_once(
|
||||
source_utterance,
|
||||
provider,
|
||||
@@ -5478,6 +5725,7 @@ class SeatDemoService:
|
||||
direct_mode_profile_id=(
|
||||
source_profile.id if source_profile is not None else None
|
||||
),
|
||||
mode_creation_scopes=selected_seats,
|
||||
assistant_plan=assistant_plan,
|
||||
# ``source_utterance`` is synthesized for a name-only, unknown
|
||||
# scene. It must not fall back to substring matching (for example
|
||||
@@ -5493,8 +5741,10 @@ class SeatDemoService:
|
||||
# already passed the ordinary text/target authorization path.
|
||||
# Its old display name cannot veto an explicitly requested new
|
||||
# seat. This override is internal, never accepted from payloads.
|
||||
scene_scope_utterance="" if source_draft is not None else None,
|
||||
scene_scope_utterance="" if source_draft is not None else utterance,
|
||||
)
|
||||
except ModeCreationSeatPolicyError as exc:
|
||||
return self._mode_creation_seat_failure(utterance, conversation_id, vehicle_profile, str(exc))
|
||||
except NativeCanCompileError:
|
||||
# Repository modes and model-composed scenes share the same native
|
||||
# compiler. A stale mode parameter must become a bounded failed
|
||||
|
||||
Reference in New Issue
Block a user