workflow: refactor InferenceClient endpoints logic - #13683
Conversation
Replace the two hardcoded task tables (`_INFERENCE_ENDPOINT_SCHEMAS`,
`_PIPELINE_TAG_TO_ENDPOINT`) with runtime introspection of the
`InferenceClient` methods. Adds ~50 lines of discovery, removes ~230 of
per-task schemas — net −54 lines — and picks up 3 endpoints that were
missing from the hardcoded table (`audio_to_audio`,
`table_question_answering`, `zero_shot_image_classification`).
Endpoint schemas now derive from method signatures:
- port `id` = param name
- port `label` = titleized param name
- port `type` = name lookup (`image`/`audio`/`video`) or annotation
(`int`/`float` → number, `bool` → boolean, else text). Unwraps
`Optional[X]` so numeric knobs with a default aren't misclassified as text
- port `required` = whether the param has a default
- output `type` = derived from method name (`text_to_image` → image,
`*_classification` → json, else text)
Pipeline-tag → endpoint resolution uses `pipeline_tag.replace("-", "_")`
with a small alias table for tags that don't match that convention
(`text2text-generation`, `conversational`, `text-to-audio`, and the four
vision tags that all route to `chat_completion`).
`chat_completion` stays as a synthetic endpoint (hand-crafted `image +
prompt → text` shape) merged in after auto-discovery, since it doesn't
correspond 1:1 to any InferenceClient method signature. The streaming
dispatch, `max_tokens=16384`, `/gradio_api/file=` handling and
reasoning-aware error messages added in #13666 are preserved.
Also broadens vision routing beyond `image-text-to-text` to also cover
`visual-question-answering`, `document-question-answering`, and
`image-to-text` — none of these are served as task-specific endpoints
by any Inference Provider, so all four now route through
`chat_completion` for consistency.
Zero-day behavior: when huggingface_hub ships a new task method, adds a
param, or changes a param's default, the schema updates automatically
with no code change here. New pipeline_tags that don't match any method
fall through to the raw inference API path (server-side dispatch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🪼 branch checks and previews
Install Gradio from this PR pip install https://huggingface.co/buckets/gradio/pypi-previews/resolve/f3f0e2ad768a115f0f9a1975263a1a23288a9e0f/gradio-6.22.0-py3-none-any.whlInstall Gradio Python Client from this PR pip install "gradio-client @ git+https://github.com/gradio-app/gradio@f3f0e2ad768a115f0f9a1975263a1a23288a9e0f#subdirectory=client/python"Import Gradio JS Client from this PR via CDN import { Client } from "https://huggingface.co/buckets/gradio/npm-previews/resolve/f3f0e2ad768a115f0f9a1975263a1a23288a9e0f/browser.js"; |
Resolves conflicts in: - workflow-store.ts: wrap switch_endpoint return in reconcileComponentRoles (main's new role-reconciliation logic) while keeping custom-port preservation. Also wrap remove_custom_port for consistency. - WorkflowNodeSF.svelte: merge add_custom_port/remove_custom_port imports with main's new setNodeSize import. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors workflow model-endpoint handling by auto-discovering huggingface_hub.InferenceClient task methods (instead of maintaining a static schema), and extends the workflow canvas to support user-added “custom param” input ports that are forwarded to the backend as kwargs / extra_body.
Changes:
- Backend: introspect
InferenceClientsignatures to generate endpoint schemas at runtime, and route custom-port extras intoextra_bodywhen supported. - Frontend: add UI + store actions for adding/removing custom input ports, and serialize model calls as dict args when custom ports are present.
- Tests: add schema “drift” coverage to detect breaking changes across
huggingface_hubupgrades.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
gradio/workflow.py |
Replaces static endpoint schemas with runtime introspection; adds tag→endpoint resolution and custom-kwarg handling. |
test/test_workflow.py |
Adds endpoint schema stability test and extends coverage for new backend behavior. |
js/workflowcanvas/workflow/workflow-executor.ts |
Sends dict args for model calls when custom ports exist. |
js/workflowcanvas/workflow/workflow-store.ts |
Preserves custom ports across schema refresh/endpoint switch; adds add/remove custom port actions. |
js/workflowcanvas/workflow/WorkflowNodeSF.svelte |
Adds “+ Add param” UI, custom-port ordering/visibility, and removal controls. |
js/workflowcanvas/workflow/workflow-types.ts |
Extends Port with a custom?: boolean flag. |
js/workflowcanvas/workflow/workflow-store.test.ts |
Adds tests for custom port add/remove and preservation across refresh/switch. |
js/workflowcanvas/workflow/model-api.ts |
Updates pipeline-tag routing to align more vision-language tags to chat completion. |
gradio/workflow_port_types.py |
Adds a new (currently unused) port-type mapping module. |
.changeset/auto-endpoints-discover.md |
Adds a manual changeset entry for the workflow refactor. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
🦄 change detectedThis Pull Request includes changes to the following packages.
|
|
Amazing, I'll give this a spin @hannahblair! Perhaps we can add one or two more Workflow demos in the |
| "automatic_speech_recognition": "Transcript", | ||
| } | ||
|
|
||
| _SKIP_PARAMS: frozenset[str] = frozenset( |
There was a problem hiding this comment.
This seems fragile (if new parameters are added, this will not work anymore), feels like there might be a better way?
| } | ||
| ) | ||
|
|
||
| _NON_TASK_METHODS: frozenset[str] = frozenset( |
|
I'm not sure if this is a good idea @hannahblair, it seems like this is more fragile than before -- any changes to InferenceClient or its methods could break this. I actually think hardcoding for all of the different tasks is probably more robust since parameter name are unlikely to change for those tasks. Perhaps I'm missing something? |
|
ok good to know, i'll defer to your judgement! two things worth flagging for the record: new tasks would still need to be added manually. and introspection derives required flags from the signatures - I'm not certain the hardcoded schemas are fully correct right now. the main risk the other way is hf_hub renaming a param and silently breaking things, but i imagine that doesn't happen often. that said, the introspection may buy less than it costs i've opened a smaller PR with just the task improvements on top of the existing hardcoded schemas #13710 |
The model-node schema table in
workflow.pywas hardcoded, so every time huggingface_hub added a new task method, we would have to manually add a new entry mirroring the signature.Changes in this PR:
Replaces the hardcoded dict with runtime introspection. Iterate
dir(InferenceClient), runinspect.signature()on each method, derive port shapes from param names + annotations. Any model on the Hub now works with no code change here.Vision tasks (image-text-to-text, visual-question-answering, document-question-answering, image-to-text) all route through
chat_completioninstead of the InferenceClient methods. This extends Workflow UX improvements, and fixgradio skills addclobbering skills via symlinked dirs #13666's fix (which only covered image-text-to-text) to the other three vision tagsAdded a "+ Add param" button on model nodes, users type a name, pick a type, get a port. It becomes an extra kwarg via extra_body in InferenceClient. Ports are marked custom: true in the workflow JSON so they survive schema refreshes and endpoint switches, and render at the top of the node with the required params for visiblity
Note that there's still a lot of other data to hardcode as a result, because introspection doesn't get everything we need. Here's Claude's explanation of each datatype:
_PORT_TYPE_BY_PARAMimage: str | Path | bytes— that annotation doesn't say "this string is an image URL." Python typing can't express modality._MEDIA_TOKEN_TO_TYPEimagetoimg, we still detect it._OUTPUT_SUFFIX_TO_TYPEbytesorImage— introspection can't tell "audio bytes" from "image bytes". Method-name suffix is the reliable signal._MEDIA_EXT_JSON_OUTPUT_METHODSlist[ClassificationOutputElement]etc. — we'd need semantic knowledge to know these serialize as structured JSON vs plain text._OUTPUT_LABELS_SKIP_PARAMS_NON_TASK_METHODSInferenceClienthas helpers like.close(),.post(),.health_check()that aren't inference tasks. HF doesn't tag them._PIPELINE_TAG_ALIASESchat_completionbecause no provider serves the task-specific endpoints.