mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-28 08:49:42 +02:00
feat: stamp API key into model override at save time to survive global provider change (#362)
* fix: stamp API key into model override at save time to survive global provider change When a workflow overrides the TTS/LLM/STT provider to match the current global config, the override dict only stores model/voice fields, not the API key. If the global config later switches to a different provider, the override can no longer inherit the API key and calls fail. Fix: enrich_overrides_with_api_keys() copies the global provider's API key (and other secret fields) into the override dict at workflow-save time, making the override self-contained regardless of future global config changes. * feat: add test coverage and masking logic --------- Co-authored-by: Abhishek Kumar <abhishek@a6k.me>
This commit is contained in:
parent
8a58b0992d
commit
5b61ad645f
6 changed files with 451 additions and 39 deletions
|
|
@ -21,10 +21,15 @@ from api.sdk_expose import sdk_expose
|
|||
from api.services.auth.depends import get_user
|
||||
from api.services.configuration.check_validity import UserConfigurationValidator
|
||||
from api.services.configuration.masking import (
|
||||
mask_workflow_configurations,
|
||||
mask_workflow_definition,
|
||||
merge_workflow_api_keys,
|
||||
)
|
||||
from api.services.configuration.resolve import resolve_effective_config
|
||||
from api.services.configuration.merge import merge_workflow_configuration_secrets
|
||||
from api.services.configuration.resolve import (
|
||||
enrich_overrides_with_api_keys,
|
||||
resolve_effective_config,
|
||||
)
|
||||
from api.services.mps_service_key_client import mps_service_key_client
|
||||
from api.services.posthog_client import capture_event
|
||||
from api.services.reports import generate_workflow_report_csv
|
||||
|
|
@ -410,7 +415,9 @@ async def create_workflow(
|
|||
"current_definition_id": workflow.current_definition_id,
|
||||
"template_context_variables": workflow.template_context_variables,
|
||||
"call_disposition_codes": workflow.call_disposition_codes,
|
||||
"workflow_configurations": workflow.workflow_configurations,
|
||||
"workflow_configurations": mask_workflow_configurations(
|
||||
workflow.workflow_configurations
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -508,7 +515,9 @@ async def create_workflow_from_template(
|
|||
"current_definition_id": workflow.current_definition_id,
|
||||
"template_context_variables": workflow.template_context_variables,
|
||||
"call_disposition_codes": workflow.call_disposition_codes,
|
||||
"workflow_configurations": workflow.workflow_configurations,
|
||||
"workflow_configurations": mask_workflow_configurations(
|
||||
workflow.workflow_configurations
|
||||
),
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
|
|
@ -653,7 +662,7 @@ async def get_workflow(
|
|||
"current_definition_id": workflow.current_definition_id,
|
||||
"template_context_variables": template_vars,
|
||||
"call_disposition_codes": workflow.call_disposition_codes,
|
||||
"workflow_configurations": workflow_configs,
|
||||
"workflow_configurations": mask_workflow_configurations(workflow_configs),
|
||||
"version_number": active_def.version_number if active_def else None,
|
||||
"version_status": active_def.status if active_def else None,
|
||||
"workflow_uuid": workflow.workflow_uuid,
|
||||
|
|
@ -691,7 +700,9 @@ async def get_workflow_versions(
|
|||
created_at=v.created_at,
|
||||
published_at=v.published_at,
|
||||
workflow_json=mask_workflow_definition(v.workflow_json),
|
||||
workflow_configurations=v.workflow_configurations,
|
||||
workflow_configurations=mask_workflow_configurations(
|
||||
v.workflow_configurations
|
||||
),
|
||||
template_context_variables=v.template_context_variables,
|
||||
)
|
||||
for v in versions
|
||||
|
|
@ -776,7 +787,9 @@ async def create_workflow_draft(
|
|||
created_at=draft.created_at,
|
||||
published_at=draft.published_at,
|
||||
workflow_json=mask_workflow_definition(draft.workflow_json),
|
||||
workflow_configurations=draft.workflow_configurations,
|
||||
workflow_configurations=mask_workflow_configurations(
|
||||
draft.workflow_configurations
|
||||
),
|
||||
template_context_variables=draft.template_context_variables,
|
||||
)
|
||||
|
||||
|
|
@ -834,7 +847,9 @@ async def update_workflow_status(
|
|||
"current_definition_id": workflow.current_definition_id,
|
||||
"template_context_variables": workflow.template_context_variables,
|
||||
"call_disposition_codes": workflow.call_disposition_codes,
|
||||
"workflow_configurations": workflow.workflow_configurations,
|
||||
"workflow_configurations": mask_workflow_configurations(
|
||||
workflow.workflow_configurations
|
||||
),
|
||||
"total_runs": run_count,
|
||||
}
|
||||
except ValueError as e:
|
||||
|
|
@ -941,15 +956,34 @@ async def update_workflow(
|
|||
|
||||
# Validate model_overrides: resolve onto global config, then
|
||||
# run the same validator used by the user-configurations endpoint.
|
||||
if request.workflow_configurations and request.workflow_configurations.get(
|
||||
"model_overrides"
|
||||
):
|
||||
# Also stamp the current global API key into the override so the override
|
||||
# remains functional if the global config later switches to a different provider.
|
||||
workflow_configurations = request.workflow_configurations
|
||||
if workflow_configurations and workflow_configurations.get("model_overrides"):
|
||||
existing_workflow = await db_client.get_workflow(
|
||||
workflow_id, organization_id=user.selected_organization_id
|
||||
)
|
||||
if existing_workflow is None:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"Workflow with id {workflow_id} not found"
|
||||
)
|
||||
existing_draft = await db_client.get_draft_version(workflow_id)
|
||||
existing_configs = (
|
||||
existing_draft.workflow_configurations
|
||||
if existing_draft
|
||||
else existing_workflow.released_definition.workflow_configurations
|
||||
)
|
||||
workflow_configurations = merge_workflow_configuration_secrets(
|
||||
workflow_configurations,
|
||||
existing_configs,
|
||||
)
|
||||
user_config = await db_client.get_user_configurations(user.id)
|
||||
try:
|
||||
effective = resolve_effective_config(
|
||||
enriched_overrides = enrich_overrides_with_api_keys(
|
||||
workflow_configurations["model_overrides"],
|
||||
user_config,
|
||||
request.workflow_configurations["model_overrides"],
|
||||
)
|
||||
effective = resolve_effective_config(user_config, enriched_overrides)
|
||||
await UserConfigurationValidator().validate(
|
||||
effective,
|
||||
organization_id=user.selected_organization_id,
|
||||
|
|
@ -957,6 +991,10 @@ async def update_workflow(
|
|||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=422, detail=str(e))
|
||||
workflow_configurations = {
|
||||
**workflow_configurations,
|
||||
"model_overrides": enriched_overrides,
|
||||
}
|
||||
|
||||
# Reject upfront if any new trigger path collides with another
|
||||
# workflow's trigger — keeps the workflow record from
|
||||
|
|
@ -979,7 +1017,7 @@ async def update_workflow(
|
|||
name=request.name,
|
||||
workflow_definition=workflow_definition,
|
||||
template_context_variables=request.template_context_variables,
|
||||
workflow_configurations=request.workflow_configurations,
|
||||
workflow_configurations=workflow_configurations,
|
||||
organization_id=user.selected_organization_id,
|
||||
)
|
||||
|
||||
|
|
@ -1015,7 +1053,7 @@ async def update_workflow(
|
|||
"current_definition_id": workflow.current_definition_id,
|
||||
"template_context_variables": template_vars,
|
||||
"call_disposition_codes": workflow.call_disposition_codes,
|
||||
"workflow_configurations": workflow_configs,
|
||||
"workflow_configurations": mask_workflow_configurations(workflow_configs),
|
||||
"version_number": active_def.version_number if active_def else None,
|
||||
"version_status": active_def.status if active_def else None,
|
||||
}
|
||||
|
|
@ -1062,7 +1100,9 @@ async def duplicate_workflow_endpoint(
|
|||
"current_definition_id": workflow.current_definition_id,
|
||||
"template_context_variables": workflow.template_context_variables,
|
||||
"call_disposition_codes": workflow.call_disposition_codes,
|
||||
"workflow_configurations": workflow.workflow_configurations,
|
||||
"workflow_configurations": mask_workflow_configurations(
|
||||
workflow.workflow_configurations
|
||||
),
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
|
|
@ -1345,7 +1385,9 @@ async def duplicate_workflow_template(
|
|||
"current_definition_id": workflow.current_definition_id,
|
||||
"template_context_variables": workflow.template_context_variables,
|
||||
"call_disposition_codes": workflow.call_disposition_codes,
|
||||
"workflow_configurations": workflow.workflow_configurations,
|
||||
"workflow_configurations": mask_workflow_configurations(
|
||||
workflow.workflow_configurations
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue