mirror of
https://github.com/langgenius/dify.git
synced 2026-11-11 05:41:09 +08:00
Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
198 lines
7.0 KiB
Python
198 lines
7.0 KiB
Python
"""Celery entrypoints for delivering persisted human input forms.
|
|
|
|
The generic form-delivery task must stay independent from email availability so
|
|
non-email providers can run even when mail is not configured. The legacy email
|
|
task keeps its historical mail and feature gates for callers that still need
|
|
email-only semantics. The generic task only checks email availability after
|
|
loading persisted delivery contexts and only when an email context is present.
|
|
"""
|
|
|
|
import logging
|
|
import time
|
|
from collections.abc import Sequence
|
|
|
|
import click
|
|
from celery import shared_task
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from core.app.layers.pause_state_persist_layer import WorkflowResumptionContext
|
|
from core.workflow.human_input_adapter import DeliveryMethodType
|
|
from extensions.ext_database import db
|
|
from extensions.ext_mail import mail
|
|
from graphon.runtime import RuntimeState, VariablePool
|
|
from models.human_input import HumanInputForm
|
|
from repositories.factory import DifyAPIRepositoryFactory
|
|
from services.feature_service import FeatureService
|
|
from services.human_input_form_delivery_provider import (
|
|
HumanInputFormDeliveryContext,
|
|
HumanInputFormDeliveryDispatcher,
|
|
HumanInputFormDeliveryProviderRegistry,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
FORM_DELIVERY_METHOD_TYPES = (DeliveryMethodType.EMAIL, DeliveryMethodType.IM)
|
|
|
|
|
|
def _has_email_context(contexts: Sequence[HumanInputFormDeliveryContext]) -> bool:
|
|
return any(context.delivery_method_type == DeliveryMethodType.EMAIL for context in contexts)
|
|
|
|
|
|
def _load_variable_pool(workflow_run_id: str | None) -> VariablePool | None:
|
|
if not workflow_run_id:
|
|
return None
|
|
|
|
session_factory = sessionmaker(bind=db.engine, expire_on_commit=False)
|
|
workflow_run_repo = DifyAPIRepositoryFactory.create_api_workflow_run_repository(session_factory)
|
|
pause_entity = workflow_run_repo.get_workflow_pause(workflow_run_id)
|
|
if pause_entity is None:
|
|
logger.info("No pause state found for workflow run %s", workflow_run_id)
|
|
return None
|
|
|
|
try:
|
|
resumption_context = WorkflowResumptionContext.loads(pause_entity.get_state().decode())
|
|
except Exception:
|
|
logger.exception("Failed to load resumption context for workflow run %s", workflow_run_id)
|
|
return None
|
|
|
|
graph_runtime_state = RuntimeState.from_snapshot(resumption_context.serialized_graph_runtime_state)
|
|
return graph_runtime_state.variable_pool
|
|
|
|
|
|
def _open_session(session_factory: sessionmaker | Session | None):
|
|
if session_factory is None:
|
|
return Session(db.engine)
|
|
if isinstance(session_factory, Session):
|
|
return session_factory
|
|
return session_factory()
|
|
|
|
|
|
def _filter_unavailable_email_contexts(
|
|
*,
|
|
contexts: Sequence[HumanInputFormDeliveryContext],
|
|
tenant_id: str,
|
|
form_id: str,
|
|
) -> tuple[HumanInputFormDeliveryContext, ...]:
|
|
contexts_tuple = tuple(contexts)
|
|
if not _has_email_context(contexts_tuple):
|
|
return contexts_tuple
|
|
|
|
email_delivery_enabled = False
|
|
if mail.is_inited():
|
|
try:
|
|
features = FeatureService.get_features(tenant_id, exclude_vector_space=True)
|
|
email_delivery_enabled = features.human_input_email_delivery_enabled
|
|
except Exception:
|
|
logger.exception(
|
|
"Failed to check human input email delivery availability; skipping email contexts, "
|
|
"tenant=%s, form_id=%s",
|
|
tenant_id,
|
|
form_id,
|
|
)
|
|
|
|
if email_delivery_enabled:
|
|
return contexts_tuple
|
|
|
|
return tuple(context for context in contexts_tuple if context.delivery_method_type != DeliveryMethodType.EMAIL)
|
|
|
|
|
|
def _is_email_delivery_enabled(*, tenant_id: str, form_id: str) -> bool:
|
|
features = FeatureService.get_features(tenant_id, exclude_vector_space=True)
|
|
email_delivery_enabled = features.human_input_email_delivery_enabled
|
|
if not email_delivery_enabled:
|
|
logger.info(
|
|
"Human input email delivery is not available for tenant=%s, form_id=%s",
|
|
tenant_id,
|
|
form_id,
|
|
)
|
|
return email_delivery_enabled
|
|
|
|
|
|
def _load_and_dispatch_form_delivery(
|
|
*,
|
|
form_id: str,
|
|
node_title: str | None,
|
|
session_factory,
|
|
delivery_method_types: Sequence[DeliveryMethodType] | None,
|
|
require_mail_inited: bool,
|
|
require_email_feature: bool,
|
|
filter_unavailable_email: bool,
|
|
log_label: str,
|
|
) -> None:
|
|
if require_mail_inited and not mail.is_inited():
|
|
return
|
|
|
|
logger.info(click.style(f"Start human input {log_label} delivery for form {form_id}", fg="green"))
|
|
start_at = time.perf_counter()
|
|
|
|
try:
|
|
with _open_session(session_factory) as session:
|
|
form = session.get(HumanInputForm, form_id)
|
|
if form is None:
|
|
logger.warning("Human input form not found, form_id=%s", form_id)
|
|
return
|
|
|
|
if require_email_feature and not _is_email_delivery_enabled(tenant_id=form.tenant_id, form_id=form_id):
|
|
return
|
|
|
|
variable_pool = _load_variable_pool(form.workflow_run_id)
|
|
registry = HumanInputFormDeliveryProviderRegistry.default(mail_client=mail)
|
|
dispatcher = HumanInputFormDeliveryDispatcher(registry=registry)
|
|
contexts = dispatcher.load_form_contexts(
|
|
session=session,
|
|
form=form,
|
|
variable_pool=variable_pool,
|
|
delivery_method_types=delivery_method_types,
|
|
)
|
|
|
|
if filter_unavailable_email:
|
|
contexts = _filter_unavailable_email_contexts(
|
|
contexts=contexts,
|
|
tenant_id=form.tenant_id,
|
|
form_id=form_id,
|
|
)
|
|
dispatcher.dispatch_contexts(contexts)
|
|
|
|
end_at = time.perf_counter()
|
|
logger.info(
|
|
click.style(
|
|
f"Human input {log_label} delivery succeeded for form {form_id}: latency: {end_at - start_at}",
|
|
fg="green",
|
|
)
|
|
)
|
|
except Exception:
|
|
logger.exception(
|
|
"Dispatch human input %s delivery failed, form_id=%s, node_title=%s",
|
|
log_label,
|
|
form_id,
|
|
node_title,
|
|
)
|
|
|
|
|
|
@shared_task(queue="mail")
|
|
def dispatch_human_input_form_delivery_task(form_id: str, node_title: str | None = None, session_factory=None):
|
|
_load_and_dispatch_form_delivery(
|
|
form_id=form_id,
|
|
node_title=node_title,
|
|
session_factory=session_factory,
|
|
delivery_method_types=FORM_DELIVERY_METHOD_TYPES,
|
|
require_mail_inited=False,
|
|
require_email_feature=False,
|
|
filter_unavailable_email=True,
|
|
log_label="form",
|
|
)
|
|
|
|
|
|
@shared_task(queue="mail")
|
|
def dispatch_human_input_email_task(form_id: str, node_title: str | None = None, session_factory=None):
|
|
_load_and_dispatch_form_delivery(
|
|
form_id=form_id,
|
|
node_title=node_title,
|
|
session_factory=session_factory,
|
|
delivery_method_types=(DeliveryMethodType.EMAIL,),
|
|
require_mail_inited=True,
|
|
require_email_feature=True,
|
|
filter_unavailable_email=False,
|
|
log_label="email",
|
|
)
|