Skip to content

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

Closed
sarathfrancis90 wants to merge 4 commits into
google:mainfrom
sarathfrancis90:fix-tool-connection-analyzer-jsondecodeerror
Closed

sarathfrancis90 wants to merge 4 commits into
google:mainfrom
sarathfrancis90:fix-tool-connection-analyzer-jsondecodeerror

Conversation

@sarathfrancis90

Copy link
Copy Markdown
Contributor

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
Contributor 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.

@rohityan rohityan self-assigned this Jun 5, 2026
@rohityan

rohityan commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

/adk-pr-analyze

@rohityan rohityan added the needs review [Status] The PR/issue is awaiting review from the maintainer label Jun 11, 2026
@rohityan
rohityan requested a review from GWeale June 11, 2026 22:27
@rohityan

Copy link
Copy Markdown
Collaborator

Hi @sarathfrancis90 , Thank you for your contribution! We appreciate you taking the time to submit this pull request. Your PR has been received by the team and is currently under review. We will provide feedback as soon as we have an update to share.

@rohityan

Copy link
Copy Markdown
Collaborator

Hi @GWeale , can you please review this.

@GWeale GWeale assigned GWeale and unassigned rohityan Jun 15, 2026
copybara-service Bot pushed a commit that referenced this pull request Jul 28, 2026
Merge #5942

**Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) 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:

```python
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"`):

```text
  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:

```python
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:**

- [x] I have added or updated unit tests for my change.
- [x] 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):

```text
$ pytest tests/unittests/tools/environment_simulation/ -n2
======================= 12 passed, 11 warnings in 1.64s ========================
```

```text
$ 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:

```python
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

- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [x] 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)`.

Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=#5942 from sarathfrancis90:fix-tool-connection-analyzer-jsondecodeerror 536dd56
PiperOrigin-RevId: 955373698
@adk-bot

adk-bot commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Thank you @sarathfrancis90 for your contribution! 🎉

Your changes have been successfully imported and merged via Copybara in commit a60d5b9.

Closing this PR as the changes are now in the main branch.

@adk-bot adk-bot added the merged [Status] This PR is merged label Jul 28, 2026
@adk-bot adk-bot closed this Jul 28, 2026
FrigaZzz pushed a commit to FrigaZzz/adk-python that referenced this pull request Aug 11, 2026
Merge google#5942

**Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) 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:

```python
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"`):

```text
  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:

```python
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:**

- [x] I have added or updated unit tests for my change.
- [x] 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):

```text
$ pytest tests/unittests/tools/environment_simulation/ -n2
======================= 12 passed, 11 warnings in 1.64s ========================
```

```text
$ 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:

```python
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

- [x] I have read the [CONTRIBUTING.md](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) document.
- [x] I have performed a self-review of my own code.
- [x] I have commented my code, particularly in hard-to-understand areas.
- [x] I have added tests that prove my fix is effective or that my feature works.
- [x] New and existing unit tests pass locally with my changes.
- [x] 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)`.

Co-authored-by: George Weale <gweale@google.com>
COPYBARA_INTEGRATE_REVIEW=google#5942 from sarathfrancis90:fix-tool-connection-analyzer-jsondecodeerror 536dd56
PiperOrigin-RevId: 955373698
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged [Status] This PR is merged needs review [Status] The PR/issue is awaiting review from the maintainer tools [Component] This issue is related to tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants