"""Workflow model resource loading and runtime service switching.""" from models import AssistantConfig from services.pipecat.service_factory import ( config_with_resource, create_llm, create_stt, create_tts, ) from pipecat.frames.frames import ( ManuallySwitchServiceFrame, OutputTransportMessageUrgentFrame, ) from pipecat.pipeline.llm_switcher import LLMSwitcher from pipecat.pipeline.service_switcher import ServiceSwitcher from pipecat.processors.frame_processor import FrameProcessor def build_workflow_voice_switcher( cfg: AssistantConfig, capability: str, base_service: FrameProcessor ): """Build one switcher and an ID lookup for every referenced voice resource.""" create = create_stt if capability == "ASR" else create_tts settings = cfg.graph.get("settings") or {} default_key = ( "defaultAsrResourceId" if capability == "ASR" else "defaultTtsResourceId" ) default_id = str(settings.get(default_key) or "") services_by_id = {} for resource_id, resource in cfg.workflow_model_resources.items(): if resource.capability != capability: continue services_by_id[resource_id] = ( base_service if resource_id == default_id else create(config_with_resource(cfg, resource)) ) primary = services_by_id.get(default_id, base_service) services = [primary] services.extend( service for service in services_by_id.values() if service is not primary ) if base_service is not primary: services.append(base_service) return ServiceSwitcher(services=services), services_by_id, primary def build_workflow_llm_switcher(cfg: AssistantConfig, base_service): """Build an LLM switcher for the global model and Agent overrides.""" settings = cfg.graph.get("settings") or {} default_id = str(settings.get("defaultLlmResourceId") or "") services_by_id = {} for resource_id, resource in cfg.workflow_model_resources.items(): if resource.capability != "LLM": continue services_by_id[resource_id] = ( base_service if resource_id == default_id else create_llm(config_with_resource(cfg, resource)) ) primary = services_by_id.get(default_id, base_service) services = [primary] services.extend( service for service in services_by_id.values() if service is not primary ) if base_service is not primary: services.append(base_service) return LLMSwitcher(llms=services), services_by_id, primary class WorkflowServiceController: """Switch one Workflow stage's model resources without leaking state.""" def __init__( self, *, worker, llm_services: dict[str, FrameProcessor], voice_services: dict[str, dict[str, FrameProcessor]], current_services: dict[str, FrameProcessor], ) -> None: self._worker = worker self._services = { "llm": llm_services, "asr": voice_services["asr"], "tts": voice_services["tts"], } self._current = dict(current_services) self._defaults = dict(current_services) async def switch( self, llm_resource_id: str | None, asr_resource_id: str | None, tts_resource_id: str | None, ) -> None: requested = ( ("llm", llm_resource_id), ("asr", asr_resource_id), ("tts", tts_resource_id), ) for kind, resource_id in requested: target = ( self._services[kind].get(resource_id) if resource_id else self._defaults[kind] ) if target is None: raise ValueError( f"Workflow {kind.upper()} 资源未加载:{resource_id}" ) if self._current[kind] is target: continue await self._worker.queue_frame( ManuallySwitchServiceFrame(service=target) ) self._current[kind] = target await self._worker.queue_frame( OutputTransportMessageUrgentFrame( message={ "type": "service-switched", "capability": kind.upper(), "resourceId": resource_id, } ) )