fix: move draft and template context handling out of create_workflow_run (#560)

* fix: move draft and template context handling out of create_workflow_run

create_workflow_run now only creates
the run with the definition_id and initial_context provided by the caller. Test call paths explicitly resolve draft definitions
and merge template context variables before creating the run, while production/runtime paths bind the published definition
without adding template defaults.

* fix: review comments

* chore: format and minor cleanups

---------

Co-authored-by: Abhishek Kumar <abhishek@a6k.me>
This commit is contained in:
Sabiha Khan 2026-07-21 13:24:51 +05:30 committed by GitHub
parent 94dce42209
commit 90c5831984
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 657 additions and 72 deletions

View file

@ -24,6 +24,7 @@ from api.services.call_concurrency import (
)
from api.services.quota_service import authorize_workflow_run_start
from api.services.telephony import registry as telephony_registry
from api.services.workflow.run_creation import prepare_workflow_run_inputs
router = APIRouter(prefix="/agent-stream")
@ -68,11 +69,11 @@ async def agent_stream_websocket(
numeric_suffix = int(str(uuid.uuid4()).replace("-", "")[:8], 16) % 100000000
workflow_run_name = f"WR-AGS-{numeric_suffix:08d}"
initial_context = {
**(workflow.template_context_variables or {}),
"provider": provider_name,
"direction": "inbound",
}
try:
run_inputs = await prepare_workflow_run_inputs(db_client, workflow)
workflow_run = await db_client.create_workflow_run(
workflow_run_name,
workflow.id,
@ -81,6 +82,7 @@ async def agent_stream_websocket(
call_type=CallType.INBOUND,
initial_context=initial_context,
organization_id=workflow.organization_id,
definition_id=run_inputs.definition_id,
)
await call_concurrency.bind_workflow_run(concurrency_slot, workflow_run.id)
except Exception:

View file

@ -23,6 +23,7 @@ from api.services.telephony.factory import (
get_default_telephony_provider,
get_telephony_provider_by_id,
)
from api.services.workflow.run_creation import prepare_workflow_run_inputs
from api.utils.common import get_backend_endpoints
router = APIRouter(prefix="/public/agent")
@ -260,14 +261,21 @@ async def _execute_resolved_target(
)
try:
run_inputs = await prepare_workflow_run_inputs(
db_client,
target.workflow,
initial_context=initial_context,
use_draft=use_draft,
include_template_context=use_draft,
)
workflow_run = await db_client.create_workflow_run(
name=workflow_run_name,
workflow_id=target.workflow.id,
mode=workflow_run_mode,
initial_context=initial_context,
initial_context=run_inputs.initial_context,
user_id=execution_user_id,
use_draft=use_draft,
organization_id=target.organization_id,
definition_id=run_inputs.definition_id,
)
await call_concurrency.bind_workflow_run(concurrency_slot, workflow_run.id)
except Exception:

View file

@ -27,6 +27,7 @@ from api.routes.turn_credentials import (
TurnCredentialsResponse,
generate_turn_credentials,
)
from api.services.workflow.run_creation import prepare_workflow_run_inputs
router = APIRouter(prefix="/public/embed")
@ -304,6 +305,12 @@ async def initialize_embed_session(
# Create workflow run
try:
workflow = await db_client.get_workflow(
embed_token.workflow_id, organization_id=embed_token.organization_id
)
if not workflow:
raise ValueError("Workflow not found")
run_inputs = await prepare_workflow_run_inputs(db_client, workflow)
workflow_run = await db_client.create_workflow_run(
name=f"Embed Run - {datetime.now(UTC).isoformat()}",
workflow_id=embed_token.workflow_id,
@ -314,6 +321,7 @@ async def initialize_embed_session(
**(init_request.context_variables or {}),
"provider": WorkflowRunMode.SMALLWEBRTC.value,
},
definition_id=run_inputs.definition_id,
)
except Exception as e:
logger.error(f"Failed to create workflow run: {e}")

View file

@ -42,6 +42,7 @@ from api.services.telephony.transfer_event_protocol import (
TransferEvent,
TransferEventType,
)
from api.services.workflow.run_creation import prepare_workflow_run_inputs
from api.utils.common import get_backend_endpoints
from api.utils.telephony_helper import (
generic_hangup_response,
@ -173,27 +174,29 @@ async def initiate_call(
try:
if not workflow_run_id:
# Merge template context variables (e.g. caller_number, called_number
# set in workflow settings for testing pre-call data fetch).
template_vars = workflow.template_context_variables or {}
numeric_suffix = int(str(uuid.uuid4()).replace("-", "")[:8], 16) % 100000000
workflow_run_name = f"WR-TEL-OUT-{numeric_suffix:08d}"
workflow_run = await db_client.create_workflow_run(
workflow_run_name,
workflow.id,
workflow_run_mode,
user_id=execution_user_id,
call_type=CallType.OUTBOUND,
run_inputs = await prepare_workflow_run_inputs(
db_client,
workflow,
initial_context={
**template_vars,
"phone_number": phone_number,
"called_number": phone_number,
"provider": provider.PROVIDER_NAME,
"telephony_configuration_id": telephony_configuration_id,
},
use_draft=True,
include_template_context=True,
)
workflow_run = await db_client.create_workflow_run(
workflow_run_name,
workflow.id,
workflow_run_mode,
user_id=execution_user_id,
call_type=CallType.OUTBOUND,
initial_context=run_inputs.initial_context,
organization_id=user.selected_organization_id,
definition_id=run_inputs.definition_id,
)
workflow_run_id = workflow_run.id
else:
@ -463,6 +466,12 @@ async def _create_inbound_workflow_run(
call_id = normalized_data.call_id
numeric_suffix = int(str(uuid.uuid4()).replace("-", "")[:8], 16) % 100000000
workflow_run_name = f"WR-TEL-IN-{numeric_suffix:08d}"
workflow = await db_client.get_workflow(
workflow_id, organization_id=organization_id
)
if not workflow:
raise HTTPException(status_code=404, detail="Workflow not found")
run_inputs = await prepare_workflow_run_inputs(db_client, workflow)
workflow_run = await db_client.create_workflow_run(
workflow_run_name,
@ -490,6 +499,7 @@ async def _create_inbound_workflow_run(
},
},
organization_id=organization_id,
definition_id=run_inputs.definition_id,
)
logger.info(

View file

@ -52,6 +52,7 @@ from api.services.workflow.configuration_policy import (
from api.services.workflow.dto import ReactFlowDTO, sanitize_workflow_definition
from api.services.workflow.duplicate import duplicate_workflow
from api.services.workflow.errors import ItemKind, WorkflowError
from api.services.workflow.run_creation import prepare_workflow_run_inputs
from api.services.workflow.run_usage_response import (
format_public_cost_info,
format_public_usage_info,
@ -1317,13 +1318,27 @@ async def create_workflow_run(
request: The create workflow run request
user: The user to create the workflow run for
"""
workflow = await db_client.get_workflow(
workflow_id, organization_id=user.selected_organization_id
)
if not workflow:
raise HTTPException(status_code=404, detail="Workflow not found")
run_inputs = await prepare_workflow_run_inputs(
db_client,
workflow,
use_draft=True,
include_template_context=True,
)
run = await db_client.create_workflow_run(
request.name,
workflow_id,
request.mode,
user.id,
use_draft=True,
organization_id=user.selected_organization_id,
definition_id=run_inputs.definition_id,
initial_context=run_inputs.initial_context,
)
return {
"id": run.id,

View file

@ -11,6 +11,7 @@ from api.db.models import UserModel, WorkflowRunTextSessionModel
from api.enums import WorkflowRunMode
from api.services.auth.depends import get_user_with_selected_organization
from api.services.quota_service import authorize_workflow_run_start
from api.services.workflow.run_creation import prepare_workflow_run_inputs
from api.services.workflow.text_chat_session_service import (
TextChatPendingTurnLostError,
TextChatSessionExecutionError,
@ -164,14 +165,26 @@ async def create_text_chat_session(
) -> WorkflowRunTextSessionResponse:
session_name = request.name or f"WR-TEXT-{uuid4().hex[:6].upper()}"
try:
workflow = await db_client.get_workflow(
workflow_id, organization_id=user.selected_organization_id
)
if not workflow:
raise ValueError(f"Workflow with ID {workflow_id} not found")
run_inputs = await prepare_workflow_run_inputs(
db_client,
workflow,
initial_context=request.initial_context,
use_draft=True,
include_template_context=True,
)
workflow_run = await db_client.create_workflow_run(
name=session_name,
workflow_id=workflow_id,
mode=WorkflowRunMode.TEXTCHAT.value,
user_id=user.id,
initial_context=request.initial_context,
use_draft=True,
initial_context=run_inputs.initial_context,
organization_id=user.selected_organization_id,
definition_id=run_inputs.definition_id,
)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))