Spaces:
Running
Running
S0-R1-C2 Dual-Engine Four-Language Claim-Citation Semantic Binding
#75
by GuXiaopeng - opened
- core/generation_runner.py +9 -9
- core/pipeline_agent.py +58 -9
core/generation_runner.py
CHANGED
|
@@ -3122,17 +3122,17 @@ The previous attempt for this same manuscript unit was rejected by validation. R
|
|
| 3122 |
f"section_content_invalid_after_retry:{display_name}:{pre_validation_rows[:3]}"
|
| 3123 |
)
|
| 3124 |
|
| 3125 |
-
|
| 3126 |
-
|
| 3127 |
-
|
| 3128 |
-
|
| 3129 |
-
|
| 3130 |
-
|
| 3131 |
-
)
|
| 3132 |
self._progress(pct, f"citation_audit:{current_index}:{total_steps}")
|
| 3133 |
used_before = set(agent.used_citations)
|
| 3134 |
en_resolved = agent.audit_and_resolve_citations(
|
| 3135 |
-
prefix + en_raw, section_name=display_name,
|
|
|
|
| 3136 |
)
|
| 3137 |
# Capture immediately: citation recovery may later replace the
|
| 3138 |
# agent's last-violation buffer. These violations belong to the
|
|
@@ -3213,7 +3213,7 @@ The previous attempt for this same manuscript unit was rejected by validation. R
|
|
| 3213 |
recovered = agent.recover_citations_on_existing_claims(
|
| 3214 |
en_resolved,
|
| 3215 |
recovery_items[:needed],
|
| 3216 |
-
section_name=display_name,
|
| 3217 |
needed=needed,
|
| 3218 |
)
|
| 3219 |
if recovered != en_resolved:
|
|
|
|
| 3122 |
f"section_content_invalid_after_retry:{display_name}:{pre_validation_rows[:3]}"
|
| 3123 |
)
|
| 3124 |
|
| 3125 |
+
# S0-R1-C2: the authoritative path never attaches a citation merely
|
| 3126 |
+
# to satisfy section-level coverage. The writer-owned claim/evidence
|
| 3127 |
+
# plan is audited as written; any validated coverage shortfall flows
|
| 3128 |
+
# to the existing claim-anchored recovery path below. This rule is
|
| 3129 |
+
# shared by ASQ and AMJ and keeps the sealed English citation binding
|
| 3130 |
+
# authoritative for ZH/RU/KK translation.
|
|
|
|
| 3131 |
self._progress(pct, f"citation_audit:{current_index}:{total_steps}")
|
| 3132 |
used_before = set(agent.used_citations)
|
| 3133 |
en_resolved = agent.audit_and_resolve_citations(
|
| 3134 |
+
prefix + en_raw, section_name=display_name, section_id=section_id,
|
| 3135 |
+
quarantine=is_quarantined
|
| 3136 |
)
|
| 3137 |
# Capture immediately: citation recovery may later replace the
|
| 3138 |
# agent's last-violation buffer. These violations belong to the
|
|
|
|
| 3213 |
recovered = agent.recover_citations_on_existing_claims(
|
| 3214 |
en_resolved,
|
| 3215 |
recovery_items[:needed],
|
| 3216 |
+
section_name=display_name, section_id=section_id,
|
| 3217 |
needed=needed,
|
| 3218 |
)
|
| 3219 |
if recovered != en_resolved:
|
core/pipeline_agent.py
CHANGED
|
@@ -3148,7 +3148,7 @@ INPUT:
|
|
| 3148 |
return f"{core.rstrip()} {tag}{punctuation}{trailing}"
|
| 3149 |
|
| 3150 |
def recover_citations_on_existing_claims(
|
| 3151 |
-
self, text, items, *, section_name='', needed=1,
|
| 3152 |
):
|
| 3153 |
"""Attach recovery citations only to claims already present in the unit.
|
| 3154 |
|
|
@@ -3233,9 +3233,21 @@ INPUT:
|
|
| 3233 |
expected_tags = {tag for _start, _end, _sentence, tag in ranked_assignments}
|
| 3234 |
audit_len = len(self.citation_audit_log)
|
| 3235 |
try:
|
| 3236 |
-
|
| 3237 |
-
|
| 3238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3239 |
except PipelineCancelledError:
|
| 3240 |
raise
|
| 3241 |
except Exception as exc:
|
|
@@ -3280,16 +3292,32 @@ INPUT:
|
|
| 3280 |
)
|
| 3281 |
return ''
|
| 3282 |
|
| 3283 |
-
def _candidate_citation_tags(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3284 |
valid_required = [
|
| 3285 |
tag for tag in required_tags
|
| 3286 |
-
if tag in self.citation_map
|
|
|
|
|
|
|
| 3287 |
]
|
| 3288 |
sentence_terms = self._citation_terms(sentence)
|
| 3289 |
scored = []
|
| 3290 |
for tag, metadata in self.citation_map.items():
|
| 3291 |
if tag in valid_required:
|
| 3292 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3293 |
evidence = " ".join(
|
| 3294 |
[
|
| 3295 |
str(metadata.get("ref", "")),
|
|
@@ -4145,6 +4173,7 @@ INPUT:
|
|
| 4145 |
self,
|
| 4146 |
text,
|
| 4147 |
section_name="",
|
|
|
|
| 4148 |
):
|
| 4149 |
"""Audit citation support while granting the audit TAG-ONLY mutation authority.
|
| 4150 |
|
|
@@ -4188,6 +4217,7 @@ INPUT:
|
|
| 4188 |
sentence,
|
| 4189 |
required_tags,
|
| 4190 |
candidate_limit,
|
|
|
|
| 4191 |
)
|
| 4192 |
evidence = []
|
| 4193 |
for tag in candidates:
|
|
@@ -4228,7 +4258,7 @@ Rules:
|
|
| 4228 |
1. Preserve the sentence's substantive meaning, every number, percentage, coefficient, quotation, variable label, and empirical direction.
|
| 4229 |
2. Every output sentence may contain at most one [CITE-XX] tag.
|
| 4230 |
3. When several sources support the same claim, retain only the single source whose supplied evidence most directly supports that claim.
|
| 4231 |
-
4.
|
| 4232 |
5. A source may be used only when its supplied title/abstract evidence directly supports the adjacent claim. Do not infer support from the reference title alone when the evidence is empty or unrelated.
|
| 4233 |
6. Use only candidate tags supplied for that item. Never invent a source, fact, number, quotation, or citation tag.
|
| 4234 |
7. If no candidate source directly supports a claim that requires external evidence, set status to unsupported and leave revised_text equal to the original sentence.
|
|
@@ -4360,6 +4390,23 @@ Return exactly:
|
|
| 4360 |
)
|
| 4361 |
continue
|
| 4362 |
if status == "split":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4363 |
violation = {
|
| 4364 |
"type":"citation_claim_split_required", "id":record["id"],
|
| 4365 |
"sentence":record["original"][:500], "reason":reason,
|
|
@@ -4562,7 +4609,7 @@ Return exactly:
|
|
| 4562 |
)
|
| 4563 |
return revised
|
| 4564 |
|
| 4565 |
-
def audit_and_resolve_citations(self, text, section_name='', quarantine=False):
|
| 4566 |
self._last_citation_semantic_violations = []
|
| 4567 |
value = str(text or '')
|
| 4568 |
if quarantine:
|
|
@@ -4576,7 +4623,9 @@ Return exactly:
|
|
| 4576 |
violations = self.find_multi_citation_violations(value)
|
| 4577 |
if has_citations and self.citation_semantic_audit.get('enabled', True):
|
| 4578 |
try:
|
| 4579 |
-
value = self.semantic_audit_claim_specific_citations(
|
|
|
|
|
|
|
| 4580 |
except PipelineCancelledError:
|
| 4581 |
raise
|
| 4582 |
except CitationAuditError as error:
|
|
|
|
| 3148 |
return f"{core.rstrip()} {tag}{punctuation}{trailing}"
|
| 3149 |
|
| 3150 |
def recover_citations_on_existing_claims(
|
| 3151 |
+
self, text, items, *, section_name='', section_id='', needed=1,
|
| 3152 |
):
|
| 3153 |
"""Attach recovery citations only to claims already present in the unit.
|
| 3154 |
|
|
|
|
| 3233 |
expected_tags = {tag for _start, _end, _sentence, tag in ranked_assignments}
|
| 3234 |
audit_len = len(self.citation_audit_log)
|
| 3235 |
try:
|
| 3236 |
+
audit_method = self.semantic_audit_claim_specific_citations
|
| 3237 |
+
audit_kwargs = {
|
| 3238 |
+
'section_name': f"{section_name} — claim-anchored citation recovery",
|
| 3239 |
+
}
|
| 3240 |
+
try:
|
| 3241 |
+
parameters = list(inspect.signature(audit_method).parameters.values())
|
| 3242 |
+
supports_section_id = any(
|
| 3243 |
+
item.name == 'section_id' or item.kind == inspect.Parameter.VAR_KEYWORD
|
| 3244 |
+
for item in parameters
|
| 3245 |
+
)
|
| 3246 |
+
except (TypeError, ValueError):
|
| 3247 |
+
supports_section_id = True
|
| 3248 |
+
if supports_section_id:
|
| 3249 |
+
audit_kwargs['section_id'] = section_id
|
| 3250 |
+
audited = audit_method(proposed, **audit_kwargs)
|
| 3251 |
except PipelineCancelledError:
|
| 3252 |
raise
|
| 3253 |
except Exception as exc:
|
|
|
|
| 3292 |
)
|
| 3293 |
return ''
|
| 3294 |
|
| 3295 |
+
def _candidate_citation_tags(
|
| 3296 |
+
self, sentence, required_tags, limit, *, section_id='', section_name=''
|
| 3297 |
+
):
|
| 3298 |
+
"""Return source-role-eligible semantic-audit candidates for one claim.
|
| 3299 |
+
|
| 3300 |
+
S0-R1-C2 closes the remaining candidate-pool ownership gap: both the
|
| 3301 |
+
writer-supplied tag and lexical alternatives must satisfy the same section
|
| 3302 |
+
source-role contract before the semantic auditor is allowed to consider
|
| 3303 |
+
them. Lexical overlap only ranks eligible candidates; it never authorizes
|
| 3304 |
+
attachment by itself.
|
| 3305 |
+
"""
|
| 3306 |
valid_required = [
|
| 3307 |
tag for tag in required_tags
|
| 3308 |
+
if tag in self.citation_map and self.citation_source_role_eligible(
|
| 3309 |
+
tag, section_id=section_id, section_name=section_name
|
| 3310 |
+
)
|
| 3311 |
]
|
| 3312 |
sentence_terms = self._citation_terms(sentence)
|
| 3313 |
scored = []
|
| 3314 |
for tag, metadata in self.citation_map.items():
|
| 3315 |
if tag in valid_required:
|
| 3316 |
continue
|
| 3317 |
+
if not self.citation_source_role_eligible(
|
| 3318 |
+
tag, metadata=metadata, section_id=section_id, section_name=section_name
|
| 3319 |
+
):
|
| 3320 |
+
continue
|
| 3321 |
evidence = " ".join(
|
| 3322 |
[
|
| 3323 |
str(metadata.get("ref", "")),
|
|
|
|
| 4173 |
self,
|
| 4174 |
text,
|
| 4175 |
section_name="",
|
| 4176 |
+
section_id="",
|
| 4177 |
):
|
| 4178 |
"""Audit citation support while granting the audit TAG-ONLY mutation authority.
|
| 4179 |
|
|
|
|
| 4217 |
sentence,
|
| 4218 |
required_tags,
|
| 4219 |
candidate_limit,
|
| 4220 |
+
section_id=section_id, section_name=section_name,
|
| 4221 |
)
|
| 4222 |
evidence = []
|
| 4223 |
for tag in candidates:
|
|
|
|
| 4258 |
1. Preserve the sentence's substantive meaning, every number, percentage, coefficient, quotation, variable label, and empirical direction.
|
| 4259 |
2. Every output sentence may contain at most one [CITE-XX] tag.
|
| 4260 |
3. When several sources support the same claim, retain only the single source whose supplied evidence most directly supports that claim.
|
| 4261 |
+
4. Use status=split only when no single candidate source directly supports the complete original sentence AND at least two distinct candidate sources are needed for genuinely different claims. If one candidate source directly supports the complete sentence, status must be ok and that single tag must be selected; do not split merely because the sentence has multiple clauses.
|
| 4262 |
5. A source may be used only when its supplied title/abstract evidence directly supports the adjacent claim. Do not infer support from the reference title alone when the evidence is empty or unrelated.
|
| 4263 |
6. Use only candidate tags supplied for that item. Never invent a source, fact, number, quotation, or citation tag.
|
| 4264 |
7. If no candidate source directly supports a claim that requires external evidence, set status to unsupported and leave revised_text equal to the original sentence.
|
|
|
|
| 4390 |
)
|
| 4391 |
continue
|
| 4392 |
if status == "split":
|
| 4393 |
+
# S0-R1-C2 decision-consistency postcondition. The audit
|
| 4394 |
+
# contract permits split only when different sources are
|
| 4395 |
+
# actually required. If the auditor itself returns exactly
|
| 4396 |
+
# one eligible tag, that is evidence that one source supports
|
| 4397 |
+
# the complete claim; keep the original sentence and bind that
|
| 4398 |
+
# one tag instead of consuming the Section retry.
|
| 4399 |
+
if len(revised_tags) == 1:
|
| 4400 |
+
selected = next(iter(revised_tags))
|
| 4401 |
+
replacements[record["id"]] = self._citation_tag_only_rewrite(
|
| 4402 |
+
record["original"], selected
|
| 4403 |
+
)
|
| 4404 |
+
self._append_citation_audit(
|
| 4405 |
+
section_name, "normalize_inconsistent_split_to_single_source",
|
| 4406 |
+
record["original"], replacements[record["id"]],
|
| 4407 |
+
reason or "Audit returned split with only one eligible supporting source.",
|
| 4408 |
+
)
|
| 4409 |
+
continue
|
| 4410 |
violation = {
|
| 4411 |
"type":"citation_claim_split_required", "id":record["id"],
|
| 4412 |
"sentence":record["original"][:500], "reason":reason,
|
|
|
|
| 4609 |
)
|
| 4610 |
return revised
|
| 4611 |
|
| 4612 |
+
def audit_and_resolve_citations(self, text, section_name='', section_id='', quarantine=False):
|
| 4613 |
self._last_citation_semantic_violations = []
|
| 4614 |
value = str(text or '')
|
| 4615 |
if quarantine:
|
|
|
|
| 4623 |
violations = self.find_multi_citation_violations(value)
|
| 4624 |
if has_citations and self.citation_semantic_audit.get('enabled', True):
|
| 4625 |
try:
|
| 4626 |
+
value = self.semantic_audit_claim_specific_citations(
|
| 4627 |
+
value, section_name=section_name, section_id=section_id
|
| 4628 |
+
)
|
| 4629 |
except PipelineCancelledError:
|
| 4630 |
raise
|
| 4631 |
except CitationAuditError as error:
|