Question Classifier can fail with JSONDecodeError "Extra data" when model returns multiple JSON objects
- Dominant language
- TypeScript
- Stars
- 156k
- Forks
- 24.6k
- Avg merge
- 22h 9m
- Merged PRs (30d)
- 610
Description
### Self Checks
- [x] I have read the Contributing Guide and searched for existing issues, including closed ones.
- [x] This is a bug report.
- [x] I am submitting this report in English.
### Dify version
Observed on self-hosted Dify app version 1.13.3 (Enterprise chart 3.9.10 and 3.9.11). The same parser implementation is still present on current `main` in `api/libs/json_in_md_parser.py`.
### Cloud or Self Hosted
Self Hosted
### Steps to reproduce
The failure occurs when a Question Classifier model returns more than one JSON object without a code fence.
A minimal parser-level reproduction is:
```python
from libs.json_in_md_parser import parse_json_markdown
text = '{"a": 1}\n{"a": 2}'
parse_json_markdown(text)
```
`parse_json_markdown()` finds the first opening `{` but uses `rfind("}")` / `rfind("]")` to determine the end of the JSON block. Therefore the extracted slice contains both JSON objects:
```text
{"a": 1}
{"a": 2}
```
Passing that slice to `json.loads()` raises:
```text
json.decoder.JSONDecodeError: Extra data
```
In a real Question Classifier workload we reproduced the same class of failure at low load, so it is not a saturation/concurrency issue. The exact `Extra data` position varies because the model output shape is nondeterministic.
### Expected Behavior
Question Classifier should parse the first valid JSON object returned by the model, or otherwise reject malformed output in a way that does not incorrectly concatenate multiple JSON objects into one parse attempt.
### Actual Behavior
`parse_json_markdown()` currently anchors from the first `{` / `[` to the last `}` / `]` in the entire model response. If the response contains two JSON objects (or JSON plus another JSON-like block), `json.loads()` receives both and raises `Extra data`.
This can cause a Question Classifier workflow execution to fail even when the model produced a valid first JSON object.
### Relevant implementation
Current `api/libs/json_in_md_parser.py` does roughly:
```python
start_candidates = [i for i in (json_string.find("{"), json_string.find("[")) if i != -1]
start_index = min(start_candidates)
end_index = max(json_string.rfind("}"), json_string.rfind("]"))
extracted_content = json_string[start_index:end_index + 1].strip()
return json.loads(extracted_content)
```
The `rfind()` end selection is the problematic part when more than one JSON value is present.
### Suggested fix
Use `json.JSONDecoder().raw_decode()` starting at the first JSON token and consume only the first complete JSON value, instead of slicing through the last closing bracket in the whole response.
For example, conceptually:
```python
decoder = json.JSONDecoder()
obj, end = decoder.raw_decode(json_string[start_index:])
return obj
```
The exact implementation should preserve the existing fenced-content fallback behavior and expected-key validation.
### Additional context
- Reproduced under low concurrency, so this is independent of load.
- The failure position varies across runs, consistent with varying model output shape rather than a fixed input-specific problem.
- Structured output is not available for this Question Classifier path in the observed version, so the parser should be robust to ordinary model output variation.
- No customer-specific data is required to reproduce this issue; the two-object example above reproduces the parser behavior directly.
Contributor guide
Research direction
Start in api/libs/json_in_md_parser.py at parse_json_markdown() and reproduce the failure with the two-object input shown in the issue. Check the existing fenced-content fallback and expected-key validation before testing a first-value parsing approach. Done means the first valid JSON value is parsed without Extra data while existing fallback and validation behavior remains intact.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 80/100