Spaces:
Running
Running
S0-R1-C5.5-R6-R6 Canonical Glossary Reviewer Authority Closure
Browse files- core/pipeline_agent.py +174 -52
core/pipeline_agent.py
CHANGED
|
@@ -74,7 +74,7 @@ class SSciPipelineAgent:
|
|
| 74 |
MAX_TRANSLATION_CLAIM_REPAIR_ATTEMPTS = 2
|
| 75 |
MAX_TRANSLATION_CLAIM_TOTAL_REPAIRS = 6
|
| 76 |
PROTECTED_ENTITY_LEDGER_VERSION = 'protected-entity-ledger-v2'
|
| 77 |
-
CANONICAL_GLOSSARY_QUALITY_VERSION = 'canonical-glossary-quality-
|
| 78 |
SEMANTIC_CLAIM_FRAME_VERSION = 'smartpaper-core-semantic-claim-frame-v1'
|
| 79 |
CITATION_CLAIM_FRAME_VERSION = 'smartpaper-citation-claim-frame-v1'
|
| 80 |
|
|
@@ -7242,16 +7242,73 @@ TEXT:
|
|
| 7242 |
# reduplication is left alone because it can be grammatical Chinese.
|
| 7243 |
if re.search(r'([\u4e00-\u9fff]{2,4})\1', compact):
|
| 7244 |
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7245 |
return False
|
| 7246 |
|
| 7247 |
-
|
| 7248 |
-
|
|
|
|
| 7249 |
|
| 7250 |
-
The
|
| 7251 |
-
|
| 7252 |
-
|
| 7253 |
-
|
| 7254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7255 |
"""
|
| 7256 |
mapping = dict(glossary or {})
|
| 7257 |
rows = (dict(concept_ledger or {}).get('items') or []) if isinstance(concept_ledger, dict) else []
|
|
@@ -7271,67 +7328,132 @@ TEXT:
|
|
| 7271 |
{'source': source, 'candidate_target': str(mapping.get(source) or '').strip()}
|
| 7272 |
for source in strict_sources
|
| 7273 |
]
|
| 7274 |
-
|
| 7275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7276 |
language_rule = self._translation_language_rule(target_lang)
|
| 7277 |
-
|
| 7278 |
-
(
|
| 7279 |
-
|
| 7280 |
-
|
| 7281 |
-
|
| 7282 |
-
|
| 7283 |
-
|
| 7284 |
-
|
| 7285 |
-
|
| 7286 |
-
|
| 7287 |
-
|
| 7288 |
-
|
| 7289 |
-
|
| 7290 |
-
|
| 7291 |
-
|
| 7292 |
-
|
| 7293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7294 |
returned = {}
|
| 7295 |
for item in items:
|
| 7296 |
if not isinstance(item, dict):
|
| 7297 |
continue
|
| 7298 |
source = re.sub(r'\s+', ' ', str(item.get('source') or '')).strip()
|
| 7299 |
-
if source:
|
| 7300 |
returned[source] = item
|
| 7301 |
-
|
| 7302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7303 |
|
| 7304 |
refined = dict(mapping)
|
| 7305 |
for source in strict_sources:
|
| 7306 |
-
item = returned[source]
|
| 7307 |
candidate = str(mapping.get(source) or '').strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7308 |
accepted_raw = item.get('accepted')
|
| 7309 |
accepted = accepted_raw is True or str(accepted_raw).strip().casefold() == 'true'
|
| 7310 |
reviewed_target = re.sub(r'\s+', ' ', str(item.get('target') or '')).strip()
|
| 7311 |
-
|
| 7312 |
-
self.
|
| 7313 |
-
|
| 7314 |
)
|
| 7315 |
-
|
| 7316 |
-
|
| 7317 |
-
|
| 7318 |
-
|
| 7319 |
-
|
| 7320 |
-
|
|
|
|
| 7321 |
raise DeepSeekRequestError(
|
| 7322 |
-
f'Canonical concept
|
| 7323 |
)
|
| 7324 |
-
|
| 7325 |
-
|
| 7326 |
-
|
| 7327 |
-
|
| 7328 |
-
|
| 7329 |
-
|
| 7330 |
-
|
| 7331 |
-
|
| 7332 |
-
|
| 7333 |
-
|
| 7334 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7335 |
)
|
| 7336 |
return refined
|
| 7337 |
|
|
|
|
| 74 |
MAX_TRANSLATION_CLAIM_REPAIR_ATTEMPTS = 2
|
| 75 |
MAX_TRANSLATION_CLAIM_TOTAL_REPAIRS = 6
|
| 76 |
PROTECTED_ENTITY_LEDGER_VERSION = 'protected-entity-ledger-v2'
|
| 77 |
+
CANONICAL_GLOSSARY_QUALITY_VERSION = 'canonical-glossary-quality-v3'
|
| 78 |
SEMANTIC_CLAIM_FRAME_VERSION = 'smartpaper-core-semantic-claim-frame-v1'
|
| 79 |
CITATION_CLAIM_FRAME_VERSION = 'smartpaper-citation-claim-frame-v1'
|
| 80 |
|
|
|
|
| 7242 |
# reduplication is left alone because it can be grammatical Chinese.
|
| 7243 |
if re.search(r'([\u4e00-\u9fff]{2,4})\1', compact):
|
| 7244 |
return True
|
| 7245 |
+
# Canonical labels are short noun phrases. Immediate repetition of the
|
| 7246 |
+
# same substantive token is therefore a high-confidence malformed
|
| 7247 |
+
# surface in Russian/Kazakh (and remains safe for embedded acronyms).
|
| 7248 |
+
words = [
|
| 7249 |
+
token.casefold() for token in re.findall(
|
| 7250 |
+
r"[A-Za-zА-Яа-яЁёӘәҒғҚқҢңӨөҰұҮүҺһІі][A-Za-zА-Яа-яЁёӘәҒғҚқҢңӨөҰұҮүҺһІі'’\-]{2,}",
|
| 7251 |
+
value,
|
| 7252 |
+
)
|
| 7253 |
+
]
|
| 7254 |
+
if any(left == right for left, right in zip(words, words[1:])):
|
| 7255 |
+
return True
|
| 7256 |
return False
|
| 7257 |
|
| 7258 |
+
@classmethod
|
| 7259 |
+
def _canonical_target_deterministic_defects(cls, source, target, target_lang):
|
| 7260 |
+
"""Return only deterministic defects that make a canonical label unsafe to freeze.
|
| 7261 |
|
| 7262 |
+
The model-based quality reviewer is secondary evidence. It may improve a
|
| 7263 |
+
safe candidate, but it must never turn an otherwise deterministic-safe
|
| 7264 |
+
label into a task-fatal error merely because the review output is
|
| 7265 |
+
contradictory, incomplete, or non-actionable.
|
| 7266 |
+
"""
|
| 7267 |
+
source_value = re.sub(r'\s+', ' ', str(source or '')).strip()
|
| 7268 |
+
target_value = re.sub(r'\s+', ' ', str(target or '')).strip()
|
| 7269 |
+
defects = []
|
| 7270 |
+
if not target_value:
|
| 7271 |
+
return ['EMPTY_TARGET']
|
| 7272 |
+
|
| 7273 |
+
compact_source = re.sub(r'[^A-Za-z0-9]', '', source_value)
|
| 7274 |
+
pure_technical_source = bool(compact_source) and ' ' not in source_value and (
|
| 7275 |
+
compact_source.isupper()
|
| 7276 |
+
or any(ch.isdigit() for ch in compact_source)
|
| 7277 |
+
or (len(compact_source) > 2 and any(ch.isupper() for ch in compact_source[1:]))
|
| 7278 |
+
)
|
| 7279 |
+
if not pure_technical_source:
|
| 7280 |
+
if target_value.casefold() == source_value.casefold() and re.search(r'[a-z]{4,}', source_value):
|
| 7281 |
+
defects.append('UNTRANSLATED')
|
| 7282 |
+
language_code = cls._translation_language_profile(target_lang).get('language_code')
|
| 7283 |
+
if language_code == 'zh' and not re.search(r'[\u4e00-\u9fff]', target_value):
|
| 7284 |
+
defects.append('TARGET_LANGUAGE_SIGNAL_MISSING')
|
| 7285 |
+
if language_code in {'ru', 'kk'} and not re.search(r'[А-Яа-яЁёӘәҒғҚқҢңӨөҰұҮүҺһІі]', target_value):
|
| 7286 |
+
defects.append('TARGET_LANGUAGE_SIGNAL_MISSING')
|
| 7287 |
+
|
| 7288 |
+
if cls._canonical_target_surface_suspicious(target_value, target_lang):
|
| 7289 |
+
defects.append('MALFORMED_SURFACE')
|
| 7290 |
+
if not cls._canonical_target_preserves_source_anchors(source_value, target_value):
|
| 7291 |
+
defects.append('PROTECTED_ANCHOR_LOSS')
|
| 7292 |
+
return list(dict.fromkeys(defects))
|
| 7293 |
+
|
| 7294 |
+
def validate_and_refine_canonical_concept_glossary(self, concept_ledger, target_lang, glossary):
|
| 7295 |
+
"""Review strict canonical terms without giving the reviewer unilateral veto power.
|
| 7296 |
+
|
| 7297 |
+
Contract:
|
| 7298 |
+
1. Server-owned deterministic invariants decide whether a candidate is
|
| 7299 |
+
safe enough to freeze (non-empty, translated target-language signal,
|
| 7300 |
+
well-formed surface, protected-anchor preservation).
|
| 7301 |
+
2. The independent model reviewer may replace a candidate only with one
|
| 7302 |
+
distinct, deterministic-safe correction.
|
| 7303 |
+
3. If the reviewer rejects/omits/contradicts a candidate but supplies no
|
| 7304 |
+
usable correction, an already safe candidate is retained with an
|
| 7305 |
+
assurance warning instead of aborting the whole manuscript.
|
| 7306 |
+
4. If the candidate itself is deterministic-unsafe and no safe correction
|
| 7307 |
+
exists, the gate remains fail-closed.
|
| 7308 |
+
|
| 7309 |
+
This prevents a secondary quality-review formatting/judgment failure from
|
| 7310 |
+
becoming a single point of failure across Chinese, Russian, and Kazakh
|
| 7311 |
+
translation while preserving genuine terminology-integrity barriers.
|
| 7312 |
"""
|
| 7313 |
mapping = dict(glossary or {})
|
| 7314 |
rows = (dict(concept_ledger or {}).get('items') or []) if isinstance(concept_ledger, dict) else []
|
|
|
|
| 7328 |
{'source': source, 'candidate_target': str(mapping.get(source) or '').strip()}
|
| 7329 |
for source in strict_sources
|
| 7330 |
]
|
| 7331 |
+
candidate_defects = {
|
| 7332 |
+
row['source']: self._canonical_target_deterministic_defects(
|
| 7333 |
+
row['source'], row['candidate_target'], target_lang
|
| 7334 |
+
)
|
| 7335 |
+
for row in payload
|
| 7336 |
+
}
|
| 7337 |
+
empty_sources = [source for source, defects in candidate_defects.items() if 'EMPTY_TARGET' in defects]
|
| 7338 |
+
if empty_sources:
|
| 7339 |
+
raise DeepSeekRequestError(
|
| 7340 |
+
'Canonical glossary quality review received an empty strict target: ' + ', '.join(empty_sources[:3])
|
| 7341 |
+
)
|
| 7342 |
+
|
| 7343 |
language_rule = self._translation_language_rule(target_lang)
|
| 7344 |
+
try:
|
| 7345 |
+
response = self.call_deepseek(
|
| 7346 |
+
(
|
| 7347 |
+
f"Independently review strict research-construct translations for {target_lang}. {language_rule} "
|
| 7348 |
+
"Judge semantic identity and natural academic terminology, not stylistic preference. "
|
| 7349 |
+
"Reject duplicated/malformed wording, concept narrowing/broadening, or a target that changes the construct. "
|
| 7350 |
+
"Every protected exact source anchor inside a construct (for example AI, LLM, model/tool IDs, hypothesis IDs, formulas, statistics or exact numbers) must remain byte-for-byte in the target canonical label; never localize, delete, duplicate, or rename such an anchor. "
|
| 7351 |
+
"If the candidate is sound, return it unchanged with accepted=true. If it is materially flawed, return exactly one corrected canonical target with accepted=false. "
|
| 7352 |
+
"Do not merge distinct constructs. Return JSON only as "
|
| 7353 |
+
'{"terms":[{"source":"...","candidate_target":"...","accepted":true,"target":"..."}]}'
|
| 7354 |
+
" and return every source exactly once."
|
| 7355 |
+
),
|
| 7356 |
+
json.dumps({'terms': payload}, ensure_ascii=False),
|
| 7357 |
+
max_tokens=max(1200, min(4200, 180 + len(payload) * 120)),
|
| 7358 |
+
temp=0.0, model_role='translation', thinking=False, allow_fallback=False,
|
| 7359 |
+
)
|
| 7360 |
+
parsed = self._extract_json_from_text(response)
|
| 7361 |
+
items = parsed.get('terms', []) if isinstance(parsed, dict) else []
|
| 7362 |
+
except DeepSeekRequestError as exc:
|
| 7363 |
+
unsafe = [source for source, defects in candidate_defects.items() if defects]
|
| 7364 |
+
if unsafe:
|
| 7365 |
+
source = unsafe[0]
|
| 7366 |
+
raise DeepSeekRequestError(
|
| 7367 |
+
f'Canonical concept has no deterministic-safe target after review failure: {source[:80]} '
|
| 7368 |
+
f'defects={candidate_defects[source]}'
|
| 7369 |
+
) from exc
|
| 7370 |
+
self._record_translation_assurance(
|
| 7371 |
+
'CANONICAL_GLOSSARY_REVIEW_UNAVAILABLE', target_lang=target_lang, severity='diagnostic',
|
| 7372 |
+
decision='PASS_WITH_WARNING', detail={'strict_sources': len(strict_sources)},
|
| 7373 |
+
)
|
| 7374 |
+
return mapping
|
| 7375 |
+
|
| 7376 |
returned = {}
|
| 7377 |
for item in items:
|
| 7378 |
if not isinstance(item, dict):
|
| 7379 |
continue
|
| 7380 |
source = re.sub(r'\s+', ' ', str(item.get('source') or '')).strip()
|
| 7381 |
+
if source in strict_sources:
|
| 7382 |
returned[source] = item
|
| 7383 |
+
extra_count = sum(
|
| 7384 |
+
1 for item in items if isinstance(item, dict)
|
| 7385 |
+
and re.sub(r'\s+', ' ', str(item.get('source') or '')).strip() not in strict_sources
|
| 7386 |
+
)
|
| 7387 |
+
if extra_count:
|
| 7388 |
+
self._record_translation_assurance(
|
| 7389 |
+
'CANONICAL_GLOSSARY_REVIEW_EXTRA_SOURCE', target_lang=target_lang, severity='diagnostic',
|
| 7390 |
+
decision='PASS_WITH_WARNING', detail={'extra_sources': extra_count},
|
| 7391 |
+
)
|
| 7392 |
|
| 7393 |
refined = dict(mapping)
|
| 7394 |
for source in strict_sources:
|
|
|
|
| 7395 |
candidate = str(mapping.get(source) or '').strip()
|
| 7396 |
+
defects = list(candidate_defects.get(source) or [])
|
| 7397 |
+
item = returned.get(source)
|
| 7398 |
+
if not item:
|
| 7399 |
+
if defects:
|
| 7400 |
+
raise DeepSeekRequestError(
|
| 7401 |
+
f'Canonical concept has no deterministic-safe target after review omission: {source[:80]} '
|
| 7402 |
+
f'defects={defects}'
|
| 7403 |
+
)
|
| 7404 |
+
self._record_translation_assurance(
|
| 7405 |
+
'CANONICAL_GLOSSARY_REVIEW_INCONCLUSIVE', target_lang=target_lang, severity='diagnostic',
|
| 7406 |
+
decision='PASS_WITH_WARNING', detail={'source': source[:120], 'reason': 'OMITTED_SOURCE'},
|
| 7407 |
+
)
|
| 7408 |
+
continue
|
| 7409 |
+
|
| 7410 |
accepted_raw = item.get('accepted')
|
| 7411 |
accepted = accepted_raw is True or str(accepted_raw).strip().casefold() == 'true'
|
| 7412 |
reviewed_target = re.sub(r'\s+', ' ', str(item.get('target') or '')).strip()
|
| 7413 |
+
reviewed_defects = (
|
| 7414 |
+
self._canonical_target_deterministic_defects(source, reviewed_target, target_lang)
|
| 7415 |
+
if reviewed_target else ['EMPTY_TARGET']
|
| 7416 |
)
|
| 7417 |
+
|
| 7418 |
+
# A sound acceptance means no remapping. If the reviewer claims to
|
| 7419 |
+
# accept but also changes the term, keep a safe frozen candidate; if
|
| 7420 |
+
# the candidate is unsafe, a distinct safe target may still rescue it.
|
| 7421 |
+
if accepted and reviewed_target in {'', candidate}:
|
| 7422 |
+
if not defects:
|
| 7423 |
+
continue
|
| 7424 |
raise DeepSeekRequestError(
|
| 7425 |
+
f'Canonical concept accepted despite deterministic defects: {source[:80]} defects={defects}'
|
| 7426 |
)
|
| 7427 |
+
|
| 7428 |
+
distinct_safe_correction = bool(
|
| 7429 |
+
reviewed_target and reviewed_target != candidate and not reviewed_defects
|
| 7430 |
+
)
|
| 7431 |
+
if distinct_safe_correction and (not accepted or defects):
|
| 7432 |
+
refined[source] = reviewed_target
|
| 7433 |
+
self._record_translation_assurance(
|
| 7434 |
+
'CANONICAL_GLOSSARY_REFINED', target_lang=target_lang, severity='repair',
|
| 7435 |
+
decision='REPAIR_LOCAL', detail={'source': source[:120]},
|
| 7436 |
+
)
|
| 7437 |
+
continue
|
| 7438 |
+
|
| 7439 |
+
if not defects:
|
| 7440 |
+
# Reviewer vetoes, omits a correction, proposes an invalid
|
| 7441 |
+
# correction, or contradicts accepted=true. None of these is
|
| 7442 |
+
# actionable evidence against an already deterministic-safe term.
|
| 7443 |
+
reason = 'NON_ACTIONABLE_REJECTION'
|
| 7444 |
+
if accepted and reviewed_target and reviewed_target != candidate:
|
| 7445 |
+
reason = 'ACCEPTED_BUT_CHANGED'
|
| 7446 |
+
elif reviewed_target and reviewed_target != candidate and reviewed_defects:
|
| 7447 |
+
reason = 'INVALID_CORRECTION'
|
| 7448 |
+
self._record_translation_assurance(
|
| 7449 |
+
'CANONICAL_GLOSSARY_REVIEW_INCONCLUSIVE', target_lang=target_lang, severity='diagnostic',
|
| 7450 |
+
decision='PASS_WITH_WARNING', detail={'source': source[:120], 'reason': reason},
|
| 7451 |
+
)
|
| 7452 |
+
continue
|
| 7453 |
+
|
| 7454 |
+
raise DeepSeekRequestError(
|
| 7455 |
+
f'Canonical concept has no deterministic-safe target after quality review: {source[:80]} '
|
| 7456 |
+
f'defects={defects}'
|
| 7457 |
)
|
| 7458 |
return refined
|
| 7459 |
|