| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- from __future__ import annotations
- import json
- import re
- import unicodedata
- from dataclasses import dataclass
- from typing import Any
- from app.agents.runtime import get_agent_runtime
- @dataclass
- class EditJudgement:
- is_extraction_error: bool
- reason: str
- confidence: float
- source: str
- def _parse_json_object(text: str) -> dict[str, Any] | None:
- cleaned = text.strip()
- if cleaned.startswith("```"):
- cleaned = re.sub(r"^```(?:json)?\s*", "", cleaned)
- cleaned = re.sub(r"\s*```$", "", cleaned)
- start = cleaned.find("{")
- end = cleaned.rfind("}")
- if start < 0 or end <= start:
- return None
- try:
- value = json.loads(cleaned[start : end + 1])
- except json.JSONDecodeError:
- return None
- return value if isinstance(value, dict) else None
- def _normalize_text(value: str) -> str:
- normalized = unicodedata.normalize("NFKC", str(value or "")).lower()
- return re.sub(r"[\s,。;、,.;::!!??\"'“”‘’()()\[\]【】]+", "", normalized)
- def _token_set(value: str) -> set[str]:
- return {
- token
- for token in re.split(r"[\s,。;、,.;::|/]+", str(value or "").strip())
- if token
- }
- def _fallback_judgement(
- original_value: str,
- edited_value: str,
- ) -> EditJudgement:
- original_normalized = _normalize_text(original_value)
- edited_normalized = _normalize_text(edited_value)
- if original_normalized == edited_normalized:
- return EditJudgement(
- is_extraction_error=False,
- reason="修改仅涉及空格、标点或显示格式,不判定为抽取错误。",
- confidence=0.95,
- source="fallback",
- )
- if (
- original_normalized
- and edited_normalized
- and (
- original_normalized in edited_normalized
- or edited_normalized in original_normalized
- )
- ):
- return EditJudgement(
- is_extraction_error=False,
- reason="修改主要属于内容截断、补充说明或显示长度调整,不判定为抽取错误。",
- confidence=0.8,
- source="fallback",
- )
- original_tokens = _token_set(original_value)
- edited_tokens = _token_set(edited_value)
- if original_tokens and original_tokens == edited_tokens:
- return EditJudgement(
- is_extraction_error=False,
- reason="字段内容仅调整顺序或分隔方式,不判定为抽取错误。",
- confidence=0.85,
- source="fallback",
- )
- return EditJudgement(
- is_extraction_error=True,
- reason="字段值发生了实质变化,暂按原抽取结果错误记录。",
- confidence=0.55,
- source="fallback",
- )
- def _normalize_model_confidence(value: Any) -> float:
- try:
- number = float(value)
- except (TypeError, ValueError):
- return 0.7
- if number > 1:
- number /= 100
- return max(0.0, min(number, 1.0))
- async def judge_extraction_edit(
- *,
- field_name: str,
- original_value: str,
- edited_value: str,
- evidence_text: str = "",
- document_title: str = "",
- extraction_id: str = "",
- ) -> EditJudgement:
- fallback = _fallback_judgement(original_value, edited_value)
- runtime = get_agent_runtime()
- if not runtime.enabled:
- return fallback
- prompt = f"""
- 你是煤矿业务资料字段抽取质量判定器。用户修改了系统抽取出的字段值,
- 请判断这次修改是否说明“原抽取结果存在抽取错误”。
- 判定规则:
- 1. 如果用户只是删除多余内容、截断过长文本、调整换行或空格、统一标点、调整顺序、
- 简化表达、补充说明或改变显示格式,则应判定为“非抽取错误”。
- 2. 如果用户修正了实质性的数值、日期、单位、名称、地点、结论或字段含义,
- 则应判定为“抽取错误”。
- 3. 只判断修改原因是否属于抽取错误,不要评价修改后的文字是否更好。
- 资料标题:{document_title or "未提供"}
- 字段名称:{field_name}
- 原抽取值:{original_value[:1200]}
- 用户修改后:{edited_value[:1200]}
- 原文证据:{evidence_text[:1200] or "未提供"}
- 只输出 JSON:
- {{"is_extraction_error": true, "reason": "简短中文理由", "confidence": 0.9}}
- """.strip()
- result = await runtime.invoke(
- message=prompt,
- thread_id=f"extraction-edit-judgement-{extraction_id or field_name}",
- context={"purpose": "field_edit_accuracy"},
- )
- if result.status != "success":
- return fallback
- parsed = _parse_json_object(result.output)
- if parsed is None or "is_extraction_error" not in parsed:
- return fallback
- raw_flag = parsed.get("is_extraction_error")
- if isinstance(raw_flag, str):
- is_extraction_error = raw_flag.strip().lower() in {"true", "yes", "1", "是"}
- else:
- is_extraction_error = bool(raw_flag)
- return EditJudgement(
- is_extraction_error=is_extraction_error,
- reason=str(parsed.get("reason") or ("属于抽取错误" if is_extraction_error else "不属于抽取错误")),
- confidence=_normalize_model_confidence(parsed.get("confidence")),
- source="llm",
- )
|