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-6972-bfe2b535c031cda0508bae6a9a6d2b7f62a886c6
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 3f58aceacdca6a4657c38b64d692ad2e8e6f592d
Author: Prateek Ganigi <[email protected]>
AuthorDate: Tue Jul 28 14:19:11 2026 -0700

    fix(workflow-operator): send provider-specific model id on HF text-gen chat 
fallback routes (#6972)
    
    ### What changes were proposed in this PR?
    
    Fixes two related bugs in the HuggingFace inference operator's
    provider-fallback loop (`_post_with_fallback` in the generated Python,
    emitted by `PythonCodegenBase.scala`) that break the `text-generation` /
    `image-text-to-text` path, which is the operator's default task.
    
    1. **Wrong model id on provider-scoped chat routes.** The chat branch
    posted the same payload, carrying `"model": self.MODEL_ID` (the HF Hub
    ID), to every provider's route (`router.huggingface.co/{provider}/...`).
    Provider-scoped routes require the provider's *own* model name
    (`providerId`, e.g. the Hub's `Qwen/Qwen2.5-72B-Instruct` is a different
    string on Groq or Fireworks), which `_resolve_providers` already fetches
    and `_call_provider` already uses for the other task families. Providers
    whose internal name differs from the Hub ID rejected every request with
    400/404, so fallback only succeeded when the names happened to match.
    The chat branch now posts a per-attempt copy with the model overridden:
    
       chat_payload = {**pipeline_payload, "model": provider_id}
    
    (a copy, not in-place mutation, pipeline_payload is reused for the next
    provider
       attempt).
    
    2. **Malformed hf-inference chat URL.** The chat URL for hf-inference
    was built as `hf-inference/v1/chat/completions`, but hf-inference
    expects the model in the URL path:
    `hf-inference/models/{model-id}/v1/chat/completions` (the form this file
    already uses for pipeline tasks). Since hf-inference sorts first in
    `PROVIDER_COST_PRIORITY`, the cheapest provider failed on every row,
    wasting a doomed request (up to the 120 s timeout) per row before
    falling through.
    
    Net effect: text-gen rows now succeed on the cheapest live provider
    instead of failing with "All inference providers failed" or silently
    drifting to pricier providers. No behavior change for other task
    families - `_call_provider` already handled them correctly.
    
    ### Any related issues, documentation, discussions?
    
    Closes #6965
    
    ### How was this PR tested?
    
    Added a generated-code test to HuggingFaceInferenceOpDescSpec ("send the
    provider-specific model id on provider-scoped chat routes") pinning both
    fixes: the corrected hf-inference chat URL, and the chat branch posting
    the per-provider payload copy (asserted as an anchored two-line block so
    pipeline routes that legitimately post pipeline_payload directly stay
    unaffected).
    
    Run:
    
    sbt "WorkflowOperator/testOnly
    org.apache.texera.amber.operator.huggingFace.*"
    
    Full HF package passes (123 tests, 12 suites), including
    PythonCodeRawInvalidTextSpec, which py_compiles the generated Python of
    all 117 Python operators, verifying the edited template still emits
    syntactically valid Python. scalafmt clean.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Co-authored with Claude Fable 5 in compliance with ASF.
---
 .../huggingFace/codegen/PythonCodegenBase.scala         | 15 ++++++++++++---
 .../huggingFace/HuggingFaceInferenceOpDescSpec.scala    | 17 +++++++++++++++++
 2 files changed, 29 insertions(+), 3 deletions(-)

diff --git 
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/PythonCodegenBase.scala
 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/PythonCodegenBase.scala
index 7cd305bfb6..f2cf479afb 100644
--- 
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/PythonCodegenBase.scala
+++ 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/codegen/PythonCodegenBase.scala
@@ -201,9 +201,18 @@ object PythonCodegenBase {
        |            prov_task = prov.get("task", "")
        |            try:
        |                if self.TASK in ("text-generation", 
"image-text-to-text"):
-       |                    route = self.CHAT_ROUTES.get(provider_name, 
"v1/chat/completions")
-       |                    url = 
f"https://router.huggingface.co/{provider_name}/{route}";
-       |                    resp = requests.post(url, headers=json_headers, 
json=pipeline_payload, timeout=120)
+       |                    if provider_name == "hf-inference":
+       |                        # hf-inference expects the model in the URL 
path, like the
+       |                        # pipeline route below.
+       |                        url = 
f"https://router.huggingface.co/hf-inference/models/{self.MODEL_ID}/v1/chat/completions";
+       |                    else:
+       |                        route = self.CHAT_ROUTES.get(provider_name, 
"v1/chat/completions")
+       |                        url = 
f"https://router.huggingface.co/{provider_name}/{route}";
+       |                    # Provider-scoped routes need the provider's own 
model name
+       |                    # (providerId), not the HF Hub ID. Copy instead of 
mutating:
+       |                    # pipeline_payload is reused for the next provider 
attempt.
+       |                    chat_payload = {**pipeline_payload, "model": 
provider_id}
+       |                    resp = requests.post(url, headers=json_headers, 
json=chat_payload, timeout=120)
        |                elif is_model_author and prov_task in 
("image-to-text", "image-text-to-text") and provider_name not in ("zai-org",):
        |                    url = 
f"https://router.huggingface.co/{provider_name}/v1/chat/completions";
        |                    img_b64 = ""
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 83f6239903..97bc9dc8ff 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
@@ -113,6 +113,23 @@ class HuggingFaceInferenceOpDescSpec extends AnyFlatSpec 
with Matchers {
     code should include("""body["choices"][0]["message"]["content"]""")
   }
 
+  it should "send the provider-specific model id on provider-scoped chat 
routes" in {
+    val code = makeDesc().generatePythonCode()
+    // hf-inference's chat-completions endpoint carries the model in the URL 
path.
+    code should include(
+      
"https://router.huggingface.co/hf-inference/models/{self.MODEL_ID}/v1/chat/completions";
+    )
+    // The chat branch posts a per-provider copy of the payload with the
+    // provider's own model name (providerId) — the Hub ID is only valid on
+    // hf-inference itself. Matched as one block so the assertion is anchored
+    // to the chat branch: pipeline routes elsewhere legitimately post the
+    // shared pipeline_payload directly.
+    code should include(
+      "chat_payload = {**pipeline_payload, \"model\": provider_id}\n" +
+        "                    resp = requests.post(url, headers=json_headers, 
json=chat_payload, timeout=120)"
+    )
+  }
+
   it should
     "emit a runtime check that rejects malformed MODEL_ID values before any HF 
URL is built" in {
     val code = makeDesc().generatePythonCode()

Reply via email to