Skip to content

fix(tools): bind JSONDecodeError in ToolConnectionAnalyzer.analyze#5942

Open
sarathfrancis90 wants to merge 1 commit into
google:mainfrom
sarathfrancis90:fix-tool-connection-analyzer-jsondecodeerror
Open

fix(tools): bind JSONDecodeError in ToolConnectionAnalyzer.analyze#5942
sarathfrancis90 wants to merge 1 commit into
google:mainfrom
sarathfrancis90:fix-tool-connection-analyzer-jsondecodeerror

Conversation

@sarathfrancis90
Copy link
Copy Markdown

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

2. Or, if no issue exists, describe the change:

Problem:

ToolConnectionAnalyzer.analyze() in
src/google/adk/tools/environment_simulation/tool_connection_analyzer.py
crashes whenever the analyzed LLM returns a response that is not valid JSON.

The JSON parse is wrapped in a try/except, but the except clause does not
bind the exception:

except json.JSONDecodeError:
    logging.warning(
        "Failed to parse tool connection analysis from LLM. Proceeding"
        " without connection map. Error: %s\nLLM Output:\n%s",
        e,                # <-- 'e' was never defined
        response_text,
    )
    return ToolConnectionMap(stateful_parameters=[])

The warning log references e, but the except clause binds nothing. So the
moment a non-JSON response is parsed, the handler itself raises
NameError: name 'e' is not defined. This masks the real parse error and
crashes analyze() instead of degrading gracefully to an empty
ToolConnectionMap as the surrounding code clearly intends.

Reproduced traceback (LLM mocked to return "this is not json at all"):

  File ".../tool_connection_analyzer.py", line 136, in analyze
    response_json = json.loads(clean_json_text.strip())
  ...
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

  File ".../tool_connection_analyzer.py", line 141, in analyze
    e,
    ^
NameError: name 'e' is not defined

Solution:

Bind the caught exception with as e so the handler can log the real parse
error and return an empty ToolConnectionMap as designed:

except json.JSONDecodeError as e:
    logging.warning(...)
    return ToolConnectionMap(stateful_parameters=[])

This is a one-character fix; the surrounding logging and fallback behaviour are
unchanged. After the fix, the same input logs the underlying parse error and
returns ToolConnectionMap(stateful_parameters=[]) without raising.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

This branch of analyze() previously had zero coverage, so I added
tests/unittests/tools/environment_simulation/test_tool_connection_analyzer.py
covering:

  • the malformed-JSON path (regression guard — fails with NameError on the
    pre-fix code, passes after the fix),
  • the valid-JSON path, and
  • the Markdown code-fence stripping path.

pytest summary (low parallelism, as run locally):

$ pytest tests/unittests/tools/environment_simulation/ -n2
======================= 12 passed, 11 warnings in 1.64s ========================
$ pytest tests/unittests/tools/environment_simulation/test_tool_connection_analyzer.py -n0 -v
...test_malformed_json_returns_empty_map_without_crashing PASSED
...test_valid_json_is_parsed_into_connection_map PASSED
...test_fenced_json_is_stripped_before_parsing PASSED
======================== 3 passed, 5 warnings in 0.57s =========================

Verified the regression test fails on the unfixed code with the exact
NameError: name 'e' is not defined and passes after the fix. isort and
pyink report no changes on the modified files.

Manual End-to-End (E2E) Tests:

Reproduced directly against the analyzer with a mocked LLM:

analyzer = ToolConnectionAnalyzer(llm_name=..., llm_config=...)
# LLM mocked to return a non-JSON string.
result = await analyzer.analyze([some_tool])
  • Before the fix: raises NameError: name 'e' is not defined.
  • After the fix: logs
    Failed to parse tool connection analysis from LLM... Error: Expecting value: line 1 column 1 (char 0)
    and returns ToolConnectionMap(stateful_parameters=[]).

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

The affected module is marked @experimental(FeatureName.ENVIRONMENT_SIMULATION).

When the LLM returns a response that is not valid JSON,
ToolConnectionAnalyzer.analyze caught json.JSONDecodeError but the
except clause did not bind the exception (`except json.JSONDecodeError:`),
while the warning log inside the handler referenced `e`. That raised
`NameError: name 'e' is not defined`, masking the real parse error and
crashing analyze() instead of degrading gracefully to an empty
ToolConnectionMap.

Bind the exception with `as e` so the handler logs the parse error and
returns an empty connection map as intended.

Add regression tests for the malformed-JSON path (previously uncovered),
plus valid-JSON and fenced-JSON parsing.
@adk-bot adk-bot added the tools [Component] This issue is related to tools label Jun 2, 2026
@sarathfrancis90
Copy link
Copy Markdown
Author

Heads up on the red pre-commit check — it isn't from this PR. This change only touches tool_connection_analyzer.py and its test, and both are pyink/isort clean. The job reformats five unrelated files under flows/llm_flows/ and models/ that are pre-existing drift on main (still would reformat at the current HEAD 47ceebac); since the hook runs across all files, it shows up on every PR. The change here is otherwise green — happy to rebase once main is reformatted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tools [Component] This issue is related to tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants