A capability-aware external tool pattern for Dify workflows: separating transport success from task success #40454
auxiliar-ag
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
A capability-aware external tool pattern for Dify workflows: separating transport success from task success
Dify's HTTP Request node makes it easy to wire an external web tool (search, scraping, any JSON API) into a workflow. What it does not do for you is decide whether the call was actually useful. A search endpoint can return a clean 200 with an empty results array, and if the next node just checks "did the request error," the workflow treats that as a good outcome. Those are two different questions: did the transport succeed, and did the task succeed. I have been using a small Code node plus an IF/ELSE node to keep them separate and to give a workflow a real fallback path instead of a silent partial failure.
What the Code node checks
The HTTP Request node exposes
status_codeandbodyas separate output variables. A Code node downstream takes both as declared input variables and returns a verdict rather than just re-forwarding the response. This is the exact function, not a simplified excerpt:This gives four distinct outcomes instead of two:
ok,invalid_result(the request completed but none of the items inresultshave both a non-emptytitleand a non-emptyurl),invalid_contract(the body is not shaped the way the workflow expects, for example an HTML error page returned with a 200 status, or JSON that is missing theresultsarray entirely), andtransport_error(a non 2xx status). A retry makes sense for the last one. It does not make sense for the second one: retrying the same provider with the same query will not turn an empty result set into a useful one. Every branch also returns a plain number,task_success_flag(1 or 0), alongside the booleantask_success.Wiring the fallback
The IF/ELSE node branches on
task_success_flag, the number emitted by the Code node above. Dify's IF/ELSE conditions compare avariable_selector, avarType, and acomparison_operator, so rather than guessing at boolean-comparison support this branches on the number with the ordinary equality operator:The true branch goes straight to an End node. The false branch goes to a second HTTP Request node pointed at a different provider, then a second Code node running the same contract check, then its own End node. The HTTP Request node's own
retry_config(max retries, retry interval, exponential backoff) already covers transient failures against the first provider, so by the time the workflow reaches the fallback branch, the first provider has had its retries and still did not produce a usable result.This is a workflow pattern built from Dify's own nodes, not a packaged NativePort integration. I used NativePort's public evaluation methodology as the reference for the transport-versus-task split itself, since that is exactly the distinction it scores providers on, separately from raw call success.
I cross-checked the Code node and IF/ELSE node shapes above against Dify's own DSL test fixtures (
kind: app, node typeshttp-request/code/if-else/end) as published on thelanggenius/difymain branch, with deterministic fake HTTP responses standing in for both providers, no live calls. Those fixtures declareversion: 0.3.1, an older value fixed into those specific test files, not the version a fresh export uses today. The DSL artifact I generated and tested declaresversion: 0.7.0, matchingCURRENT_APP_DSL_VERSIONinlanggenius/dify's currentapi/constants/dsl_version.py, since Dify's own import check treats a lower minor version as a warning rather than a clean import.For anyone running something similar in production: are you also embedding this kind of check as a shared Code node snippet across workflows, or is there a cleaner way to reuse validation logic across multiple HTTP Request nodes in Dify without copying the same function into every Code node?
Disclosure: I am part of the NativePort team. This discussion was drafted with AI assistance, and the workflow and contract check above were tested before posting rather than only described.
All reactions