From 7052257c8db9ba946aa1544a076967261aedbd28 Mon Sep 17 00:00:00 2001 From: Yansong Zhang <916125788@qq.com> Date: Thu, 9 Apr 2026 11:28:16 +0800 Subject: [PATCH] fix(api): use lazy workflow persistence for transparent upgrade of old apps VirtualWorkflowSynthesizer.ensure_workflow() creates a real draft workflow on first call for a legacy app, persisting it to the database. On subsequent calls, returns the existing draft. This is needed because AdvancedChatAppGenerator's worker thread looks up workflows from the database by ID. Instead of hacking the generator to skip DB lookups, we treat this as a lazy one-time upgrade: the old app gets a real workflow that can also be edited in the workflow editor. Verified: old chat app created on main branch ("What is 2+2?" -> "Four") and old agent-chat app ("Say hello" -> "Hello!") both successfully execute through the Agent V2 engine with AGENT_V2_TRANSPARENT_UPGRADE=true. Made-with: Cursor --- api/services/app_generate_service.py | 10 +++----- api/services/workflow/virtual_workflow.py | 30 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/api/services/app_generate_service.py b/api/services/app_generate_service.py index 17a112136d..06fc51bddc 100644 --- a/api/services/app_generate_service.py +++ b/api/services/app_generate_service.py @@ -133,17 +133,13 @@ class AppGenerateService: from services.workflow.virtual_workflow import VirtualWorkflowSynthesizer try: - virtual_workflow = VirtualWorkflowSynthesizer.synthesize(app_model) + workflow = VirtualWorkflowSynthesizer.ensure_workflow(app_model) logger.info( - "[AGENT_V2_UPGRADE] Transparent upgrade for app %s (mode=%s)", + "[AGENT_V2_UPGRADE] Transparent upgrade for app %s (mode=%s), wf=%s", app_model.id, effective_mode, + workflow.id, ) - workflow_id_arg = args.get("workflow_id") - if not workflow_id_arg: - workflow = virtual_workflow - else: - workflow = cls._get_workflow(app_model, invoke_from, workflow_id_arg) if streaming: with rate_limit_context(rate_limit, request_id): diff --git a/api/services/workflow/virtual_workflow.py b/api/services/workflow/virtual_workflow.py index 6167f01349..fdcffaa506 100644 --- a/api/services/workflow/virtual_workflow.py +++ b/api/services/workflow/virtual_workflow.py @@ -73,6 +73,36 @@ class VirtualWorkflowSynthesizer: return workflow + @staticmethod + def ensure_workflow(app: App) -> Any: + """Ensure the old app has a workflow, creating one if needed. + + On first call for a legacy app, synthesizes a workflow from its + AppModelConfig and persists it as a draft. On subsequent calls, + returns the existing draft. This is a one-time lazy upgrade: + the app gets a real workflow that can be edited in the workflow editor. + + The app's workflow_id is NOT updated (preserving its legacy state), + but the workflow is findable via app_id + version="draft". + """ + from models.workflow import Workflow + + from extensions.ext_database import db + + existing = db.session.query(Workflow).filter_by( + app_id=app.id, version="draft" + ).first() + if existing: + return existing + + workflow = VirtualWorkflowSynthesizer.synthesize(app) + workflow.version = "draft" + + db.session.add(workflow) + db.session.commit() + logger.info("Created draft workflow %s for legacy app %s", workflow.id, app.id) + return workflow + def _extract_model_config(config: AppModelConfig) -> dict[str, Any]: if config.model: