Judge JSON 解析与容错 深度解析
本页只展开一个模块,把导入契约、核心实现、流程和失败模式拆成可回溯行号。
导入链与契约
入口代码说明该模块接受什么输入、输出什么结构,以及它依赖哪些相邻模块。
▸入口片段
28def parse_judge_output(raw_output: str, response_tag: str) -> Dict[str, Any]: 29 """Parse LLM Judge output and extract scores. 30 31 Args: 32 raw_output: The raw text output from the judge model. 33 response_tag: The tag wrapping the final JSON output (default: "final_evaluation"). 34 """ 35 try: 36 cleaned = raw_output.strip() if raw_output else "" 37 if not cleaned: 38 logger.warning("LLM Judge returned empty output") 39 return _get_error_result("Empty output", raw_output) 40 41 # Remove thinking tags 42 cleaned = _remove_thinking_tags(cleaned, response_tag=response_tag) 43 44 # Extract content from response tag if present 45 json_content = _extract_tagged_content(cleaned, response_tag) 46 47 # If no tagged content found, use the whole cleaned text 48 text_to_parse = json_content if json_content else cleaned 49 50 # Try extraction strategies in order on the target text 51 json_str = ( 52 _extract_from_markdown(text_to_parse) or 53 _extract_best_json_object(text_to_parse) or 54 _extract_last_json(text_to_parse) 55 ) 56 57 # If that failed, and we had a tag, maybe the tag content itself IS the JSON (without markdown blocks) 58 if not json_str and json_content: 59 json_str = ( 60 _extract_best_json_object(json_content) or 61 _extract_last_json(json_content) 62 ) 63
这个 parser 的第一层目标是把 judge 的自然语言输出收敛为 JSON。src/eval/lwm_eval_utils/judge_parser.py:28 定义入口,src/eval/lwm_eval_utils/judge_parser.py:36 先处理空白,src/eval/lwm_eval_utils/judge_parser.py:37 到 src/eval/lwm_eval_utils/judge_parser.py:39 把空输出变成结构化错误。这样 run_judge 不需要猜测 judge 是否真的返回了可用评分。
第二层是清理推理模型常见噪声。src/eval/lwm_eval_utils/judge_parser.py:41 到 src/eval/lwm_eval_utils/judge_parser.py:42 复用 output_parser 的 thinking tag 删除逻辑;src/eval/lwm_eval_utils/judge_parser.py:45 优先提取指定 response tag;src/eval/lwm_eval_utils/judge_parser.py:48 决定解析目标文本。如果 judge 没有严格包 tag,这里仍能继续尝试整段文本。
第三层是多策略 JSON 提取。src/eval/lwm_eval_utils/judge_parser.py:51 到 src/eval/lwm_eval_utils/judge_parser.py:55 依次尝试 Markdown JSON、最佳 JSON object、最后一个 JSON;src/eval/lwm_eval_utils/judge_parser.py:58 到 src/eval/lwm_eval_utils/judge_parser.py:62 又对 tag 内容本身做兜底。这个顺序很重要,因为 judge prompt 要求 final_evaluation 里放 Markdown JSON,但实际模型可能多写解释、漏写 fence 或输出多个对象。
核心实现,逐段拆
▸中段状态转换
这一段通常体现最关键的状态转换:解析、选择、打分、写入或安全检查。
64 if not json_str: 65 logger.warning("No valid JSON object found in LLM Judge output") 66 logger.debug(f"Raw output (first 500 chars): {raw_output[:500] if raw_output else ''}") 67 return _get_error_result("No JSON object found", raw_output) 68 69 # Repair and parse JSON 70 json_str = _repair_json(json_str) 71 try: 72 result = json.loads(json_str) 73 except json.JSONDecodeError as e: 74 repaired = _extract_scores_only(json_str) 75 if repaired: 76 result = json.loads(repaired) 77 else: 78 raise e 79 80 # Extract and validate scores 81 scores = _extract_scores(result) 82 if not scores: 83 logger.warning("Missing or invalid 'scores' field in LLM Judge output") 84 logger.debug(f"Available keys: {list(result.keys())}") 85 return _get_error_result(f"Invalid scores. Keys: {list(result.keys())}", raw_output) 86 87 valid_scores = [v for v in scores.values() if v > 0] 88 return { 89 "strengths": _to_list(result.get("strengths", [])), 90 "weaknesses": _to_list(result.get("weaknesses", [])), 91 "scores": scores, 92 "total_score": sum(valid_scores) / len(valid_scores) if valid_scores else 0, 93 "success": True, 94 "judge_raw_output": raw_output, 95 } 96 97 except json.JSONDecodeError as e: 98 logger.warning(f"JSON decode error parsing LLM Judge output: {e}") 99 logger.debug(f"Raw output (first 500 chars):\n{raw_output[:500] if raw_output else ''}") 100 return _get_error_result(f"JSON error: {e}", raw_output) 101 except Exception as e: 102 logger.warning(f"Unexpected error parsing LLM Judge output: {e}") 103 logger.debug(f"Raw output (first 500 chars):\n{raw_output[:500] if raw_output else ''}")
解析失败不会静默退化成零分。src/eval/lwm_eval_utils/judge_parser.py:64 到 src/eval/lwm_eval_utils/judge_parser.py:67 在完全找不到 JSON 时返回错误对象;src/eval/lwm_eval_utils/judge_parser.py:70 先尝试修复常见 JSON 形态;src/eval/lwm_eval_utils/judge_parser.py:72 做标准 json.loads;如果失败,src/eval/lwm_eval_utils/judge_parser.py:74 到 src/eval/lwm_eval_utils/judge_parser.py:78 只抽取 scores 子结构作为最后补救。
成功路径也有严格校验。src/eval/lwm_eval_utils/judge_parser.py:81 抽取五维分数,src/eval/lwm_eval_utils/judge_parser.py:82 到 src/eval/lwm_eval_utils/judge_parser.py:85 在 scores 缺失时返回错误;src/eval/lwm_eval_utils/judge_parser.py:87 过滤无效分;src/eval/lwm_eval_utils/judge_parser.py:88 到 src/eval/lwm_eval_utils/judge_parser.py:95 返回 strengths、weaknesses、scores、平均分和原始输出。异常兜底则在 src/eval/lwm_eval_utils/judge_parser.py:97 到 src/eval/lwm_eval_utils/judge_parser.py:104 保留错误原因。
▸末段收束
收束段决定失败是否显式返回、结果是否稳定落盘,以及调用者能否继续恢复。
145 for m in reversed(matches): 146 c = m.group(1).strip() 147 if c.startswith('{') and c.endswith('}') and '"scores"' in c: 148 return c 149 150 # Fallback to last valid JSON block 151 for m in reversed(matches): 152 c = m.group(1).strip() 153 if c.startswith('{') and c.endswith('}'): 154 return c 155 return None 156 157 158def _extract_best_json_object(text: str) -> Optional[str]: 159 """Extract all JSON objects, return last one with 'scores'.""" 160 objects = [] 161 pos = 0 162 while True: 163 start = text.find('{', pos) 164 if start == -1: 165 break 166 obj = _match_braces_forward(text, start) 167 if obj: 168 objects.append(obj) 169 pos = start + len(obj) 170 else: 171 pos = start + 1 172 173 if not objects: 174 return None 175 176 # Prefer object with "scores" 177 for obj in reversed(objects): 178 if '"scores"' in obj: 179 return obj 180 return objects[-1]
Markdown 提取偏向最后一个带 scores 的代码块。src/eval/lwm_eval_utils/judge_parser.py:138 定义从 fence 中取 JSON,src/eval/lwm_eval_utils/judge_parser.py:140 收集所有 code block,src/eval/lwm_eval_utils/judge_parser.py:145 反向遍历,src/eval/lwm_eval_utils/judge_parser.py:147 要求对象边界和 scores 字段,src/eval/lwm_eval_utils/judge_parser.py:150 到 src/eval/lwm_eval_utils/judge_parser.py:155 才退回“最后一个 JSON block”。这能过滤掉 judge 思考过程里的示例 JSON。
普通文本 JSON 则靠括号匹配。src/eval/lwm_eval_utils/judge_parser.py:158 定义对象提取,src/eval/lwm_eval_utils/judge_parser.py:163 找左花括号,src/eval/lwm_eval_utils/judge_parser.py:166 调用前向匹配,src/eval/lwm_eval_utils/judge_parser.py:176 到 src/eval/lwm_eval_utils/judge_parser.py:180 仍然优先带 scores 的最后对象。这个策略服务于评测稳定性:judge 可以先解释,最后再给最终 JSON。
关键流程
▸流程拆解
failure → explicit fallback / error record → caller decision
如果后续改动触碰这些行,主页面对应的风险判断也要重跑。括号匹配 helper 是 JSON 提取可靠性的底座:src/eval/lwm_eval_utils/judge_parser.py:183 从最后一个右括号反向找对象,src/eval/lwm_eval_utils/judge_parser.py:189 维护深度和字符串状态,src/eval/lwm_eval_utils/judge_parser.py:192 避免把字符串里的引号误判成边界,src/eval/lwm_eval_utils/judge_parser.py:195 到 src/eval/lwm_eval_utils/judge_parser.py:200 在深度归零时返回完整对象。
前向匹配处理的是文本里出现多个对象的情况。src/eval/lwm_eval_utils/judge_parser.py:205 定义从指定左括号开始匹配,src/eval/lwm_eval_utils/judge_parser.py:207 先校验起点,src/eval/lwm_eval_utils/judge_parser.py:210 维护 depth、in_str、escape,src/eval/lwm_eval_utils/judge_parser.py:216 处理字符串转义,src/eval/lwm_eval_utils/judge_parser.py:224 到 src/eval/lwm_eval_utils/judge_parser.py:229 在对象闭合时返回片段。这些细节解释了 parser 为什么能容忍 judge 输出中的说明文字。
边界 / 失败模式
▸边界列表
- 空输入或缺失字段必须被显式处理。
- 外部命令、模型调用或文件写入失败不能覆盖原始上下文。
- 路径别名仅用于索引兼容;实际源码位置见每个代码块的 repo 标注。