fix: compose named cockpit modes from capabilities
This commit is contained in:
@@ -19,6 +19,7 @@ from pathlib import Path
|
||||
from typing import Protocol
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
INTERNAL_SCENE_BUILDER_MARKER = "根据该场景名称使用当前可控原子能力"
|
||||
|
||||
|
||||
class CodexAssistantError(RuntimeError):
|
||||
@@ -99,6 +100,7 @@ class _VehicleTurnContext:
|
||||
capability_queries: int = 0
|
||||
action_succeeded: bool | None = None
|
||||
action_state: str | None = None
|
||||
action_result: Mapping[str, object] | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -139,9 +141,9 @@ _MODE_PROFILE_CREATE_PATTERN = re.compile(
|
||||
r"(?:构建|创建|新建|新增|生成|添加)(?:一下)?"
|
||||
r"(?:一个|个|一套|套)?"
|
||||
r"(?:名为|叫做|叫)?[“\"'「『]?"
|
||||
r"[^,。,.!!::\s“”\"'「」『』]{1,64}?模式[”\"'」』]?"
|
||||
r"(?P<name>[^,。,.!!::\s“”\"'「」『』]{1,64}?模式)[”\"'」』]?"
|
||||
r"(?:配置)?(?:并(?:添加|加入)(?:到)?功能模式(?:栏)?)?"
|
||||
r"(?:[,,::].{1,800})?[。.!!]?$"
|
||||
r"(?:[,,::](?P<instruction>.{1,800}))?[。.!!]?$"
|
||||
)
|
||||
_HOT_DISCOMFORT_PATTERN = re.compile(r"热|闷热|好热|太晒")
|
||||
_COLD_DISCOMFORT_PATTERN = re.compile(r"冷|好冷")
|
||||
@@ -164,12 +166,114 @@ def _normalize_instruction(value: object) -> str:
|
||||
return re.sub(r"[\s,。!?!?、;;::]", "", str(value).lower())
|
||||
|
||||
|
||||
def _mode_profile_create_parts(message: str) -> tuple[str, str] | None:
|
||||
"""Return the requested scene name and optional explicit settings."""
|
||||
|
||||
normalized = "".join(str(message).strip().split())
|
||||
match = _MODE_PROFILE_CREATE_PATTERN.fullmatch(normalized)
|
||||
if match is None:
|
||||
return None
|
||||
return (
|
||||
str(match.group("name") or "").strip(),
|
||||
str(match.group("instruction") or "").strip(",,::。.!!"),
|
||||
)
|
||||
|
||||
|
||||
def is_mode_profile_create_request(message: str) -> bool:
|
||||
"""Return whether the original user text is a bounded mode-create request."""
|
||||
|
||||
return _mode_profile_create_parts(message) is not None
|
||||
|
||||
|
||||
def _catalog_exact_mode_match_count(
|
||||
catalog: Mapping[str, object],
|
||||
requested_name: str,
|
||||
) -> int | None:
|
||||
"""Count exact visible mode matches; ``None`` means catalog unavailable."""
|
||||
|
||||
needle = "".join(str(requested_name).casefold().split())
|
||||
source_status = catalog.get("source_status")
|
||||
if "modes" not in catalog or (
|
||||
isinstance(source_status, Mapping)
|
||||
and source_status.get("modes") != "ready"
|
||||
):
|
||||
return None
|
||||
matched_ids: set[str] = set()
|
||||
raw_modes = catalog.get("modes", [])
|
||||
if not isinstance(raw_modes, Sequence) or isinstance(
|
||||
raw_modes,
|
||||
(str, bytes, bytearray),
|
||||
):
|
||||
return None
|
||||
for raw_mode in raw_modes:
|
||||
if not isinstance(raw_mode, Mapping):
|
||||
continue
|
||||
candidates = [raw_mode.get("id"), raw_mode.get("name")]
|
||||
aliases = raw_mode.get("aliases", [])
|
||||
if isinstance(aliases, Sequence) and not isinstance(
|
||||
aliases,
|
||||
(str, bytes, bytearray),
|
||||
):
|
||||
candidates.extend(aliases)
|
||||
if needle in {
|
||||
"".join(str(candidate or "").casefold().split())
|
||||
for candidate in candidates
|
||||
if str(candidate or "").strip()
|
||||
}:
|
||||
matched_ids.add(str(raw_mode.get("id") or ""))
|
||||
matched_ids.discard("")
|
||||
return len(matched_ids)
|
||||
|
||||
|
||||
def _vehicle_result_succeeded(result: Mapping[str, object]) -> bool:
|
||||
"""Only a terminal, fully successful service result completes a tool call."""
|
||||
|
||||
return str(result.get("status") or "").lower() == "succeeded"
|
||||
|
||||
|
||||
def _mode_profile_result_succeeded(result: Mapping[str, object]) -> bool:
|
||||
"""Require a complete, non-physical mode preview before claiming success."""
|
||||
|
||||
if (
|
||||
not _vehicle_result_succeeded(result)
|
||||
or str(result.get("execution_mode") or "").lower() != "dry_run"
|
||||
or result.get("hardware_execution_allowed") is not False
|
||||
or result.get("orchestration_route") != "mode_profile_create_preview"
|
||||
):
|
||||
return False
|
||||
draft = result.get("mode_profile_draft")
|
||||
if not isinstance(draft, Mapping):
|
||||
return False
|
||||
operation_count = draft.get("operation_count")
|
||||
if (
|
||||
not isinstance(operation_count, int)
|
||||
or isinstance(operation_count, bool)
|
||||
or operation_count <= 0
|
||||
):
|
||||
return False
|
||||
intent_ir = result.get("intent_ir")
|
||||
spec = intent_ir.get("spec") if isinstance(intent_ir, Mapping) else None
|
||||
actions = spec.get("actions") if isinstance(spec, Mapping) else None
|
||||
if not isinstance(actions, Sequence) or isinstance(
|
||||
actions,
|
||||
(str, bytes, bytearray),
|
||||
):
|
||||
return False
|
||||
if len(actions) != operation_count or not all(
|
||||
isinstance(action, Mapping) for action in actions
|
||||
):
|
||||
return False
|
||||
validation = result.get("validation", [])
|
||||
return not (
|
||||
isinstance(validation, Sequence)
|
||||
and not isinstance(validation, (str, bytes, bytearray))
|
||||
and any(
|
||||
isinstance(issue, Mapping) and issue.get("severity") == "error"
|
||||
for issue in validation
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _vehicle_verification_status(result: Mapping[str, object]) -> str:
|
||||
"""Normalize the service's execution evidence without inventing feedback."""
|
||||
|
||||
@@ -231,7 +335,7 @@ def _authorize_vehicle_request(
|
||||
):
|
||||
return _VehicleAuthorization(None, "knowledge_or_non_vehicle_request")
|
||||
|
||||
if _MODE_PROFILE_CREATE_PATTERN.fullmatch(normalized):
|
||||
if is_mode_profile_create_request(normalized):
|
||||
# Named-mode creation is a bounded configuration operation. The
|
||||
# vehicle service parses the same original sentence again and always
|
||||
# validates the generated scene in dry-run before offering it to the
|
||||
@@ -373,7 +477,27 @@ class CodexAssistantService:
|
||||
self._fast_implicit_controls and authorization.reason in _FAST_COMFORT_REASONS
|
||||
)
|
||||
mode_profile_preview = authorization.reason == "explicit_mode_profile_create"
|
||||
direct_vehicle_control = fast_comfort_control or mode_profile_preview
|
||||
mode_parts = _mode_profile_create_parts(message) if mode_profile_preview else None
|
||||
exact_mode_matches = (
|
||||
_catalog_exact_mode_match_count(catalog, mode_parts[0])
|
||||
if mode_parts
|
||||
else None
|
||||
)
|
||||
direct_mode_profile_preview = bool(
|
||||
mode_parts
|
||||
and (
|
||||
mode_parts[1]
|
||||
# Ambiguous or unavailable mode catalogs fail closed in the
|
||||
# local builder. Only a known-zero match is safe to hand to
|
||||
# semantic scene composition.
|
||||
or exact_mode_matches != 0
|
||||
)
|
||||
)
|
||||
# Explicit device settings and repository-backed scenes remain on the
|
||||
# zero-model-latency path. A genuinely new name-only scene needs Codex
|
||||
# to compose a candidate plan from the live capability catalog; the
|
||||
# vehicle service still limits it to dry-run and requires confirmation.
|
||||
direct_vehicle_control = fast_comfort_control or direct_mode_profile_preview
|
||||
route = (
|
||||
"capability_catalog"
|
||||
if authoritative_answer
|
||||
@@ -385,7 +509,7 @@ class CodexAssistantService:
|
||||
"n7web-capability-registry"
|
||||
if authoritative_answer
|
||||
else "n7web-mode-builder"
|
||||
if mode_profile_preview
|
||||
if direct_mode_profile_preview
|
||||
else "n7web-control-router"
|
||||
if fast_comfort_control
|
||||
else "codex-app-server"
|
||||
@@ -414,6 +538,8 @@ class CodexAssistantService:
|
||||
"正在读取工程能力目录……"
|
||||
if authoritative_answer
|
||||
else "正在构建并校验功能模式……"
|
||||
if direct_mode_profile_preview
|
||||
else "Codex 正在理解场景并组合工程能力……"
|
||||
if mode_profile_preview
|
||||
else "正在快速处理座舱舒适需求……"
|
||||
if fast_comfort_control
|
||||
@@ -542,6 +668,8 @@ class CodexAssistantService:
|
||||
safe_result = self._safe_vehicle_result(result)
|
||||
action_state, successful, completion_text = _vehicle_action_state(result)
|
||||
if mode_profile_preview:
|
||||
successful = _mode_profile_result_succeeded(result)
|
||||
action_state = "simulated" if successful else "failed"
|
||||
completion_text = (
|
||||
"功能模式预览已生成" if successful else "功能模式预览生成失败"
|
||||
)
|
||||
@@ -632,6 +760,14 @@ class CodexAssistantService:
|
||||
reason = clean(failure.get("reason"))
|
||||
next_step = clean(failure.get("next_step"))
|
||||
if reason and next_step:
|
||||
if (
|
||||
INTERNAL_SCENE_BUILDER_MARKER in reason
|
||||
or INTERNAL_SCENE_BUILDER_MARKER in next_step
|
||||
):
|
||||
return (
|
||||
"未能根据场景名称生成通过工程能力校验的座舱动作",
|
||||
"请稍后重试;若该场景尚未登记,可补充希望联动的设备状态",
|
||||
)
|
||||
return reason, next_step
|
||||
|
||||
unresolved = result.get("unresolved_clauses")
|
||||
@@ -644,6 +780,8 @@ class CodexAssistantService:
|
||||
clean(item, limit=120)
|
||||
for item in unresolved[:3]
|
||||
if clean(item, limit=120)
|
||||
and INTERNAL_SCENE_BUILDER_MARKER
|
||||
not in clean(item, limit=120)
|
||||
)
|
||||
)
|
||||
if clauses:
|
||||
@@ -716,6 +854,40 @@ class CodexAssistantService:
|
||||
if isinstance(draft, Mapping)
|
||||
else "新功能模式"
|
||||
)
|
||||
if isinstance(draft, Mapping) and draft.get("existing_mode_id"):
|
||||
raw_operation_count = draft.get("operation_count")
|
||||
operation_count = (
|
||||
raw_operation_count
|
||||
if isinstance(raw_operation_count, int)
|
||||
and not isinstance(raw_operation_count, bool)
|
||||
and raw_operation_count >= 0
|
||||
else 0
|
||||
)
|
||||
if draft.get("generation_source") == "registered_mode_profile":
|
||||
return (
|
||||
f"工程中已有“{name}”,已按当前车型载入预览并验证"
|
||||
f"{operation_count}项设备动作。可以先试运行,确认后更新现有模式。"
|
||||
)
|
||||
return (
|
||||
f"已按你提供的设置为现有“{name}”生成预览并验证"
|
||||
f"{operation_count}项设备动作。可以先试运行,确认后更新现有模式。"
|
||||
)
|
||||
if (
|
||||
isinstance(draft, Mapping)
|
||||
and draft.get("generation_source") == "codex_scene_composition"
|
||||
):
|
||||
raw_operation_count = draft.get("operation_count")
|
||||
operation_count = (
|
||||
raw_operation_count
|
||||
if isinstance(raw_operation_count, int)
|
||||
and not isinstance(raw_operation_count, bool)
|
||||
and raw_operation_count > 0
|
||||
else 0
|
||||
)
|
||||
return (
|
||||
f"已理解“{name}”的场景意图,并从当前工程能力中组合、验证了"
|
||||
f"{operation_count}项设备动作。确认预览后可保存为功能模式,也可以先试运行。"
|
||||
)
|
||||
return f"已为你生成“{name}”预览。确认动作后可保存为功能模式,也可以先试运行。"
|
||||
|
||||
@staticmethod
|
||||
@@ -765,6 +937,9 @@ class CodexAssistantService:
|
||||
query_capabilities,
|
||||
)
|
||||
vehicle_control_expected = tool_context.authorization.instruction is not None
|
||||
mode_profile_expected = (
|
||||
tool_context.authorization.reason == "explicit_mode_profile_create"
|
||||
)
|
||||
with self._tool_contexts_lock:
|
||||
self._tool_contexts[request_token] = tool_context
|
||||
|
||||
@@ -938,8 +1113,6 @@ class CodexAssistantService:
|
||||
raise CodexAssistantError(str(detail or f"Codex turn {status}"))
|
||||
if not final_answer:
|
||||
final_answer = streamed_answer.strip()
|
||||
if not final_answer:
|
||||
raise CodexAssistantError("Codex produced an empty response")
|
||||
if vehicle_control_expected:
|
||||
if not tool_context.called:
|
||||
tool_context.action_succeeded = False
|
||||
@@ -955,9 +1128,25 @@ class CodexAssistantService:
|
||||
}
|
||||
)
|
||||
elif tool_context.action_succeeded is not True:
|
||||
final_answer = "车辆控制未完成,受控执行链未返回成功结果。"
|
||||
final_answer = (
|
||||
self._mode_profile_preview_answer(
|
||||
tool_context.action_result or {},
|
||||
False,
|
||||
)
|
||||
if mode_profile_expected
|
||||
else "车辆控制未完成,受控执行链未返回成功结果。"
|
||||
)
|
||||
elif mode_profile_expected:
|
||||
final_answer = self._mode_profile_preview_answer(
|
||||
tool_context.action_result or {},
|
||||
True,
|
||||
)
|
||||
elif tool_context.action_state == "submitted":
|
||||
final_answer = "控制指令已提交,正在等待车辆状态确认。"
|
||||
elif not final_answer and tool_context.action_state == "simulated":
|
||||
final_answer = "车辆控制模拟编排已完成。"
|
||||
elif not final_answer and tool_context.action_state == "verified":
|
||||
final_answer = "车辆控制已完成并通过状态确认。"
|
||||
if (
|
||||
emit(
|
||||
{
|
||||
@@ -969,6 +1158,8 @@ class CodexAssistantService:
|
||||
is False
|
||||
):
|
||||
return {"cancelled": True, "answer": final_answer}
|
||||
if not final_answer:
|
||||
raise CodexAssistantError("Codex produced an empty response")
|
||||
work_succeeded = (
|
||||
tool_context.called and tool_context.action_succeeded is True
|
||||
if vehicle_control_expected
|
||||
@@ -980,7 +1171,11 @@ class CodexAssistantService:
|
||||
"message_id": message_id,
|
||||
"success": work_succeeded,
|
||||
"display_text": (
|
||||
"车辆控制模拟编排已完成"
|
||||
"功能模式预览已生成"
|
||||
if mode_profile_expected and work_succeeded
|
||||
else "功能模式预览生成失败"
|
||||
if mode_profile_expected
|
||||
else "车辆控制模拟编排已完成"
|
||||
if vehicle_control_expected
|
||||
and work_succeeded
|
||||
and tool_context.action_state == "simulated"
|
||||
@@ -1106,6 +1301,15 @@ class CodexAssistantService:
|
||||
secret=request_token,
|
||||
)
|
||||
action_state, successful, completion_text = _vehicle_action_state(result)
|
||||
if context.authorization.reason == "explicit_mode_profile_create":
|
||||
successful = _mode_profile_result_succeeded(result)
|
||||
action_state = "simulated" if successful else "failed"
|
||||
completion_text = (
|
||||
"功能模式预览已生成"
|
||||
if successful
|
||||
else "功能模式预览生成失败"
|
||||
)
|
||||
context.action_result = result
|
||||
context.action_succeeded = successful
|
||||
context.action_state = action_state
|
||||
context.events.put(
|
||||
@@ -1158,7 +1362,17 @@ class CodexAssistantService:
|
||||
if "decision_summary" in normalized_plan:
|
||||
normalized_plan["decision_summary"] = ["Codex 结构化计划已提交应用安全链复核。"]
|
||||
if "ambiguities" in normalized_plan:
|
||||
normalized_plan["ambiguities"] = []
|
||||
normalized_plan["ambiguities"] = (
|
||||
[
|
||||
{
|
||||
"field": "assistant_plan",
|
||||
"question": "Codex 候选计划包含未解决歧义。",
|
||||
}
|
||||
]
|
||||
if isinstance(normalized_plan["ambiguities"], list)
|
||||
and normalized_plan["ambiguities"]
|
||||
else []
|
||||
)
|
||||
requests = normalized_plan.get("requests")
|
||||
if isinstance(requests, list):
|
||||
for request in requests:
|
||||
@@ -1482,6 +1696,9 @@ class CodexAssistantService:
|
||||
"vehicle_id",
|
||||
"domains",
|
||||
"operation_count",
|
||||
"existing_mode_id",
|
||||
"existing_mode_revision",
|
||||
"generation_source",
|
||||
)
|
||||
if key in mode_profile_draft
|
||||
}
|
||||
@@ -1507,6 +1724,7 @@ class CodexAssistantService:
|
||||
)
|
||||
for item in unresolved_items
|
||||
if isinstance(item, str)
|
||||
and INTERNAL_SCENE_BUILDER_MARKER not in item
|
||||
],
|
||||
}
|
||||
peripheral = result.get("peripheral")
|
||||
|
||||
Reference in New Issue
Block a user