| import json |
| import re |
| from typing import Dict, Any |
|
|
| |
| |
|
|
|
|
| def _strip_code_fences(s: str) -> str: |
| """ |
| Hilangkan ```json ... ``` atau ``` ... ``` di sekitar output, jika ada. |
| """ |
| s = s.strip() |
| fence = re.compile(r"^```[a-zA-Z0-9]*\s*([\s\S]*?)\s*```$", re.MULTILINE) |
| m = fence.match(s) |
| return m.group(1).strip() if m else s |
|
|
|
|
| def _extract_json_object(s: str) -> str: |
| """ |
| Ambil substring JSON {...} pertama jika model nyampur teks lain. |
| """ |
| |
| |
| start = s.find("{") |
| if start == -1: |
| return s |
| |
| depth = 0 |
| for i in range(start, len(s)): |
| if s[i] == "{": |
| depth += 1 |
| elif s[i] == "}": |
| depth -= 1 |
| if depth == 0: |
| return s[start : i + 1] |
| return s |
|
|
|
|
| def _safe_parse_choice(content: str) -> Dict[str, bool | str]: |
| """ |
| Parse output model jadi dict {"use_tools": bool, "tool_name": str} |
| Tangani berbagai kemungkinan format yang 'nakal'. |
| """ |
| raw = _strip_code_fences(content) |
| raw = _extract_json_object(raw) |
|
|
| try: |
| obj = json.loads(raw) |
| except Exception: |
| |
| fixed = raw.replace("True", "true").replace("False", "false") |
| obj = json.loads(fixed) |
|
|
| if not isinstance(obj, dict): |
| raise ValueError("Model output is not a JSON object") |
|
|
| |
| if "use_tools" not in obj or "tool_name" not in obj: |
| raise ValueError("Missing required keys: use_tools/tool_name") |
|
|
| |
| use_tools = bool(obj["use_tools"]) |
| tool_name = str(obj["tool_name"]).strip() |
|
|
| |
| if not use_tools: |
| tool_name = "none" |
|
|
| return {"use_tools": use_tools, "tool_name": tool_name} |
|
|