| import json |
| import re |
| from typing import Dict, Any, List, Optional |
|
|
| |
| |
| |
| |
|
|
| def _fix_json_quotes(s: str) -> str: |
| """First-stage repair: handle incorrect quotes and basic structure.""" |
| |
| s = re.sub(r'\bTrue\b', 'true', s) |
| s = re.sub(r'\bFalse\b', 'false', s) |
| s = re.sub(r'\bNone\b', 'null', s) |
| |
| |
| |
| |
| try: |
| temp_s = s.replace("'", '"') |
| json.loads(temp_s) |
| return temp_s |
| except json.JSONDecodeError: |
| |
| pass |
|
|
| |
| s = re.sub(r'([\{\s,])(\w+)\s*:', r'\1"\2":', s) |
| return s |
|
|
| def _repair_reasoning_field_robust(json_str: str) -> str: |
| """Second-stage repair: specifically fix unescaped double quotes inside the 'reasoning' field.""" |
| pattern = re.compile( |
| r'("reasoning"\s*:\s*")' |
| r'(.*?)' |
| r'(?="\s*[,}])', |
| re.DOTALL |
| ) |
|
|
| def replacer(match): |
| prefix = match.group(1) |
| content = match.group(2) |
| |
| fixed_content = content.replace('"', '\\"') |
| return prefix + fixed_content |
|
|
| return pattern.sub(replacer, json_str) |
|
|
| def _fallback_extract_and_rebuild(input_str: str) -> str: |
| """Final fallback strategy: abandon repair, directly extract information, and rebuild a valid JSON.""" |
| |
| |
| reasoning_text = "" |
| reason_match = re.search(r'["\']reasoning["\']\s*:\s*["\']?(.*?)["\']?\s*,\s*["\']score["\']', input_str, re.DOTALL | re.IGNORECASE) |
| if reason_match: |
| reasoning_text = reason_match.group(1).strip() |
| |
| reasoning_text = reasoning_text.replace('\\"', '"') |
| else: |
| |
| |
| score_part_match = re.search(r'["\']score["\']\s*:.*', input_str, re.IGNORECASE) |
| if score_part_match: |
| reasoning_text = input_str[:score_part_match.start()].strip() |
| else: |
| |
| reasoning_text = input_str |
| |
| |
| scores = [] |
| |
| score_match = re.search(r'["\']score["\']\s*:\s*(.*)', input_str, re.DOTALL | re.IGNORECASE) |
| search_area = score_match.group(1) if score_match else input_str |
| |
| |
| numbers = re.findall(r'[-+]?\d*\.?\d+', search_area) |
| if numbers: |
| scores = [float(num) for num in numbers] |
|
|
| |
| rebuilt_data = { |
| "reasoning": reasoning_text, |
| "score": scores |
| } |
| return json.dumps(rebuilt_data, ensure_ascii=False) |
|
|
|
|
| def _format_and_validate_dict(data: Dict[str, Any]) -> Optional[Dict[str, Any]]: |
| """Validate and format the parsed dictionary to ensure it meets the final output standard.""" |
| if not isinstance(data, dict): |
| return None |
|
|
| |
| reasoning = "" |
| for key in ["reasoning", "reason", "rationale"]: |
| if key in data and isinstance(data[key], str): |
| reasoning = data[key] |
| break |
|
|
| |
| scores = [] |
| if 'score' in data: |
| score_val = data['score'] |
| if isinstance(score_val, list): |
| scores = [float(s) for s in score_val if isinstance(s, (int, float, str))] |
| elif isinstance(score_val, (int, float)): |
| scores = [float(score_val)] |
| |
| |
| if reasoning or scores: |
| return {"score": scores, "reasoning": reasoning} |
| |
| return None |
|
|
| |
| |
| |
|
|
| def parse_vlm_output_to_dict(input_string: str) -> Dict[str, Any]: |
| """ |
| A highly robust function to parse a VLM's output string into a dictionary |
| containing 'score' and 'reasoning'. |
| |
| It uses a multi-stage repair pipeline, progressively degrading from standard |
| JSON parsing to a final information extraction fallback. |
| """ |
| |
| if not input_string or not input_string.strip(): |
| return {"score": [], "reasoning": "Input was empty."} |
| |
| |
| json_match = re.search(r'\{.*\}', input_string, re.DOTALL) |
| target_str = json_match.group(0) if json_match else input_string.strip() |
|
|
| |
| |
| |
| fixer_pipeline = [ |
| lambda s: s, |
| _fix_json_quotes, |
| _repair_reasoning_field_robust, |
| ] |
|
|
| for fixer in fixer_pipeline: |
| try: |
| fixed_str = fixer(target_str) |
| data = json.loads(fixed_str) |
| validated_data = _format_and_validate_dict(data) |
| if validated_data is not None: |
| return validated_data |
| except (json.JSONDecodeError, TypeError): |
| |
| continue |
| |
| |
| |
| try: |
| fallback_str = _fallback_extract_and_rebuild(target_str) |
| |
| data = json.loads(fallback_str) |
| |
| validated_data = _format_and_validate_dict(data) |
| if validated_data: |
| return validated_data |
| except Exception: |
| |
| pass |
| |
| return { |
| "score": [], |
| "reasoning": f"Failed to parse after all strategies. Original output: '{input_string}'" |
| } |
|
|