fix: decouple org preference and ai model preferences

This commit is contained in:
Abhishek Kumar 2026-06-09 15:40:34 +05:30
parent e26d902425
commit 01d898fc72
21 changed files with 460 additions and 238 deletions

View file

@ -14,10 +14,10 @@ from api.schemas.ai_model_configuration import (
DOGRAH_DEFAULT_LANGUAGE,
DOGRAH_DEFAULT_VOICE,
DOGRAH_SPEED_OPTIONS,
OrganizationAIModelConfigurationPreferences,
OrganizationAIModelConfigurationResponse,
OrganizationAIModelConfigurationV2,
)
from api.schemas.organization_preferences import OrganizationPreferences
from api.schemas.telephony_config import (
TelephonyConfigRequest,
TelephonyConfigurationCreateRequest,
@ -39,13 +39,11 @@ from api.services.configuration.ai_model_configuration import (
check_for_masked_keys_in_ai_model_configuration_v2,
compile_ai_model_configuration_v2,
convert_legacy_ai_model_configuration_to_v2,
get_organization_ai_model_configuration_preferences,
get_organization_ai_model_configuration_v2,
get_resolved_ai_model_configuration,
mask_ai_model_configuration_v2,
merge_ai_model_configuration_v2_secrets,
migrate_workflow_model_configurations_to_v2,
upsert_organization_ai_model_configuration_preferences,
upsert_organization_ai_model_configuration_v2,
)
from api.services.configuration.check_validity import UserConfigurationValidator
@ -57,6 +55,10 @@ from api.services.configuration.registry import (
ServiceProviders,
ServiceType,
)
from api.services.organization_preferences import (
get_organization_preferences,
upsert_organization_preferences,
)
from api.services.posthog_client import capture_event
from api.services.telephony import registry as telephony_registry
from api.services.telephony.factory import get_telephony_provider_by_id
@ -218,8 +220,6 @@ async def _model_configuration_v2_response(
return OrganizationAIModelConfigurationResponse(
configuration=mask_ai_model_configuration_v2(raw_configuration),
effective_configuration=mask_user_config(resolved.effective),
preferences=resolved.preferences
or OrganizationAIModelConfigurationPreferences(),
source=resolved.source,
)
@ -363,30 +363,47 @@ async def migrate_model_configuration_v2(
)
@router.get(
"/model-configurations/preferences",
response_model=OrganizationAIModelConfigurationPreferences,
)
async def get_model_configuration_preferences(
@router.get("/preferences", response_model=OrganizationPreferences)
async def get_preferences(
user: UserModel = Depends(get_user_with_selected_organization),
):
organization_id = user.selected_organization_id
return await get_organization_ai_model_configuration_preferences(organization_id)
return await get_organization_preferences(organization_id)
@router.put("/preferences", response_model=OrganizationPreferences)
async def save_preferences(
request: OrganizationPreferences,
user: UserModel = Depends(get_user_with_selected_organization),
):
organization_id = user.selected_organization_id
return await upsert_organization_preferences(
organization_id,
request,
)
@router.get(
"/model-configurations/preferences",
response_model=OrganizationPreferences,
include_in_schema=False,
)
async def get_model_configuration_preferences_legacy(
user: UserModel = Depends(get_user_with_selected_organization),
):
return await get_preferences(user=user)
@router.put(
"/model-configurations/preferences",
response_model=OrganizationAIModelConfigurationPreferences,
response_model=OrganizationPreferences,
include_in_schema=False,
)
async def save_model_configuration_preferences(
request: OrganizationAIModelConfigurationPreferences,
async def save_model_configuration_preferences_legacy(
request: OrganizationPreferences,
user: UserModel = Depends(get_user_with_selected_organization),
):
organization_id = user.selected_organization_id
return await upsert_organization_ai_model_configuration_preferences(
organization_id,
request,
)
return await save_preferences(request=request, user=user)
def preserve_masked_fields(provider: str, request_dict: dict, existing: dict):

View file

@ -53,7 +53,7 @@ class InitiateCallRequest(BaseModel):
workflow_run_id: int | None = None
phone_number: str | None = None
# Optional explicit telephony config to use for the test call. If omitted,
# falls back to the user's per-user default (when set), then the org default.
# falls back to the org default.
telephony_configuration_id: int | None = None
# Optional caller-ID phone number to dial out from. Must belong to the
# resolved telephony configuration; otherwise the provider picks one.
@ -82,12 +82,9 @@ async def initiate_call(
"""Initiate a call using the configured telephony provider from web browser. This is
supposed to be a test call method for the draft version of the agent."""
from api.services.configuration.ai_model_configuration import (
get_organization_ai_model_configuration_preferences,
)
from api.services.organization_preferences import get_organization_preferences
user_configuration = await db_client.get_user_configurations(user.id)
preferences = await get_organization_ai_model_configuration_preferences(
preferences = await get_organization_preferences(
user.selected_organization_id,
db=db_client,
)
@ -124,17 +121,12 @@ async def initiate_call(
detail="telephony_not_configured",
)
phone_number = (
request.phone_number
or preferences.test_phone_number
or user_configuration.test_phone_number
)
phone_number = request.phone_number or preferences.test_phone_number
if not phone_number:
raise HTTPException(
status_code=400,
detail="Phone number must be provided in request or set in user "
"configuration",
detail="Phone number must be provided in request or set in organization preferences",
)
workflow = await db_client.get_workflow(

View file

@ -11,9 +11,7 @@ from api.db.models import (
)
from api.services.auth.depends import get_user
from api.services.configuration.ai_model_configuration import (
get_organization_ai_model_configuration_preferences,
get_resolved_ai_model_configuration,
upsert_organization_ai_model_configuration_preferences,
)
from api.services.configuration.check_validity import (
APIKeyStatusResponse,
@ -24,6 +22,10 @@ from api.services.configuration.masking import check_for_masked_keys, mask_user_
from api.services.configuration.merge import merge_user_configurations
from api.services.configuration.registry import REGISTRY, ServiceType
from api.services.mps_service_key_client import mps_service_key_client
from api.services.organization_preferences import (
get_organization_preferences,
upsert_organization_preferences,
)
router = APIRouter(prefix="/user")
@ -101,13 +103,12 @@ async def get_user_configurations(
organization_id=user.selected_organization_id,
)
masked_config = mask_user_config(resolved_config.effective)
if resolved_config.preferences:
if resolved_config.preferences.test_phone_number is not None:
masked_config["test_phone_number"] = (
resolved_config.preferences.test_phone_number
)
if resolved_config.preferences.timezone is not None:
masked_config["timezone"] = resolved_config.preferences.timezone
if user.selected_organization_id:
preferences = await get_organization_preferences(user.selected_organization_id)
if preferences.test_phone_number is not None:
masked_config["test_phone_number"] = preferences.test_phone_number
if preferences.timezone is not None:
masked_config["timezone"] = preferences.timezone
# Add organization pricing info if available
if user.selected_organization_id:
@ -133,43 +134,49 @@ async def update_user_configurations(
# Remove organization_pricing from incoming dict as it's read-only
incoming_dict.pop("organization_pricing", None)
preferences_update = {
key: incoming_dict.pop(key)
for key in ("test_phone_number", "timezone")
if key in incoming_dict
}
# Merge via helper
try:
user_configurations = merge_user_configurations(existing_config, incoming_dict)
except ValidationError as e:
raise HTTPException(status_code=422, detail=str(e))
if incoming_dict:
# Merge via helper
try:
user_configurations = merge_user_configurations(
existing_config, incoming_dict
)
except ValidationError as e:
raise HTTPException(status_code=422, detail=str(e))
try:
check_for_masked_keys(user_configurations)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
try:
check_for_masked_keys(user_configurations)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
try:
validator = UserConfigurationValidator()
await validator.validate(
user_configurations,
organization_id=user.selected_organization_id,
created_by=user.provider_id,
try:
validator = UserConfigurationValidator()
await validator.validate(
user_configurations,
organization_id=user.selected_organization_id,
created_by=user.provider_id,
)
except ValueError as e:
raise HTTPException(status_code=422, detail=e.args[0])
user_configurations = await db_client.update_user_configuration(
user.id, user_configurations
)
except ValueError as e:
raise HTTPException(status_code=422, detail=e.args[0])
else:
user_configurations = existing_config
user_configurations = await db_client.update_user_configuration(
user.id, user_configurations
)
if user.selected_organization_id and (
request.test_phone_number is not None or request.timezone is not None
):
preferences = await get_organization_ai_model_configuration_preferences(
user.selected_organization_id
)
if request.test_phone_number is not None:
preferences.test_phone_number = request.test_phone_number
if request.timezone is not None:
preferences.timezone = request.timezone
await upsert_organization_ai_model_configuration_preferences(
if user.selected_organization_id and preferences_update:
preferences = await get_organization_preferences(user.selected_organization_id)
if "test_phone_number" in preferences_update:
preferences.test_phone_number = preferences_update["test_phone_number"]
if "timezone" in preferences_update:
preferences.timezone = preferences_update["timezone"]
await upsert_organization_preferences(
user.selected_organization_id,
preferences,
)
@ -177,9 +184,7 @@ async def update_user_configurations(
# Return masked version of updated config
masked_config = mask_user_config(user_configurations)
if user.selected_organization_id:
preferences = await get_organization_ai_model_configuration_preferences(
user.selected_organization_id
)
preferences = await get_organization_preferences(user.selected_organization_id)
if preferences.test_phone_number is not None:
masked_config["test_phone_number"] = preferences.test_phone_number
if preferences.timezone is not None: