From da7cb4dc29460fb2e61d5c95cde0276bed691498 Mon Sep 17 00:00:00 2001 From: FFXN <31929997+FFXN@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:40:28 +0800 Subject: [PATCH] fix: evaluation (#35744) Co-authored-by: jyong <718720800@qq.com> Co-authored-by: Yansong Zhang <916125788@qq.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: hj24 Co-authored-by: hj24 Co-authored-by: Joel Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Co-authored-by: CodingOnStar Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- .../console/app/workflow_app_log.py | 5 +++++ api/controllers/service_api/app/workflow.py | 5 +++++ api/fields/workflow_app_log_fields.py | 5 +++++ api/services/workflow_app_service.py | 8 +++++--- .../console/app/test_workflow_app_log_api.py | 19 ++++++++++++++++++ .../service_api/app/test_workflow.py | 20 +++++++++++++++++++ 6 files changed, 59 insertions(+), 3 deletions(-) diff --git a/api/controllers/console/app/workflow_app_log.py b/api/controllers/console/app/workflow_app_log.py index c9bf48103b..1a2599604d 100644 --- a/api/controllers/console/app/workflow_app_log.py +++ b/api/controllers/console/app/workflow_app_log.py @@ -139,6 +139,11 @@ class WorkflowAppLogPartialResponse(ResponseModel): return int(value.timestamp()) return value + @field_validator("evaluation", mode="before") + @classmethod + def _normalize_evaluation(cls, value: Any) -> list[dict[str, Any]] | list[WorkflowAppLogEvaluationItemResponse]: + return value or [] + class WorkflowArchivedLogPartialResponse(ResponseModel): id: str diff --git a/api/controllers/service_api/app/workflow.py b/api/controllers/service_api/app/workflow.py index f489bb1f4a..24c729d185 100644 --- a/api/controllers/service_api/app/workflow.py +++ b/api/controllers/service_api/app/workflow.py @@ -174,6 +174,11 @@ class WorkflowAppLogPartialResponse(ResponseModel): def _normalize_timestamp(cls, value: datetime | int | None) -> int | None: return _to_timestamp(value) + @field_validator("evaluation", mode="before") + @classmethod + def _normalize_evaluation(cls, value: Any) -> list[dict[str, Any]] | list[WorkflowAppLogEvaluationItemResponse]: + return value or [] + class WorkflowAppLogPaginationResponse(ResponseModel): page: int diff --git a/api/fields/workflow_app_log_fields.py b/api/fields/workflow_app_log_fields.py index 689e3c30c6..a5eb12e468 100644 --- a/api/fields/workflow_app_log_fields.py +++ b/api/fields/workflow_app_log_fields.py @@ -136,6 +136,11 @@ class WorkflowAppLogPartialResponse(ResponseModel): def _normalize_timestamp(cls, value: datetime | int | None) -> int | None: return _to_timestamp(value) + @field_validator("evaluation", mode="before") + @classmethod + def _normalize_evaluation(cls, value: Any) -> list[dict[str, Any]] | list[WorkflowAppLogEvaluationItemResponse]: + return value or [] + class WorkflowArchivedLogPartialResponse(ResponseModel): id: str diff --git a/api/services/workflow_app_service.py b/api/services/workflow_app_service.py index d2d6c7a92c..995c301e3f 100644 --- a/api/services/workflow_app_service.py +++ b/api/services/workflow_app_service.py @@ -24,6 +24,8 @@ class LogView: - Exposes `details_` for marshalling to `details` in API response - Exposes `evaluation_` for marshalling evaluation metrics in API response + - Normalizes missing evaluation results to an empty list because the API + schema treats "no evaluation data" as an empty collection, not `null` - Proxies all other attributes to the underlying `WorkflowAppLog` """ @@ -35,14 +37,14 @@ class LogView: ): self.log = log self.details_ = details - self.evaluation_ = evaluation + self.evaluation_ = evaluation or [] @property def details(self) -> LogViewDetails | None: return self.details_ @property - def evaluation(self) -> list[dict] | None: + def evaluation(self) -> list[dict]: return self.evaluation_ def __getattr__(self, name): @@ -192,7 +194,7 @@ class WorkflowAppService: eval_map = self._batch_query_evaluation_metrics(session, workflow_run_ids) items = [ - LogView(log, details, evaluation=eval_map.get(log.workflow_run_id)) + LogView(log, details, evaluation=eval_map.get(log.workflow_run_id, [])) for log, details in logs_with_details ] return { diff --git a/api/tests/unit_tests/controllers/console/app/test_workflow_app_log_api.py b/api/tests/unit_tests/controllers/console/app/test_workflow_app_log_api.py index fa3830ada4..3dc94bae59 100644 --- a/api/tests/unit_tests/controllers/console/app/test_workflow_app_log_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_workflow_app_log_api.py @@ -65,6 +65,25 @@ def test_workflow_app_log_pagination_response_normalizes_nested_fields(): assert response["data"][0]["evaluation"][0]["nodeInfo"]["node_id"] == "node-1" +def test_workflow_app_log_pagination_response_normalizes_null_evaluation_to_empty_list(): + response = workflow_app_log_module.WorkflowAppLogPaginationResponse.model_validate( + { + "page": 1, + "limit": 20, + "total": 1, + "has_more": False, + "data": [ + { + "id": "log-1", + "evaluation": None, + } + ], + } + ).model_dump(mode="json") + + assert response["data"][0]["evaluation"] == [] + + def test_workflow_archived_log_pagination_response_normalizes_nested_fields(): created_at = datetime(2026, 1, 2, 3, 4, 5, tzinfo=UTC) response = workflow_app_log_module.WorkflowArchivedLogPaginationResponse.model_validate( diff --git a/api/tests/unit_tests/controllers/service_api/app/test_workflow.py b/api/tests/unit_tests/controllers/service_api/app/test_workflow.py index 74a3c75839..d883d2d2b9 100644 --- a/api/tests/unit_tests/controllers/service_api/app/test_workflow.py +++ b/api/tests/unit_tests/controllers/service_api/app/test_workflow.py @@ -29,6 +29,7 @@ from controllers.service_api.app.workflow import ( DifyAPIRepositoryFactory, GraphEngineManager, WorkflowAppLogApi, + WorkflowAppLogPaginationResponse, WorkflowLogQuery, WorkflowRunApi, WorkflowRunByIdApi, @@ -238,6 +239,25 @@ class TestWorkflowAppService: assert result.limit == 20 +def test_workflow_app_log_pagination_response_normalizes_null_evaluation_to_empty_list(): + response = WorkflowAppLogPaginationResponse.model_validate( + { + "page": 1, + "limit": 20, + "total": 1, + "has_more": False, + "data": [ + { + "id": "log-1", + "evaluation": None, + } + ], + } + ).model_dump(mode="json") + + assert response["data"][0]["evaluation"] == [] + + class TestWorkflowExecutionStatus: """Test WorkflowExecutionStatus enum."""