958 lines
37 KiB
Python
Executable File
958 lines
37 KiB
Python
Executable File
"""
|
||
DiscoveryAgent V1 - CellVoyager风格的科学发现Agent
|
||
|
||
功能:
|
||
1. 分析执行结果,识别异常/新模式
|
||
2. 生成科学假设
|
||
3. 设计验证实验
|
||
4. 迭代优化分析计划
|
||
|
||
基于CellVoyager论文的REPLAN机制
|
||
"""
|
||
from typing import Dict, List, Optional, Any
|
||
from dataclasses import dataclass, field, asdict, is_dataclass
|
||
from enum import Enum
|
||
import json
|
||
import asyncio
|
||
|
||
from .base import AgentMessage, AgentState, BaseAgent
|
||
from gfa_core.mas.llm import LLMManager
|
||
from gfa_core.mas.dual_kb_qa import query_dual_kb_qa
|
||
|
||
|
||
class DiscoveryType(Enum):
|
||
"""发现类型"""
|
||
ANOMALY = "anomaly" # 异常检测
|
||
PATTERN = "pattern" # 新模式
|
||
CORRELATION = "correlation" # 相关性
|
||
HYPOTHESIS = "hypothesis" # 新假设
|
||
|
||
|
||
@dataclass
|
||
class AnomalyDetection:
|
||
"""异常检测结果"""
|
||
detected: bool
|
||
description: str
|
||
severity: str # low, medium, high
|
||
affected_genes: List[str] = field(default_factory=list)
|
||
expected_value: Any = None
|
||
actual_value: Any = None
|
||
|
||
|
||
@dataclass
|
||
class GeneratedHypothesis:
|
||
"""生成的假设"""
|
||
hypothesis: str
|
||
rationale: str
|
||
confidence: float
|
||
testable: bool
|
||
suggested_experiments: List[str] = field(default_factory=list)
|
||
required_data: List[str] = field(default_factory=list)
|
||
|
||
|
||
@dataclass
|
||
class DiscoveryResult:
|
||
"""发现结果"""
|
||
discovery_type: DiscoveryType
|
||
description: str
|
||
confidence: float
|
||
hypothesis: Optional[GeneratedHypothesis] = None
|
||
anomalies: List[AnomalyDetection] = field(default_factory=list)
|
||
updated_plan: Optional[Dict] = None
|
||
should_continue: bool = False
|
||
|
||
|
||
class DiscoveryAgent(BaseAgent):
|
||
"""
|
||
科学发现Agent - CellVoyager风格
|
||
|
||
核心流程:
|
||
1. INSPECT - 检查结果
|
||
2. ANALYZE - 分析异常/模式
|
||
3. HYPOTHESIZE - 生成假设
|
||
4. DESIGN - 设计验证
|
||
5. REPLAN - 更新计划
|
||
"""
|
||
name = "discovery"
|
||
role = "Scientific Discovery Assistant"
|
||
|
||
def __init__(self, llm_manager: LLMManager):
|
||
super().__init__(llm_manager)
|
||
self.discovery_history: List[DiscoveryResult] = []
|
||
self.max_iterations = 3
|
||
|
||
async def execute(self, state: AgentState, message: AgentMessage) -> AgentState:
|
||
"""
|
||
执行发现流程(科研模式:存储解读结果,支持按需生成深度报告)
|
||
|
||
Args:
|
||
state: 当前状态,包含执行结果
|
||
message: 消息,可能包含特定发现指令
|
||
- mode: "quick" (快速模式,只存储结果)
|
||
"deep" (深度模式,立即生成解读)
|
||
"generate_report" (生成综合报告)
|
||
|
||
Returns:
|
||
更新后的状态,包含发现结果
|
||
"""
|
||
mode = message.payload.get("mode", "quick") # 默认快速模式
|
||
print(f"[DiscoveryAgent] 开始科学发现流程 (模式: {mode})...")
|
||
|
||
# 特殊模式:生成综合深度报告
|
||
if mode == "generate_report":
|
||
return await self._generate_comprehensive_report(state, message)
|
||
|
||
# 1. 获取当前结果(支持多种数据格式)
|
||
execution_results = self._normalize_execution_results(state)
|
||
current_plan = state.results.get("plan", state.results.get("task_plan", {}))
|
||
past_discoveries = state.results.get("discoveries", [])
|
||
|
||
# 提取基因家族名称
|
||
gene_family = self._extract_gene_family_from_state(state)
|
||
print(f"[DiscoveryAgent] 分析基因家族: {gene_family}")
|
||
|
||
# 2. 检查异常(使用RAG获取文献基准)
|
||
anomalies = await self._detect_anomalies(execution_results, gene_family)
|
||
print(f"[DiscoveryAgent] 检测到 {len(anomalies)} 个异常")
|
||
|
||
# 3. 分析模式
|
||
patterns = await self._analyze_patterns(execution_results)
|
||
print(f"[DiscoveryAgent] 识别到 {len(patterns)} 个模式")
|
||
|
||
# 4. 生成假设(只要有可用执行摘要即调用 LLM,避免「无异常就无话」)
|
||
hypothesis = None
|
||
if execution_results:
|
||
hypothesis = await self._generate_hypothesis(
|
||
anomalies, patterns, execution_results
|
||
)
|
||
if hypothesis:
|
||
print(f"[DiscoveryAgent] 生成假设: {hypothesis.hypothesis[:80]}...")
|
||
|
||
# 5. 决定是否继续迭代
|
||
should_continue = self._should_continue_iteration(
|
||
anomalies, patterns, hypothesis, len(past_discoveries)
|
||
)
|
||
|
||
# 6. 更新计划(如需要)
|
||
updated_plan = None
|
||
if should_continue and hypothesis:
|
||
updated_plan = await self._update_plan(current_plan, hypothesis)
|
||
print(f"[DiscoveryAgent] 更新分析计划")
|
||
|
||
# 7. 构建发现结果
|
||
discovery_result = DiscoveryResult(
|
||
discovery_type=self._determine_discovery_type(anomalies, patterns),
|
||
description=self._build_description(anomalies, patterns, hypothesis),
|
||
confidence=hypothesis.confidence if hypothesis else 0.5,
|
||
hypothesis=hypothesis,
|
||
anomalies=anomalies,
|
||
updated_plan=updated_plan,
|
||
should_continue=should_continue
|
||
)
|
||
|
||
# 8. 构建完整发现结果(包含原始数据,供后续深度解读使用)
|
||
discovery_data = {
|
||
"type": discovery_result.discovery_type.value,
|
||
"description": discovery_result.description,
|
||
"confidence": discovery_result.confidence,
|
||
"hypothesis": {
|
||
"text": hypothesis.hypothesis if hypothesis else None,
|
||
"rationale": hypothesis.rationale if hypothesis else None,
|
||
"confidence": hypothesis.confidence if hypothesis else None,
|
||
"suggested_experiments": hypothesis.suggested_experiments if hypothesis else []
|
||
},
|
||
"anomalies": [
|
||
{
|
||
"description": a.description,
|
||
"severity": a.severity,
|
||
"affected_genes": a.affected_genes
|
||
}
|
||
for a in anomalies
|
||
],
|
||
"should_continue": should_continue,
|
||
# 新增:存储原始数据,供深度解读使用
|
||
"raw_data": {
|
||
"execution_results": execution_results,
|
||
"patterns": [p for p in patterns],
|
||
"analysis_timestamp": self._get_timestamp()
|
||
}
|
||
}
|
||
|
||
# 深度模式:立即生成详细解读
|
||
if mode == "deep":
|
||
deep_interpretation = await self._generate_deep_interpretation(discovery_data)
|
||
discovery_data["deep_interpretation"] = deep_interpretation
|
||
print(f"[DiscoveryAgent] 已生成深度解读")
|
||
|
||
state.results["discovery_result"] = discovery_data
|
||
|
||
stage_hint = message.payload.get("stage") or ""
|
||
if not stage_hint:
|
||
pr = message.payload.get("parse_result") or state.results.get("parsed_report") or {}
|
||
if isinstance(pr, dict):
|
||
stage_hint = pr.get("stage", "unknown")
|
||
|
||
history_entry = {**discovery_data, "stage": stage_hint}
|
||
self.discovery_history.append(discovery_result)
|
||
past_discoveries.append(history_entry)
|
||
state.results["discoveries"] = past_discoveries
|
||
|
||
# 添加消息
|
||
state.messages.append({
|
||
"role": "discovery",
|
||
"content": discovery_result.description,
|
||
"metadata": {
|
||
"confidence": discovery_result.confidence,
|
||
"should_continue": should_continue
|
||
}
|
||
})
|
||
|
||
print(f"[DiscoveryAgent] 发现流程完成,置信度: {discovery_result.confidence:.2f}")
|
||
|
||
return state
|
||
|
||
def _normalize_execution_results(self, state: AgentState) -> Dict:
|
||
"""
|
||
标准化执行结果格式
|
||
|
||
支持多种输入格式:
|
||
1. Dict格式: {"identify": {...}, "phylogeny": {...}}
|
||
2. List格式: [{"stage": "identify", ...}, {"stage": "phylogeny", ...}]
|
||
3. 从parsed_report提取
|
||
"""
|
||
results = {}
|
||
|
||
# 尝试从execution_results获取(可能是list或dict)
|
||
execution_results = state.results.get("execution_results", {})
|
||
if isinstance(execution_results, dict):
|
||
results = execution_results
|
||
elif isinstance(execution_results, list):
|
||
# 转换为dict格式
|
||
for item in execution_results:
|
||
if isinstance(item, dict):
|
||
stage = item.get("stage", "unknown")
|
||
results[stage] = item
|
||
|
||
# 尝试从parsed_report获取更详细的数据
|
||
parsed_report = state.results.get("parsed_report", {})
|
||
if parsed_report:
|
||
stage = parsed_report.get("stage", "unknown")
|
||
if stage not in results:
|
||
results[stage] = {}
|
||
# 合并parsed数据
|
||
results[stage].update({
|
||
"metrics": parsed_report.get("metrics", {}),
|
||
"parsed_data": parsed_report.get("parsed_data", {}),
|
||
"status": parsed_report.get("status", "unknown")
|
||
})
|
||
|
||
# 尝试从单个execution_result获取(最新执行结果)
|
||
execution_result = state.results.get("execution_result", {})
|
||
if execution_result:
|
||
manifest = execution_result.get("manifest", {})
|
||
stage = manifest.get("stage", "unknown")
|
||
if stage not in results:
|
||
results[stage] = {}
|
||
results[stage].update({
|
||
"metrics": manifest.get("metrics", {}),
|
||
"status": execution_result.get("status", "unknown"),
|
||
"manifest": manifest
|
||
})
|
||
|
||
return results
|
||
|
||
def _bucket_metrics(self, bucket: Any) -> Dict[str, Any]:
|
||
if not isinstance(bucket, dict):
|
||
return {}
|
||
m = bucket.get("metrics")
|
||
if isinstance(m, dict) and m:
|
||
return m
|
||
manifest = bucket.get("manifest") or {}
|
||
if isinstance(manifest, dict):
|
||
mm = manifest.get("metrics")
|
||
if isinstance(mm, dict):
|
||
return mm
|
||
return {}
|
||
|
||
def _discovery_record_as_dict(self, discovery: Any) -> Dict[str, Any]:
|
||
"""兼容历史中的 DiscoveryResult dataclass 与 dict。"""
|
||
if isinstance(discovery, dict):
|
||
return discovery
|
||
if isinstance(discovery, DiscoveryResult):
|
||
hyp = discovery.hypothesis
|
||
hyp_dict = {}
|
||
if hyp is not None:
|
||
hyp_dict = {
|
||
"text": hyp.hypothesis,
|
||
"rationale": hyp.rationale,
|
||
"confidence": hyp.confidence,
|
||
"suggested_experiments": hyp.suggested_experiments or [],
|
||
}
|
||
return {
|
||
"type": discovery.discovery_type.value,
|
||
"description": discovery.description,
|
||
"confidence": discovery.confidence,
|
||
"should_continue": discovery.should_continue,
|
||
"stage": getattr(discovery, "stage", None),
|
||
"hypothesis": hyp_dict,
|
||
"anomalies": [
|
||
asdict(a) if is_dataclass(a) else a for a in (discovery.anomalies or [])
|
||
],
|
||
}
|
||
return {"description": str(discovery), "type": "unknown"}
|
||
|
||
async def _detect_anomalies(self, results: Dict, gene_family: str = "SPL") -> List[AnomalyDetection]:
|
||
"""
|
||
检测异常
|
||
|
||
检查执行结果中的异常情况:
|
||
- 基因数量异常(过多/过少)- 基于RAG文献数据
|
||
- 表达模式异常
|
||
- 进化关系异常
|
||
"""
|
||
anomalies = []
|
||
|
||
# 检查鉴定结果 - 支持多种数据路径
|
||
identify_data = results.get("identify", {})
|
||
|
||
# 尝试多种路径获取基因数量
|
||
gene_count = 0
|
||
if "parsed_data" in identify_data:
|
||
gene_count = identify_data["parsed_data"].get("gene_count", 0)
|
||
if gene_count == 0 and "metrics" in identify_data:
|
||
gene_count = identify_data["metrics"].get("final_members", 0)
|
||
if gene_count == 0 and "manifest" in identify_data:
|
||
gene_count = identify_data["manifest"].get("metrics", {}).get("final_members", 0)
|
||
|
||
# 基因数量异常检测 - 使用RAG获取文献基准
|
||
if gene_count > 0:
|
||
# 查询RAG获取该基因家族的典型规模
|
||
expected_range = await self._get_gene_family_size_from_rag(gene_family)
|
||
min_expected = expected_range.get("min", 5)
|
||
max_expected = expected_range.get("max", 200)
|
||
median_expected = expected_range.get("median", (min_expected + max_expected) // 2)
|
||
|
||
# 动态阈值:低于最小值50%或高于最大值200%视为异常
|
||
# 例如:文献报道10-30个,低于5个或高于60个视为异常
|
||
lower_threshold = max(3, int(min_expected * 0.5)) # 至少低于3个才触发
|
||
upper_threshold = int(max_expected * 2.0)
|
||
|
||
if gene_count < lower_threshold:
|
||
anomalies.append(AnomalyDetection(
|
||
detected=True,
|
||
description=f"鉴定到的基因数量过少 ({gene_count}个),远低于文献报道的{min_expected}-{max_expected}个",
|
||
severity="high",
|
||
affected_genes=[],
|
||
expected_value=f"{min_expected}-{max_expected} (中位数: {median_expected})",
|
||
actual_value=gene_count
|
||
))
|
||
elif gene_count > upper_threshold:
|
||
anomalies.append(AnomalyDetection(
|
||
detected=True,
|
||
description=f"鉴定到的基因数量过多 ({gene_count}个),远高于文献报道的{min_expected}-{max_expected}个",
|
||
severity="medium",
|
||
affected_genes=[],
|
||
expected_value=f"{min_expected}-{max_expected} (中位数: {median_expected})",
|
||
actual_value=gene_count
|
||
))
|
||
|
||
# 检查表达分析结果
|
||
if "expression" in results:
|
||
expr_data = results["expression"]
|
||
# 检测异常表达模式
|
||
# TODO: 实现具体检测逻辑
|
||
|
||
return anomalies
|
||
|
||
async def _get_gene_family_size_from_rag(self, gene_family: str) -> Dict:
|
||
"""
|
||
从RAG查询基因家族的典型规模
|
||
|
||
查询几个模式物种的数据,返回统计范围
|
||
"""
|
||
try:
|
||
import aiohttp
|
||
import os
|
||
|
||
api_url = os.environ.get("LEGACY_RAG_API_URL", "").rstrip("/")
|
||
if api_url:
|
||
async with aiohttp.ClientSession() as session:
|
||
async with session.get(
|
||
f"{api_url}/api/v1/rag/family/baseline",
|
||
params={"family": gene_family}
|
||
) as response:
|
||
if response.status == 200:
|
||
data = await response.json()
|
||
baseline = data.get("baseline") or {}
|
||
if baseline.get("count", 0) and baseline.get("min") is not None and baseline.get("max") is not None:
|
||
return {
|
||
"min": int(baseline.get("min")),
|
||
"max": int(baseline.get("max")),
|
||
"median": baseline.get("median"),
|
||
"count": int(baseline.get("count") or 0),
|
||
"by_species": baseline.get("by_species") or [],
|
||
"source": "rag_family_baseline"
|
||
}
|
||
|
||
# 查询RAG获取基因家族规模信息
|
||
query = f"{gene_family} gene family member count size Arabidopsis rice"
|
||
|
||
data = await asyncio.to_thread(query_dual_kb_qa, query, top_k=5, use_llm=False)
|
||
sources = data.get("sources", [])
|
||
|
||
# 从文献中提取基因数量
|
||
sizes = []
|
||
for source in sources:
|
||
content = str(source.get("content") or source.get("evidence") or "")
|
||
# 尝试提取数字(如 "16 members", "about 20 genes")
|
||
import re
|
||
# 匹配模式:数字 + members/genes/family
|
||
matches = re.findall(r'(\d+)\s+(?:members?|genes?|family)', content, re.IGNORECASE)
|
||
for match in matches:
|
||
try:
|
||
size = int(match)
|
||
if 1 <= size <= 1000: # 合理范围
|
||
sizes.append(size)
|
||
except:
|
||
pass
|
||
|
||
if sizes:
|
||
sizes.sort()
|
||
return {
|
||
"min": min(sizes),
|
||
"max": max(sizes),
|
||
"median": sizes[len(sizes)//2],
|
||
"count": len(sizes),
|
||
"source": "dual_kb_qa"
|
||
}
|
||
except Exception as e:
|
||
print(f"[Discovery] RAG查询失败: {e}")
|
||
|
||
# 回退到默认阈值(更严格的默认值)
|
||
return {"min": 10, "max": 100, "median": 30, "source": "default"}
|
||
|
||
def _extract_gene_family_from_state(self, state: AgentState) -> str:
|
||
"""从状态中提取基因家族名称"""
|
||
# 尝试从多个来源提取
|
||
intent = state.results.get("results", {}).get("intent", {}) or state.results.get("intent", {})
|
||
if intent and "gene_family" in intent:
|
||
return intent["gene_family"]
|
||
|
||
# 从user_input提取
|
||
user_input = state.results.get("user_input", {})
|
||
if user_input and "gene_family" in user_input:
|
||
return user_input["gene_family"]
|
||
|
||
# 从prefix提取(如 "Ft.SPL" -> "SPL")
|
||
prefix = state.results.get("prefix", "")
|
||
if prefix and "." in prefix:
|
||
return prefix.split(".")[-1]
|
||
|
||
return "SPL" # 默认
|
||
|
||
async def _analyze_patterns(self, results: Dict) -> List[Dict]:
|
||
"""
|
||
基于 manifest 常见 metrics 做轻量模式提炼(给假设生成结构化抓手)。
|
||
"""
|
||
patterns: List[Dict] = []
|
||
|
||
id_b = results.get("identify") or {}
|
||
mid = self._bucket_metrics(id_b)
|
||
gcount = mid.get("final_members") or id_b.get("parsed_data", {}).get("gene_count")
|
||
if gcount:
|
||
patterns.append({
|
||
"type": "gene_family_size_observed",
|
||
"label": "鉴定规模",
|
||
"detail": f"当前鉴定成员数(final_members/gene_count)={gcount}",
|
||
})
|
||
|
||
de_b = results.get("rnaseq_de") or {}
|
||
mde = self._bucket_metrics(de_b)
|
||
if mde.get("de_genes_total") is not None:
|
||
tot = int(mde.get("de_genes_total") or 0)
|
||
fam = mde.get("family_analysis") or {}
|
||
dfg = fam.get("de_family_genes")
|
||
tfg = fam.get("total_family_genes")
|
||
patterns.append({
|
||
"type": "rnaseq_de_overview",
|
||
"label": "差异表达规模",
|
||
"detail": (
|
||
f"全局 DE 基因 {tot}(上/下={mde.get('de_genes_up')}/{mde.get('de_genes_down')});"
|
||
f"家族内 DE {dfg}/{tfg}"
|
||
),
|
||
})
|
||
if tot >= 80 and dfg == 0:
|
||
patterns.append({
|
||
"type": "global_de_decoupled_from_family",
|
||
"label": "家族与全局差异脱节",
|
||
"detail": "全局大量差异基因但家族层面计数为 0,提示胁迫响应可能主要在非家族基因或其它家族",
|
||
})
|
||
|
||
for st in ("rnaseq_kegg_enrich", "rnaseq_go_enrich"):
|
||
eb = results.get(st) or {}
|
||
me = self._bucket_metrics(eb)
|
||
kp = me.get("kegg_enriched_pathways")
|
||
gt = me.get("go_enriched_terms")
|
||
if kp is not None or gt is not None:
|
||
patterns.append({
|
||
"type": "enrichment_yield",
|
||
"label": f"{st} 产出",
|
||
"detail": f"KEGG pathways={kp}, GO terms={gt}, DE_input={me.get('de_genes_input')}",
|
||
})
|
||
|
||
phy_b = results.get("phylogeny") or {}
|
||
mp = self._bucket_metrics(phy_b)
|
||
if mp.get("tips") or mp.get("taxa_count"):
|
||
patterns.append({
|
||
"type": "phylogeny_resolution",
|
||
"label": "系统发育规模",
|
||
"detail": f"tips/taxa≈{mp.get('tips') or mp.get('taxa_count')}",
|
||
})
|
||
|
||
return patterns
|
||
|
||
async def _generate_hypothesis(
|
||
self,
|
||
anomalies: List[AnomalyDetection],
|
||
patterns: List[Dict],
|
||
results: Dict
|
||
) -> GeneratedHypothesis:
|
||
"""
|
||
基于异常和模式生成科学假设
|
||
|
||
使用LLM生成可验证的假设
|
||
"""
|
||
# 构建上下文
|
||
context = {
|
||
"anomalies": [
|
||
{"description": a.description, "severity": a.severity}
|
||
for a in anomalies
|
||
],
|
||
"patterns": patterns,
|
||
"results_summary": self._summarize_results(results)
|
||
}
|
||
|
||
prompt = f"""基于以下基因家族分析结果(可包含空的 anomalies/patterns),生成科学假设。
|
||
若未发现异常,请根据定量指标提出可检验的下一步科学问题,避免泛泛而谈。
|
||
|
||
分析结果摘要:
|
||
{json.dumps(context, ensure_ascii=False, indent=2)}
|
||
|
||
请生成:
|
||
1. 一个具体的、可验证的科学假设
|
||
2. 假设的生物学依据
|
||
3. 建议的验证实验
|
||
4. 需要的额外数据
|
||
5. 置信度评估 (0.0-1.0)
|
||
|
||
以JSON格式返回:
|
||
{{
|
||
"hypothesis": "假设描述",
|
||
"rationale": "依据",
|
||
"confidence": 0.8,
|
||
"testable": true,
|
||
"suggested_experiments": ["实验1", "实验2"],
|
||
"required_data": ["数据1", "数据2"]
|
||
}}"""
|
||
|
||
try:
|
||
response = await self._acall_llm(prompt, temperature=0.7)
|
||
content = response.content
|
||
|
||
# 解析JSON
|
||
if "```json" in content:
|
||
content = content.split("```json")[1].split("```")[0]
|
||
elif "```" in content:
|
||
content = content.split("```")[1].split("```")[0]
|
||
|
||
data = json.loads(content.strip())
|
||
|
||
return GeneratedHypothesis(
|
||
hypothesis=data.get("hypothesis", "无法生成假设"),
|
||
rationale=data.get("rationale", ""),
|
||
confidence=data.get("confidence", 0.5),
|
||
testable=data.get("testable", False),
|
||
suggested_experiments=data.get("suggested_experiments", []),
|
||
required_data=data.get("required_data", [])
|
||
)
|
||
except Exception as e:
|
||
print(f"[DiscoveryAgent] 生成假设失败: {e}")
|
||
return GeneratedHypothesis(
|
||
hypothesis="基于当前结果无法生成明确假设",
|
||
rationale="数据不足或分析结果不明确",
|
||
confidence=0.3,
|
||
testable=False
|
||
)
|
||
|
||
def _should_continue_iteration(
|
||
self,
|
||
anomalies: List[AnomalyDetection],
|
||
patterns: List[Dict],
|
||
hypothesis: Optional[GeneratedHypothesis],
|
||
iteration_count: int
|
||
) -> bool:
|
||
"""
|
||
决定是否继续迭代
|
||
|
||
条件:
|
||
1. 检测到重要异常(high或medium严重性)
|
||
2. 生成了高置信度假设
|
||
3. 未达到最大迭代次数
|
||
"""
|
||
if iteration_count >= self.max_iterations:
|
||
return False
|
||
|
||
# 有高严重性异常(必须修复)
|
||
high_severity = any(a.severity == "high" for a in anomalies)
|
||
|
||
# 有中等严重性异常且没有高置信度假设时也需要迭代
|
||
medium_severity = any(a.severity == "medium" for a in anomalies)
|
||
|
||
# 有高置信度假设
|
||
high_confidence = hypothesis and hypothesis.confidence > 0.7
|
||
|
||
# 有值得关注的异常模式(非normal模式)
|
||
significant_patterns = any(
|
||
p.get("type") not in ["normal_gene_family_size"] for p in patterns
|
||
)
|
||
|
||
return high_severity or (medium_severity and not high_confidence) or significant_patterns
|
||
|
||
async def _update_plan(
|
||
self,
|
||
current_plan: Dict,
|
||
hypothesis: GeneratedHypothesis,
|
||
learning_agent=None # 传入Learning Agent获取参数建议
|
||
) -> Dict:
|
||
"""
|
||
基于假设更新分析计划
|
||
|
||
根据异常类型和假设,返回可执行的分析阶段
|
||
|
||
返回格式必须与 PlanningAgentV2 一致:
|
||
{
|
||
"stages": [{"id": "identify", "params": {...}}, ...],
|
||
"job_id": "...",
|
||
"prefix": "...",
|
||
"workspace": "..."
|
||
}
|
||
"""
|
||
import copy
|
||
|
||
# 获取当前计划的元信息
|
||
job_id = current_plan.get("job_id", "")
|
||
prefix = current_plan.get("prefix", "")
|
||
workspace = current_plan.get("workspace", "")
|
||
|
||
# 根据异常/假设确定需要重新执行的阶段
|
||
# 这里是示例逻辑,实际应根据 hypothesis.content 分析
|
||
stages_to_add = []
|
||
|
||
# 如果假设涉及参数调整,重新执行相关阶段
|
||
if hypothesis and hypothesis.confidence > 0.7:
|
||
# 解析假设中提到的建议
|
||
suggested = hypothesis.suggested_experiments if hypothesis.suggested_experiments else []
|
||
|
||
# 根据建议映射到可执行的 stage_id
|
||
# 例如: "调整鉴定参数" -> 重新执行 identify
|
||
# 例如: "增加motif数量" -> 重新执行 motif
|
||
stage_mapping = {
|
||
"鉴定": "identify",
|
||
"进化树": "phylogeny",
|
||
"motif": "motif",
|
||
"结构域": "domain",
|
||
"基因结构": "structure",
|
||
"启动子": "promoter",
|
||
"种间共线性": "interspecies_synteny",
|
||
"物种间共线性": "interspecies_synteny",
|
||
"跨物种共线性": "interspecies_synteny",
|
||
"种内共线性": "synteny",
|
||
"共线性": "synteny",
|
||
"表达": "expression",
|
||
}
|
||
|
||
for suggestion in suggested:
|
||
for keyword, stage_id in stage_mapping.items():
|
||
if keyword in suggestion:
|
||
stages_to_add.append({
|
||
"id": stage_id,
|
||
"reason": f"基于假设: {hypothesis.hypothesis[:50]}",
|
||
"params_adjustment": self._extract_param_adjustment(suggestion)
|
||
})
|
||
break
|
||
|
||
# 如果没有特定阶段,默认重新执行 identify(参数调整)
|
||
if not stages_to_add and hypothesis:
|
||
stages_to_add.append({
|
||
"id": "identify",
|
||
"reason": f"基于异常检测重新鉴定",
|
||
"params_adjustment": {"evalue": "放宽到 0.01"}
|
||
})
|
||
|
||
# 构建新计划
|
||
updated_plan = {
|
||
"stages": stages_to_add,
|
||
"job_id": job_id,
|
||
"prefix": prefix,
|
||
"workspace": workspace,
|
||
"triggered_by_hypothesis": hypothesis.hypothesis if hypothesis else "",
|
||
"iteration_reason": "DiscoveryAgent 建议重新分析"
|
||
}
|
||
|
||
return updated_plan
|
||
|
||
def _extract_param_adjustment(self, suggestion: str) -> Dict:
|
||
"""从建议中提取参数调整"""
|
||
adjustments = {}
|
||
|
||
# 简单的关键词匹配
|
||
if "放宽" in suggestion or "增加" in suggestion:
|
||
adjustments["mode"] = "relaxed"
|
||
if "严格" in suggestion or "减少" in suggestion:
|
||
adjustments["mode"] = "strict"
|
||
|
||
return adjustments
|
||
|
||
def _determine_discovery_type(
|
||
self,
|
||
anomalies: List[AnomalyDetection],
|
||
patterns: List[Dict]
|
||
) -> DiscoveryType:
|
||
"""确定发现类型"""
|
||
if anomalies:
|
||
return DiscoveryType.ANOMALY
|
||
elif patterns:
|
||
return DiscoveryType.PATTERN
|
||
else:
|
||
return DiscoveryType.HYPOTHESIS
|
||
|
||
def _build_description(
|
||
self,
|
||
anomalies: List[AnomalyDetection],
|
||
patterns: List[Dict],
|
||
hypothesis: Optional[GeneratedHypothesis]
|
||
) -> str:
|
||
"""构建发现描述"""
|
||
parts = []
|
||
|
||
if anomalies:
|
||
parts.append(f"检测到{len(anomalies)}个异常:")
|
||
for a in anomalies:
|
||
parts.append(f" - {a.description} (严重性: {a.severity})")
|
||
|
||
if patterns:
|
||
parts.append(f"\n数据模式 ({len(patterns)}):")
|
||
for p in patterns:
|
||
lab = p.get("label") or p.get("type")
|
||
parts.append(f" - [{lab}] {p.get('detail', '')}")
|
||
|
||
if hypothesis:
|
||
parts.append(f"\n生成假设: {hypothesis.hypothesis}")
|
||
parts.append(f"置信度: {hypothesis.confidence:.2f}")
|
||
elif not anomalies and not patterns:
|
||
parts.append("当前未发现显著异常或结构化模式;建议查看下游 manifest 是否齐全。")
|
||
|
||
return "\n".join(parts)
|
||
|
||
def _summarize_results(self, results: Dict) -> Dict:
|
||
"""汇总各阶段关键 metrics,供假设生成使用"""
|
||
summary: Dict[str, Any] = {}
|
||
|
||
if "identify" in results:
|
||
mid = self._bucket_metrics(results["identify"])
|
||
summary["identify"] = {
|
||
"final_members": mid.get("final_members"),
|
||
}
|
||
|
||
if "phylogeny" in results:
|
||
summary["phylogeny"] = self._bucket_metrics(results["phylogeny"])
|
||
|
||
if "rnaseq_de" in results:
|
||
summary["rnaseq_de"] = self._bucket_metrics(results["rnaseq_de"])
|
||
|
||
for k in ("rnaseq_quant", "rnaseq_align", "annotation"):
|
||
if k in results:
|
||
summary[k] = self._bucket_metrics(results[k])
|
||
|
||
for k in ("rnaseq_go_enrich", "rnaseq_kegg_enrich"):
|
||
if k in results:
|
||
summary[k] = self._bucket_metrics(results[k])
|
||
|
||
return summary
|
||
|
||
async def _generate_deep_interpretation(self, discovery_data: Dict) -> Dict:
|
||
"""生成深度解读(科研模式)"""
|
||
stage = discovery_data.get("type", "unknown")
|
||
hypothesis = discovery_data.get("hypothesis", {})
|
||
anomalies = discovery_data.get("anomalies", [])
|
||
raw_compact = json.dumps(
|
||
discovery_data.get("raw_data") or {},
|
||
ensure_ascii=False,
|
||
indent=2,
|
||
)
|
||
if len(raw_compact) > 3200:
|
||
raw_compact = raw_compact[:3200] + "\n…(截断)"
|
||
|
||
prompt = f"""你是一个基因家族分析专家。请基于以下发现结果,生成详细的科学解读。
|
||
|
||
发现类型: {stage}
|
||
假设: {hypothesis.get('text', '无')}
|
||
假设依据: {hypothesis.get('rationale', '无')}
|
||
异常检测: {len(anomalies)} 个异常
|
||
|
||
原始上下文摘录(patterns + execution_results 摘要):
|
||
{raw_compact}
|
||
|
||
请生成以下内容的详细解读(JSON格式):
|
||
{{
|
||
"biological_significance": "该发现的生物学意义(2-3句)",
|
||
"mechanism_hypothesis": "可能的分子机制假设",
|
||
"functional_implications": "对基因功能的影响",
|
||
"evolutionary_insights": "进化层面的启示",
|
||
"experimental_suggestions": ["建议的验证实验1", "建议的验证实验2"],
|
||
"literature_context": "与已知研究的关联"
|
||
}}
|
||
|
||
只返回JSON,不要其他内容。"""
|
||
|
||
try:
|
||
response = await self._acall_llm(prompt, temperature=0.7)
|
||
content = response.content if hasattr(response, 'content') else str(response)
|
||
|
||
# 提取JSON
|
||
if "```json" in content:
|
||
content = content.split("```json")[1].split("```")[0]
|
||
elif "```" in content:
|
||
content = content.split("```")[1].split("```")[0]
|
||
|
||
interpretation = json.loads(content.strip())
|
||
return interpretation
|
||
|
||
except Exception as e:
|
||
print(f"[DiscoveryAgent] 深度解读生成失败: {e}")
|
||
return {
|
||
"biological_significance": "需要进一步分析",
|
||
"mechanism_hypothesis": "暂不明确",
|
||
"error": str(e)
|
||
}
|
||
|
||
async def _generate_comprehensive_report(self, state: AgentState, message: AgentMessage) -> AgentState:
|
||
"""生成综合深度报告(所有阶段完成后调用,或按需调用)"""
|
||
print(f"[DiscoveryAgent] 生成综合深度报告...")
|
||
|
||
raw_discoveries = state.results.get("discoveries", [])
|
||
all_discoveries = [self._discovery_record_as_dict(d) for d in raw_discoveries]
|
||
execution_results = state.results.get("execution_results", {})
|
||
|
||
# 获取当前阶段(如果是按需解读,只解读到当前阶段)
|
||
current_stage = message.payload.get("current_stage", None)
|
||
|
||
# 过滤发现结果:如果只解读到某个阶段
|
||
if current_stage:
|
||
filtered_discoveries = []
|
||
for discovery in all_discoveries:
|
||
filtered_discoveries.append(discovery)
|
||
# 如果找到当前阶段,停止添加
|
||
if discovery.get("stage") == current_stage:
|
||
break
|
||
all_discoveries = filtered_discoveries
|
||
print(f"[DiscoveryAgent] 生成阶段性报告,包含 {len(all_discoveries)} 个阶段")
|
||
|
||
# 构建综合报告
|
||
report_sections = []
|
||
stage_names = []
|
||
|
||
for i, discovery in enumerate(all_discoveries, 1):
|
||
stage = discovery.get("stage") or discovery.get("type", f"阶段{i}")
|
||
stage_names.append(stage)
|
||
description = discovery.get("description", "")
|
||
|
||
section = f"## 阶段 {i}: {stage}\n\n{description}\n"
|
||
|
||
# 如果有深度解读,加入报告
|
||
if "deep_interpretation" in discovery:
|
||
interp = discovery["deep_interpretation"]
|
||
section += f"\n### 科学解读\n"
|
||
section += f"- **生物学意义**: {interp.get('biological_significance', 'N/A')}\n"
|
||
section += f"- **机制假设**: {interp.get('mechanism_hypothesis', 'N/A')}\n"
|
||
section += f"- **功能影响**: {interp.get('functional_implications', 'N/A')}\n"
|
||
|
||
report_sections.append(section)
|
||
|
||
# 生成阶段性综合结论(强调阶段间的关联)
|
||
conclusion_prompt = f"""你是一个基因家族分析专家。请基于以下各阶段的分析结果,生成阶段性综合结论。
|
||
|
||
已完成的阶段: {' → '.join(stage_names)}
|
||
|
||
各阶段结果:
|
||
{chr(10).join([f"阶段 {i+1} ({d.get('stage', d.get('type', 'unknown'))}): {d.get('description', '')[:200]}" for i, d in enumerate(all_discoveries)])}
|
||
|
||
请重点分析:
|
||
1. 各阶段结果的内在关联(如:鉴定到的基因数如何影响进化树构建)
|
||
2. 阶段性科学发现(基于目前已完成的工作)
|
||
3. 下一步分析的建议(基于当前结果)
|
||
4. 潜在的生物学意义
|
||
|
||
请用中文生成专业、详细的解读。"""
|
||
|
||
try:
|
||
conclusion_response = await self._acall_llm(conclusion_prompt, temperature=0.7)
|
||
conclusion = conclusion_response.content if hasattr(conclusion_response, 'content') else str(conclusion_response)
|
||
except Exception as e:
|
||
conclusion = f"综合结论生成失败: {e}"
|
||
|
||
# 组装完整报告
|
||
report_title = f"阶段性综合深度报告({' → '.join(stage_names)})" if len(stage_names) < 5 else "基因家族分析综合深度报告"
|
||
|
||
comprehensive_report = f"""# 🔬 {report_title}
|
||
|
||
**分析流程**: {' → '.join(stage_names)}
|
||
|
||
**报告类型**: {'阶段性综合解读' if current_stage else '完整分析报告'}
|
||
|
||
---
|
||
|
||
{chr(10).join(report_sections)}
|
||
|
||
## 📊 阶段性综合结论
|
||
|
||
{conclusion}
|
||
|
||
---
|
||
*报告生成时间: {self._get_timestamp()}*
|
||
"""
|
||
|
||
# 更新状态
|
||
state.results["comprehensive_report"] = comprehensive_report
|
||
state.results["discovery_result"] = {
|
||
"report_generated": True,
|
||
"sections_count": len(report_sections),
|
||
"stages_included": stage_names,
|
||
"mode": "comprehensive",
|
||
"is_partial": current_stage is not None # 标记是否为阶段性报告
|
||
}
|
||
|
||
state.messages.append({
|
||
"role": "discovery",
|
||
"content": f"📊 阶段性综合报告已生成(包含 {len(stage_names)} 个阶段)" if current_stage else "📊 完整综合报告已生成",
|
||
"metadata": {
|
||
"report_length": len(comprehensive_report),
|
||
"stages": stage_names
|
||
}
|
||
})
|
||
|
||
print(f"[DiscoveryAgent] 综合报告已生成,包含 {len(stage_names)} 个阶段,长度: {len(comprehensive_report)} 字符")
|
||
|
||
return state
|
||
|
||
def _get_timestamp(self) -> str:
|
||
"""获取当前时间戳"""
|
||
from datetime import datetime
|
||
return datetime.now().isoformat()
|
||
|
||
|
||
# 便捷函数
|
||
def create_discovery_agent(llm_manager: LLMManager) -> DiscoveryAgent:
|
||
"""创建DiscoveryAgent实例"""
|
||
return DiscoveryAgent(llm_manager)
|