Spaces:
Running on Zero
Running on Zero
fix: parse qwen thinking json output
Browse filesCo-authored-by: Codex <noreply@openai.com>
- model_runtime.py +21 -6
- tests/test_model_runtime.py +10 -0
model_runtime.py
CHANGED
|
@@ -252,14 +252,29 @@ def _loads_lenient(content: str) -> dict[str, Any]:
|
|
| 252 |
try:
|
| 253 |
parsed: Any = json.loads(text)
|
| 254 |
except json.JSONDecodeError:
|
| 255 |
-
|
| 256 |
-
if
|
| 257 |
raise ValueError("Model response was not valid JSON.")
|
| 258 |
-
|
| 259 |
-
parsed = json.loads(text[start : end + 1])
|
| 260 |
-
except json.JSONDecodeError as exc:
|
| 261 |
-
raise ValueError("Model response was not valid JSON.") from exc
|
| 262 |
|
| 263 |
if not isinstance(parsed, dict):
|
| 264 |
raise ValueError("Model response was not a JSON object.")
|
| 265 |
return parsed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
try:
|
| 253 |
parsed: Any = json.loads(text)
|
| 254 |
except json.JSONDecodeError:
|
| 255 |
+
candidates = list(_json_object_candidates(text))
|
| 256 |
+
if not candidates:
|
| 257 |
raise ValueError("Model response was not valid JSON.")
|
| 258 |
+
parsed = candidates[-1]
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
if not isinstance(parsed, dict):
|
| 261 |
raise ValueError("Model response was not a JSON object.")
|
| 262 |
return parsed
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
def _json_object_candidates(text: str) -> list[dict[str, Any]]:
|
| 266 |
+
decoder = json.JSONDecoder()
|
| 267 |
+
candidates: list[dict[str, Any]] = []
|
| 268 |
+
cursor = 0
|
| 269 |
+
while True:
|
| 270 |
+
start = text.find("{", cursor)
|
| 271 |
+
if start == -1:
|
| 272 |
+
return candidates
|
| 273 |
+
try:
|
| 274 |
+
parsed, consumed = decoder.raw_decode(text[start:])
|
| 275 |
+
except json.JSONDecodeError:
|
| 276 |
+
cursor = start + 1
|
| 277 |
+
continue
|
| 278 |
+
if isinstance(parsed, dict):
|
| 279 |
+
candidates.append(parsed)
|
| 280 |
+
cursor = start + max(consumed, 1)
|
tests/test_model_runtime.py
CHANGED
|
@@ -74,6 +74,16 @@ class ModelRuntimeTests(unittest.TestCase):
|
|
| 74 |
|
| 75 |
self.assertEqual(memo["outcome_audit_memo"], MEMO_JSON["outcome_audit_memo"])
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
def test_run_model_assist_uses_selected_model(self) -> None:
|
| 78 |
result, narrative = analyze_trace_file(Path("examples/sample_trace_redacted.jsonl"))
|
| 79 |
generate = RecordingGenerator()
|
|
|
|
| 74 |
|
| 75 |
self.assertEqual(memo["outcome_audit_memo"], MEMO_JSON["outcome_audit_memo"])
|
| 76 |
|
| 77 |
+
def test_parse_model_json_uses_final_object_after_thinking_braces(self) -> None:
|
| 78 |
+
raw = (
|
| 79 |
+
"<think>Draft {not json} and a scratch object "
|
| 80 |
+
'{"draft": "ignore this"} before the final answer.</think>\n'
|
| 81 |
+
+ json.dumps(MEMO_JSON)
|
| 82 |
+
)
|
| 83 |
+
memo = parse_model_json(raw)
|
| 84 |
+
|
| 85 |
+
self.assertEqual(memo["executive_memo"], MEMO_JSON["executive_memo"])
|
| 86 |
+
|
| 87 |
def test_run_model_assist_uses_selected_model(self) -> None:
|
| 88 |
result, narrative = analyze_trace_file(Path("examples/sample_trace_redacted.jsonl"))
|
| 89 |
generate = RecordingGenerator()
|