Skip to content

Commit 15ab68c

Browse files
committed
Improve GUI push controls and reading reports
1 parent 9faf885 commit 15ab68c

14 files changed

Lines changed: 1269 additions & 236 deletions

File tree

agents/master-coordinator/scripts/llm_parser.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,21 +1684,33 @@ def synthesize_reading_report_with_llm(
16841684
)
16851685

16861686
system_prompt = (
1687-
"你是科研论文精读助手。请基于提供的论文元数据、摘要、可用章节摘录和用户画像,"
1688-
"生成一份适合飞书文档使用的结构化中文精读内容。"
1687+
"你是科研论文精读助手,目标是给用户提供尽可能丰富、可判断、可复核的中文精读原材料。"
1688+
"完整性优先于压缩:覆盖论文动机、相关研究、技术路线、实验设计、关键发现、局限、可探索方向和总体判断;"
1689+
"不要为了简短省略重要机制、边界条件、反例或与用户画像相关的细节。"
1690+
"结构必须清晰,适合飞书文档阅读;每个长字段应写成多段中文,可以包含编号、短表格式描述或分点。"
1691+
"用户会自行筛选,你负责尽量不漏信息;允许适度重复以保持上下文完整,但不要灌水。"
1692+
"默认使用中文。只有关键词、论文专名、方法名、数据集名、模型名和必要术语保留英文。"
1693+
"请假设用户具备较强科研背景,从论文前沿问题和机制解释开始,不要写泛泛科普。"
16891694
"如果提供了 retrieved_evidence,请优先参考这些 PDF 语义检索命中的证据片段,"
16901695
"再结合 heuristic_draft 做润色和补全;当二者冲突时,优先采用 retrieved_evidence。"
16911696
"如果提供了 field_evidence_map,请让每个输出字段优先参考它对应的证据锚点,"
1692-
"不要把 results 证据写到 research_background,也不要把 background 证据误写成方法贡献。"
1693-
"如果 PDF 文本明显包含页眉、图注、作者脚注、参考文献、表格残片或公式噪声,请主动忽略,"
1694-
"并改用更干净的摘要、Introduction、Method、Results 或 Conclusion 信息归纳。"
1695-
"如果 user_profile.report_preferences 显示 prefer_more_evidence=true 或 preferred_style=evidence_first,"
1696-
"请优先写出更具体的证据锚点和方法/结果依据,不要只给空泛总结。"
1697-
"不要编造具体实验数值;如果信息不足,请保持克制并明确指出需要回原文核对。"
1697+
"不要把 results 证据写到 problem_analysis,也不要把 background 证据误写成实验结果。"
1698+
"如果 PDF 文本明显包含页眉、图注、作者脚注、参考文献、表格残片、断行错误、公式噪声或 arXiv 页眉,"
1699+
"请主动忽略,并改用更干净的摘要、Introduction、Related Work、Method、Experiments、Discussion 或 Conclusion 信息归纳。"
1700+
"对关键判断做校准:已由论文明确支持的内容直接陈述;基于摘要/片段推断的内容标注“合理推断”;"
1701+
"证据不足、需要回原文确认的地方要在对应句子内说明。"
1702+
"不要编造具体实验数值、数据集、baseline、公式含义、作者动机或代码资源;信息不足时明确指出缺口。"
16981703
"可以改写证据,不要大段逐字复制。"
1704+
"输出应接近深度读书笔记,而不是推荐卡片。每个 qa 字段通常写 250-700 个中文字;"
1705+
"如果论文信息充分,paper_summary 可以更长,用于完整复述论文主线。"
16991706
"只输出 JSON 对象,不要 Markdown,不要解释,不要代码块。字段包括:"
1700-
"one_sentence_summary, research_background, core_method, key_results, "
1707+
"one_sentence_summary, clean_abstract_summary, problem_analysis, related_work, solution_approach, experiments, "
1708+
"future_directions, paper_summary, research_background, core_method, key_results, "
17011709
"main_contributions, limitations, relevance_points, reading_focus, keywords, recommendation_label, analysis_note。"
1710+
"problem_analysis 对应“这篇论文试图解决什么问题”;related_work 对应“有哪些相关研究”;"
1711+
"solution_approach 对应“论文如何解决这个问题”;experiments 对应“论文做了哪些实验”;"
1712+
"future_directions 对应“有什么可以进一步探索的点”;paper_summary 对应“总结一下论文的主要内容”。"
1713+
"research_background/core_method/key_results/main_contributions/limitations/relevance_points/reading_focus 是兼容旧模板的摘要字段,也要填写。"
17021714
"main_contributions, limitations, relevance_points, reading_focus, keywords 必须是字符串数组;其他字段是字符串。"
17031715
"keywords 用 5-8 个英文小写短语,按论文核心话题排序,例如 [\"diffusion model\", \"video generation\", \"long context\"]。"
17041716
"analysis_note 用一句话说明本次生成是否参考了 PDF 检索证据。"
@@ -1708,7 +1720,7 @@ def synthesize_reading_report_with_llm(
17081720
result = _generate_json_with_configured_llm(
17091721
system_prompt=system_prompt,
17101722
user_text=user_text,
1711-
max_tokens=int(os.environ.get("READING_REPORT_LLM_MAX_TOKENS", "4096")),
1723+
max_tokens=int(os.environ.get("READING_REPORT_LLM_MAX_TOKENS", "12000")),
17121724
timeout_override=_get_reading_report_timeout(),
17131725
)
17141726
if not isinstance(result, dict):
@@ -1718,7 +1730,21 @@ def synthesize_reading_report_with_llm(
17181730
result.setdefault("generation_model", fallback_metadata["generation_model"])
17191731

17201732
normalized: Dict[str, Any] = {}
1721-
for key in ("one_sentence_summary", "research_background", "core_method", "key_results", "recommendation_label", "analysis_note"):
1733+
for key in (
1734+
"one_sentence_summary",
1735+
"clean_abstract_summary",
1736+
"problem_analysis",
1737+
"related_work",
1738+
"solution_approach",
1739+
"experiments",
1740+
"future_directions",
1741+
"paper_summary",
1742+
"research_background",
1743+
"core_method",
1744+
"key_results",
1745+
"recommendation_label",
1746+
"analysis_note",
1747+
):
17221748
value = str(result.get(key, "")).strip()
17231749
if value:
17241750
normalized[key] = value

agents/reading-agent/main.py

Lines changed: 131 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import hashlib
2020
import shutil
2121
import tempfile
22+
import inspect
2223
from html import unescape
2324
from datetime import datetime
2425
from typing import Dict, List, Optional, Any, Tuple
@@ -92,7 +93,7 @@
9293
or "2026-04-27-v4"
9394
)
9495
READING_REPORT_EVIDENCE_CACHE_ENABLED = os.environ.get("READING_REPORT_EVIDENCE_CACHE_ENABLED", "1").strip().lower() not in {"0", "false", "off", "no"}
95-
READING_REPORT_OUTPUT_VERSION = os.environ.get("READING_REPORT_OUTPUT_VERSION", "2026-05-31-v4").strip() or "2026-05-31-v4"
96+
READING_REPORT_OUTPUT_VERSION = os.environ.get("READING_REPORT_OUTPUT_VERSION", "2026-06-04-v5").strip() or "2026-06-04-v5"
9697
READING_REPORT_PROFILE_RETRIEVAL_WEIGHT = float(os.environ.get("READING_REPORT_PROFILE_RETRIEVAL_WEIGHT", "0.25"))
9798
HTTP_RETRY_TOTAL = int(os.environ.get("PAPERFLOW_HTTP_RETRIES", "2"))
9899
HTTP_RETRY_BACKOFF = float(os.environ.get("PAPERFLOW_HTTP_BACKOFF", "0.8"))
@@ -132,7 +133,7 @@ def _resolve_configured_dir(
132133
base_dir = Path(configured).expanduser() if configured else PROJECT_ROOT / default_relative
133134
if not base_dir.is_absolute():
134135
base_dir = PROJECT_ROOT / base_dir
135-
if user_id:
136+
if user_id and not configured:
136137
base_dir = role_utils.apply_output_scope(
137138
base_dir,
138139
user_id,
@@ -2930,7 +2931,7 @@ def build_heuristic_report_payload(
29302931
}
29312932

29322933

2933-
def _normalize_string_list(value: Any, limit: int = 4) -> List[str]:
2934+
def _normalize_string_list(value: Any, limit: int = 4, max_chars: int = 180) -> List[str]:
29342935
if isinstance(value, str):
29352936
candidates = re.split(r"\n+|[;;]+", value)
29362937
elif isinstance(value, list):
@@ -2942,10 +2943,55 @@ def _normalize_string_list(value: Any, limit: int = 4) -> List[str]:
29422943
for candidate in candidates:
29432944
cleaned = re.sub(r"^\s*[-*•\d\.\)\(]+\s*", "", str(candidate)).strip()
29442945
if len(cleaned) >= 4:
2945-
items.append(_truncate_text(cleaned, 180))
2946+
items.append(_truncate_text(cleaned, max_chars))
29462947
return _unique_preserve_order(items)[:limit]
29472948

29482949

2950+
def _looks_like_extraction_noise(text: str) -> bool:
2951+
cleaned = _clean_text(text)
2952+
if not cleaned:
2953+
return False
2954+
lowered = cleaned.lower()
2955+
noise_markers = (
2956+
"arxiv:",
2957+
"[cs.",
2958+
"correspondence to:",
2959+
"copyright",
2960+
"accepted by",
2961+
"proceedings of",
2962+
"doi:",
2963+
)
2964+
marker_hits = sum(1 for marker in noise_markers if marker in lowered)
2965+
short_line_count = sum(1 for line in cleaned.splitlines() if 0 < len(line.strip()) <= 18)
2966+
digit_ratio = sum(ch.isdigit() for ch in cleaned) / max(1, len(cleaned))
2967+
return marker_hits >= 2 or short_line_count >= 6 or digit_ratio > 0.12
2968+
2969+
2970+
def _append_template_qa_block(lines: List[str], qid: str, question: str, content: Any) -> None:
2971+
lines.append(f"{qid}: {question}")
2972+
lines.append("")
2973+
if isinstance(content, list):
2974+
values = [_clean_text(item) for item in content if _clean_text(item)]
2975+
if values:
2976+
for item in values:
2977+
lines.append(f"- {item}")
2978+
else:
2979+
lines.append("当前信息不足,建议回到原文对应章节核对。")
2980+
else:
2981+
text = _clean_text(content)
2982+
if text:
2983+
for paragraph in re.split(r"\n{2,}", text):
2984+
paragraph = paragraph.strip()
2985+
if paragraph:
2986+
lines.append(paragraph)
2987+
lines.append("")
2988+
if lines and lines[-1] == "":
2989+
lines.pop()
2990+
else:
2991+
lines.append("当前信息不足,建议回到原文对应章节核对。")
2992+
lines.append("")
2993+
2994+
29492995
def _synthesize_report_with_llm(
29502996
paper: Dict[str, Any],
29512997
user_profile: Dict[str, Any],
@@ -2979,12 +3025,39 @@ def _synthesize_report_with_llm(
29793025
return {"analysis_note": fallback_note}
29803026

29813027

3028+
def _enrich_paper_for_reading_report_compat(
3029+
paper: Dict[str, Any],
3030+
*,
3031+
user_id: Optional[str] = None,
3032+
) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]], Optional[str]]:
3033+
"""Call the enrichment hook while tolerating older one-argument test doubles."""
3034+
try:
3035+
signature = inspect.signature(enrich_paper_for_reading_report)
3036+
if "user_id" in signature.parameters:
3037+
return enrich_paper_for_reading_report(paper, user_id=user_id)
3038+
except (TypeError, ValueError):
3039+
pass
3040+
return enrich_paper_for_reading_report(paper)
3041+
3042+
29823043
def _merge_report_payload(base: Dict[str, Any], llm_payload: Optional[Dict[str, Any]]) -> Dict[str, Any]:
29833044
if not isinstance(llm_payload, dict):
29843045
return base
29853046

29863047
merged = dict(base)
2987-
for key in ("one_sentence_summary", "research_background", "core_method", "key_results"):
3048+
for key in (
3049+
"one_sentence_summary",
3050+
"clean_abstract_summary",
3051+
"problem_analysis",
3052+
"related_work",
3053+
"solution_approach",
3054+
"experiments",
3055+
"future_directions",
3056+
"paper_summary",
3057+
"research_background",
3058+
"core_method",
3059+
"key_results",
3060+
):
29883061
value = _clean_text(llm_payload.get(key))
29893062
if value:
29903063
merged[key] = value
@@ -2994,7 +3067,7 @@ def _merge_report_payload(base: Dict[str, Any], llm_payload: Optional[Dict[str,
29943067
merged["analysis_note"] = _append_analysis_note(merged.get("analysis_note"), analysis_note)
29953068

29963069
for key in ("main_contributions", "limitations", "relevance_points", "reading_focus"):
2997-
values = _normalize_string_list(llm_payload.get(key))
3070+
values = _normalize_string_list(llm_payload.get(key), max_chars=320)
29983071
if values:
29993072
merged[key] = values
30003073

@@ -3052,6 +3125,9 @@ def generate_reading_report(
30523125
payload = report_payload or build_heuristic_report_payload(paper, user_profile)
30533126
title = _clean_text(paper.get("title")) or "Untitled Paper"
30543127
abstract = _clean_abstract_text(payload.get("abstract") or paper.get("abstract"))
3128+
clean_abstract_summary = _clean_text(payload.get("clean_abstract_summary"))
3129+
if clean_abstract_summary and (not _clean_text(abstract) or _looks_like_extraction_noise(abstract)):
3130+
abstract = clean_abstract_summary
30553131
if not _clean_text(abstract):
30563132
fallback_source = _get_direct_pdf_url(paper) or _get_first_url(
30573133
paper,
@@ -3124,52 +3200,51 @@ def generate_reading_report(
31243200
lines.append(f"> {paragraph.strip()}")
31253201
lines.append("")
31263202

3127-
_append_qa_block(
3128-
lines,
3129-
"Q1",
3130-
"这篇论文试图解决什么问题?",
3131-
payload.get("research_background") or "建议先回到原文摘要和引言确认研究问题。",
3132-
)
3133-
_append_qa_block(
3134-
lines,
3135-
"Q2",
3136-
"它提出了什么方法?",
3137-
payload.get("core_method") or "当前未成功提炼方法细节,请重点阅读 Method / Approach 部分。",
3138-
)
3139-
_append_qa_block(
3140-
lines,
3141-
"Q3",
3142-
"主要结果是什么?",
3143-
payload.get("key_results") or "当前没有提炼出明确结果,请重点核对实验表格和主要指标。",
3144-
)
3145-
_append_qa_block(
3146-
lines,
3147-
"Q4",
3148-
"主要贡献或创新点是什么?",
3149-
payload.get("main_contributions") or [],
3150-
)
3151-
_append_qa_block(
3152-
lines,
3153-
"Q5",
3154-
"局限性和注意事项是什么?",
3155-
payload.get("limitations") or [],
3156-
)
3157-
_append_qa_block(
3158-
lines,
3159-
"Q6",
3160-
"这篇论文和我的研究有什么关系?",
3161-
payload.get("relevance_points") or [],
3162-
)
3163-
reading_plan = []
3164-
if analysis_note:
3165-
reading_plan.append(analysis_note)
3166-
reading_plan.extend(payload.get("reading_focus") or [])
3167-
_append_qa_block(
3168-
lines,
3169-
"Q7",
3170-
"我应该怎么读?",
3171-
reading_plan,
3172-
)
3203+
problem_analysis = payload.get("problem_analysis") or payload.get("research_background") or "建议先回到原文摘要和引言确认研究问题。"
3204+
related_work = payload.get("related_work")
3205+
if not related_work:
3206+
related_work_items = payload.get("main_contributions") or []
3207+
related_work = (
3208+
"当前自动解析没有稳定提取出 Related Work 的完整脉络。可先从论文引言、相关工作章节和引用线索核对它主要对比了哪些方法。"
3209+
)
3210+
if related_work_items:
3211+
related_work += "\n\n从当前证据可见,论文的定位至少包括:" + ";".join(str(item) for item in related_work_items[:3]) + "。"
3212+
solution_approach = payload.get("solution_approach") or payload.get("core_method") or "当前未成功提炼方法细节,请重点阅读 Method / Approach 部分。"
3213+
experiments = payload.get("experiments")
3214+
if not experiments:
3215+
result_text = _clean_text(payload.get("key_results"))
3216+
experiments = result_text or "当前没有提炼出明确实验设置,请重点核对 Experiments / Results、表格、图示和主要指标。"
3217+
future_directions = payload.get("future_directions")
3218+
if not future_directions:
3219+
future_items = []
3220+
future_items.extend(payload.get("limitations") or [])
3221+
future_items.extend(payload.get("reading_focus") or [])
3222+
future_directions = future_items or ["当前信息不足,建议从局限性、失败案例、消融缺口和跨领域泛化四个角度回原文继续挖掘。"]
3223+
paper_summary = payload.get("paper_summary")
3224+
if not paper_summary:
3225+
summary_parts = [
3226+
_clean_text(payload.get("one_sentence_summary")),
3227+
_clean_text(payload.get("research_background")),
3228+
_clean_text(payload.get("core_method")),
3229+
_clean_text(payload.get("key_results")),
3230+
]
3231+
contributions = payload.get("main_contributions") or []
3232+
limitations = payload.get("limitations") or []
3233+
relevance = payload.get("relevance_points") or []
3234+
if contributions:
3235+
summary_parts.append("主要贡献包括:" + ";".join(str(item) for item in contributions[:4]) + "。")
3236+
if limitations:
3237+
summary_parts.append("需要注意的边界包括:" + ";".join(str(item) for item in limitations[:3]) + "。")
3238+
if relevance:
3239+
summary_parts.append("与用户画像的关系:" + ";".join(str(item) for item in relevance[:3]) + "。")
3240+
paper_summary = "\n\n".join(part for part in summary_parts if part) or "当前信息不足,建议回到原文摘要、引言、方法和结论串联主线。"
3241+
3242+
_append_template_qa_block(lines, "Q1", "这篇论文试图解决什么问题?", problem_analysis)
3243+
_append_template_qa_block(lines, "Q2", "有哪些相关研究?", related_work)
3244+
_append_template_qa_block(lines, "Q3", "论文如何解决这个问题?", solution_approach)
3245+
_append_template_qa_block(lines, "Q4", "论文做了哪些实验?", experiments)
3246+
_append_template_qa_block(lines, "Q5", "有什么可以进一步探索的点?", future_directions)
3247+
_append_template_qa_block(lines, "Q6", "总结一下论文的主要内容", paper_summary)
31733248

31743249
if report_evidence_anchors:
31753250
bucket_labels = {
@@ -3521,7 +3596,10 @@ def create_reading_report(
35213596
)
35223597

35233598
try:
3524-
enriched_paper, parsed_pdf, pdf_error = enrich_paper_for_reading_report(raw_paper, user_id=user_id)
3599+
enriched_paper, parsed_pdf, pdf_error = _enrich_paper_for_reading_report_compat(
3600+
raw_paper,
3601+
user_id=user_id,
3602+
)
35253603
heuristic_payload = build_heuristic_report_payload(
35263604
enriched_paper,
35273605
profile,

agents/wiki-agent/ingest/from_reading_report.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,15 @@ def ingest_reading_report(
243243
sections = [
244244
("abstract", "Abstract", paper.get("abstract")),
245245
("tldr", "TL;DR", payload.get("one_sentence_summary")),
246-
("Q1-research-background", "Q1 Research background", payload.get("research_background")),
247-
("Q2-core-method", "Q2 Core method", payload.get("core_method")),
248-
("Q3-key-results", "Q3 Key results", payload.get("key_results")),
249-
("Q4-contributions", "Q4 Contributions", payload.get("main_contributions")),
250-
("Q5-limitations", "Q5 Limitations", payload.get("limitations")),
251-
("Q6-relevance", "Q6 Relevance", payload.get("relevance_points")),
252-
("Q7-reading-plan", "Q7 Reading plan", payload.get("reading_focus")),
246+
("Q1-problem-analysis", "Q1 Problem analysis", payload.get("problem_analysis") or payload.get("research_background")),
247+
("Q2-related-work", "Q2 Related work", payload.get("related_work")),
248+
("Q3-solution-approach", "Q3 Solution approach", payload.get("solution_approach") or payload.get("core_method")),
249+
("Q4-experiments", "Q4 Experiments", payload.get("experiments") or payload.get("key_results")),
250+
("Q5-future-directions", "Q5 Future directions", payload.get("future_directions") or payload.get("limitations")),
251+
("Q6-paper-summary", "Q6 Paper summary", payload.get("paper_summary")),
252+
("legacy-contributions", "Legacy contributions", payload.get("main_contributions")),
253+
("legacy-relevance", "Legacy relevance", payload.get("relevance_points")),
254+
("reading-plan", "Reading plan", payload.get("reading_focus")),
253255
("recommendation-reason", "Recommendation reason", payload.get("recommendation_reason")),
254256
]
255257

config/scoring_weights.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,14 @@ quality_venue_weights:
5555
ACL/EMNLP: 0.7
5656
arXiv: 0.5
5757

58-
push_target_count: 50
58+
push_target_count: 100
5959
push_max_count: 100
6060
push_min_count: 20
6161
source_diversity_min_total: 12
6262
source_diversity_min_per_bucket: 3
6363
source_diversity_max_share: 0.70
6464

6565
# 推送参数
66-
push_target_count: 50 # 目标推送论文数量
66+
push_target_count: 100 # 目标推送论文数量
6767
push_max_count: 100 # 最大推送数量
6868
push_min_count: 20 # 最少推送数量(过少时降低阈值)

0 commit comments

Comments
 (0)