diff --git a/graphify/extract.py b/graphify/extract.py index fb7741d6..f40fa928 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -12164,16 +12164,17 @@ def _extract_parallel( try: with concurrent.futures.ProcessPoolExecutor(max_workers=max_workers) as pool: futures = { - pool.submit(_extract_single_file, item): item[0] for item in work_items + pool.submit(_extract_single_file, item): pos + for pos, item in enumerate(work_items) } for future in concurrent.futures.as_completed(futures): try: idx, result = future.result() per_file[idx] = result except Exception as exc: - idx = futures[future] + pos = futures[future] print( - f" warning: worker failed for {work_items[idx][1]}: {exc}", + f" warning: worker failed for {work_items[pos][1]}: {exc}", file=sys.stderr, flush=True, ) done_count += 1 diff --git a/graphify/llm.py b/graphify/llm.py index 335e1b3f..c0d294a5 100644 --- a/graphify/llm.py +++ b/graphify/llm.py @@ -721,7 +721,12 @@ def _parse_llm_json(raw: str) -> dict: else: stripped = after_fence.strip() try: - return json.loads(stripped) + parsed = json.loads(stripped) + if isinstance(parsed, dict): + return parsed + # Top-level array/scalar (common LLM output) is not a usable graph + # fragment; fall through to the next strategy rather than returning a + # non-dict that callers will try to subscript (e.g. result["input_tokens"]). except json.JSONDecodeError: pass # Strategy 2: extract the first balanced JSON object found anywhere in @@ -751,7 +756,10 @@ def _parse_llm_json(raw: str) -> dict: depth -= 1 if depth == 0: try: - return json.loads(stripped[start : i + 1]) + parsed = json.loads(stripped[start : i + 1]) + if isinstance(parsed, dict): + return parsed + break except json.JSONDecodeError: break print( diff --git a/graphify/tree_html.py b/graphify/tree_html.py index 3d825add..94b10df4 100644 --- a/graphify/tree_html.py +++ b/graphify/tree_html.py @@ -549,7 +549,7 @@ def emit_html( ) -> str: # Escape sequences so embedded JSON cannot break out of the #