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
This commit is contained in:
Yansong Zhang
2026-04-09 11:28:16 +08:00
parent edfcab6455
commit 7052257c8d
2 changed files with 33 additions and 7 deletions

View File

@ -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):

View File

@ -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: