ranking_prompt.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. """LLM 精排 Prompt 构建 —— 根据 profile 生成不同排序策略的系统提示与用户提示."""
  2. from __future__ import annotations
  3. from typing import Any
  4. from .method_acronym import is_method_acronym_token
  5. RANKER_SYSTEM_PROMPT = (
  6. "你是学术文献评估专家。根据候选论文列表评估与排序;不得虚构论文。\n\n"
  7. "排序原则:\n"
  8. "1. 优先选择与用户查询语义最相关的论文\n"
  9. "2. 识别经典/里程碑论文:引用量极高(>500)且发表≥5年的开创性工作应排在前面\n"
  10. "3. 顶会/顶刊论文(Nature/Science/NeurIPS/CVPR/ICML/ICLR等)优先\n"
  11. "4. 在相关性接近时,被广泛引用的论文优先于新发论文\n"
  12. "5. 平衡新颖性:若用户明显在找最新方法,可适当降低经典论文权重\n\n"
  13. "请严格按照要求的JSON格式输出。"
  14. )
  15. RANKER_SYSTEM_PROMPT_RETRY = (
  16. "你是学术文献评估专家。根据候选论文列表评估与排序;不得虚构论文。\n\n"
  17. "排序原则:优先语义相关,识别经典/里程碑论文,顶会论文优先,被广泛引用的论文优先。\n"
  18. "请严格按照要求的JSON格式输出。"
  19. )
  20. def ranker_short_focus_query(query: str) -> bool:
  21. q = (query or "").strip()
  22. if not q:
  23. return False
  24. if is_method_acronym_token(q):
  25. return True
  26. return len(q.split()) <= 2 and len(q) <= 24
  27. def build_retrieval_constraints_block(
  28. *,
  29. target_titles: list[str] | None,
  30. authors: list[str] | None,
  31. venues: list[str] | None,
  32. year_from: int | None,
  33. year_to: int | None,
  34. method_acronym: str | None = None,
  35. ) -> str:
  36. parts: list[str] = []
  37. ma = (method_acronym or "").strip()
  38. if ma:
  39. parts.append(
  40. f"- 方法缩写 **{ma}**:优先标题/摘要含「{ma}」的原始论文;"
  41. f"若用户指某会议上的该方法,应匹配该方法的正式论文(锚定标题优先)"
  42. )
  43. tt = [str(t).strip() for t in (target_titles or []) if str(t).strip()][:4]
  44. if tt:
  45. parts.append(f"- 目标论文标题(优先精确匹配):{'; '.join(tt)}")
  46. au = [str(a).strip() for a in (authors or []) if str(a).strip()][:6]
  47. if au:
  48. parts.append(f"- 目标作者:{', '.join(au)}")
  49. vv = [str(v).strip() for v in (venues or []) if str(v).strip()][:4]
  50. if vv:
  51. parts.append(f"- 会议/期刊约束:{', '.join(vv)}")
  52. if year_from is not None or year_to is not None:
  53. yf = year_from if year_from is not None else "?"
  54. yt = year_to if year_to is not None else yf
  55. parts.append(f"- 年份范围:{yf}–{yt}")
  56. if not parts:
  57. return ""
  58. return "\n## 检索约束(必须遵守)\n" + "\n".join(parts) + "\n"
  59. def _profile_task_and_dims(
  60. profile: str,
  61. *,
  62. n_papers: int,
  63. top_k: int,
  64. target_venue: str | None,
  65. ) -> tuple[str, str]:
  66. if profile == "novelty":
  67. return (
  68. f"从 {n_papers} 篇中选出 top {top_k}(「近期进展 / 新工作」),"
  69. "按「新且与查询相关」降序排列;相关度接近时优先更新、更前瞻的工作。",
  70. """## 评估维度(novelty)
  71. 1. **时效与趋势**(35%):年份更新;反映该方向最新设定或基准
  72. 2. **主题相关性**(30%):与查询任务一致(可略宽于 accuracy)
  73. 3. **新意与贡献**(25%):架构/目标/数据/结论上相比既有方法有明确新点
  74. 4. **可信度底线**(10%):实验充分;无关或空壳工作后排""",
  75. )
  76. if profile == "classic":
  77. return (
  78. f"从 {n_papers} 篇中选出 top {top_k}(「原始奠基 / 里程碑式经典工作」),"
  79. "优先**开创性论文**(查询所指方法/架构的原始提出);近年引用/综述后排,除非用户明确要综述。",
  80. """## 评估维度(classic)
  81. 1. **开创性与匹配**(40%):是否为查询所指方法/架构的**原始提出论文**或公认首作
  82. 2. **引用与影响力**(35%):总引用与领域地位;仅讨论该方法的 survey 后排
  83. 3. **权威出处**(15%):顶会/期刊正式收录
  84. 4. **时效**(10%):开创性相近时优先更早的奠基论文""",
  85. )
  86. dims = """## 评估维度(accuracy)
  87. 1. **主题相关性**(40%):论文主题与查询匹配程度
  88. 2. **方法创新性**(25%):方法新颖与创新点
  89. 3. **结果质量**(20%):实验充分性、结果可靠性
  90. 4. **权威与可引用性**(15%):顶会/期刊与引用表现;经典工作可优先于纯新文"""
  91. if target_venue:
  92. dims = """## 评估维度(accuracy · 会议检索)
  93. 1. **主题相关性**(35%):论文主题与查询匹配程度
  94. 2. **届次与年份**(25%):相关度接近时**优先最近一届**(年份更大者优先)
  95. 3. **方法创新性**(20%):方法新颖与创新点
  96. 4. **结果质量**(10%):实验充分性、结果可靠性
  97. 5. **权威与可引用性**(10%):正式 proceedings 与引用表现"""
  98. return (
  99. f"从 {n_papers} 篇中选出最相关的 top {top_k},按与检索意图匹配程度降序排列。",
  100. dims,
  101. )
  102. def build_ranking_prompt(
  103. papers: list[Any],
  104. query: str,
  105. top_k: int,
  106. ranking_profile: str = "accuracy",
  107. *,
  108. abstract_max_chars: int = 500,
  109. target_venue: str | None = None,
  110. main_conference_proceedings_only: bool = False,
  111. intent_source_message: str | None = None,
  112. target_titles: list[str] | None = None,
  113. authors: list[str] | None = None,
  114. venues: list[str] | None = None,
  115. year_from: int | None = None,
  116. year_to: int | None = None,
  117. method_acronym: str | None = None,
  118. ) -> str:
  119. max_abs = max(120, int(abstract_max_chars or 500))
  120. papers_desc: list[str] = []
  121. for i, rp in enumerate(papers, 1):
  122. paper = rp.paper
  123. title = getattr(paper, "title", "N/A")
  124. abstract = getattr(paper, "abstract", "") or ""
  125. if len(abstract) > max_abs:
  126. abstract = abstract[:max_abs] + "..."
  127. year = getattr(paper, "year", "N/A")
  128. venue = getattr(paper, "venue", "") or getattr(paper, "journal", "N/A")
  129. citations = getattr(paper, "citations", 0) or 0
  130. papers_desc.append(
  131. f"\n【论文 {i}】\n标题:{title}\n年份:{year}\n会议/期刊:{venue}\n"
  132. f"引用数:{citations}\n摘要:{abstract}\n"
  133. )
  134. constraint_hint = build_retrieval_constraints_block(
  135. target_titles=target_titles,
  136. authors=authors,
  137. venues=venues or ([target_venue] if target_venue else None),
  138. year_from=year_from,
  139. year_to=year_to,
  140. method_acronym=method_acronym,
  141. )
  142. venue_hint = ""
  143. if target_venue:
  144. venue_hint = f"""
  145. ## 会场约束
  146. 用户限定了 **{target_venue}** 会议。按以下优先级判断:
  147. 1. **优先**:会议/期刊字段明确标注 {target_venue} 或其 proceedings 全称
  148. 2. **降级**:仅标题/摘要提及 {target_venue} 但会议字段不明(arXiv预印本)
  149. 3. **末位**:会议字段明确为其他会议
  150. 4. 同相关度时**发表年份更近**优先;主会优先于 workshop/symposium。
  151. 每条 reason 注明关联判断依据。
  152. """
  153. main_track_hint = ""
  154. if main_conference_proceedings_only and target_venue:
  155. um = (intent_source_message or "").strip()
  156. um_block = f"\n### 用户原始表述\n{um[:700]}\n" if um else ""
  157. main_track_hint = f"""
  158. ## 主会议录用
  159. 仅保留 **{target_venue}** 主会正式论文;排除 workshop、卫星会等。{um_block}
  160. 依据「会议/期刊」字段判断,**非主会论文不得进入前 {top_k}**(不足则少填)。
  161. 每条 reason 说明认定为主会的依据。
  162. """
  163. profile = (ranking_profile or "accuracy").strip().lower()
  164. if profile not in ("accuracy", "novelty", "classic"):
  165. profile = "accuracy"
  166. short_disambig = ""
  167. if ranker_short_focus_query(query):
  168. short_disambig = """
  169. ## 短查询消歧
  170. 同名缩写论文:优先副标题更匹配 ML 顶会主流问题且会议为高等级 proceedings 的论文;
  171. 下调任务/数据形态与用户意图明显不符的论文。每条 reason 说明区分依据。
  172. """
  173. task_line, dims = _profile_task_and_dims(
  174. profile, n_papers=len(papers), top_k=top_k, target_venue=target_venue
  175. )
  176. papers_block = "\n---\n".join(papers_desc)
  177. return f"""你是一位学术文献评估专家,请根据用户检索需求对以下论文精排序。
  178. ## 用户检索需求
  179. {query}
  180. {constraint_hint}{short_disambig}{venue_hint}{main_track_hint}
  181. ## 候选论文列表
  182. {papers_block}
  183. ## 排序任务
  184. {task_line}
  185. {dims}
  186. ## 输出格式
  187. JSON 格式:
  188. ```json
  189. {{"rankings": [
  190. {{"rank": 1, "paper_index": 1, "fine_score": 9.2, "reason": "排序理由:..."}},
  191. ...
  192. ]}}
  193. ```
  194. 要求(必须遵守):
  195. 1. paper_index 对应序号 1-{len(papers)},不得虚构论文
  196. 2. fine_score 范围 0-10,保留一位小数
  197. 3. reason 用中文 2-3 句话简洁说明
  198. 4. 只输出 JSON,无其他内容
  199. 5. 仅依据上方列表判断,不得编造不存在的论文、会议或结果
  200. """