File size: 2,927 Bytes
399944f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import ast
import json
from typing import Any, Callable, Dict, TypeVar
import rich

from kbdebugger.utils.json import _extract_json_object

T = TypeVar("T")

def parse_response(
    raw: str,
    *,
    coercer: Callable[[Dict[str, Any]], T],
    default: T,
) -> T:
    """
    Fast, ordered parser for LLM outputs.

    Attempts (in order):
      1. literal_eval(full)
      2. json.loads(full)
      3. literal_eval(extracted {...})
      4. json.loads(extracted {...})

    As soon as a dict is obtained, it is passed to `coercer`.
    If coercer returns a non-default value → return it immediately.

    Otherwise → continue to next attempt.
    If all fail → return default.
    """

    s = raw.strip()
    if not s:
        return default

    # -------------------------
    # Helper: try parser + coercer
    # -------------------------
    def try_parse_and_coerce(parse_fn, input_str, label: str):
        try:
            data = parse_fn(input_str)
            if isinstance(data, dict):
                # rich.print(f"[parse_response] ✅ {label} produced dict.")
                result = coercer(data)
                if result != default and result is not None:
                    # rich.print(f"[parse_response] ✅ coercer accepted dict ({label}).")
                    return result
                else:
                    rich.print(f"[parse_response] ⚠️ coercer rejected dict ({label}).")
        except Exception as e:
            # Parsing failed — silently continue
            rich.print(f"[parse_response] … {label} failed: {e}")
        return None

    # --------------------------------
    # 1. literal_eval(raw)
    # --------------------------------
    out = try_parse_and_coerce(
        lambda x: ast.literal_eval(x),
        s,
        "literal_eval(full)"
    )
    if out is not None:
        return out

    # --------------------------------
    # 2. json.loads(raw)
    # --------------------------------
    out = try_parse_and_coerce(
        lambda x: json.loads(x),
        s,
        "json.loads(full)"
    )
    if out is not None:
        return out

    # --------------------------------
    # Extract {...}
    # --------------------------------
    obj_str = _extract_json_object(s)
    if obj_str:
        # 3. literal_eval(extracted)
        out = try_parse_and_coerce(
            lambda x: ast.literal_eval(x),
            obj_str,
            "literal_eval(extracted)"
        )
        if out is not None:
            return out

        # 4. json.loads(extracted)
        out = try_parse_and_coerce(
            lambda x: json.loads(x),
            obj_str,
            "json.loads(extracted)"
        )
        if out is not None:
            return out

    # --------------------------------
    # All failed
    # --------------------------------
    rich.print("[parse_response] ❌ No valid dict produced. Returning default.")
    return default