Repository navigation
fix(llm): retry structured calls that return no result - #845
Open
Thanatos9404 wants to merge 1 commit into
Open
Thanatos9404 wants to merge 1 commit into
Thanatos9404 wants to merge 1 commit into
Conversation
LangChain's tool-call parsers return None when a reply has no tool call, for example when a forced tool call is answered in prose. Only the auto tool choice and explicit function_calling bindings turned that into a StructuredOutputParseError. On every other binding, None reached parse_response: TP4 and the semantic analyzers raised NotImplementedError, which the batch runners re-raise as a misconfiguration. For TP4 the scan then ended with execution_successful false and exit code 2. Treat a None structured result like any other malformed structured response. It now gets the existing bounded retries, and if they run out the batch is recorded as llm_structured_response_invalid (skipped), so the analysis is reported as incomplete instead of failed. Fixes NVIDIA#843 Signed-off-by: Yashvardhan Thanvi <yashvt9404@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #843
LangChain's tool-call parsers return
Nonewhen a reply has no tool call.bind_structured_outputalready turns that into aStructuredOutputParseErrorfor auto tool choice bindings (#529) and explicitfunction_calling(#691), so those are retried and end asllm_structured_response_invalid. Every other binding returns theNoneunchanged, including the default forced tool call on Bedrock (ChatBedrockConverse) and on the Anthropic API (ChatAnthropic)._invoke_batchthen passesNonetoparse_response:_TP4Analyzer.parse_responseraisesNotImplementedError("TP4 requires a structured assessment response").arun_batches_detailedre-raisesNotImplementedErroras a misconfiguration, so_check_tp4recordsfailed/llm_batch_failedafter one attempt and the scan ends withexecution_successful: falseand exit code 2, as in the issue.parse_response, used by the three semantic analyzers, raises the same kind ofNotImplementedError; the semantic node then reports the whole analyzerunavailableand drops the other batches' results.LLMMetaAnalyzer.parse_responsefails withAttributeError, and the batch fails asllm_batch_failedwithout a retry.This PR checks for a
Noneresult inside the existingtryin_invoke_batchand_ainvoke_batchand raisesStructuredOutputParseErrorthere. A missing result now takes the same path as any other malformed structured response: three bounded retries, then a skipped batch withllm_structured_response_invalid. For TP4 the analyzer endsdegraded, the report shows partial analysis, andexecution_successfulstays true. If a retry returns a valid assessment, TP4 completes normally.The check sits in the base class rather than in TP4's
parse_responsebecauseparse_responseruns outside thattry, and errors raised there deliberately propagate without a retry (test_custom_parser_validation_error_propagates_without_retry).This is the follow-up suggested in the #691 review (#691 (comment)). #691 applied it to explicit
function_callingand kept other bindings failing on purpose; this applies it to every structured binding.On current
mainthe issue's model,global.anthropic.claude-sonnet-5-5, is registered as rejecting forced tool calls (#806), so on Bedrock it already takes the auto tool choice path and is retried. Models that keep the forced tool call, such as the Bedrock defaultus.anthropic.claude-sonnet-4-6, still hit the crash onmain. The 2.13.0 release branch (#833) leaves out #806 and has the same_invoke_batchcode, so the issue's exact setup still exits 2 there. Thellm_analyzer_base.pychange applies cleanly toyashraj/release-v2.13.0if a backport is wanted.Tests
tests/nodes/test_llm_analyzer_base.py, in bothTestRunBatchesandTestARunBatches:test_missing_structured_response_recovers_on_retry:None, then a valid result. Two calls, one 0.5 s backoff, no failure.test_missing_structured_response_isolated_after_three_retries: fourNoneresults. The batch fails withValidationError/llm_structured_response_invalidafter the 0.5, 1 and 2 s backoffs, and the next batch still succeeds.tests/test_mcp_tool_poisoning.py, inTestTP4Fallbacks:test_missing_structured_response_is_retried:None, then a valid assessment. TP4 endscompletedafter two calls.test_bedrock_forced_tool_call_answered_in_prose_is_retried: a realChatBedrockConverseforBEDROCK_DEFAULT_MODEL(forced tool choice) whose_ageneratereturns prose, so LangChain's parser returnsNone. TP4 makes four calls, endsdegradedwithllm_structured_response_invalid, and records nofailedrows.test_tool_refusal_stays_incomplete(from fix: prefer tool output for OpenAI Opus 5 TP4 #691) pinned the old behaviour: one call, thenfailed. It now expects four calls,degraded, a skippedllm_structured_response_invalidrow, andfinalize_ledgerreturningexecution_successful: trueandis_complete: false.All seven fail on
main@ 2404212 (the base tests withNotImplementedError, the TP4 tests with one call where two or four are expected) and pass on this branch.Checks
On Windows, Python 3.12.5,
uv sync --frozen --all-extras, at 2404212 plus this commit:pytest -m "not integration and not provider" tests/: passes except about 40 Windows-only tests (environment size limit,os.geteuid, symlinks, paths and encodings, plus one timing-sensitive test) that fail the same way on unmodifiedmain; none touch the LLM code.tests/test_mcp_tool_poisoning.py,tests/nodes/test_llm_analyzer_base.py,tests/nodes/test_meta_analyzer.py,tests/nodes/test_meta_analyzer_fallback.py,tests/nodes/test_semantic_quality_policy.py,tests/nodes/analyzers/test_semantic_developer_intent.py,tests/nodes/analyzers/test_semantic_security_discovery.py,tests/test_semantic_runtime.py,tests/unit/test_llm_utils.py,tests/unit/test_tp4_openai_http.py,tests/unit/test_bedrock_provider.py): 770 passed.ruff check src/ tests/andruff format --check src/ tests/(whatmake lintandmake format-checkrun): pass.I also ran the TP4 node offline against a real
ChatBedrockConversewhose botocore client was stubbed withStubberto answer in prose. Forus.anthropic.claude-sonnet-4-6,mainmakes one Converse call and endsfailed/NotImplementedErrorwithexecution_successful: false; this branch makes four calls and endsdegraded/llm_structured_response_invalidwithexecution_successful: true, and a prose reply followed by a valid tool call completes after two calls. No live provider calls were made.