This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-7242-96f47c786548c26db30efa666fdf6bb43b5406bd in repository https://gitbox.apache.org/repos/asf/texera.git
commit 29e254010546b25dbac703ef59e2f1a2546772f4 Author: Prateek Ganigi <[email protected]> AuthorDate: Mon Aug 3 16:33:33 2026 -0700 fix(workflow-operator): retry other providers on a 401 instead of aborting the HF fallback (#7242) ### What changes were proposed in this PR? In the HuggingFace operator's `_post_with_fallback`, a 401 from any single provider immediately aborted the whole fallback chain (reported as "Invalid HF API token"), even when another provider would have served the model with the same token, one provider can 401 (e.g. missing provider-specific permission) while others accept the token. This removes the 401 short-circuit and adds 401 to the `RETRYABLE` set, so the loop tries the remaining providers. "Invalid HF API token" is now surfaced only when the final provider still returns 401 (i.e. every provider rejected the token). ### Any related issues, documentation, discussions? Closes #7194. ### How was this PR tested? `sbt "WorkflowOperator/testOnly org.apache.texera.amber.operator.huggingFace.* org.apache.texera.amber.util.PythonCodeRawInvalidTextSpec"`: passes (125 tests). Added a test asserting 401 is retryable and the old short-circuit is gone; `PythonCodeRawInvalidTextSpec` py-compiles the generated Python. scalafmt clean. ### Was this PR authored or co-authored using generative AI tooling? No, this PR was not authored or co-authored using generative AI tooling. --- .../operator/huggingFace/codegen/HuggingFaceCodegenBase.scala | 4 +--- .../operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala | 8 ++++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala index 77bf488d6c..a4dfc7ca3f 100644 --- a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala +++ b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/HuggingFaceCodegenBase.scala @@ -195,7 +195,7 @@ object HuggingFaceCodegenBase { | Returns (response, provider_summary). provider_summary is None on | success or a string describing what failed. | ''' - | RETRYABLE = (400, 404, 422, 429, 502, 503) + | RETRYABLE = (400, 401, 404, 422, 429, 502, 503) | last_resp = None | errors = [] | for prov in providers: @@ -247,8 +247,6 @@ object HuggingFaceCodegenBase { | continue | if resp.status_code in (200, 201): | return resp, None - | if resp.status_code == 401: - | return resp, None | try: | detail = resp.json().get("error", resp.text[:200]) | except Exception: diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala index 99eb14ed6f..85e38c08e2 100644 --- a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala +++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceInferenceOpDescSpec.scala @@ -649,6 +649,14 @@ class HuggingFaceInferenceOpDescSpec extends AnyFlatSpec with Matchers { outSchema.getAttributeNames.contains("hf_response") shouldBe true } + it should "treat a 401 as retryable so one provider's auth failure doesn't abort the fallback" in { + val code = makeDesc().generatePythonCode() + // 401 is in the retryable set -> the loop tries the next provider instead of bailing. + code should include("RETRYABLE = (400, 401, 404, 422, 429, 502, 503)") + // The old short-circuit (return immediately on the first 401) must be gone. + code should not include (" if resp.status_code == 401:\n return resp, None") + } + it should "mask the API token field as a password widget in the generated schema" in { val tokenProp = OperatorMetadataGenerator .generateOperatorJsonSchema(classOf[HuggingFaceInferenceOpDesc])
