mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 02:43:49 +08:00
feat: get available evaluation workflow.
This commit is contained in:
parent
3865483d95
commit
a20c6bd4bc
@ -158,6 +158,7 @@ class WorkflowListQuery(BaseModel):
|
|||||||
limit: int = Field(default=10, ge=1, le=100)
|
limit: int = Field(default=10, ge=1, le=100)
|
||||||
user_id: str | None = None
|
user_id: str | None = None
|
||||||
named_only: bool = False
|
named_only: bool = False
|
||||||
|
keyword: str | None = Field(default=None, max_length=255)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowUpdatePayload(BaseModel):
|
class WorkflowUpdatePayload(BaseModel):
|
||||||
|
|||||||
@ -7,14 +7,15 @@ from typing import TYPE_CHECKING, ParamSpec, TypeVar, Union
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
from flask import Response, request
|
from flask import Response, request
|
||||||
from flask_restx import Resource, fields
|
from flask_restx import Resource, fields, marshal
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from werkzeug.exceptions import BadRequest, NotFound
|
from werkzeug.exceptions import BadRequest, Forbidden, NotFound
|
||||||
|
|
||||||
from controllers.common.schema import register_schema_models
|
from controllers.common.schema import register_schema_models
|
||||||
from controllers.console import console_ns
|
from controllers.console import console_ns
|
||||||
|
from controllers.console.app.workflow import WorkflowListQuery
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
edit_permission_required,
|
edit_permission_required,
|
||||||
@ -23,6 +24,7 @@ from controllers.console.wraps import (
|
|||||||
from core.evaluation.entities.evaluation_entity import EvaluationCategory, EvaluationConfigData, EvaluationRunRequest
|
from core.evaluation.entities.evaluation_entity import EvaluationCategory, EvaluationConfigData, EvaluationRunRequest
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from extensions.ext_storage import storage
|
from extensions.ext_storage import storage
|
||||||
|
from fields.member_fields import simple_account_fields
|
||||||
from graphon.file import helpers as file_helpers
|
from graphon.file import helpers as file_helpers
|
||||||
from libs.helper import TimestampField
|
from libs.helper import TimestampField
|
||||||
from libs.login import current_account_with_tenant, login_required
|
from libs.login import current_account_with_tenant, login_required
|
||||||
@ -36,6 +38,7 @@ from services.errors.evaluation import (
|
|||||||
EvaluationNotFoundError,
|
EvaluationNotFoundError,
|
||||||
)
|
)
|
||||||
from services.evaluation_service import EvaluationService
|
from services.evaluation_service import EvaluationService
|
||||||
|
from services.workflow_service import WorkflowService
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from models.evaluation import EvaluationRun, EvaluationRunItem
|
from models.evaluation import EvaluationRun, EvaluationRunItem
|
||||||
@ -125,6 +128,33 @@ evaluation_detail_fields = {
|
|||||||
|
|
||||||
evaluation_detail_model = console_ns.model("EvaluationDetail", evaluation_detail_fields)
|
evaluation_detail_model = console_ns.model("EvaluationDetail", evaluation_detail_fields)
|
||||||
|
|
||||||
|
available_evaluation_workflow_list_fields = {
|
||||||
|
"id": fields.String,
|
||||||
|
"app_id": fields.String,
|
||||||
|
"app_name": fields.String,
|
||||||
|
"type": fields.String,
|
||||||
|
"version": fields.String,
|
||||||
|
"marked_name": fields.String,
|
||||||
|
"marked_comment": fields.String,
|
||||||
|
"hash": fields.String,
|
||||||
|
"created_by": fields.Nested(simple_account_fields),
|
||||||
|
"created_at": TimestampField,
|
||||||
|
"updated_by": fields.Nested(simple_account_fields, allow_null=True),
|
||||||
|
"updated_at": TimestampField,
|
||||||
|
}
|
||||||
|
|
||||||
|
available_evaluation_workflow_pagination_fields = {
|
||||||
|
"items": fields.List(fields.Nested(available_evaluation_workflow_list_fields)),
|
||||||
|
"page": fields.Integer,
|
||||||
|
"limit": fields.Integer,
|
||||||
|
"has_more": fields.Boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
available_evaluation_workflow_pagination_model = console_ns.model(
|
||||||
|
"AvailableEvaluationWorkflowPagination",
|
||||||
|
available_evaluation_workflow_pagination_fields,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_evaluation_target(view_func: Callable[P, R]):
|
def get_evaluation_target(view_func: Callable[P, R]):
|
||||||
"""
|
"""
|
||||||
@ -601,6 +631,81 @@ class EvaluationVersionApi(Resource):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@console_ns.route("/workspaces/current/available-evaluation-workflows")
|
||||||
|
class AvailableEvaluationWorkflowsApi(Resource):
|
||||||
|
@console_ns.expect(console_ns.models[WorkflowListQuery.__name__])
|
||||||
|
@console_ns.doc("list_available_evaluation_workflows")
|
||||||
|
@console_ns.doc(description="List published evaluation workflows in the current workspace (all apps)")
|
||||||
|
@console_ns.response(
|
||||||
|
200,
|
||||||
|
"Available evaluation workflows retrieved",
|
||||||
|
available_evaluation_workflow_pagination_model,
|
||||||
|
)
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
@edit_permission_required
|
||||||
|
def get(self):
|
||||||
|
"""List published evaluation-type workflows for the current tenant (cross-app)."""
|
||||||
|
current_user, current_tenant_id = current_account_with_tenant()
|
||||||
|
|
||||||
|
args = WorkflowListQuery.model_validate(request.args.to_dict(flat=True)) # type: ignore
|
||||||
|
page = args.page
|
||||||
|
limit = args.limit
|
||||||
|
user_id = args.user_id
|
||||||
|
named_only = args.named_only
|
||||||
|
keyword = args.keyword
|
||||||
|
|
||||||
|
if user_id and user_id != current_user.id:
|
||||||
|
raise Forbidden()
|
||||||
|
|
||||||
|
workflow_service = WorkflowService()
|
||||||
|
with Session(db.engine) as session:
|
||||||
|
workflows, has_more = workflow_service.list_published_evaluation_workflows(
|
||||||
|
session=session,
|
||||||
|
tenant_id=current_tenant_id,
|
||||||
|
page=page,
|
||||||
|
limit=limit,
|
||||||
|
user_id=user_id,
|
||||||
|
named_only=named_only,
|
||||||
|
keyword=keyword,
|
||||||
|
)
|
||||||
|
|
||||||
|
app_ids = {w.app_id for w in workflows}
|
||||||
|
if app_ids:
|
||||||
|
apps = session.scalars(select(App).where(App.id.in_(app_ids))).all()
|
||||||
|
app_names = {a.id: a.name for a in apps}
|
||||||
|
else:
|
||||||
|
app_names = {}
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for wf in workflows:
|
||||||
|
items.append(
|
||||||
|
{
|
||||||
|
"id": wf.id,
|
||||||
|
"app_id": wf.app_id,
|
||||||
|
"app_name": app_names.get(wf.app_id, ""),
|
||||||
|
"type": wf.type.value,
|
||||||
|
"version": wf.version,
|
||||||
|
"marked_name": wf.marked_name,
|
||||||
|
"marked_comment": wf.marked_comment,
|
||||||
|
"hash": wf.unique_hash,
|
||||||
|
"created_by": wf.created_by_account,
|
||||||
|
"created_at": wf.created_at,
|
||||||
|
"updated_by": wf.updated_by_account,
|
||||||
|
"updated_at": wf.updated_at,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
marshal(
|
||||||
|
{"items": items, "page": page, "limit": limit, "has_more": has_more},
|
||||||
|
available_evaluation_workflow_pagination_fields,
|
||||||
|
),
|
||||||
|
200,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---- Serialization Helpers ----
|
# ---- Serialization Helpers ----
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import uuid
|
|||||||
from collections.abc import Callable, Generator, Mapping, Sequence
|
from collections.abc import Callable, Generator, Mapping, Sequence
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from sqlalchemy import exists, select
|
from sqlalchemy import and_, exists, or_, select
|
||||||
from sqlalchemy.orm import Session, sessionmaker
|
from sqlalchemy.orm import Session, sessionmaker
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
@ -58,6 +58,7 @@ from graphon.variables import VariableBase
|
|||||||
from graphon.variables.input_entities import VariableEntityType
|
from graphon.variables.input_entities import VariableEntityType
|
||||||
from graphon.variables.variables import Variable
|
from graphon.variables.variables import Variable
|
||||||
from libs.datetime_utils import naive_utc_now
|
from libs.datetime_utils import naive_utc_now
|
||||||
|
from libs.helper import escape_like_pattern
|
||||||
from models import Account
|
from models import Account
|
||||||
from models.human_input import HumanInputFormRecipient, RecipientType
|
from models.human_input import HumanInputFormRecipient, RecipientType
|
||||||
from models.model import App, AppMode
|
from models.model import App, AppMode
|
||||||
@ -240,6 +241,59 @@ class WorkflowService:
|
|||||||
|
|
||||||
return workflows, has_more
|
return workflows, has_more
|
||||||
|
|
||||||
|
def list_published_evaluation_workflows(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
session: Session,
|
||||||
|
tenant_id: str,
|
||||||
|
page: int,
|
||||||
|
limit: int,
|
||||||
|
user_id: str | None,
|
||||||
|
named_only: bool = False,
|
||||||
|
keyword: str | None = None,
|
||||||
|
) -> tuple[Sequence[Workflow], bool]:
|
||||||
|
"""
|
||||||
|
List published evaluation-type workflows for a tenant (cross-app), excluding draft rows.
|
||||||
|
|
||||||
|
When ``keyword`` is non-empty, match workflows whose marked name or parent app name contains
|
||||||
|
the substring (case-insensitive, LIKE wildcards escaped).
|
||||||
|
"""
|
||||||
|
stmt = select(Workflow).where(
|
||||||
|
Workflow.tenant_id == tenant_id,
|
||||||
|
Workflow.type == WorkflowType.EVALUATION,
|
||||||
|
Workflow.version != Workflow.VERSION_DRAFT,
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_id:
|
||||||
|
stmt = stmt.where(Workflow.created_by == user_id)
|
||||||
|
|
||||||
|
if named_only:
|
||||||
|
stmt = stmt.where(Workflow.marked_name != "")
|
||||||
|
|
||||||
|
keyword_stripped = keyword.strip() if keyword else ""
|
||||||
|
if keyword_stripped:
|
||||||
|
escaped = escape_like_pattern(keyword_stripped)
|
||||||
|
pattern = f"%{escaped}%"
|
||||||
|
stmt = stmt.join(
|
||||||
|
App,
|
||||||
|
and_(Workflow.app_id == App.id, App.tenant_id == tenant_id),
|
||||||
|
).where(
|
||||||
|
or_(
|
||||||
|
Workflow.marked_name.ilike(pattern, escape="\\"),
|
||||||
|
App.name.ilike(pattern, escape="\\"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
stmt = stmt.order_by(Workflow.created_at.desc()).limit(limit + 1).offset((page - 1) * limit)
|
||||||
|
|
||||||
|
workflows = session.scalars(stmt).all()
|
||||||
|
|
||||||
|
has_more = len(workflows) > limit
|
||||||
|
if has_more:
|
||||||
|
workflows = workflows[:-1]
|
||||||
|
|
||||||
|
return workflows, has_more
|
||||||
|
|
||||||
def sync_draft_workflow(
|
def sync_draft_workflow(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user