diff --git a/api/handler/evaluation.go b/api/handler/evaluation.go index b5c74474..61bb188f 100644 --- a/api/handler/evaluation.go +++ b/api/handler/evaluation.go @@ -114,6 +114,29 @@ func (h *EvaluationHandler) GetEvaluation(ctx *gin.Context) { } +func (h *EvaluationHandler) GetEvaluationByTaskID(ctx *gin.Context) { + currentUser := httpbase.GetCurrentUser(ctx) + taskID := ctx.Param("task_id") + if taskID == "" { + httpbase.BadRequest(ctx, "task_id is required") + return + } + var req = &types.EvaluationGetReq{} + req.TaskID = taskID + req.Username = currentUser + evaluation, err := h.evaluation.GetEvaluation(ctx.Request.Context(), *req) + if err != nil { + slog.ErrorContext(ctx.Request.Context(), "Failed to get evaluation job by task id", slog.Any("error", err)) + if errors.Is(err, errorx.ErrForbidden) { + httpbase.ForbiddenError(ctx, err) + return + } + httpbase.ServerError(ctx, err) + return + } + httpbase.OK(ctx, evaluation) +} + // deleteEvaluation godoc // @Security ApiKey // @Summary delete model evaluation diff --git a/api/handler/evaluation_test.go b/api/handler/evaluation_test.go index 20c6d27f..aa664ddf 100644 --- a/api/handler/evaluation_test.go +++ b/api/handler/evaluation_test.go @@ -121,6 +121,21 @@ func TestEvaluationHandler_GetClawEvaluation(t *testing.T) { tester.ResponseEq(t, 200, tester.OKText, &types.EvaluationRes{ID: 1, TaskType: types.TaskTypeClawEval}) } +func TestEvaluationHandler_GetClawEvaluationByTaskID(t *testing.T) { + tester := NewEvaluationTester(t).WithHandleFunc(func(h *EvaluationHandler) gin.HandlerFunc { + return h.GetEvaluationByTaskID + }) + tester.WithUser() + + tester.mocks.evaluation.EXPECT().GetEvaluation(tester.Ctx(), types.EvaluationGetReq{ + Username: "u", + TaskID: "task-123", + }).Return(&types.EvaluationRes{ID: 123, TaskId: "task-123", TaskType: types.TaskTypeClawEval}, nil) + tester.WithParam("task_id", "task-123").Execute() + + tester.ResponseEq(t, 200, tester.OKText, &types.EvaluationRes{ID: 123, TaskId: "task-123", TaskType: types.TaskTypeClawEval}) +} + func TestEvaluationHandler_GetForbidden(t *testing.T) { tester := NewEvaluationTester(t).WithHandleFunc(func(h *EvaluationHandler) gin.HandlerFunc { return h.GetEvaluation diff --git a/api/router/api.go b/api/router/api.go index 098c2cec..161748cf 100644 --- a/api/router/api.go +++ b/api/router/api.go @@ -612,6 +612,7 @@ func createEvaluationRoutes(apiGroup *gin.RouterGroup, middlewareCollection midd { evaluationsGroup.POST("", evaluationHandler.RunEvaluation) evaluationsGroup.DELETE("/:id", evaluationHandler.DeleteEvaluation) + evaluationsGroup.GET("/task/:task_id", evaluationHandler.GetEvaluationByTaskID) evaluationsGroup.GET("/:id", evaluationHandler.GetEvaluation) evaluationsGroup.GET("/:id/logs", evaluationHandler.GetLogs) } diff --git a/component/evaluation.go b/component/evaluation.go index fd6b8fdd..035d13ec 100644 --- a/component/evaluation.go +++ b/component/evaluation.go @@ -289,10 +289,25 @@ func (c *evaluationComponentImpl) DeleteEvaluation(ctx context.Context, req type // get evaluation result func (c *evaluationComponentImpl) GetEvaluation(ctx context.Context, req types.EvaluationGetReq) (*types.EvaluationRes, error) { - wf, err := c.workflowStore.FindByID(ctx, req.ID) + var ( + wf database.ArgoWorkflow + err error + ) + if req.TaskID != "" { + wfObj, findErr := c.workflowStore.FindByTaskID(ctx, req.TaskID) + if findErr != nil { + return nil, fmt.Errorf("fail to get evaluation result, %w", findErr) + } + wf = *wfObj + } else { + wf, err = c.workflowStore.FindByID(ctx, req.ID) + } if err != nil { return nil, fmt.Errorf("fail to get evaluation result, %w", err) } + if !isEvaluationResultTaskType(wf.TaskType) { + return nil, errorx.ErrForbiddenMsg("workflow is not an evaluation job") + } if wf.Username != req.Username { canRead, err := c.repoComponent.CheckCurrentUserPermission(ctx, req.Username, wf.Username, membership.RoleRead) if err != nil { @@ -361,6 +376,15 @@ func (c *evaluationComponentImpl) GetEvaluation(ctx context.Context, req types.E return res, nil } +func isEvaluationResultTaskType(taskType types.TaskType) bool { + switch taskType { + case types.TaskTypeEvaluation, types.TaskTypeClawEval: + return true + default: + return false + } +} + func (c *evaluationComponentImpl) OrgEvaluations(ctx context.Context, req *types.OrgEvaluationsReq) ([]types.ArgoWorkFlowRes, int, error) { if req.CurrentUser != "" { canRead, err := c.repoComponent.CheckCurrentUserPermission(ctx, req.CurrentUser, req.Namespace, membership.RoleRead) diff --git a/component/evaluation_claw_test.go b/component/evaluation_claw_test.go index 3e5d806c..9ffad073 100644 --- a/component/evaluation_claw_test.go +++ b/component/evaluation_claw_test.go @@ -146,11 +146,12 @@ func TestEvaluationComponent_CreateClawEvaluation_AutoBuiltinJudgeAPIKey(t *test r.Model == "glm-5.1" && r.JudgeApiKey == "gk-builtin-key" && r.JudgeBaseURL == "http://aigateway.test/v1" - })).Return(&types.ArgoWorkFlowRes{ID: 2}, nil) + })).Return(&types.ArgoWorkFlowRes{ID: 4, TaskId: "task-123"}, nil) resp, err := c.CreateEvaluation(ctx, req) require.NoError(t, err) - require.Equal(t, int64(2), resp.ID) + require.Equal(t, int64(4), resp.ID) + require.Equal(t, "task-123", resp.TaskId) } func TestEvaluationComponent_CreateClawEvaluation_AutoJudgeAPIKeyFallback(t *testing.T) { @@ -317,6 +318,42 @@ func TestEvaluationComponent_GetClawEvaluation(t *testing.T) { require.ErrorIs(t, err, errorx.ErrForbidden) } +func TestEvaluationComponent_GetClawEvaluationByTaskID(t *testing.T) { + ctx := context.Background() + c, _, _, mockWorkflow := newTestEvaluationComponentForClaw(t) + + mockWorkflow.EXPECT().FindByTaskID(ctx, "task-1").Return(&database.ArgoWorkflow{ + ID: 123, + RepoIds: []string{"glm-5.1"}, + TaskType: types.TaskTypeClawEval, + Username: "user1", + TaskName: "claw-job", + TaskId: "task-1", + Status: "Succeeded", + }, nil) + + res, err := c.GetEvaluation(ctx, types.EvaluationGetReq{TaskID: "task-1", Username: "user1"}) + require.NoError(t, err) + require.Equal(t, int64(123), res.ID) + require.Equal(t, "task-1", res.TaskId) + require.Equal(t, types.TaskTypeClawEval, res.TaskType) +} + +func TestEvaluationComponent_GetEvaluationByTaskIDRejectsNonEvaluation(t *testing.T) { + ctx := context.Background() + c, _, _, mockWorkflow := newTestEvaluationComponentForClaw(t) + + mockWorkflow.EXPECT().FindByTaskID(ctx, "finetune-task").Return(&database.ArgoWorkflow{ + ID: 123, + TaskType: types.TaskTypeFinetune, + Username: "user1", + TaskId: "finetune-task", + }, nil) + + _, err := c.GetEvaluation(ctx, types.EvaluationGetReq{TaskID: "finetune-task", Username: "user1"}) + require.ErrorIs(t, err, errorx.ErrForbidden) +} + func TestEvaluationComponent_GetClawEvaluationSummary(t *testing.T) { summaryServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json")