diff --git a/dashscope/_error_registry.py b/dashscope/_error_registry.py index 36cda129..ba581ce6 100644 --- a/dashscope/_error_registry.py +++ b/dashscope/_error_registry.py @@ -486,14 +486,12 @@ def format_message( SDK_AGENTIC_RL_FUNCTION_LAYER_ERROR = SdkErrorDef( name="sdk.agentic_rl.FunctionLayerError", - message="The function dependency layer is not usable.", - solution=( - "The dependency layer could not be created, or its build failed, " - "timed out, or its status could not be queried. For a build " - "failure, check that requirements.txt or setup.py is valid and " - "every dependency is installable, then review the layer build " - "logs. For a timeout, raise FC_LAYER_STATUS_TIMEOUT to allow more " - "time. For a status query error, retry once connectivity recovers." + message="Creating a layer of function failed.", + solution=( + "Failed to create the function dependency layer. Check that " + "the requirements.txt or setup.py is valid and all " + "dependencies are installable. Review the layer build logs " + "for details." ), allow_retry=True, vars=[], diff --git a/dashscope/applications/finetune/reinforcement/common/model.py b/dashscope/applications/finetune/reinforcement/common/model.py index ffdcff50..f07c7f68 100644 --- a/dashscope/applications/finetune/reinforcement/common/model.py +++ b/dashscope/applications/finetune/reinforcement/common/model.py @@ -91,6 +91,34 @@ ValueErrorWithCode, ) +_LAYER_CREATE_REJECTED_SOLUTION = ( + "Review the server response and layer request. Fix invalid requirements " + "or request parameters before retrying." +) +_LAYER_CREATE_LOCAL_SOLUTION = ( + "Check that the requirements file exists, is readable, and contains " + "valid dependency declarations before retrying." +) +_LAYER_CREATE_TRANSIENT_SOLUTION = ( + "Retry after connectivity recovers. If the error persists, check the " + "Function Compute layer service and request logs." +) +_LAYER_STATUS_QUERY_SOLUTION = ( + "Retry the layer status query after connectivity recovers." +) +_LAYER_BUILD_FAILED_SOLUTION = ( + "Check that requirements.txt or setup.py is valid and every dependency " + "is installable, then review the layer build logs before retrying." +) +_LAYER_STATUS_MISSING_SOLUTION = ( + "Retry the status query. If the response still has no status, report the " + "response and layer code to the service owner." +) +_LAYER_BUILD_TIMEOUT_SOLUTION = ( + "Increase FC_LAYER_STATUS_TIMEOUT if the layer is still building, then " + "query the same layer code again instead of creating a duplicate layer." +) + class MountStorage(BaseModel): model_config = ConfigDict(extra="forbid") @@ -322,11 +350,12 @@ class FunctionComponentModel(BaseModel): def generate_id(self, func_type: FunctionType) -> str: """Generate unique OSS resource identifier.""" - self.oss_id = generate_random_id(func_type.value) + oss_id = generate_random_id(func_type.value) + self.oss_id = oss_id logger.debug( - f"Generated OSS ID | Type: {func_type.name}, ID: {self.oss_id}", + f"Generated OSS ID | Type: {func_type.name}, ID: {oss_id}", ) - return self.oss_id + return oss_id async def get_oss(self, oss_id: Optional[str] = None) -> str: """Retrieve OSS signed URL for deployment package.""" @@ -393,6 +422,8 @@ async def create_layer( if result.get("status", {}).get("code", 500) != 200: raise FunctionLayerError( f"create rejected by server | {result}", + allow_retry=False, + solution=_LAYER_CREATE_REJECTED_SOLUTION, ) layer_code = result.get("output", {}).get("layer_code", "") @@ -403,11 +434,21 @@ async def create_layer( except FunctionLayerError: raise + except (InputError, OSError) as e: + raise FunctionLayerError( + f"create input error | {e}", + error_code=2013, + layer_name=layer_name, + allow_retry=False, + solution=_LAYER_CREATE_LOCAL_SOLUTION, + ) from e except Exception as e: raise FunctionLayerError( f"create error | {e}", error_code=2013, layer_name=layer_name, + allow_retry=True, + solution=_LAYER_CREATE_TRANSIENT_SOLUTION, ) from e return layer_code @@ -487,6 +528,8 @@ async def get_layer(self, layer_code: str) -> str: f"status query error | layer_code: {layer_code} | {e}", error_code=2014, layer_code=layer_code, + allow_retry=True, + solution=_LAYER_STATUS_QUERY_SOLUTION, ) from e status = result.get("output", {}).get("status", "") @@ -504,6 +547,8 @@ async def get_layer(self, layer_code: str) -> str: f"{layer_code} | {result}", error_code=2015, layer_code=layer_code, + allow_retry=False, + solution=_LAYER_BUILD_FAILED_SOLUTION, ) if not status: @@ -512,6 +557,8 @@ async def get_layer(self, layer_code: str) -> str: f"{layer_code} | {result}", error_code=2016, layer_code=layer_code, + allow_retry=True, + solution=_LAYER_STATUS_MISSING_SOLUTION, ) if attempt == 1: @@ -536,6 +583,8 @@ async def get_layer(self, layer_code: str) -> str: f"FC_LAYER_STATUS_TIMEOUT if the build needs longer", error_code=2017, layer_code=layer_code, + allow_retry=True, + solution=_LAYER_BUILD_TIMEOUT_SOLUTION, ) def clean_temp_files(self, tmp_path: str) -> None: diff --git a/tests/legacy_1x/unit/test_agentic_rl_layer_status.py b/tests/legacy_1x/unit/test_agentic_rl_layer_status.py index cd50cfd9..0e70ac18 100644 --- a/tests/legacy_1x/unit/test_agentic_rl_layer_status.py +++ b/tests/legacy_1x/unit/test_agentic_rl_layer_status.py @@ -69,6 +69,19 @@ def _component(with_runtime=True): return component +async def test_create_layer_rejection_is_not_retryable(monkeypatch): + _stub_responses( + monkeypatch, + [{"status": {"code": 400}, "message": "invalid requirements"}], + ) + + with pytest.raises(FunctionLayerError) as excinfo: + await FunctionComponentModel().create_layer(requirements_file="") + + assert excinfo.value.allow_retry is False + assert "Fix invalid requirements" in excinfo.value.solution + + async def test_get_layer_returns_ready_status(monkeypatch): calls = _stub_responses(monkeypatch, [{"output": {"status": "SUCCESS"}}]) @@ -108,6 +121,8 @@ async def test_get_layer_raises_on_failed_build(monkeypatch): assert "Function layer failed: build reported FAILED" in message assert excinfo.value.layer_code == LAYER_CODE assert excinfo.value.layer_name is None + assert excinfo.value.allow_retry is False + assert "requirements.txt" in excinfo.value.solution async def test_get_layer_raises_on_region_rejection(monkeypatch): @@ -117,6 +132,8 @@ async def test_get_layer_raises_on_region_rejection(monkeypatch): await FunctionComponentModel().get_layer(LAYER_CODE) assert "cn-wulanchabu" in str(excinfo.value) + assert excinfo.value.allow_retry is True + assert "Retry the status query" in excinfo.value.solution assert calls["count"] == 1 @@ -132,6 +149,8 @@ async def test_get_layer_raises_when_build_stalls(monkeypatch): assert "timed out" in message assert "RUNNING" in message assert "FC_LAYER_STATUS_TIMEOUT" in message + assert excinfo.value.allow_retry is True + assert "FC_LAYER_STATUS_TIMEOUT" in excinfo.value.solution assert calls["count"] > 3 @@ -142,6 +161,8 @@ async def test_get_layer_raises_on_transport_error(monkeypatch): await FunctionComponentModel().get_layer(LAYER_CODE) assert "connection reset" in str(excinfo.value) + assert excinfo.value.allow_retry is True + assert "connectivity recovers" in excinfo.value.solution async def test_load_reports_layer_failure_as_such(monkeypatch): diff --git a/tests/test_error_registry.py b/tests/test_error_registry.py index fe858214..37dd4914 100644 --- a/tests/test_error_registry.py +++ b/tests/test_error_registry.py @@ -158,6 +158,19 @@ def test_internal_entries_well_formed(): assert all(isinstance(v, str) for v in entry.vars) +def test_function_layer_error_matches_generated_registry_source(): + entry = _by_name(SDK)["sdk.agentic_rl.FunctionLayerError"] + assert entry.message == "Creating a layer of function failed." + assert entry.solution == ( + "Failed to create the function dependency layer. Check that the " + "requirements.txt or setup.py is valid and all dependencies are " + "installable. Review the layer build logs for details." + ) + # Python-only runtime controls belong at the raise site, not in the + # cross-language generated catalog. + assert "FC_LAYER_STATUS_TIMEOUT" not in entry.solution + + def test_internal_vars_match_message_placeholders(): """Every declared var is used, and every placeholder is declared.""" for entry in SDK: