import logging from collections.abc import Callable from functools import wraps from typing import ParamSpec, TypeVar, Union from flask import request from flask_restx import Resource, fields from pydantic import BaseModel, Field from werkzeug.exceptions import NotFound from controllers.common.schema import register_schema_models from controllers.console import console_ns from controllers.console.wraps import ( account_initialization_required, edit_permission_required, setup_required, ) from extensions.ext_database import db from libs.helper import TimestampField from libs.login import current_account_with_tenant, login_required from models import App from models.snippet import CustomizedSnippet logger = logging.getLogger(__name__) P = ParamSpec("P") R = TypeVar("R") # Valid evaluation target types EVALUATE_TARGET_TYPES = {"app", "snippets"} class VersionQuery(BaseModel): """Query parameters for version endpoint.""" version: str register_schema_models( console_ns, VersionQuery, ) # Response field definitions file_info_fields = { "id": fields.String, "name": fields.String, } evaluation_log_fields = { "created_at": TimestampField, "created_by": fields.String, "test_file": fields.Nested( console_ns.model( "EvaluationTestFile", file_info_fields, ) ), "result_file": fields.Nested( console_ns.model( "EvaluationResultFile", file_info_fields, ), allow_null=True, ), "version": fields.String, } evaluation_log_list_model = console_ns.model( "EvaluationLogList", { "data": fields.List(fields.Nested(console_ns.model("EvaluationLog", evaluation_log_fields))), }, ) customized_matrix_fields = { "evaluation_workflow_id": fields.String, "input_fields": fields.Raw, "output_fields": fields.Raw, } condition_fields = { "name": fields.List(fields.String), "comparison_operator": fields.String, "value": fields.String, } judgement_conditions_fields = { "logical_operator": fields.String, "conditions": fields.List(fields.Nested(console_ns.model("EvaluationCondition", condition_fields))), } evaluation_detail_fields = { "evaluation_model": fields.String, "evaluation_model_provider": fields.String, "customized_matrix": fields.Nested( console_ns.model("EvaluationCustomizedMatrix", customized_matrix_fields), allow_null=True, ), "judgement_conditions": fields.Nested( console_ns.model("EvaluationJudgementConditions", judgement_conditions_fields), allow_null=True, ), } evaluation_detail_model = console_ns.model("EvaluationDetail", evaluation_detail_fields) def get_evaluation_target(view_func: Callable[P, R]): """ Decorator to resolve polymorphic evaluation target (app or snippet). Validates the target_type parameter and fetches the corresponding model (App or CustomizedSnippet) with tenant isolation. """ @wraps(view_func) def decorated_view(*args: P.args, **kwargs: P.kwargs): target_type = kwargs.get("evaluate_target_type") target_id = kwargs.get("evaluate_target_id") if target_type not in EVALUATE_TARGET_TYPES: raise NotFound(f"Invalid evaluation target type: {target_type}") _, current_tenant_id = current_account_with_tenant() target_id = str(target_id) # Remove path parameters del kwargs["evaluate_target_type"] del kwargs["evaluate_target_id"] target: Union[App, CustomizedSnippet] | None = None if target_type == "app": target = ( db.session.query(App).where(App.id == target_id, App.tenant_id == current_tenant_id).first() ) elif target_type == "snippets": target = ( db.session.query(CustomizedSnippet) .where(CustomizedSnippet.id == target_id, CustomizedSnippet.tenant_id == current_tenant_id) .first() ) if not target: raise NotFound(f"{str(target_type)} not found") kwargs["target"] = target kwargs["target_type"] = target_type return view_func(*args, **kwargs) return decorated_view @console_ns.route("///dataset-template/download") class EvaluationDatasetTemplateDownloadApi(Resource): @console_ns.doc("download_evaluation_dataset_template") @console_ns.response(200, "Template download URL generated successfully") @console_ns.response(404, "Target not found") @setup_required @login_required @account_initialization_required @get_evaluation_target @edit_permission_required def post(self, target: Union[App, CustomizedSnippet], target_type: str): """ Download evaluation dataset template. Generates a download URL for the evaluation dataset template based on the target type (app or snippets). """ # TODO: Implement actual template generation logic # This is a placeholder implementation return { "download_url": f"/api/evaluation/{target_type}/{target.id}/template.csv", } @console_ns.route("///evaluation") class EvaluationDetailApi(Resource): @console_ns.doc("get_evaluation_detail") @console_ns.response(200, "Evaluation details retrieved successfully", evaluation_detail_model) @console_ns.response(404, "Target not found") @setup_required @login_required @account_initialization_required @get_evaluation_target def get(self, target: Union[App, CustomizedSnippet], target_type: str): """ Get evaluation details for the target. Returns evaluation configuration including model settings, customized matrix, and judgement conditions. """ # TODO: Implement actual evaluation detail retrieval # This is a placeholder implementation return { "evaluation_model": None, "evaluation_model_provider": None, "customized_matrix": None, "judgement_conditions": None, } @console_ns.route("///evaluation/logs") class EvaluationLogsApi(Resource): @console_ns.doc("get_evaluation_logs") @console_ns.response(200, "Evaluation logs retrieved successfully", evaluation_log_list_model) @console_ns.response(404, "Target not found") @setup_required @login_required @account_initialization_required @get_evaluation_target def get(self, target: Union[App, CustomizedSnippet], target_type: str): """ Get offline evaluation logs for the target. Returns a list of evaluation runs with test files, result files, and version information. """ # TODO: Implement actual evaluation logs retrieval # This is a placeholder implementation return { "data": [], } @console_ns.route("///evaluation/files/") class EvaluationFileDownloadApi(Resource): @console_ns.doc("download_evaluation_file") @console_ns.response(200, "File download URL generated successfully") @console_ns.response(404, "Target or file not found") @setup_required @login_required @account_initialization_required @get_evaluation_target def get(self, target: Union[App, CustomizedSnippet], target_type: str, file_id: str): """ Download evaluation test file or result file. Returns file information and download URL for the specified file. """ file_id = str(file_id) # TODO: Implement actual file download logic # This is a placeholder implementation return { "created_at": None, "created_by": None, "test_file": None, "result_file": None, "version": None, } @console_ns.route("///evaluation/version") class EvaluationVersionApi(Resource): @console_ns.doc("get_evaluation_version_detail") @console_ns.expect(console_ns.models.get(VersionQuery.__name__)) @console_ns.response(200, "Version details retrieved successfully") @console_ns.response(404, "Target or version not found") @setup_required @login_required @account_initialization_required @get_evaluation_target def get(self, target: Union[App, CustomizedSnippet], target_type: str): """ Get evaluation target version details. Returns the workflow graph for the specified version. """ version = request.args.get("version") if not version: return {"message": "version parameter is required"}, 400 # TODO: Implement actual version detail retrieval # For now, return the current graph if available graph = {} if target_type == "snippets" and isinstance(target, CustomizedSnippet): graph = target.graph_dict return { "graph": graph, }