From 905fabcacf6e136dd1d7a06c0d71b3341f72b23a Mon Sep 17 00:00:00 2001 From: cj2026-bit <647646783@qq.com> Date: Thu, 12 Nov 2026 21:19:25 +0800 Subject: [PATCH 1/2] fix(evaluation): require a selected evaluator instead of Jiuwen fallback --- backend/contracts/errors/codes.py | 2 + backend/contracts/errors/messages.py | 1 + .../api/evaluation/agent_evaluation.py | 16 ++- .../services/evaluation/execution.py | 57 ++++---- .../management/services/evaluation/runs.py | 15 +-- test/backend/app/test_agent_evaluation_app.py | 19 +++ .../services/test_agent_evaluation_service.py | 127 +++++++++++------- 7 files changed, 146 insertions(+), 91 deletions(-) diff --git a/backend/contracts/errors/codes.py b/backend/contracts/errors/codes.py index c15efd3605..8cc2ddc478 100644 --- a/backend/contracts/errors/codes.py +++ b/backend/contracts/errors/codes.py @@ -271,6 +271,7 @@ class ErrorCode(Enum): AGENT_EVALUATION_AGENT_NOT_FOUND = "160206" AGENT_EVALUATION_JUDGE_MODEL_REQUIRED = "160207" AGENT_EVALUATION_ONLY_CREATOR_CAN_DELETE = "160208" + AGENT_EVALUATION_EVALUATOR_REQUIRED = "160209" # 03 - AI generation AGENT_EVALUATION_QUERY_GENERATION_FAILED = "160301" AGENT_EVALUATION_QUERY_GENERATION_FORMAT = "160302" @@ -403,6 +404,7 @@ class ErrorCode(Enum): ErrorCode.AGENT_EVALUATION_AGENT_NOT_FOUND: 404, ErrorCode.AGENT_EVALUATION_JUDGE_MODEL_REQUIRED: 400, ErrorCode.AGENT_EVALUATION_ONLY_CREATOR_CAN_DELETE: 403, + ErrorCode.AGENT_EVALUATION_EVALUATOR_REQUIRED: 400, ErrorCode.AGENT_EVALUATION_QUERY_GENERATION_FAILED: 500, ErrorCode.AGENT_EVALUATION_QUERY_GENERATION_FORMAT: 500, ErrorCode.AGENT_EVALUATION_QUERY_GENERATION_EMPTY: 400, diff --git a/backend/contracts/errors/messages.py b/backend/contracts/errors/messages.py index 8e12acbc07..0fee51700f 100644 --- a/backend/contracts/errors/messages.py +++ b/backend/contracts/errors/messages.py @@ -166,6 +166,7 @@ class ErrorMessage: ErrorCode.AGENT_EVALUATION_AGENT_NOT_FOUND: "Agent not found.", ErrorCode.AGENT_EVALUATION_JUDGE_MODEL_REQUIRED: "Judge model ID is required.", ErrorCode.AGENT_EVALUATION_ONLY_CREATOR_CAN_DELETE: "Only the creator or a tenant administrator can delete this evaluation run.", + ErrorCode.AGENT_EVALUATION_EVALUATOR_REQUIRED: "At least one published evaluator is required.", ErrorCode.AGENT_EVALUATION_QUERY_GENERATION_FAILED: "Failed to generate test queries.", ErrorCode.AGENT_EVALUATION_QUERY_GENERATION_FORMAT: "AI returned invalid format for test queries.", ErrorCode.AGENT_EVALUATION_QUERY_GENERATION_EMPTY: "AI generated no valid test queries.", diff --git a/backend/management/api/evaluation/agent_evaluation.py b/backend/management/api/evaluation/agent_evaluation.py index 91d7efef9c..2944806d32 100644 --- a/backend/management/api/evaluation/agent_evaluation.py +++ b/backend/management/api/evaluation/agent_evaluation.py @@ -6,11 +6,15 @@ from typing import Any from fastapi import APIRouter, Body, Header, Query, Request -from fastapi.responses import JSONResponse, StreamingResponse +from fastapi.responses import JSONResponse, Response, StreamingResponse from pydantic import BaseModel, Field, StrictInt, TypeAdapter, ValidationError from contracts.errors.codes import ErrorCode -from contracts.errors.exceptions import AppException, UnauthorizedError +from contracts.errors.exceptions import ( + AppException, + RuntimeUpstreamError, + UnauthorizedError, +) # AppException is caught by global middleware (maps ErrorCode to HTTP status) from management.db.evaluation.runs import update_annotation_schema_ids @@ -552,6 +556,14 @@ async def trial_run_api( except UnauthorizedError: raise AppException(ErrorCode.COMMON_UNAUTHORIZED, _AUTH_REQUIRED_MSG) + except RuntimeUpstreamError as exc: + # Preserve the runtime's structured error (code/message/status) instead + # of collapsing a validation failure into a generic 500. + return Response( + content=exc.content, + status_code=exc.status_code, + media_type="application/json", + ) except Exception as exc: logger.exception( "trial_run_api ERROR: tenant=%s user=%s agent_id=%s version=%s " diff --git a/backend/management/services/evaluation/execution.py b/backend/management/services/evaluation/execution.py index 5259dc5ccd..3faedc0be9 100644 --- a/backend/management/services/evaluation/execution.py +++ b/backend/management/services/evaluation/execution.py @@ -8,10 +8,6 @@ from contracts.errors.exceptions import AppException from contracts.agent import AgentRequest -try: - from adapters.jiuwen_sdk_adapter import JiuwenSDKAdapter -except ModuleNotFoundError: - JiuwenSDKAdapter = None import asyncio import json import uuid @@ -19,7 +15,6 @@ from datetime import datetime from statistics import mean -from adapters.exception import JiuwenSDKUnavailableError from contracts.constants.constants import MAX_TURNS_PER_SESSION from management.db.evaluation.runs import create_agent_evaluation_cases, get_agent_evaluation, list_agent_evaluation_cases, update_agent_evaluation_case_result, update_agent_evaluation_status from management.db.client import get_db_session @@ -246,7 +241,6 @@ def _execute_single_case( case: dict[str, Any], run: dict[str, Any], judge_model_id: int, - adapter: Any, evaluators: dict[int, dict[str, Any]], judge_system_prompt: str, context_window: int = 4096, @@ -304,7 +298,6 @@ def _execute_single_case( agent_version_no=agent_version_no, query=query, judge_model_id=judge_model_id, - adapter=adapter, evaluators=evaluators, judge_system_prompt=judge_system_prompt, language=run.get("language", "zh"), @@ -559,12 +552,14 @@ def execute_agent_evaluation_run( raise AppException(ErrorCode.AGENT_EVALUATION_JUDGE_MODEL_REQUIRED) judge_model_id = int(judge_model_id) - if JiuwenSDKAdapter is None: - raise JiuwenSDKUnavailableError("Jiuwen SDK adapter is unavailable.") - adapter = JiuwenSDKAdapter(model_id=judge_model_id, tenant_id=tenant_id) - # Preload evaluators and judge template (loaded once, reused for all cases) evaluators = _preload_evaluators_for_run(run, tenant_id) + if not evaluators: + raise AppException( + ErrorCode.AGENT_EVALUATION_EVALUATOR_REQUIRED, + "No usable evaluator is configured for this run. " + "Select at least one published evaluator and retry.", + ) judge_system_prompt = compose_auxiliary_prompt( run.get("language", "zh"), "evaluation_judge" ).system @@ -600,7 +595,6 @@ def execute_agent_evaluation_run( case=c, run=run, judge_model_id=judge_model_id, - adapter=adapter, evaluators=evaluators, judge_system_prompt=judge_system_prompt, context_window=context_window, @@ -672,7 +666,6 @@ async def _evaluate_query( agent_version_no: int, query: str, judge_model_id: int, - adapter: Any, evaluators: dict[int, dict[str, Any]], judge_system_prompt: str, runtime_events: list[dict] | None = None, @@ -683,6 +676,11 @@ async def _evaluate_query( conversation_id: int | None = None, ) -> tuple[str, list[dict] | None, dict, dict]: """Run agent + score with evaluators. Returns (answer, events, scores, reasons).""" + if not evaluators: + raise AppException( + ErrorCode.AGENT_EVALUATION_EVALUATOR_REQUIRED, + "At least one published evaluator is required to score an evaluation.", + ) answer_text, events = await _run_agent_to_final_answer( agent_id=agent_id, tenant_id=tenant_id, @@ -692,27 +690,18 @@ async def _evaluate_query( history=history, conversation_id=conversation_id, ) - if evaluators: - score, reason = _score_with_evaluators( - evaluators=evaluators, - judge_system_prompt=judge_system_prompt, - tenant_id=tenant_id, - query=query, - expected=expected, - actual=answer_text, - judge_model_id=judge_model_id, - runtime_events=runtime_events or events, - context_window=context_window, - conversation_history=history, - ) - else: - score, reason = adapter.evaluate_semantic_consistency( - question=query, - expected_answer="", - model_answer=answer_text, - ) - score = {"semantic_consistency": score} - reason = {"semantic_consistency": reason} + score, reason = _score_with_evaluators( + evaluators=evaluators, + judge_system_prompt=judge_system_prompt, + tenant_id=tenant_id, + query=query, + expected=expected, + actual=answer_text, + judge_model_id=judge_model_id, + runtime_events=runtime_events or events, + context_window=context_window, + conversation_history=history, + ) return answer_text, events, score, reason diff --git a/backend/management/services/evaluation/runs.py b/backend/management/services/evaluation/runs.py index b4331021f1..fae0f3c8b5 100644 --- a/backend/management/services/evaluation/runs.py +++ b/backend/management/services/evaluation/runs.py @@ -10,10 +10,6 @@ from contracts.agent import AgentRequest from nexent.core.agents.prompt.auxiliary import AuxiliaryPrompt -try: - from adapters.jiuwen_sdk_adapter import JiuwenSDKAdapter -except ModuleNotFoundError: - JiuwenSDKAdapter = None import ast import asyncio import json @@ -26,7 +22,6 @@ from math import isfinite from statistics import mean -from adapters.exception import JiuwenSDKUnavailableError from contracts.constants.constants import MAX_CONCURRENT_RUNS, MAX_EVALUATORS_PER_RUN, MAX_TOTAL_RUNS, MAX_TURNS_PER_SESSION from contracts.constants.constants import MAX_FAILURE_EXAMPLES from management.db.evaluation.runs import count_active_runs, count_total_runs, create_agent_evaluation, create_agent_evaluation_cases, get_agent_evaluation, get_evaluation_case_scores, hard_delete_agent_evaluation, list_agent_evaluation_cases, list_agent_evaluations_by_agent, update_agent_evaluation_analysis_report, update_agent_evaluation_case_result, update_agent_evaluation_status @@ -286,12 +281,13 @@ async def trial_run_evaluator_impl( ev = get_evaluator(eid, tenant_id) if ev and ev.get("status") == "PUBLISHED": evaluators[eid] = ev + if not evaluators: + raise AppException( + ErrorCode.AGENT_EVALUATION_EVALUATOR_REQUIRED, + "At least one published evaluator is required for a trial run.", + ) judge_system_prompt = compose_auxiliary_prompt(language, "evaluation_judge").system - if JiuwenSDKAdapter is None: - raise JiuwenSDKUnavailableError("Jiuwen SDK adapter is unavailable") - adapter = JiuwenSDKAdapter(model_id=judge_model_id, tenant_id=tenant_id) - answer_text, _runtime_events, score, reason = await _evaluate_query( tenant_id=tenant_id, user_id=user_id, @@ -299,7 +295,6 @@ async def trial_run_evaluator_impl( agent_version_no=agent_version_no, query=query, judge_model_id=judge_model_id, - adapter=adapter, evaluators=evaluators, judge_system_prompt=judge_system_prompt, language=language, diff --git a/test/backend/app/test_agent_evaluation_app.py b/test/backend/app/test_agent_evaluation_app.py index abc14db490..ad8cad1b2c 100644 --- a/test/backend/app/test_agent_evaluation_app.py +++ b/test/backend/app/test_agent_evaluation_app.py @@ -658,6 +658,25 @@ def test_app_exception_propagates(self, client): ) assert response.status_code == 404 + def test_runtime_upstream_error_is_forwarded(self, client): + from contracts.errors.exceptions import RuntimeUpstreamError + + _mock_impls( + forward_agent_evaluation_trial_run=AsyncMock( + side_effect=RuntimeUpstreamError( + status_code=400, + content=b'{"code":"160209","message":"At least one published evaluator is required.","details":null}', + headers={"content-type": "application/json"}, + ) + ) + ) + response = client.post( + "/agent-evaluations/trial-run", + json={"agent_id": 1, "query": "q", "judge_model_id": 2}, + ) + assert response.status_code == 400 + assert response.json()["code"] == "160209" + # --------------------------------------------------------------------------- # safe-extract helpers diff --git a/test/backend/services/test_agent_evaluation_service.py b/test/backend/services/test_agent_evaluation_service.py index a963d1a8a2..df5f416128 100644 --- a/test/backend/services/test_agent_evaluation_service.py +++ b/test/backend/services/test_agent_evaluation_service.py @@ -315,6 +315,7 @@ class _ErrorCode: AGENT_EVALUATION_QUERY_GENERATION_EMPTY = "AGENT_EVALUATION_QUERY_GENERATION_EMPTY" AGENT_EVALUATION_AGENT_NOT_FOUND = "AGENT_EVALUATION_AGENT_NOT_FOUND" AGENT_EVALUATION_JUDGE_MODEL_REQUIRED = "AGENT_EVALUATION_JUDGE_MODEL_REQUIRED" + AGENT_EVALUATION_EVALUATOR_REQUIRED = "AGENT_EVALUATION_EVALUATOR_REQUIRED" EVALUATION_NOT_FOUND = "EVALUATION_NOT_FOUND" AGENT_NOT_FOUND = "AGENT_NOT_FOUND" MODEL_NOT_FOUND = "MODEL_NOT_FOUND" @@ -1287,23 +1288,35 @@ def test_create_agent_evaluation_run_uses_resolved_version_no(service_module): # --------------------------------------------------------------------------- # ``execute_agent_evaluation_run`` — exercises the synchronous background loop. -# We stub the agent invocation + adapter so the loop runs without real I/O. +# We stub the agent invocation and evaluator scorer so the loop runs without +# real I/O. # --------------------------------------------------------------------------- def _wire_executor_dependencies(service_module, cases): """Wire the collaborators touched by ``execute_agent_evaluation_run``.""" _wire_full_db_module(service_module) - bind_import_attribute(service_module, 'JiuwenSDKAdapter', MagicMock()) - adapter = MagicMock() - adapter.evaluate_semantic_consistency.return_value = (1, "ok") - service_module.JiuwenSDKAdapter.return_value = adapter + bind_import_attribute( + service_module, + "get_evaluator", + MagicMock( + return_value={ + "evaluator_id": 1, + "name": "eval-a", + "status": "PUBLISHED", + "pass_threshold": 0.5, + } + ), + ) + scorer = MagicMock(return_value=({"eval-a": 1.0}, {"eval-a": "ok"})) + bind_import_attribute(service_module, "_score_with_evaluators", scorer) service_module.get_agent_evaluation.return_value = { "agent_evaluation_id": 50, "agent_id": 11, "agent_version_no": 4, "judge_model_id": 99, + "evaluator_config": {"evaluator_ids": [1]}, } # Use side_effect so the pagination loop gets cases on first call, then # an empty list on the second call to break out of the while-True loop. @@ -1315,7 +1328,7 @@ async def _fake_run_to_final_answer(**_): return "agent-said-X", [] bind_import_attribute(service_module, '_run_agent_to_final_answer', _fake_run_to_final_answer) - return adapter + return scorer def _make_exec_case(case_id, query="q", expected="a"): @@ -1328,13 +1341,19 @@ def _make_exec_case(case_id, query="q", expected="a"): def test_execute_agent_evaluation_run_completes_with_overall_score(service_module): cases = [_make_exec_case(1), _make_exec_case(2)] - adapter = _wire_executor_dependencies(service_module, cases) + _wire_executor_dependencies(service_module, cases) service_module.execute_agent_evaluation_run("t1", "u1", 50, judge_model_id=99) - # Adapter is constructed with the judge model id we passed in. - service_module.JiuwenSDKAdapter.assert_called_once_with(model_id=99, tenant_id="t1") - adapter.evaluate_semantic_consistency.assert_called() + # The selected evaluator scores each case; no adapter fallback is involved. + service_module._score_with_evaluators.assert_called() + completed_cases = [ + c + for c in service_module.update_agent_evaluation_case_result.call_args_list + if c.kwargs.get("status") == "COMPLETED" + ] + assert len(completed_cases) == 2 + assert all(c.kwargs["score"] == {"eval-a": 1.0} for c in completed_cases) # Final transition should mark the run COMPLETED with the mean score. completed_calls = [ @@ -1374,14 +1393,7 @@ async def _flaky(**_): def test_execute_agent_evaluation_run_top_level_error_marks_run_failed(service_module): """An exception raised before the loop starts must transition the run FAILED.""" _wire_full_db_module(service_module) - bind_import_attribute(service_module, 'JiuwenSDKAdapter', MagicMock(side_effect=RuntimeError('adapter init boom'))) - service_module.get_agent_evaluation.return_value = { - "agent_evaluation_id": 50, - "agent_id": 11, - "agent_version_no": 4, - "judge_model_id": 99, - } - + service_module.get_agent_evaluation.side_effect = RuntimeError("db boom") service_module.execute_agent_evaluation_run("t1", "u1", 50, judge_model_id=99) failed = [ @@ -1390,7 +1402,7 @@ def test_execute_agent_evaluation_run_top_level_error_marks_run_failed(service_m if c.kwargs.get("status") == "FAILED" ] assert failed, "expected the outer except branch to mark the run FAILED" - assert "adapter init boom" in (failed[0].kwargs.get("error_message") or "") + assert "db boom" in (failed[0].kwargs.get("error_message") or "") def test_execute_agent_evaluation_run_falls_back_to_persisted_judge_model( @@ -1398,18 +1410,17 @@ def test_execute_agent_evaluation_run_falls_back_to_persisted_judge_model( ): """When the queued judge_model_id is lost the persisted one is reused.""" cases = [_make_exec_case(1)] - _wire_executor_dependencies(service_module, cases) + scorer = _wire_executor_dependencies(service_module, cases) service_module.execute_agent_evaluation_run("t1", "u1", 50, judge_model_id=None) - # The adapter should be constructed with the model id from the run record. - service_module.JiuwenSDKAdapter.assert_called_once_with(model_id=99, tenant_id="t1") + # The persisted judge model is the one handed to the evaluator scorer. + assert scorer.call_args_list[0].kwargs["judge_model_id"] == 99 def test_execute_agent_evaluation_run_missing_judge_model_raises(service_module): """Both the argument and persisted judge_model_id are absent — outer except fires.""" _wire_full_db_module(service_module) - bind_import_attribute(service_module, 'JiuwenSDKAdapter', MagicMock()) service_module.get_agent_evaluation.return_value = { "agent_evaluation_id": 50, "agent_id": 11, @@ -2271,7 +2282,7 @@ def test_with_evaluators(self, service_module, monkeypatch): answer, events, score, reason = asyncio.run( service_module._evaluate_query( tenant_id="t1", user_id="u1", agent_id=1, agent_version_no=1, - query="q", judge_model_id=99, adapter=None, + query="q", judge_model_id=99, evaluators={1: {"name": "n1"}}, judge_system_prompt="sys", ) ) @@ -2279,24 +2290,24 @@ def test_with_evaluators(self, service_module, monkeypatch): assert score == {"n1": 0.9} service_module._score_with_evaluators.assert_called_once() - def test_without_evaluators_uses_adapter(self, service_module, monkeypatch): + def test_without_evaluators_raises_before_agent(self, service_module, monkeypatch): import asyncio + from contracts.errors.exceptions import AppException + + run_agent = AsyncMock(return_value=("answer", [])) set_import_attribute(monkeypatch, - service_module, "_run_agent_to_final_answer", - AsyncMock(return_value=("answer", [])), + service_module, "_run_agent_to_final_answer", run_agent, ) - adapter = MagicMock() - adapter.evaluate_semantic_consistency.return_value = (1, "consistent") - answer, events, score, reason = asyncio.run( - service_module._evaluate_query( - tenant_id="t1", user_id="u1", agent_id=1, agent_version_no=1, - query="q", judge_model_id=99, adapter=adapter, - evaluators={}, judge_system_prompt="sys", + with pytest.raises(AppException): + asyncio.run( + service_module._evaluate_query( + tenant_id="t1", user_id="u1", agent_id=1, agent_version_no=1, + query="q", judge_model_id=99, + evaluators={}, judge_system_prompt="sys", + ) ) - ) - assert score == {"semantic_consistency": 1} - assert reason == {"semantic_consistency": "consistent"} + run_agent.assert_not_called() class TestCasePassStatusHelpers: @@ -2568,13 +2579,12 @@ def test_group_cases_by_session(self, service_module): # --------------------------------------------------------------------------- -# ``execute_agent_evaluation_run`` — SDK-unavailable + overlong-session edges. +# ``execute_agent_evaluation_run`` — missing-evaluator + overlong-session edges. # --------------------------------------------------------------------------- -def test_execute_run_sdk_unavailable_marks_failed(service_module): +def test_execute_run_without_configured_evaluators_marks_failed(service_module): _wire_full_db_module(service_module) - bind_import_attribute(service_module, 'JiuwenSDKAdapter', None) service_module.get_agent_evaluation.return_value = { "agent_evaluation_id": 50, "agent_id": 11, @@ -2584,6 +2594,24 @@ def test_execute_run_sdk_unavailable_marks_failed(service_module): service_module.execute_agent_evaluation_run("t1", "u1", 50, judge_model_id=99) last = service_module.update_agent_evaluation_status.call_args_list[-1] assert last.kwargs["status"] == service_module.EvalRunStatus.FAILED + assert "evaluator" in (last.kwargs.get("error_message") or "").lower() + + +def test_execute_run_with_unusable_evaluators_marks_failed(service_module): + """Configured IDs that no longer resolve to PUBLISHED rows fail the run.""" + _wire_full_db_module(service_module) + bind_import_attribute(service_module, 'get_evaluator', MagicMock(return_value=None)) + service_module.get_agent_evaluation.return_value = { + "agent_evaluation_id": 50, + "agent_id": 11, + "agent_version_no": 4, + "judge_model_id": 99, + "evaluator_config": {"evaluator_ids": [1]}, + } + service_module.execute_agent_evaluation_run("t1", "u1", 50, judge_model_id=99) + last = service_module.update_agent_evaluation_status.call_args_list[-1] + assert last.kwargs["status"] == service_module.EvalRunStatus.FAILED + assert "evaluator" in (last.kwargs.get("error_message") or "").lower() def test_execute_run_truncates_overlong_session(service_module): @@ -2936,7 +2964,6 @@ def test_success(self, service_module): import asyncio bind_import_attribute(service_module, 'get_evaluator', MagicMock(side_effect=[{'status': 'PUBLISHED', 'name': 'a'}, {'status': 'DRAFT', 'name': 'b'}])) bind_import_attribute(service_module, 'load_prompt', MagicMock(return_value={'SYSTEM_PROMPT': 'sp'})) - bind_import_attribute(service_module, 'JiuwenSDKAdapter', MagicMock()) async def _fake_eval(**kwargs): return "ans", [], {"judge-a": 0.9}, {"judge-a": "ok"} @@ -2952,11 +2979,21 @@ async def _fake_eval(**kwargs): "reasons": {"judge-a": "ok"}, } - def test_sdk_unavailable_raises(self, service_module): + def test_without_usable_evaluators_raises(self, service_module): import asyncio - bind_import_attribute(service_module, 'load_prompt', MagicMock(return_value={'SYSTEM_PROMPT': 'sp'})) - bind_import_attribute(service_module, 'JiuwenSDKAdapter', None) - with pytest.raises(service_module.JiuwenSDKUnavailableError): + + from contracts.errors.exceptions import AppException + + eval_mock = MagicMock() + bind_import_attribute(service_module, '_evaluate_query', eval_mock) + with pytest.raises(AppException): asyncio.run( service_module.trial_run_evaluator_impl("t1", "u1", 1, 2, "q", 99) ) + + bind_import_attribute(service_module, 'get_evaluator', MagicMock(return_value={'status': 'DRAFT', 'name': 'd'})) + with pytest.raises(AppException): + asyncio.run( + service_module.trial_run_evaluator_impl("t1", "u1", 1, 2, "q", 99, [1]) + ) + eval_mock.assert_not_called() From 93c8f54d719e0476e353d5d05cddb110bdaf5a1e Mon Sep 17 00:00:00 2001 From: cj2026-bit <647646783@qq.com> Date: Thu, 12 Nov 2026 21:49:32 +0800 Subject: [PATCH 2/2] fix(evaluation): surface trial-run errors in the UI --- frontend/app/[locale]/evaluation/page.tsx | 9 +++++++++ frontend/public/locales/en/common.json | 1 + frontend/public/locales/zh/common.json | 1 + 3 files changed, 11 insertions(+) diff --git a/frontend/app/[locale]/evaluation/page.tsx b/frontend/app/[locale]/evaluation/page.tsx index 1ac8afed41..754c1423ea 100644 --- a/frontend/app/[locale]/evaluation/page.tsx +++ b/frontend/app/[locale]/evaluation/page.tsx @@ -852,8 +852,17 @@ function RunsTab() { } ); const d = await r.json(); + if (!r.ok) { + message.error( + d?.message || + d?.detail || + t("agentEvaluation.trialFailed") + ); + return; + } setTrialResult(d.data || d); } catch { + message.error(t("agentEvaluation.trialFailed")); } finally { setTrialRunning(false); } diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 8eea726623..d225cd21b5 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -5254,6 +5254,7 @@ "agentEvaluation.trialResult": "Trial Result", "agentEvaluation.trialRun": "Trial Run", "agentEvaluation.trialRunning": "Running trial, please wait...", + "agentEvaluation.trialFailed": "Trial run failed. Please check the configuration and try again.", "agentEvaluation.versionRequired": "Version *", "agentEvaluation.scoreMin": "Score Min", "agentEvaluation.scoreMax": "Score Max (≤100)", diff --git a/frontend/public/locales/zh/common.json b/frontend/public/locales/zh/common.json index 870cac1c5c..19359d10ad 100644 --- a/frontend/public/locales/zh/common.json +++ b/frontend/public/locales/zh/common.json @@ -5192,6 +5192,7 @@ "agentEvaluation.trialResult": "试运行结果", "agentEvaluation.trialRun": "试运行", "agentEvaluation.trialRunning": "试运行中,请稍候...", + "agentEvaluation.trialFailed": "试运行失败,请检查配置后重试", "agentEvaluation.versionRequired": "版本 *", "agentEvaluation.scoreMin": "评分下限", "agentEvaluation.scoreMax": "评分上限 (≤100)",