feat(qa): 长文答案 prompt 改造(5000 字 + 图文标记指令)

- 删除 short paragraphs / concise overview 压缩约束
- 新增至少 5000 中文字答案要求(## 小节组织,证据不足时扩展背景/机制/跨物种比较/展望)
- 新增 [[FIG:PMCID]] 图片位置标记指令(每论文至多 1 处、总数 ≤6)
- max_tokens 保持 12000(此前已改)
- 对应容器镜像 plantai-rag-qa-normalized:20260828-longanswer 的同步

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
potaser
2026-08-29 01:36:32 +08:00
co-authored by Claude Fable 5
parent 1993b0a640
commit fdf24c8ab0
+88 -16
View File
@@ -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