395 lines
16 KiB
Python
Executable File
395 lines
16 KiB
Python
Executable File
"""
|
|
PlanningAgent V2 - 增强版任务规划
|
|
支持根据用户意图生成合理的分析流程
|
|
集成Knowledge Agent获取参数推荐
|
|
"""
|
|
import json
|
|
import re
|
|
from typing import Dict, List, Optional
|
|
from .base import AgentMessage, AgentState, BaseAgent
|
|
from gfa_core.mas.llm import LLMManager
|
|
|
|
|
|
class PlanningAgent(BaseAgent):
|
|
"""
|
|
任务规划Agent V2
|
|
|
|
根据用户意图和可用资源,生成最优的分析执行计划
|
|
"""
|
|
name = "planning"
|
|
role = "Workflow Planning Specialist"
|
|
|
|
# 标准分析流程定义
|
|
STANDARD_FLOWS = {
|
|
"prepare": ["genome_prepare"],
|
|
"basic": ["identify"],
|
|
"identify_refine": ["identify_refine"],
|
|
"phylogeny": ["identify", "phylogeny"],
|
|
"structure": ["identify", "phylogeny", "motif", "domain", "structure"],
|
|
"promoter": ["identify", "phylogeny", "promoter"],
|
|
"synteny": ["identify", "phylogeny", "synteny"],
|
|
"karyotype": ["identify", "phylogeny", "karyotype"],
|
|
"interspecies": ["identify", "phylogeny", "interspecies_synteny"],
|
|
"expression": ["identify", "phylogeny", "expression"],
|
|
"complete": [
|
|
"identify", "phylogeny", "motif", "domain",
|
|
"structure", "promoter", "synteny", "expression"
|
|
],
|
|
"rnaseq": [
|
|
"rnaseq_qc",
|
|
"rnaseq_align",
|
|
"rnaseq_quant",
|
|
"rnaseq_sample_qc",
|
|
"rnaseq_de",
|
|
"rnaseq_go_enrich",
|
|
"rnaseq_kegg_enrich",
|
|
"rnaseq_fgsea",
|
|
"rnaseq_wgcna",
|
|
],
|
|
"evolution": ["identify", "phylogeny", "kaks"],
|
|
"interaction": ["identify", "ppi"]
|
|
}
|
|
|
|
# 阶段依赖关系
|
|
STAGE_DEPENDENCIES = {
|
|
"genome_prepare": [],
|
|
"identify_refine": [],
|
|
"phylogeny": ["identify"],
|
|
"motif": ["identify"],
|
|
"domain": ["identify"],
|
|
"structure": ["identify"],
|
|
"promoter": ["identify", "phylogeny"],
|
|
"synteny": ["identify"],
|
|
"karyotype": ["identify"],
|
|
"interspecies_synteny": ["identify"],
|
|
"expression": ["identify"],
|
|
"kaks": ["identify", "phylogeny"],
|
|
"ppi": ["identify"],
|
|
"rnaseq_align": ["rnaseq_qc"],
|
|
"rnaseq_quant": ["rnaseq_align"],
|
|
"rnaseq_sample_qc": ["rnaseq_quant"],
|
|
"rnaseq_wgcna": ["rnaseq_sample_qc"],
|
|
"rnaseq_de": ["rnaseq_sample_qc"],
|
|
"rnaseq_go_enrich": ["rnaseq_de"],
|
|
"rnaseq_kegg_enrich": ["rnaseq_de"],
|
|
"rnaseq_fgsea": ["rnaseq_de"],
|
|
"rnaseq_enrich": ["rnaseq_de"],
|
|
"rnaseq_candidate_family": ["rnaseq_de", "rnaseq_go_enrich", "rnaseq_kegg_enrich", "rnaseq_fgsea"]
|
|
}
|
|
|
|
# 阶段描述
|
|
STAGE_DESCRIPTIONS = {
|
|
"genome_prepare": "数据准备总流程 - 多物种文件整理、GFF/FASTA标准化、最长转录本和参考序列合并",
|
|
"identify_refine": "修订鉴定成员 - 根据人工修订ID列表重建final_members.id和final_members.pep.fa",
|
|
"identify": "基因家族鉴定 - 使用HMM和BLAST识别基因家族成员",
|
|
"phylogeny": "系统发育分析 - 构建进化树和亚家族分类",
|
|
"motif": "Motif分析 - 识别保守的蛋白基序",
|
|
"domain": "结构域分析 - 分析蛋白结构域组成",
|
|
"structure": "结构预测 - 预测蛋白三维结构",
|
|
"promoter": "启动子分析 - 分析顺式作用元件",
|
|
"synteny": "共线性分析 - 分析基因组共线性关系",
|
|
"karyotype": "核型分析 - 生成染色体分布图",
|
|
"interspecies_synteny": "物种间共线性 - 跨物种比较分析",
|
|
"expression": "表达分析 - 分析基因表达模式",
|
|
"kaks": "Ka/Ks分析 - 计算进化速率和选择压力",
|
|
"ppi": "PPI分析 - 蛋白互作网络分析",
|
|
"rnaseq_qc": "RNA-seq质控 - FastQC质量评估",
|
|
"rnaseq_align": "RNA-seq比对 - reads比对到基因组",
|
|
"rnaseq_quant": "RNA-seq定量 - 基因表达量计算",
|
|
"rnaseq_sample_qc": "RNA-seq样本质量评估 - VST、PCA、样本相关性与聚类",
|
|
"rnaseq_wgcna": "RNA-seq WGCNA - 加权共表达网络与模块性状相关性",
|
|
"rnaseq_de": "差异表达 - DESeq2差异分析",
|
|
"rnaseq_go_enrich": "RNA-seq GO富集分析 - 独立使用 gene2go 背景",
|
|
"rnaseq_kegg_enrich": "RNA-seq KEGG富集分析 - 独立使用 gene2ko/KO 背景",
|
|
"rnaseq_fgsea": "RNA-seq fgsea - 基于排序基因和 KEGG 基因集的 GSEA",
|
|
"rnaseq_enrich": "RNA-seq富集分析 - 兼容旧入口;规划中优先使用 GO/KEGG 子阶段"
|
|
}
|
|
|
|
def __init__(self, llm_manager: LLMManager):
|
|
super().__init__(llm_manager)
|
|
|
|
async def execute(self, state: AgentState, message: AgentMessage) -> AgentState:
|
|
"""
|
|
执行任务规划
|
|
|
|
根据用户意图生成分析执行计划
|
|
"""
|
|
query = message.payload.get("query", "")
|
|
intent = state.results.get("results", {}).get("intent", {}) or state.results.get("intent", {})
|
|
|
|
# 提取关键信息
|
|
gene_family = intent.get("gene_family", "") or self._extract_gene_family(query)
|
|
species = intent.get("species", "") or self._extract_species(query)
|
|
stage = intent.get("stage", "")
|
|
|
|
# 确定分析流程
|
|
flow = self._determine_flow(query, stage)
|
|
|
|
# 生成执行计划
|
|
plan = await self._generate_plan(query, gene_family, species, flow)
|
|
|
|
# 更新状态
|
|
state.results["task_plan"] = plan
|
|
state.results["planning_info"] = {
|
|
"gene_family": gene_family,
|
|
"species": species,
|
|
"flow_type": flow,
|
|
"total_stages": len(plan.get("stages", []))
|
|
}
|
|
|
|
state.messages.append({
|
|
"role": "planning",
|
|
"content": f"📋 已生成分析计划: {len(plan.get('stages', []))}个阶段",
|
|
})
|
|
|
|
return state
|
|
|
|
def _extract_gene_family(self, query: str) -> str:
|
|
"""从查询中提取基因家族名称"""
|
|
# 常见基因家族模式
|
|
patterns = [
|
|
r'(\w+)\s*基因家族',
|
|
r'基因家族\s*(\w+)',
|
|
r'\b(SPL|NAC|WRKY|MYB|bHLH|WD40|C2H2|C3H|NBS|AP2|ARF|AUX|B3|BZIP|C2C2|CCAAT|CO-like|CPP|CSD|CTF|CYP|Dof|E2F|ERF|FAR1|G2-like|GATA|GeBP|GRAS|GRF|HB|HMA|HMG|HRT|HSF|JUMONJI|KNOX|LBD|LFY|LIM|LOB|LSD|MADS|MULE|MYB|NAC|ND|Nin|NLP|NPR|OFP|Orphan|Ots|PHD|PLATZ|PPR|Pseudo|RWP|S1Fa|SAP|SBP|Sigma|SMC|SRS|TALE|TCP|Tify|TRAF|Trihelix|TST|TUB|ULT|VOZ|Whirly|WOX|WRKY|XAL2|YABBY|ZF)\b'
|
|
]
|
|
|
|
for pattern in patterns:
|
|
match = re.search(pattern, query, re.IGNORECASE)
|
|
if match:
|
|
return match.group(1).upper()
|
|
|
|
return "SPL" # 默认
|
|
|
|
def _extract_species(self, query: str) -> str:
|
|
"""从查询中提取物种信息"""
|
|
species_map = {
|
|
'ft': 'Ft', 'tartary': 'Ft', '苦荞': 'Ft',
|
|
'at': 'At', 'arabidopsis': 'At', '拟南芥': 'At',
|
|
'os': 'Os', 'rice': 'Os', '水稻': 'Os',
|
|
'zm': 'Zm', 'maize': 'Zm', '玉米': 'Zm',
|
|
'gm': 'Gm', 'soybean': 'Gm', '大豆': 'Gm',
|
|
'sl': 'Sl', 'tomato': 'Sl', '番茄': 'Sl'
|
|
}
|
|
|
|
query_lower = query.lower()
|
|
for key, value in species_map.items():
|
|
if key in query_lower:
|
|
return value
|
|
|
|
return "Ft" # 默认
|
|
|
|
def _determine_flow(self, query: str, stage: str) -> str:
|
|
"""确定分析流程类型"""
|
|
query_lower = query.lower()
|
|
|
|
# RNA-seq关键词
|
|
if any(kw in query_lower for kw in ['rnaseq', 'rna-seq', '转录组', '表达谱']):
|
|
return "rnaseq"
|
|
|
|
# 进化分析关键词
|
|
if any(kw in query_lower for kw in ['kaks', 'ka/ks', '进化', '选择压力']):
|
|
return "evolution"
|
|
|
|
# PPI关键词
|
|
if any(kw in query_lower for kw in ['ppi', '互作', 'interaction', 'string']):
|
|
return "interaction"
|
|
|
|
# 根据明确指定的stage
|
|
if stage:
|
|
flow_map = {
|
|
"identify": "basic",
|
|
"phylogeny": "phylogeny",
|
|
"motif": "structure",
|
|
"domain": "structure",
|
|
"structure": "structure",
|
|
"promoter": "promoter",
|
|
"synteny": "synteny",
|
|
"karyotype": "karyotype",
|
|
"interspecies_synteny": "interspecies",
|
|
"expression": "expression",
|
|
"kaks": "evolution",
|
|
"ppi": "interaction"
|
|
}
|
|
return flow_map.get(stage, "complete")
|
|
|
|
# 完整分析关键词
|
|
if any(kw in query_lower for kw in ['完整', '全套', '全部', 'complete', 'full']):
|
|
return "complete"
|
|
|
|
return "complete" # 默认完整分析
|
|
|
|
async def _generate_plan(self, query: str, gene_family: str,
|
|
species: str, flow: str) -> dict:
|
|
"""生成详细执行计划"""
|
|
|
|
# 获取流程阶段列表
|
|
stages = self.STANDARD_FLOWS.get(flow, self.STANDARD_FLOWS["complete"])
|
|
|
|
# 构建阶段详情
|
|
stage_details = []
|
|
for i, stage_id in enumerate(stages):
|
|
stage_info = {
|
|
"id": stage_id,
|
|
"name": self.STAGE_DESCRIPTIONS.get(stage_id, stage_id),
|
|
"description": self.STAGE_DESCRIPTIONS.get(stage_id, ""),
|
|
"order": i + 1,
|
|
"dependencies": self.STAGE_DEPENDENCIES.get(stage_id, []),
|
|
"params": await self._get_default_params(stage_id, gene_family, species)
|
|
}
|
|
stage_details.append(stage_info)
|
|
|
|
# 构建执行计划
|
|
plan = {
|
|
"query": query,
|
|
"gene_family": gene_family,
|
|
"species": species,
|
|
"flow_type": flow,
|
|
"prefix": f"{species}.{gene_family}",
|
|
"stages": stage_details,
|
|
"total_stages": len(stages),
|
|
"estimated_time": self._estimate_time(stages),
|
|
"required_inputs": self._get_required_inputs(stages),
|
|
"outputs": self._get_outputs(stages, gene_family, species)
|
|
}
|
|
|
|
return plan
|
|
|
|
async def _get_default_params(self, stage_id: str, gene_family: str, species: str) -> dict:
|
|
"""获取阶段的默认参数,并尝试从Knowledge Agent获取推荐"""
|
|
default_params = {
|
|
"identify": {"cpu": 8, "dom_i_evalue_max": 0.001, "hmm_cov_min": 0.3, "blast_evalue": "1e-05"},
|
|
"phylogeny": {"cpu": 8, "bootstrap": 1000, "model": "MFP"},
|
|
"motif": {"cpu": 8, "nmotifs": 10, "min_width": 6, "max_width": 200},
|
|
"domain": {"cpu": 8, "e_value": 1e-5},
|
|
"structure": {"cpu": 8},
|
|
"promoter": {"cpu": 8},
|
|
"synteny": {"cpu": 8},
|
|
"karyotype": {"cpu": 8},
|
|
"interspecies_synteny": {"cpu": 8},
|
|
"expression": {"cpu": 8},
|
|
"kaks": {"cpu": 8, "model": "YN00"},
|
|
"ppi": {"species": "ath", "score": 200},
|
|
"rnaseq_qc": {"cpu": 8},
|
|
"rnaseq_align": {"cpu": 8},
|
|
"rnaseq_quant": {"cpu": 8},
|
|
"rnaseq_de": {"cpu": 8},
|
|
"rnaseq_enrich": {"cpu": 8, "top_pathways": 20}
|
|
}
|
|
|
|
params = default_params.get(stage_id, {"cpu": 8}).copy()
|
|
params["prefix"] = f"{species}.{gene_family}"
|
|
|
|
# 尝试从 Knowledge Agent 获取参数推荐
|
|
try:
|
|
recommended = await self._get_params_from_knowledge(stage_id, gene_family, species)
|
|
if recommended:
|
|
# 合并推荐参数(推荐参数优先)
|
|
params.update(recommended)
|
|
print(f"[PlanningAgent] 从Knowledge获取到{stage_id}阶段参数推荐")
|
|
except Exception as e:
|
|
print(f"[PlanningAgent] Knowledge参数推荐失败: {e},使用默认参数")
|
|
|
|
return params
|
|
|
|
async def _get_params_from_knowledge(self, stage_id: str, gene_family: str, species: str) -> dict:
|
|
"""从Knowledge Agent获取参数推荐"""
|
|
from .knowledge import create_knowledge_agent
|
|
from gfa_core.mas.llm import LLMManager
|
|
|
|
# 创建 Knowledge Agent
|
|
llm_manager = LLMManager()
|
|
knowledge_agent = create_knowledge_agent(llm_manager)
|
|
|
|
# 构建查询
|
|
query = f"推荐{stage_id}阶段的分析参数,基因家族:{gene_family},物种:{species}"
|
|
|
|
# 构建消息
|
|
from .base import AgentMessage, AgentState
|
|
import uuid
|
|
|
|
message = AgentMessage(
|
|
sender="planning",
|
|
receiver="knowledge",
|
|
message_type="query",
|
|
payload={"query": query, "mode": "consultation"}
|
|
)
|
|
|
|
state = AgentState(
|
|
session_id=f"planning_{uuid.uuid4().hex[:8]}",
|
|
results={},
|
|
messages=[]
|
|
)
|
|
|
|
# 执行查询
|
|
result_state = await knowledge_agent.execute(state, message)
|
|
|
|
# 解析推荐参数
|
|
answer = result_state.results.get("knowledge_response", "")
|
|
|
|
# 尝试从回答中提取参数(简化版)
|
|
recommended = {}
|
|
if "cpu" in answer.lower():
|
|
# 提取CPU推荐
|
|
import re
|
|
cpu_match = re.search(r'(\d+)\s*核', answer)
|
|
if cpu_match:
|
|
recommended["cpu"] = int(cpu_match.group(1))
|
|
|
|
return recommended
|
|
|
|
def _estimate_time(self, stages: list) -> str:
|
|
"""估算总执行时间"""
|
|
time_map = {
|
|
"identify": 5, "phylogeny": 30, "motif": 20, "domain": 15,
|
|
"structure": 30, "promoter": 20, "synteny": 30, "karyotype": 10,
|
|
"interspecies_synteny": 40, "expression": 15, "kaks": 20, "ppi": 10,
|
|
"rnaseq_qc": 30, "rnaseq_align": 60, "rnaseq_quant": 30,
|
|
"rnaseq_sample_qc": 10, "rnaseq_de": 20, "rnaseq_enrich": 15,
|
|
"rnaseq_go_enrich": 15, "rnaseq_kegg_enrich": 15,
|
|
"rnaseq_fgsea": 10, "rnaseq_wgcna": 30
|
|
}
|
|
|
|
total_minutes = sum(time_map.get(s, 10) for s in stages)
|
|
|
|
if total_minutes < 60:
|
|
return f"约{total_minutes}分钟"
|
|
else:
|
|
hours = total_minutes // 60
|
|
mins = total_minutes % 60
|
|
return f"约{hours}小时{mins}分钟" if mins > 0 else f"约{hours}小时"
|
|
|
|
def _get_required_inputs(self, stages: list) -> list:
|
|
"""获取所需输入文件"""
|
|
inputs = []
|
|
if "identify" in stages:
|
|
inputs.extend([
|
|
{"name": "HMM模型文件", "type": "hmm", "required": True, "example": "SPL_Ath.hmm"},
|
|
{"name": "蛋白质序列", "type": "fasta", "required": True, "example": "Ft.pep.fasta"}
|
|
])
|
|
if any(s.startswith("rnaseq") for s in stages):
|
|
inputs.extend([
|
|
{"name": "FASTQ文件", "type": "fastq", "required": True, "example": "*.fastq.gz"},
|
|
{"name": "样本配置", "type": "yaml", "required": True, "example": "samples.yaml"}
|
|
])
|
|
return inputs
|
|
|
|
def _get_outputs(self, stages: list, gene_family: str, species: str) -> list:
|
|
"""获取预期输出"""
|
|
prefix = f"{species}.{gene_family}"
|
|
outputs = []
|
|
output_map = {
|
|
"identify": [{"name": "最终成员列表", "file": f"{prefix}.final_members.id"}],
|
|
"phylogeny": [{"name": "进化树", "file": f"{prefix}.tree.nwk"}],
|
|
"kaks": [{"name": "Ka/Ks结果", "file": f"{prefix}.kaks_results.csv"}],
|
|
"ppi": [{"name": "PPI网络", "file": f"{prefix}.ppi_network.tsv"}]
|
|
}
|
|
for stage in stages:
|
|
if stage in output_map:
|
|
outputs.extend(output_map[stage])
|
|
return outputs
|
|
|
|
|
|
def create_planning_agent(llm_manager):
|
|
"""创建PlanningAgent实例"""
|
|
return PlanningAgent(llm_manager)
|