from __future__ import annotations from typing import Any, Callable from app.services.scenes.gate_rules import GateRule, SceneRoute from app.services.scenes.scene_descriptor import SceneDescriptor class SceneRegistry: """场景注册表单例。 所有场景在 import 时注册,门控/路由/执行/字段过滤全部从这里查询。 gate_classify 节点是它的唯一消费者(单一决策点)。 """ def __init__(self) -> None: self._scenes: dict[str, SceneDescriptor] = {} self._flow_to_scene: dict[str, str] = {} # ---- 注册 ---- def register(self, descriptor: SceneDescriptor) -> SceneDescriptor: self._scenes[descriptor.scene_id] = descriptor if descriptor.flow_id: self._flow_to_scene[descriptor.flow_id] = descriptor.scene_id return descriptor # ---- 查询 ---- def get(self, scene_id: str) -> SceneDescriptor | None: return self._scenes.get(str(scene_id or "").strip()) def all_scenes(self) -> list[SceneDescriptor]: return list(self._scenes.values()) def scenes_sorted_by_priority(self) -> list[SceneDescriptor]: """按 priority 升序排列(数字小优先)。""" return sorted(self._scenes.values(), key=lambda s: s.priority) def all_scene_ids(self) -> list[str]: return [s.scene_id for s in self._scenes.values()] def all_assigned_agents(self) -> list[str]: return [s.assigned_agent for s in self._scenes.values() if s.assigned_agent] def all_flow_ids(self) -> list[str]: return [s.flow_id for s in self._scenes.values() if s.flow_id] def all_signal_keywords(self) -> set[str]: keywords: set[str] = set() for scene in self._scenes.values(): keywords.update(scene.signal_keywords) return keywords def all_side_effect_actions(self) -> set[str]: actions: set[str] = set() for scene in self._scenes.values(): actions.update(scene.side_effect_actions) return actions def all_noop_actions(self) -> set[str]: actions: set[str] = set() for scene in self._scenes.values(): actions.update(scene.noop_actions) return actions def resolve_scene_by_action(self, action_type: str) -> SceneDescriptor | None: normalized = str(action_type or "").strip() for scene in self._scenes.values(): if normalized in scene.side_effect_actions or normalized in scene.noop_actions: return scene return None def resolve_scene_by_flow(self, flow_id: str) -> SceneDescriptor | None: scene_id = self._flow_to_scene.get(str(flow_id or "").strip()) return self.get(scene_id) if scene_id else None def field_allowlist_for( self, scene_id: str, *, fallback: frozenset[str] | None = None, ) -> frozenset[str]: scene = self.get(scene_id) if scene and scene.ontology_fields: return frozenset(scene.ontology_fields) return fallback or frozenset() def resumable_scenes(self) -> list[SceneDescriptor]: """返回所有声明了 can_resume=True 的场景。""" return [s for s in self._scenes.values() if s.can_resume] def prompt_fragments(self) -> str: """拼接所有场景的 prompt_fragment,供 system prompt 注入。""" fragments = [s.prompt_fragment for s in self._scenes.values() if s.prompt_fragment] return "".join(fragments) def intent_summary(self) -> str: """拼接场景列表摘要,供 system prompt 引用。""" fragments = [f"{s.scene_id}({s.label})" for s in self._scenes.values()] return "、".join(fragments) if fragments else "(暂无已注册场景)" # 全局单例 REGISTRY = SceneRegistry() def register_scene(descriptor: SceneDescriptor) -> SceneDescriptor: """注册场景到全局单例。""" return REGISTRY.register(descriptor)