qwen-agentworld7 域 LWM 评测
§ · Nested detail page

Judge JSON 解析与容错 深度解析

本页只展开一个模块,把导入契约、核心实现、流程和失败模式拆成可回溯行号。

§ 00 · IMPORT CHAIN

导入链与契约

入口代码说明该模块接受什么输入、输出什么结构,以及它依赖哪些相邻模块。

▸入口片段

src/eval/lwm_eval_utils/judge_parser.py · L28-63 repo: eval/lwm_eval_utils/judge_parser.pyPython
  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 或输出多个对象。

§ 01 · CORE IMPL

核心实现,逐段拆

▸中段状态转换

这一段通常体现最关键的状态转换:解析、选择、打分、写入或安全检查。

src/eval/lwm_eval_utils/judge_parser.py · L64-103 repo: eval/lwm_eval_utils/judge_parser.pyPython
  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 保留错误原因。

▸末段收束

收束段决定失败是否显式返回、结果是否稳定落盘,以及调用者能否继续恢复。

src/eval/lwm_eval_utils/judge_parser.py · L145-180 repo: eval/lwm_eval_utils/judge_parser.pyPython
 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。

§ 02 · KEY FLOW

关键流程

▸流程拆解

input → normalize → validate → output
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 输出中的说明文字。

§ 03 · EDGE CASES

边界 / 失败模式

▸边界列表

  • 空输入或缺失字段必须被显式处理。
  • 外部命令、模型调用或文件写入失败不能覆盖原始上下文。
  • 路径别名仅用于索引兼容;实际源码位置见每个代码块的 repo 标注。