From fdf24c8ab0cce448d230e0ed01f6d37300f30627 Mon Sep 17 00:00:00 2001 From: potaser Date: Sat, 29 Aug 2026 01:36:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(qa):=20=E9=95=BF=E6=96=87=E7=AD=94?= =?UTF-8?q?=E6=A1=88=20prompt=20=E6=94=B9=E9=80=A0=EF=BC=885000=20?= =?UTF-8?q?=E5=AD=97=20+=20=E5=9B=BE=E6=96=87=E6=A0=87=E8=AE=B0=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 short paragraphs / concise overview 压缩约束 - 新增至少 5000 中文字答案要求(## 小节组织,证据不足时扩展背景/机制/跨物种比较/展望) - 新增 [[FIG:PMCID]] 图片位置标记指令(每论文至多 1 处、总数 ≤6) - max_tokens 保持 12000(此前已改) - 对应容器镜像 plantai-rag-qa-normalized:20260828-longanswer 的同步 Co-Authored-By: Claude Fable 5 --- src/rag/qa_v1_1_73/answer_composer.py | 104 ++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 16 deletions(-) diff --git a/src/rag/qa_v1_1_73/answer_composer.py b/src/rag/qa_v1_1_73/answer_composer.py index c7de578..d88f024 100644 --- a/src/rag/qa_v1_1_73/answer_composer.py +++ b/src/rag/qa_v1_1_73/answer_composer.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import re from typing import Any, Dict, List from .evidence_pack import format_evidence_pack @@ -13,14 +14,33 @@ class AnswerComposer: Use a natural explanatory style. Do not answer with a table unless the user explicitly asks for a table. -Write the answer as substantive bullet points or short paragraphs. Each point should state one supported finding, explain its biological meaning in relation to the user's question, and cite the supporting records. +Write the answer as substantive, well-organized paragraphs. Each point should state one supported finding, explain its biological meaning in relation to the user's question, and cite the supporting records. Do not merely list SRT triples. Synthesize the provided SRTs and evidence into readable biological statements, but do not add unsupported claims. Use all supplied evidence that directly answers the question or materially completes an explanation. Combine redundant records into one finding with shared citations rather than repeating them. Omit evidence that is wholly unrelated; do not mention or cite unrelated records merely to maximize citation coverage. +Before writing, silently perform this final-answer consistency check using only +the supplied Data Report, GraphChains, and Agent-approved subquestion groups: +1. List each directly supported answer slot and ensure that it is stated once + in the answer. +2. Never call a requested fact unsupported, unavailable, absent, or + insufficient when a supplied record directly states that fact. +3. For a supported multi-hop GraphChain, follow the continuous chain through + its final requested terminal. Do not stop at a bridge entity when the next + supplied hop answers the question. +4. When only part of a question is supported, answer every supported part + first, then limit any uncertainty statement strictly to the unsupported + part. + +These are evidence-use checks, not a license to infer missing facts. Do not +invent a bridge, relation, or terminal that is not present in the supplied +report. + For broad family-function, survey, or open-ended questions, begin with a concise overview and then organize the answer by the distinct functions supported by the evidence. For each function, explain the richest supported process chain available: the relevant context or trigger when present, the function, any intermediate biological process, and the resulting biological outcome. This process-level explanation is valuable even when no specific gene member or molecular target is available. +For a broad gene-family survey, consolidate the evidence into roughly five to eight major functional or mechanistic themes when the records support that many. Prefer one to three of the most relevant citations per theme and merge redundant family-level records. A complete Chinese answer must be at least 5000 Chinese characters (or 3000 words for English). Organize with ## subheadings and bullet lists, covering each evidence-supported aspect in depth; do not compress or summarize away detail. If the evidence is insufficient for 5000 characters, expand the background, mechanism analysis, cross-species comparison and research outlook with clearly-labeled reasoning. Never fabricate citations to reach the length target. + Specific genes, targets, binding sites, and experimental methods are not required unless the question asks for them. When supplied evidence contains useful examples, include them selectively to deepen the explanation without changing a family-level question into an unnecessary gene-by-gene catalogue. Match causal language to the evidence. Do not turn a process-level graph chain, association, prediction, or co-occurrence into a direct molecular mechanism. State direct activation, repression, binding, or causation only when the supplied records support it. @@ -69,8 +89,23 @@ Do not use unsupported general knowledge. By default, prefer not to use general subquestion_evidence_groups: List[Dict[str, Any]] | None = None, ) -> str: context_data = self._build_context_data(evidence, graph_chain_context) + allowed_evidence_markers = { + marker + for item in evidence + for marker in ( + str(item.evidence_id or "").strip(), + str(item.relation_id or "").strip(), + str((item.raw or {}).get("assertion_id") or "").strip(), + ) + if marker + } + allowed_chain_ids = set( + re.findall(r"^Chain summary\s+(\S+)", graph_chain_context, flags=re.MULTILINE) + ) subquestion_block = self._build_subquestion_context( - subquestion_evidence_groups or [] + subquestion_evidence_groups or [], + allowed_evidence_markers=allowed_evidence_markers, + allowed_chain_ids=allowed_chain_ids, ) correction_block = ( f"--- Citation correction required ---\n\n{correction_note}\n\n" @@ -106,7 +141,7 @@ Claim verification: These retrieval notes are only hints for selecting relevant records. Do not structure the answer around them mechanically. - {handoff_block}{subquestion_block}{correction_block}--- Multi-hop Path Rule --- + {handoff_block}{subquestion_block}--- Multi-hop Path Rule --- When GraphChains are supplied, prefer a continuous path that covers the entities and relations stated in the question in their given order. Answer with the terminal object reached by the final relation asked in the question. @@ -119,11 +154,11 @@ Answer in the same language as the user's question unless the user requests anot Preserve gene symbols and Latin species names exactly while translating explanatory prose into the user's language. -Use natural explanatory bullet points or short paragraphs. +Use natural explanatory paragraphs organized with ## subheadings. Do not use a table unless the user explicitly asks for one. -Begin with the direct answer to the user's question. For broad function or survey questions, give a concise overview before the detailed evidence-supported functions. +Begin with the direct answer to the user's question. For broad function or survey questions, give an overview before the detailed evidence-supported functions. Then explain as many distinct, relevant findings as the supplied evidence supports; do not impose a fixed 1-4 point limit. Each point should interpret the evidence in relation to the question, not merely restate a triple. @@ -135,16 +170,26 @@ If the records contain related but not directly answering evidence, mention it o If the provided data are insufficient, say so directly. +--- Figure Marker --- + +When citing a paper for the first time, you may insert a marker [[FIG:PMCID]] on its own line immediately after the paragraph containing the citation, to indicate where a figure from that paper should be displayed. Use at most one marker per paper, at most 6 markers total. Do not write image URLs yourself; the system will resolve markers into figures. + +{correction_block} Now answer the following question: {question} """, temperature=0.1, - max_tokens=3500, + max_tokens=12000, ) @staticmethod - def _build_subquestion_context(groups: List[Dict[str, Any]]) -> str: + def _build_subquestion_context( + groups: List[Dict[str, Any]], + *, + allowed_evidence_markers: set[str], + allowed_chain_ids: set[str], + ) -> str: if not groups: return "" sections = [ @@ -178,17 +223,44 @@ Now answer the following question: for item in group.get("evidence") or [] if isinstance(item, EvidenceItem) ] - if evidence: - sections.append("Accepted evidence:\n" + format_evidence_pack(evidence)) + evidence_references: List[str] = [] + for item in evidence: + markers = { + str(item.evidence_id or "").strip(), + str(item.relation_id or "").strip(), + str((item.raw or {}).get("assertion_id") or "").strip(), + } + markers.discard("") + if not markers.intersection(allowed_evidence_markers): + continue + reference = ( + str(item.relation_id or "").strip() + or str((item.raw or {}).get("assertion_id") or "").strip() + or str(item.evidence_id or "").strip() + ) + evidence_references.append( + f"- {reference}: {item.source} -- {item.relation} --> {item.target}" + ) + if evidence_references: + sections.append( + "Accepted evidence references (full records appear once in the Data Report):\n" + + "\n".join(evidence_references) + ) else: - sections.append("Accepted evidence: none") - graph_chain_context = str( - group.get("graph_chain_context") or "" - ).strip() - if graph_chain_context: - sections.append("Accepted chains:\n" + graph_chain_context) + sections.append("Accepted evidence references: none") + chain_ids = [ + str(chain.get("chain_id") or "").strip() + for chain in group.get("chains") or [] + if isinstance(chain, dict) + and str(chain.get("chain_id") or "").strip() in allowed_chain_ids + ] + if chain_ids: + sections.append( + "Accepted chain references (full chains appear once in GraphChains): " + + ", ".join(dict.fromkeys(chain_ids)) + ) else: - sections.append("Accepted chains: none") + sections.append("Accepted chain references: none") return "\n".join(sections).strip() + "\n\n" @staticmethod