Spaces:
Running
Running
R14-TQ2-PQ1-R4-R2 UI Mode Evidence Completion Latency Fix
#67
by GuXiaopeng - opened
- app.py +47 -8
- core/generation_runner.py +38 -11
- core/pipeline_agent.py +11 -30
- core/qualitative_engine.py +6 -24
- i18n.py +8 -8
app.py
CHANGED
|
@@ -11,6 +11,7 @@ import shutil
|
|
| 11 |
import subprocess
|
| 12 |
import sys
|
| 13 |
import time
|
|
|
|
| 14 |
from datetime import datetime, timezone
|
| 15 |
from pathlib import Path
|
| 16 |
|
|
@@ -681,6 +682,11 @@ authenticated_user = require_alipay_login(_current_language_code())
|
|
| 681 |
if not bool(st.session_state.get("sp_task_recovery_initialized")):
|
| 682 |
recover_generation_task()
|
| 683 |
st.session_state["sp_task_recovery_initialized"] = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
_user_workspace = os.path.join(WORKSPACE_DIR, "users", _safe_key(authenticated_user.get("id")))
|
| 685 |
os.makedirs(_user_workspace, exist_ok=True)
|
| 686 |
|
|
@@ -749,16 +755,16 @@ def _render_monitor_box(task: dict, lang_code: str) -> None:
|
|
| 749 |
|
| 750 |
progress_markup = ""
|
| 751 |
if status in ACTIVE_TASK_STATUSES:
|
| 752 |
-
progress_markup = f"""
|
| 753 |
<div id='sp-live-progress-wrap' style='margin:0 0 12px 0;'>
|
| 754 |
<div id='sp-live-progress-text' style='margin-bottom:7px;'>{progress}% — {html.escape(node_label)}</div>
|
| 755 |
<div style='width:100%;height:10px;background:rgba(128,128,128,.24);border-radius:999px;overflow:hidden;'>
|
| 756 |
<div id='sp-live-progress-fill' style='width:{progress}%;height:100%;background:currentColor;border-radius:999px;transition:width .35s ease;'></div>
|
| 757 |
</div>
|
| 758 |
</div>
|
| 759 |
-
"""
|
| 760 |
|
| 761 |
-
st.markdown(f"""
|
| 762 |
<div class='log-box' id='sp-live-monitor'>
|
| 763 |
{progress_markup}
|
| 764 |
<div><b>{tr(lang_code, 'monitor_pipeline')}:</b> <span id='sp-live-status'>{html.escape(status_label)}</span></div>
|
|
@@ -775,7 +781,7 @@ def _render_monitor_box(task: dict, lang_code: str) -> None:
|
|
| 775 |
<span style='background: #d1fae5;padding:4px 8px;border-radius:4px;color:black;'>🌐 OpenAlex: <b id='sp-live-oa'>{oa_calls}</b></span>
|
| 776 |
</div>
|
| 777 |
</div>
|
| 778 |
-
""", unsafe_allow_html=True)
|
| 779 |
|
| 780 |
|
| 781 |
def _watch_task_status_live(task: dict) -> dict | None:
|
|
@@ -990,24 +996,24 @@ def _render_checkpoint_preview(task: dict, lang_code: str) -> None:
|
|
| 990 |
with left:
|
| 991 |
st.markdown(tr(lang_code, "english_draft"))
|
| 992 |
st.markdown(
|
| 993 |
-
f"""
|
| 994 |
<div id='sp-live-manuscript-shell' style='border:1px solid #e5e7eb;border-radius:8px;padding:14px 16px;min-height:220px;max-height:68vh;overflow:auto;background:#ffffff;color:#111827;'>
|
| 995 |
<div id='sp-live-manuscript-status' style='white-space:pre-wrap;font-size:0.88em;color:#374151;opacity:1;margin:0 0 8px 0;display:{english_status_display};'>{html.escape(english_status_initial)}</div>
|
| 996 |
<div id='sp-live-manuscript-preview' data-sp-placeholder='{english_placeholder}' style='white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-family:inherit;line-height:1.72;color:#111827;'>{html.escape(english_initial)}</div>
|
| 997 |
<span id='sp-live-manuscript-cursor' aria-hidden='true' style='display:inline-block;width:7px;height:1.05em;background:#111827;margin-left:2px;vertical-align:-0.12em;animation:spBlink 1s steps(1,end) infinite;'></span>
|
| 998 |
</div>
|
| 999 |
-
""",
|
| 1000 |
unsafe_allow_html=True,
|
| 1001 |
)
|
| 1002 |
with right:
|
| 1003 |
st.markdown(tr(lang_code, "target_draft", flag=get_target_flag(lang_code)))
|
| 1004 |
st.markdown(
|
| 1005 |
-
f"""
|
| 1006 |
<div id='sp-live-translation-shell' style='border:1px solid #e5e7eb;border-radius:8px;padding:14px 16px;min-height:220px;max-height:68vh;overflow:auto;background:#ffffff;color:#111827;'>
|
| 1007 |
<div id='sp-live-translation-preview' data-sp-placeholder='{target_placeholder}' style='white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-family:inherit;line-height:1.72;color:#111827;'>{html.escape(target_initial)}</div>
|
| 1008 |
<span id='sp-live-translation-cursor' aria-hidden='true' style='display:inline-block;width:7px;height:1.05em;background:#111827;margin-left:2px;vertical-align:-0.12em;animation:spBlink 1s steps(1,end) infinite;'></span>
|
| 1009 |
</div>
|
| 1010 |
-
""",
|
| 1011 |
unsafe_allow_html=True,
|
| 1012 |
)
|
| 1013 |
st.markdown("<style>@keyframes spBlink { 0%,49% {opacity:1} 50%,100% {opacity:0} }</style>", unsafe_allow_html=True)
|
|
@@ -1178,6 +1184,35 @@ def _scroll_to_generation_action_once() -> None:
|
|
| 1178 |
)
|
| 1179 |
|
| 1180 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1181 |
def render_workspace_controls(user):
|
| 1182 |
top_toolbar = st.container()
|
| 1183 |
with top_toolbar:
|
|
@@ -1197,6 +1232,9 @@ def render_workspace_controls(user):
|
|
| 1197 |
|
| 1198 |
task = st.session_state.get("sp_generation_task") or {}
|
| 1199 |
task_status = str(task.get("status") or "")
|
|
|
|
|
|
|
|
|
|
| 1200 |
active = task_status in ACTIVE_TASK_STATUSES
|
| 1201 |
st.session_state["is_running"] = active
|
| 1202 |
|
|
@@ -1279,6 +1317,7 @@ def render_workspace_controls(user):
|
|
| 1279 |
st.info(tr(lang_code, "pipeline_start"))
|
| 1280 |
_inject_generation_action_scroll("asq")
|
| 1281 |
|
|
|
|
| 1282 |
inject_file_uploader_i18n(lang_code)
|
| 1283 |
|
| 1284 |
|
|
|
|
| 11 |
import subprocess
|
| 12 |
import sys
|
| 13 |
import time
|
| 14 |
+
import textwrap
|
| 15 |
from datetime import datetime, timezone
|
| 16 |
from pathlib import Path
|
| 17 |
|
|
|
|
| 682 |
if not bool(st.session_state.get("sp_task_recovery_initialized")):
|
| 683 |
recover_generation_task()
|
| 684 |
st.session_state["sp_task_recovery_initialized"] = True
|
| 685 |
+
_recovered_task = st.session_state.get("sp_generation_task") or {}
|
| 686 |
+
_recovered_mode = str((_recovered_task or {}).get("mode") or "").strip().lower()
|
| 687 |
+
if _recovered_mode in {"amj", "asq"}:
|
| 688 |
+
# The task record, not the first visual tab, owns the research mode after refresh.
|
| 689 |
+
st.session_state["run_mode"] = _recovered_mode
|
| 690 |
_user_workspace = os.path.join(WORKSPACE_DIR, "users", _safe_key(authenticated_user.get("id")))
|
| 691 |
os.makedirs(_user_workspace, exist_ok=True)
|
| 692 |
|
|
|
|
| 755 |
|
| 756 |
progress_markup = ""
|
| 757 |
if status in ACTIVE_TASK_STATUSES:
|
| 758 |
+
progress_markup = textwrap.dedent(f"""
|
| 759 |
<div id='sp-live-progress-wrap' style='margin:0 0 12px 0;'>
|
| 760 |
<div id='sp-live-progress-text' style='margin-bottom:7px;'>{progress}% — {html.escape(node_label)}</div>
|
| 761 |
<div style='width:100%;height:10px;background:rgba(128,128,128,.24);border-radius:999px;overflow:hidden;'>
|
| 762 |
<div id='sp-live-progress-fill' style='width:{progress}%;height:100%;background:currentColor;border-radius:999px;transition:width .35s ease;'></div>
|
| 763 |
</div>
|
| 764 |
</div>
|
| 765 |
+
""").strip()
|
| 766 |
|
| 767 |
+
st.markdown(textwrap.dedent(f"""
|
| 768 |
<div class='log-box' id='sp-live-monitor'>
|
| 769 |
{progress_markup}
|
| 770 |
<div><b>{tr(lang_code, 'monitor_pipeline')}:</b> <span id='sp-live-status'>{html.escape(status_label)}</span></div>
|
|
|
|
| 781 |
<span style='background: #d1fae5;padding:4px 8px;border-radius:4px;color:black;'>🌐 OpenAlex: <b id='sp-live-oa'>{oa_calls}</b></span>
|
| 782 |
</div>
|
| 783 |
</div>
|
| 784 |
+
""").strip(), unsafe_allow_html=True)
|
| 785 |
|
| 786 |
|
| 787 |
def _watch_task_status_live(task: dict) -> dict | None:
|
|
|
|
| 996 |
with left:
|
| 997 |
st.markdown(tr(lang_code, "english_draft"))
|
| 998 |
st.markdown(
|
| 999 |
+
textwrap.dedent(f"""
|
| 1000 |
<div id='sp-live-manuscript-shell' style='border:1px solid #e5e7eb;border-radius:8px;padding:14px 16px;min-height:220px;max-height:68vh;overflow:auto;background:#ffffff;color:#111827;'>
|
| 1001 |
<div id='sp-live-manuscript-status' style='white-space:pre-wrap;font-size:0.88em;color:#374151;opacity:1;margin:0 0 8px 0;display:{english_status_display};'>{html.escape(english_status_initial)}</div>
|
| 1002 |
<div id='sp-live-manuscript-preview' data-sp-placeholder='{english_placeholder}' style='white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-family:inherit;line-height:1.72;color:#111827;'>{html.escape(english_initial)}</div>
|
| 1003 |
<span id='sp-live-manuscript-cursor' aria-hidden='true' style='display:inline-block;width:7px;height:1.05em;background:#111827;margin-left:2px;vertical-align:-0.12em;animation:spBlink 1s steps(1,end) infinite;'></span>
|
| 1004 |
</div>
|
| 1005 |
+
""").strip(),
|
| 1006 |
unsafe_allow_html=True,
|
| 1007 |
)
|
| 1008 |
with right:
|
| 1009 |
st.markdown(tr(lang_code, "target_draft", flag=get_target_flag(lang_code)))
|
| 1010 |
st.markdown(
|
| 1011 |
+
textwrap.dedent(f"""
|
| 1012 |
<div id='sp-live-translation-shell' style='border:1px solid #e5e7eb;border-radius:8px;padding:14px 16px;min-height:220px;max-height:68vh;overflow:auto;background:#ffffff;color:#111827;'>
|
| 1013 |
<div id='sp-live-translation-preview' data-sp-placeholder='{target_placeholder}' style='white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-family:inherit;line-height:1.72;color:#111827;'>{html.escape(target_initial)}</div>
|
| 1014 |
<span id='sp-live-translation-cursor' aria-hidden='true' style='display:inline-block;width:7px;height:1.05em;background:#111827;margin-left:2px;vertical-align:-0.12em;animation:spBlink 1s steps(1,end) infinite;'></span>
|
| 1015 |
</div>
|
| 1016 |
+
""").strip(),
|
| 1017 |
unsafe_allow_html=True,
|
| 1018 |
)
|
| 1019 |
st.markdown("<style>@keyframes spBlink { 0%,49% {opacity:1} 50%,100% {opacity:0} }</style>", unsafe_allow_html=True)
|
|
|
|
| 1184 |
)
|
| 1185 |
|
| 1186 |
|
| 1187 |
+
def _select_active_task_tab(task: dict, lang_code: str) -> None:
|
| 1188 |
+
"""Keep the existing visual tab aligned with the immutable task mode after rerun/refresh."""
|
| 1189 |
+
status = str((task or {}).get("status") or "")
|
| 1190 |
+
mode = str((task or {}).get("mode") or "").strip().lower()
|
| 1191 |
+
if status not in ACTIVE_TASK_STATUSES and status != "resumable":
|
| 1192 |
+
return
|
| 1193 |
+
if mode not in {"amj", "asq"}:
|
| 1194 |
+
return
|
| 1195 |
+
label = tr(lang_code, "tab_asq" if mode == "asq" else "tab_amj")
|
| 1196 |
+
components.html(
|
| 1197 |
+
f"""
|
| 1198 |
+
<script>
|
| 1199 |
+
(() => {{
|
| 1200 |
+
const parentDocument = window.parent.document;
|
| 1201 |
+
const expected = {json.dumps(label)};
|
| 1202 |
+
const select = () => {{
|
| 1203 |
+
const tabs = Array.from(parentDocument.querySelectorAll('[role="tab"]'));
|
| 1204 |
+
const target = tabs.find((node) => (node.textContent || '').trim() === expected.trim());
|
| 1205 |
+
if (target && target.getAttribute('aria-selected') !== 'true') target.click();
|
| 1206 |
+
}};
|
| 1207 |
+
[0, 80, 220, 500, 900].forEach((delay) => window.parent.setTimeout(select, delay));
|
| 1208 |
+
}})();
|
| 1209 |
+
</script>
|
| 1210 |
+
""",
|
| 1211 |
+
height=0,
|
| 1212 |
+
width=0,
|
| 1213 |
+
)
|
| 1214 |
+
|
| 1215 |
+
|
| 1216 |
def render_workspace_controls(user):
|
| 1217 |
top_toolbar = st.container()
|
| 1218 |
with top_toolbar:
|
|
|
|
| 1232 |
|
| 1233 |
task = st.session_state.get("sp_generation_task") or {}
|
| 1234 |
task_status = str(task.get("status") or "")
|
| 1235 |
+
task_mode = str(task.get("mode") or "").strip().lower()
|
| 1236 |
+
if task_mode in {"amj", "asq"}:
|
| 1237 |
+
st.session_state["run_mode"] = task_mode
|
| 1238 |
active = task_status in ACTIVE_TASK_STATUSES
|
| 1239 |
st.session_state["is_running"] = active
|
| 1240 |
|
|
|
|
| 1317 |
st.info(tr(lang_code, "pipeline_start"))
|
| 1318 |
_inject_generation_action_scroll("asq")
|
| 1319 |
|
| 1320 |
+
_select_active_task_tab(task, lang_code)
|
| 1321 |
inject_file_uploader_i18n(lang_code)
|
| 1322 |
|
| 1323 |
|
core/generation_runner.py
CHANGED
|
@@ -1923,7 +1923,13 @@ class GenerationRunner:
|
|
| 1923 |
progress_callback=lambda: self._progress(9, "S2 + OpenAlex"),
|
| 1924 |
)
|
| 1925 |
if not agent.citation_map:
|
| 1926 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1927 |
state["literature_candidate_target"] = literature_candidate_target
|
| 1928 |
if user_lit_context:
|
| 1929 |
core_refs = agent.parse_user_core_literature(user_lit_context)
|
|
@@ -1942,9 +1948,16 @@ class GenerationRunner:
|
|
| 1942 |
self._progress(12, "literature_synthesis")
|
| 1943 |
prewrite_started = time.monotonic()
|
| 1944 |
prewrite_calls = agent.ds_calls
|
| 1945 |
-
|
| 1946 |
-
|
| 1947 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1948 |
agent.record_prewrite_stage("literature_synthesis", prewrite_started, prewrite_calls)
|
| 1949 |
synthesis_result = re.sub(r"<think>.*?</think>", "", synthesis_result, flags=re.DOTALL).strip()
|
| 1950 |
state.update({"synthesis_result": synthesis_result, "agent_state": agent.export_state()})
|
|
@@ -2385,6 +2398,13 @@ class GenerationRunner:
|
|
| 2385 |
"Never stack sources, substitute tags, or use literature as evidence for this study's own facts.\n"
|
| 2386 |
f"<CLAIM_EVIDENCE_PLAN>\n{claim_plan_text}\n</CLAIM_EVIDENCE_PLAN>"
|
| 2387 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2388 |
if mode == "asq" and section_id not in {"findings", "appendix"}:
|
| 2389 |
citation_rule += (
|
| 2390 |
"\nASQ EVIDENCE-CHANNEL RULE: A sentence whose factual subject is this study's setting, participants, interviews, data sources, cases, actors, observations, events, quotations, analytic steps, or empirical findings must not carry an external [CITE-XX] tag. "
|
|
@@ -2715,7 +2735,13 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2715 |
references.append(f"- {ref}")
|
| 2716 |
seen_refs.add(ref)
|
| 2717 |
if not references:
|
| 2718 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2719 |
validated_by_top_level = {}
|
| 2720 |
claims_by_top_level = {}
|
| 2721 |
for unit_name, section_id in citation_unit_section_map.items():
|
|
@@ -2738,15 +2764,16 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2738 |
for section_id, planned_minimum in citation_top_level_minimums.items():
|
| 2739 |
validated_tags = set(validated_by_top_level.get(section_id) or [])
|
| 2740 |
external_claims = max(0, int(claims_by_top_level.get(section_id, 0)))
|
| 2741 |
-
#
|
| 2742 |
# top-level planning minimum is therefore capped by the number of
|
| 2743 |
# externally-oriented claims that actually survived drafting.
|
| 2744 |
required = min(int(planned_minimum), external_claims) if external_claims else 0
|
| 2745 |
if len(validated_tags) < required:
|
| 2746 |
final_shortfalls.append((section_id, len(validated_tags), required))
|
| 2747 |
final_reference_count = len(references)
|
| 2748 |
-
if final_shortfalls:
|
| 2749 |
-
|
|
|
|
| 2750 |
state, final_shortfalls, phase="final_claim_evidence_gate",
|
| 2751 |
preview_status="limited",
|
| 2752 |
)
|
|
@@ -2782,7 +2809,7 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2782 |
quality_diagnostics, self._validate_target_length(full_draft_en, target_words)
|
| 2783 |
)
|
| 2784 |
body_only, _reference_tail = agent.validate_reference_tail(
|
| 2785 |
-
full_draft_en, minimum_references=
|
| 2786 |
)
|
| 2787 |
agent.validate_manuscript_structure(body_only, adaptive_structure, translated=False)
|
| 2788 |
agent.enforce_resolved_claim_specific_citations(body_only)
|
|
@@ -2801,7 +2828,7 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2801 |
full_draft_en = replace_abstract(full_draft_en, final_abstract)
|
| 2802 |
full_draft_en = self._replace_keywords(full_draft_en, final_keywords)
|
| 2803 |
body_only, _reference_tail = agent.validate_reference_tail(
|
| 2804 |
-
full_draft_en, minimum_references=
|
| 2805 |
)
|
| 2806 |
agent.validate_manuscript_structure(body_only, adaptive_structure, translated=False)
|
| 2807 |
agent.enforce_resolved_claim_specific_citations(body_only)
|
|
@@ -2874,7 +2901,7 @@ Output only final academic prose. Do not report the word count."""
|
|
| 2874 |
)
|
| 2875 |
self._shutdown_translation_worker(wait=True)
|
| 2876 |
body_only, reference_tail = agent.validate_reference_tail(
|
| 2877 |
-
full_draft_en, minimum_references=max(
|
| 2878 |
)
|
| 2879 |
section_order = self._unique_top_level_sections(adaptive_structure)
|
| 2880 |
if not section_order:
|
|
|
|
| 1923 |
progress_callback=lambda: self._progress(9, "S2 + OpenAlex"),
|
| 1924 |
)
|
| 1925 |
if not agent.citation_map:
|
| 1926 |
+
self._record_evidence_coverage_event(
|
| 1927 |
+
state, [("retrieved literature pool", 0, 1)],
|
| 1928 |
+
phase="retrieval_pool_empty", preview_status="limited_risk",
|
| 1929 |
+
)
|
| 1930 |
+
logging.warning(
|
| 1931 |
+
"No directly validated literature was retrieved; continuing in evidence-limited mode without padding references."
|
| 1932 |
+
)
|
| 1933 |
state["literature_candidate_target"] = literature_candidate_target
|
| 1934 |
if user_lit_context:
|
| 1935 |
core_refs = agent.parse_user_core_literature(user_lit_context)
|
|
|
|
| 1948 |
self._progress(12, "literature_synthesis")
|
| 1949 |
prewrite_started = time.monotonic()
|
| 1950 |
prewrite_calls = agent.ds_calls
|
| 1951 |
+
if not str(refs_text or "").strip() and not str(user_lit_context or "").strip():
|
| 1952 |
+
synthesis_result = (
|
| 1953 |
+
"EVIDENCE-LIMITED CONTEXT: no directly validated external literature is currently available. "
|
| 1954 |
+
"Continue with the frozen study evidence, cautious conceptual reasoning, and general scholarly knowledge, "
|
| 1955 |
+
"but do not invent citations or bibliographic metadata and do not present general model knowledge as retrieved evidence."
|
| 1956 |
+
)
|
| 1957 |
+
else:
|
| 1958 |
+
synthesis_result = self._stream_text(
|
| 1959 |
+
agent.stream_synthesize_literature(topic, refs_text, user_core_literature=user_lit_context)
|
| 1960 |
+
)
|
| 1961 |
agent.record_prewrite_stage("literature_synthesis", prewrite_started, prewrite_calls)
|
| 1962 |
synthesis_result = re.sub(r"<think>.*?</think>", "", synthesis_result, flags=re.DOTALL).strip()
|
| 1963 |
state.update({"synthesis_result": synthesis_result, "agent_state": agent.export_state()})
|
|
|
|
| 2398 |
"Never stack sources, substitute tags, or use literature as evidence for this study's own facts.\n"
|
| 2399 |
f"<CLAIM_EVIDENCE_PLAN>\n{claim_plan_text}\n</CLAIM_EVIDENCE_PLAN>"
|
| 2400 |
)
|
| 2401 |
+
if not claim_plan_text.strip():
|
| 2402 |
+
citation_rule += (
|
| 2403 |
+
"\nEVIDENCE-LIMITED WRITING RULE: no directly validated source is assigned to this unit. "
|
| 2404 |
+
"Continue the scholarly argument using frozen study evidence, cautious conceptual reasoning, and general scholarly knowledge. "
|
| 2405 |
+
"Do not invent citations, authors, titles, DOI values, or bibliographic metadata; do not describe general model knowledge as if it were retrieved or directly validated literature. "
|
| 2406 |
+
"Use appropriately qualified language for propositions, interpretations, and research opportunities."
|
| 2407 |
+
)
|
| 2408 |
if mode == "asq" and section_id not in {"findings", "appendix"}:
|
| 2409 |
citation_rule += (
|
| 2410 |
"\nASQ EVIDENCE-CHANNEL RULE: A sentence whose factual subject is this study's setting, participants, interviews, data sources, cases, actors, observations, events, quotations, analytic steps, or empirical findings must not carry an external [CITE-XX] tag. "
|
|
|
|
| 2735 |
references.append(f"- {ref}")
|
| 2736 |
seen_refs.add(ref)
|
| 2737 |
if not references:
|
| 2738 |
+
self._record_evidence_coverage_event(
|
| 2739 |
+
state, [("final validated references", 0, 1)],
|
| 2740 |
+
phase="final_validated_references_empty", preview_status="limited",
|
| 2741 |
+
)
|
| 2742 |
+
logging.warning(
|
| 2743 |
+
"Final validated reference list is empty; completing in evidence-limited mode without unrelated or invented references."
|
| 2744 |
+
)
|
| 2745 |
validated_by_top_level = {}
|
| 2746 |
claims_by_top_level = {}
|
| 2747 |
for unit_name, section_id in citation_unit_section_map.items():
|
|
|
|
| 2764 |
for section_id, planned_minimum in citation_top_level_minimums.items():
|
| 2765 |
validated_tags = set(validated_by_top_level.get(section_id) or [])
|
| 2766 |
external_claims = max(0, int(claims_by_top_level.get(section_id, 0)))
|
| 2767 |
+
# Evidence authority is need-based, not a word-count quota. The
|
| 2768 |
# top-level planning minimum is therefore capped by the number of
|
| 2769 |
# externally-oriented claims that actually survived drafting.
|
| 2770 |
required = min(int(planned_minimum), external_claims) if external_claims else 0
|
| 2771 |
if len(validated_tags) < required:
|
| 2772 |
final_shortfalls.append((section_id, len(validated_tags), required))
|
| 2773 |
final_reference_count = len(references)
|
| 2774 |
+
if final_shortfalls or final_reference_count == 0:
|
| 2775 |
+
if final_shortfalls:
|
| 2776 |
+
self._record_evidence_coverage_event(
|
| 2777 |
state, final_shortfalls, phase="final_claim_evidence_gate",
|
| 2778 |
preview_status="limited",
|
| 2779 |
)
|
|
|
|
| 2809 |
quality_diagnostics, self._validate_target_length(full_draft_en, target_words)
|
| 2810 |
)
|
| 2811 |
body_only, _reference_tail = agent.validate_reference_tail(
|
| 2812 |
+
full_draft_en, minimum_references=final_reference_count
|
| 2813 |
)
|
| 2814 |
agent.validate_manuscript_structure(body_only, adaptive_structure, translated=False)
|
| 2815 |
agent.enforce_resolved_claim_specific_citations(body_only)
|
|
|
|
| 2828 |
full_draft_en = replace_abstract(full_draft_en, final_abstract)
|
| 2829 |
full_draft_en = self._replace_keywords(full_draft_en, final_keywords)
|
| 2830 |
body_only, _reference_tail = agent.validate_reference_tail(
|
| 2831 |
+
full_draft_en, minimum_references=final_reference_count
|
| 2832 |
)
|
| 2833 |
agent.validate_manuscript_structure(body_only, adaptive_structure, translated=False)
|
| 2834 |
agent.enforce_resolved_claim_specific_citations(body_only)
|
|
|
|
| 2901 |
)
|
| 2902 |
self._shutdown_translation_worker(wait=True)
|
| 2903 |
body_only, reference_tail = agent.validate_reference_tail(
|
| 2904 |
+
full_draft_en, minimum_references=max(0, int(state.get("final_reference_count") or 0))
|
| 2905 |
)
|
| 2906 |
section_order = self._unique_top_level_sections(adaptive_structure)
|
| 2907 |
if not section_order:
|
core/pipeline_agent.py
CHANGED
|
@@ -1247,34 +1247,14 @@ class SSciPipelineAgent:
|
|
| 1247 |
"{topic}", topic).replace("{user_injection}", user_injection).replace(
|
| 1248 |
"{refs_text}", refs_text).replace("{directive}", APP_CONFIG['deep_synthesis_directive'])
|
| 1249 |
|
| 1250 |
-
def
|
| 1251 |
-
|
| 1252 |
-
reasoning_budget = int((APP_CONFIG.get("prewrite_reasoning_max_tokens") or {}).get("literature_synthesis", 2400))
|
| 1253 |
-
yield from self.stream_deepseek(
|
| 1254 |
-
sys_prompt,
|
| 1255 |
-
user_prompt,
|
| 1256 |
-
max_tokens=reasoning_budget,
|
| 1257 |
-
model_role="reasoning",
|
| 1258 |
-
thinking=True,
|
| 1259 |
-
allow_fallback=False,
|
| 1260 |
-
reasoning_only_recovery=True,
|
| 1261 |
-
)
|
| 1262 |
-
return
|
| 1263 |
-
except DeepSeekReasoningOnlyExhaustionError as exc:
|
| 1264 |
-
logging.warning(
|
| 1265 |
-
"Literature synthesis reasoning-only exhaustion; retrying once with thinking disabled: %s",
|
| 1266 |
-
exc,
|
| 1267 |
-
)
|
| 1268 |
-
self._touch_execution_progress("literature_synthesis_direct_recovery")
|
| 1269 |
-
|
| 1270 |
-
recovery_system = (
|
| 1271 |
sys_prompt
|
| 1272 |
-
+ "\n\
|
| 1273 |
-
"
|
| 1274 |
-
"citation boundaries, uncertainty, and scope; do not invent facts."
|
| 1275 |
)
|
| 1276 |
yield from self.stream_deepseek(
|
| 1277 |
-
|
| 1278 |
user_prompt,
|
| 1279 |
max_tokens=8000,
|
| 1280 |
model_role="reasoning",
|
|
@@ -1282,7 +1262,7 @@ class SSciPipelineAgent:
|
|
| 1282 |
allow_fallback=False,
|
| 1283 |
)
|
| 1284 |
|
| 1285 |
-
return
|
| 1286 |
|
| 1287 |
# Generate abstract and conclusion anchors from literature plus evidence.
|
| 1288 |
def stream_generate_research_logic(
|
|
@@ -1800,12 +1780,14 @@ USER KEYWORD SEEDS: {keywords}
|
|
| 1800 |
tail = value[match.end():].strip()
|
| 1801 |
if not body:
|
| 1802 |
raise DeepSeekAPIError('References tail validation failed: manuscript body is empty.')
|
|
|
|
| 1803 |
if not tail:
|
|
|
|
|
|
|
| 1804 |
raise DeepSeekAPIError('References tail validation failed: References is empty.')
|
| 1805 |
if re.search(r'(?m)^#\s+', tail):
|
| 1806 |
raise DeepSeekAPIError('References tail validation failed: another H1 appears after References.')
|
| 1807 |
refs = [line.strip() for line in tail.splitlines() if line.strip().startswith('- ')]
|
| 1808 |
-
required = max(1, int(minimum_references or 1))
|
| 1809 |
if len(refs) < required:
|
| 1810 |
raise DeepSeekAPIError(
|
| 1811 |
f'References tail validation failed: references={len(refs)}, required>={required}.'
|
|
@@ -3378,14 +3360,13 @@ Return exactly:
|
|
| 3378 |
for technical_attempt in range(technical_retries):
|
| 3379 |
self._check_cancelled()
|
| 3380 |
try:
|
| 3381 |
-
compact_retry = technical_attempt > 0
|
| 3382 |
response = self.call_deepseek(
|
| 3383 |
system_prompt,
|
| 3384 |
user_prompt,
|
| 3385 |
-
max_tokens=3200 if
|
| 3386 |
temp=0.0,
|
| 3387 |
model_role="reasoning",
|
| 3388 |
-
thinking=
|
| 3389 |
allow_fallback=False,
|
| 3390 |
)
|
| 3391 |
parsed = self._extract_json_from_text(response)
|
|
|
|
| 1247 |
"{topic}", topic).replace("{user_injection}", user_injection).replace(
|
| 1248 |
"{refs_text}", refs_text).replace("{directive}", APP_CONFIG['deep_synthesis_directive'])
|
| 1249 |
|
| 1250 |
+
def _stream_direct_synthesis():
|
| 1251 |
+
direct_system = (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1252 |
sys_prompt
|
| 1253 |
+
+ "\n\nDIRECT SYNTHESIS MODE: Produce the final literature synthesis directly without extended hidden reasoning. "
|
| 1254 |
+
"Preserve the supplied evidence, citation boundaries, uncertainty, and scope; do not invent facts or sources."
|
|
|
|
| 1255 |
)
|
| 1256 |
yield from self.stream_deepseek(
|
| 1257 |
+
direct_system,
|
| 1258 |
user_prompt,
|
| 1259 |
max_tokens=8000,
|
| 1260 |
model_role="reasoning",
|
|
|
|
| 1262 |
allow_fallback=False,
|
| 1263 |
)
|
| 1264 |
|
| 1265 |
+
return _stream_direct_synthesis()
|
| 1266 |
|
| 1267 |
# Generate abstract and conclusion anchors from literature plus evidence.
|
| 1268 |
def stream_generate_research_logic(
|
|
|
|
| 1780 |
tail = value[match.end():].strip()
|
| 1781 |
if not body:
|
| 1782 |
raise DeepSeekAPIError('References tail validation failed: manuscript body is empty.')
|
| 1783 |
+
required = max(0, int(minimum_references if minimum_references is not None else 1))
|
| 1784 |
if not tail:
|
| 1785 |
+
if required == 0:
|
| 1786 |
+
return body, ""
|
| 1787 |
raise DeepSeekAPIError('References tail validation failed: References is empty.')
|
| 1788 |
if re.search(r'(?m)^#\s+', tail):
|
| 1789 |
raise DeepSeekAPIError('References tail validation failed: another H1 appears after References.')
|
| 1790 |
refs = [line.strip() for line in tail.splitlines() if line.strip().startswith('- ')]
|
|
|
|
| 1791 |
if len(refs) < required:
|
| 1792 |
raise DeepSeekAPIError(
|
| 1793 |
f'References tail validation failed: references={len(refs)}, required>={required}.'
|
|
|
|
| 3360 |
for technical_attempt in range(technical_retries):
|
| 3361 |
self._check_cancelled()
|
| 3362 |
try:
|
|
|
|
| 3363 |
response = self.call_deepseek(
|
| 3364 |
system_prompt,
|
| 3365 |
user_prompt,
|
| 3366 |
+
max_tokens=3200 if technical_attempt == 0 else 4200,
|
| 3367 |
temp=0.0,
|
| 3368 |
model_role="reasoning",
|
| 3369 |
+
thinking=False,
|
| 3370 |
allow_fallback=False,
|
| 3371 |
)
|
| 3372 |
parsed = self._extract_json_from_text(response)
|
core/qualitative_engine.py
CHANGED
|
@@ -335,30 +335,12 @@ Rules:
|
|
| 335 |
# content retries here. Only a completed but locally invalid packet
|
| 336 |
# reaches the second attempt.
|
| 337 |
if local_attempt == 0:
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
base_prompt,
|
| 345 |
-
max_tokens=reasoning_budget,
|
| 346 |
-
model_role="reasoning",
|
| 347 |
-
thinking=True,
|
| 348 |
-
allow_fallback=False,
|
| 349 |
-
reasoning_only_recovery=True,
|
| 350 |
-
)
|
| 351 |
-
)
|
| 352 |
-
except Exception as exc:
|
| 353 |
-
if exc.__class__.__name__ != "DeepSeekReasoningOnlyExhaustionError":
|
| 354 |
-
raise
|
| 355 |
-
logging.warning("Qualitative packet reasoning-only exhaustion; switching once to direct final-output mode.")
|
| 356 |
-
response = self.agent.call_deepseek(
|
| 357 |
-
system_prompt + "\n\nDIRECT OUTPUT MODE: Produce the complete evidence packet directly without extended hidden reasoning.",
|
| 358 |
-
base_prompt,
|
| 359 |
-
max_tokens=8000, temp=0.0, model_role="reasoning",
|
| 360 |
-
thinking=False, allow_fallback=False, response_format={"type": "json_object"},
|
| 361 |
-
)
|
| 362 |
else:
|
| 363 |
retry_prompt = base_prompt + (
|
| 364 |
"\n\nLOCAL VALIDATION RETRY: The previous completed response failed local "
|
|
|
|
| 335 |
# content retries here. Only a completed but locally invalid packet
|
| 336 |
# reaches the second attempt.
|
| 337 |
if local_attempt == 0:
|
| 338 |
+
response = self.agent.call_deepseek(
|
| 339 |
+
system_prompt + "\n\nDIRECT OUTPUT MODE: Produce the complete evidence packet directly without extended hidden reasoning.",
|
| 340 |
+
base_prompt,
|
| 341 |
+
max_tokens=8000, temp=0.0, model_role="reasoning",
|
| 342 |
+
thinking=False, allow_fallback=False, response_format={"type": "json_object"},
|
| 343 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
else:
|
| 345 |
retry_prompt = base_prompt + (
|
| 346 |
"\n\nLOCAL VALIDATION RETRY: The previous completed response failed local "
|
i18n.py
CHANGED
|
@@ -319,8 +319,8 @@ TEXTS: dict[str, dict[str, str]] = {
|
|
| 319 |
"node_error": "🚨 节点异常已中止。",
|
| 320 |
"literature_insufficient": "当前没有检索到可用于形成有效参考文献列表的直接相关文献。系统未使用无关文献凑数。请检查研究题目、关键词或补充核心文献后重试。",
|
| 321 |
"upstream_translation_stopped": "英文母稿未形成可交付的有效参考文献列表,因此后续翻译已停止;当前页面中已显示的译文不是完整论文。",
|
| 322 |
-
"evidence_limited_running": "文献证据覆盖提示:当前
|
| 323 |
-
"evidence_limited_completed": "论文已完整生成。
|
| 324 |
"node_error_detail": "错误代码:{code}|{error}|系统已保存断点,可继续恢复。",
|
| 325 |
"task_cancelled": "任务已取消,断点和临时文件已删除。",
|
| 326 |
"task_start_failed": "无法启动论文任务:{error}",
|
|
@@ -519,8 +519,8 @@ TEXTS: dict[str, dict[str, str]] = {
|
|
| 519 |
"node_error": "🚨 The node stopped because of an error.",
|
| 520 |
"literature_insufficient": "No directly relevant literature was retrieved that can support a valid final reference list. Unrelated sources were not used to fill a quota. Check the topic or keywords, or provide relevant core literature, and try again.",
|
| 521 |
"upstream_translation_stopped": "The English source manuscript could not form a deliverable validated reference list, so downstream translation stopped. Any translated text currently shown is incomplete and is not a final paper.",
|
| 522 |
-
"evidence_limited_running": "Literature-evidence coverage notice:
|
| 523 |
-
"evidence_limited_completed": "The paper was generated in full
|
| 524 |
"node_error_detail": "Error code: {code} | {error} | A checkpoint has been saved and can be resumed.",
|
| 525 |
"task_cancelled": "The task was cancelled. Its checkpoint and temporary files were deleted.",
|
| 526 |
"task_start_failed": "The paper task could not be started: {error}",
|
|
@@ -719,8 +719,8 @@ TEXTS: dict[str, dict[str, str]] = {
|
|
| 719 |
"node_error": "🚨 Выполнение узла остановлено из-за ошибки.",
|
| 720 |
"literature_insufficient": "Не удалось найти прямо релевантную литературу, достаточную для формирования валидного итогового списка источников. Система не использовала нерелевантные публикации ради количества. Проверьте тему и ключевые слова либо добавьте релевантные основные источники и повторите попытку.",
|
| 721 |
"upstream_translation_stopped": "Для английской исходной рукописи не удалось сформировать пригодный к выдаче валидный список источников, поэтому дальнейший перевод остановлен. Уже показанный перевод неполный и не является окончательной статьёй.",
|
| 722 |
-
"evidence_limited_running": "Уведомление о
|
| 723 |
-
"evidence_limited_completed": "Статья полностью сформирована
|
| 724 |
"node_error_detail": "Код ошибки: {code} | {error} | Контрольная точка сохранена; работу можно продолжить.",
|
| 725 |
"task_cancelled": "Задача отменена. Контрольная точка и временные файлы удалены.",
|
| 726 |
"task_start_failed": "Не удалось запустить подготовку статьи: {error}",
|
|
@@ -919,8 +919,8 @@ TEXTS: dict[str, dict[str, str]] = {
|
|
| 919 |
"node_error": "🚨 Қате салдарынан түйін тоқтатылды.",
|
| 920 |
"literature_insufficient": "Жарамды соңғы әдебиеттер тізімін құруға жеткілікті тікелей релевантты әдебиет табылмады. Жүйе санды толтыру үшін қатысы жоқ дереккөздерді пайдаланбады. Тақырып пен кілт сөздерді тексеріңіз немесе релевантты негізгі әдебиетті қосып, қайта көріңіз.",
|
| 921 |
"upstream_translation_stopped": "Ағылшын бастапқы қолжазбасы үшін жеткізуге жарамды тексерілген әдебиеттер тізімі қалыптаспады, сондықтан кейінгі аударма тоқтатылды. Қазір көрсетілген аударма толық емес және соңғы мақала болып саналмайды.",
|
| 922 |
-
"evidence_limited_running": "Әдеби дәлелдер
|
| 923 |
-
"evidence_limited_completed": "Мақала т
|
| 924 |
"node_error_detail": "Қате коды: {code} | {error} | Бақылау нүктесі сақталды; жұмысты жалғастыруға болады.",
|
| 925 |
"task_cancelled": "Тапсырма тоқтатылды. Бақылау нүктесі мен уақытша файлдар жойылды.",
|
| 926 |
"task_start_failed": "Мақала тапсырмасын бастау мүмкін болмады: {error}",
|
|
|
|
| 319 |
"node_error": "🚨 节点异常已中止。",
|
| 320 |
"literature_insufficient": "当前没有检索到可用于形成有效参考文献列表的直接相关文献。系统未使用无关文献凑数。请检查研究题目、关键词或补充核心文献后重试。",
|
| 321 |
"upstream_translation_stopped": "英文母稿未形成可交付的有效参考文献列表,因此后续翻译已停止;当前页面中已显示的译文不是完整论文。",
|
| 322 |
+
"evidence_limited_running": "文献证据覆盖提示:当前主题可能较前沿、较小众或具有较强跨学科特征,因此数据库中能够直接核验的高相关文献可能较少。这不会中断论文生成,也不代表研究价值不足。系统将继续完成论文,并坚持不使用无关或无法核验的文献凑数。建议后续结合领域核心文献进一步完善;对尚未被充分研究的问题保持探索,可能形成有价值的学术贡献。",
|
| 323 |
+
"evidence_limited_completed": "论文已完整生成。当前主题的直接可核验文献证据覆盖有限,可能与研究较前沿、较小众或跨学科有关;这不代表研究价值不足。系统未使用无关或无法核验的文献凑数。正式投稿前建议结合 EVIDENCE_COVERAGE_REPORT.txt 补充或复核领域核心文献,并继续审慎推进有价值的新问题探索。",
|
| 324 |
"node_error_detail": "错误代码:{code}|{error}|系统已保存断点,可继续恢复。",
|
| 325 |
"task_cancelled": "任务已取消,断点和临时文件已删除。",
|
| 326 |
"task_start_failed": "无法启动论文任务:{error}",
|
|
|
|
| 519 |
"node_error": "🚨 The node stopped because of an error.",
|
| 520 |
"literature_insufficient": "No directly relevant literature was retrieved that can support a valid final reference list. Unrelated sources were not used to fill a quota. Check the topic or keywords, or provide relevant core literature, and try again.",
|
| 521 |
"upstream_translation_stopped": "The English source manuscript could not form a deliverable validated reference list, so downstream translation stopped. Any translated text currently shown is incomplete and is not a final paper.",
|
| 522 |
+
"evidence_limited_running": "Literature-evidence coverage notice: this topic may be frontier, niche, or strongly interdisciplinary, so databases may contain relatively few high-relevance sources that can be directly validated. This will not interrupt manuscript generation and does not imply low research value. The system will continue the paper without padding it with irrelevant or unverifiable references. Consider adding core field literature later; careful exploration of under-studied questions may yield a valuable academic contribution.",
|
| 523 |
+
"evidence_limited_completed": "The paper was generated in full despite limited directly validated literature coverage. The topic may be frontier, niche, or interdisciplinary; this does not imply low research value. No irrelevant or unverifiable references were added merely to reach a count. Before formal submission, review EVIDENCE_COVERAGE_REPORT.txt and supplement or recheck core field literature while continuing careful exploration of the research question.",
|
| 524 |
"node_error_detail": "Error code: {code} | {error} | A checkpoint has been saved and can be resumed.",
|
| 525 |
"task_cancelled": "The task was cancelled. Its checkpoint and temporary files were deleted.",
|
| 526 |
"task_start_failed": "The paper task could not be started: {error}",
|
|
|
|
| 719 |
"node_error": "🚨 Выполнение узла остановлено из-за ошибки.",
|
| 720 |
"literature_insufficient": "Не удалось найти прямо релевантную литературу, достаточную для формирования валидного итогового списка источников. Система не использовала нерелевантные публикации ради количества. Проверьте тему и ключевые слова либо добавьте релевантные основные источники и повторите попытку.",
|
| 721 |
"upstream_translation_stopped": "Для английской исходной рукописи не удалось сформировать пригодный к выдаче валидный список источников, поэтому дальнейший перевод остановлен. Уже показанный перевод неполный и не является окончательной статьёй.",
|
| 722 |
+
"evidence_limited_running": "Уведомление о литературных доказательствах: тема может быть передовой, узкоспециализированной или междисциплинарной, поэтому в базах данных может быть немного высокорелевантных источников, которые удаётся напрямую подтвердить. Это не остановит создание статьи и не означает низкую научную ценность исследования. Система продолжит работу и не будет добавлять нерелевантные или непроверяемые источники ради количества. Позднее рекомендуется дополнить основную литературу по области; тщательное изучение недостаточно исследованных вопросов может дать ценный научный вклад.",
|
| 723 |
+
"evidence_limited_completed": "Статья полностью сформирована при ограниченном покрытии напрямую подтверждённой литературой. Тема может быть передовой, узкой или междисциплинарной; это не означает низкую научную ценность. Нерелевантные или непроверяемые источники не добавлялись ради количества. Перед подачей рекомендуется изучить EVIDENCE_COVERAGE_REPORT.txt, дополнить или перепроверить основную литературу и продолжить осторожное исследование нового вопроса.",
|
| 724 |
"node_error_detail": "Код ошибки: {code} | {error} | Контрольная точка сохранена; работу можно продолжить.",
|
| 725 |
"task_cancelled": "Задача отменена. Контрольная точка и временные файлы удалены.",
|
| 726 |
"task_start_failed": "Не удалось запустить подготовку статьи: {error}",
|
|
|
|
| 919 |
"node_error": "🚨 Қате салдарынан түйін тоқтатылды.",
|
| 920 |
"literature_insufficient": "Жарамды соңғы әдебиеттер тізімін құруға жеткілікті тікелей релевантты әдебиет табылмады. Жүйе санды толтыру үшін қатысы жоқ дереккөздерді пайдаланбады. Тақырып пен кілт сөздерді тексеріңіз немесе релевантты негізгі әдебиетті қосып, қайта көріңіз.",
|
| 921 |
"upstream_translation_stopped": "Ағылшын бастапқы қолжазбасы үшін жеткізуге жарамды тексерілген әдебиеттер тізімі қалыптаспады, сондықтан кейінгі аударма тоқтатылды. Қазір көрсетілген аударма толық емес және соңғы мақала болып саналмайды.",
|
| 922 |
+
"evidence_limited_running": "Әдеби дәлелдер туралы ескерту: тақырып өте жаңа, тар ауқымды немесе пәнаралық болуы мүмкін, сондықтан дерекқорларда тікелей тексеруге болатын жоғары релевантты еңбектер аз кездесуі ықтимал. Бұл мақала жасауды тоқтатпайды және зерттеудің ғылыми құндылығы төмен дегенді білдірмейді. Жүйе жұмысты жалғастырады және санды толтыру үшін қатысы жоқ немесе тексерілмейтін дереккөздерді қоспайды. Кейін саланың негізгі әдебиетін толықтырған жөн; аз зерттелген мәселелерді мұқият зерделеу құнды ғылыми үлеске әкелуі мүмкін.",
|
| 923 |
+
"evidence_limited_completed": "Мақала тікелей тексерілген әдебиет қамтуы шектеулі болғанына қарамастан толық жасалды. Тақырып жаңа, тар ауқымды немесе пәнаралық болуы мүмкін; бұл зерттеу құндылығы төмен дегенді білдірмейді. Сан үшін қатысы жоқ немесе тексерілмейтін дереккөздер қосылған жоқ. Ресми тапсыру алдында EVIDENCE_COVERAGE_REPORT.txt файлын қарап, негізгі әдебиетті толықтыру немесе қайта тексеру және жаңа зерттеу мәселесін мұқият дамытуды жалғастыру ұсынылады.",
|
| 924 |
"node_error_detail": "Қате коды: {code} | {error} | Бақылау нүктесі сақталды; жұмысты жалғастыруға болады.",
|
| 925 |
"task_cancelled": "Тапсырма тоқтатылды. Бақылау нүктесі мен уақытша файлдар жойылды.",
|
| 926 |
"task_start_failed": "Мақала тапсырмасын бастау мүмкін болмады: {error}",
|