mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
* feat: add tests and migrations * feat: workflow versioning among published and draft * feat: add a new settings page to simplify workflow detail page * fix: fix tsclient generation
83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
"""Resolve effective config by merging per-workflow model overrides onto global config."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from api.schemas.user_configuration import UserConfiguration
|
|
from api.services.configuration.registry import (
|
|
REGISTRY,
|
|
ServiceType,
|
|
)
|
|
|
|
# Maps override key → (UserConfiguration field, ServiceType for registry lookup)
|
|
_SECTION_MAP: dict[str, ServiceType] = {
|
|
"llm": ServiceType.LLM,
|
|
"tts": ServiceType.TTS,
|
|
"stt": ServiceType.STT,
|
|
"realtime": ServiceType.REALTIME,
|
|
}
|
|
|
|
|
|
def _build_section_from_override(service_type: ServiceType, override: dict):
|
|
"""Construct a typed config object from a raw override dict using the registry."""
|
|
provider = override.get("provider")
|
|
if not provider:
|
|
return None
|
|
registry = REGISTRY.get(service_type, {})
|
|
config_cls = registry.get(provider)
|
|
if config_cls is None:
|
|
return None
|
|
return config_cls(**override)
|
|
|
|
|
|
def resolve_effective_config(
|
|
user_config: UserConfiguration,
|
|
model_overrides: dict | None,
|
|
) -> UserConfiguration:
|
|
"""Deep-merge workflow model_overrides onto global user config.
|
|
|
|
- If model_overrides is None or empty, returns a copy of user_config unchanged.
|
|
- For each section (llm, tts, stt, realtime), if the override contains that key:
|
|
- If the global section is None, construct a new config from the override.
|
|
- If the provider changes, construct a new config from the override.
|
|
- Otherwise, merge override fields onto the existing config (model_copy).
|
|
- is_realtime is a simple boolean override.
|
|
- Sections not in the override are inherited from global unchanged.
|
|
- The original user_config is never mutated.
|
|
"""
|
|
if not model_overrides:
|
|
return user_config.model_copy(deep=True)
|
|
|
|
effective = user_config.model_copy(deep=True)
|
|
|
|
# Handle is_realtime boolean
|
|
if "is_realtime" in model_overrides:
|
|
effective.is_realtime = model_overrides["is_realtime"]
|
|
|
|
# Handle service sections
|
|
for section_key, service_type in _SECTION_MAP.items():
|
|
if section_key not in model_overrides:
|
|
continue
|
|
|
|
override = model_overrides[section_key]
|
|
base = getattr(effective, section_key)
|
|
|
|
if base is None:
|
|
# No global config for this section — build from override
|
|
setattr(
|
|
effective,
|
|
section_key,
|
|
_build_section_from_override(service_type, override),
|
|
)
|
|
elif "provider" in override and override["provider"] != base.provider:
|
|
# Provider changed — must construct new typed object
|
|
setattr(
|
|
effective,
|
|
section_key,
|
|
_build_section_from_override(service_type, override),
|
|
)
|
|
else:
|
|
# Same provider — merge fields onto existing config
|
|
merged = base.model_copy(update=override)
|
|
setattr(effective, section_key, merged)
|
|
|
|
return effective
|