1026 lines
56 KiB
Python
Executable File
1026 lines
56 KiB
Python
Executable File
"""
|
||
IntentAgent V2 - LLM驱动的智能意图识别
|
||
使用大模型理解用户自然语言,提取分析意图
|
||
"""
|
||
import json
|
||
import re
|
||
from typing import Dict, List, Optional, Any
|
||
from .base import AgentMessage, AgentState, BaseAgent, QueryMode
|
||
from gfa_core.mas.llm import LLMManager
|
||
from gfa_core.mas.stage_catalog import (
|
||
STAGE_DEPENDENCIES as CATALOG_STAGE_DEPENDENCIES,
|
||
STAGE_INFO as CATALOG_STAGE_INFO,
|
||
complete_dependencies as catalog_complete_dependencies,
|
||
)
|
||
from gfa_core.mas.utils.intent_parser import (
|
||
is_gwas_fmt_intent,
|
||
parse_gene_family,
|
||
parse_gene_family_scope_selection,
|
||
resolve_gwas_intent,
|
||
resolve_population_intent,
|
||
)
|
||
|
||
|
||
class IntentAgentV2(BaseAgent):
|
||
"""
|
||
智能意图识别Agent V2
|
||
|
||
使用LLM理解用户自然语言,提取:
|
||
- 分析目标(基因家族鉴定、进化分析、结构分析等)
|
||
- 基因家族名称
|
||
- 物种信息
|
||
- 期望的分析流程
|
||
- 特殊要求
|
||
"""
|
||
name = "intent_v2"
|
||
role = "Intent Understanding Specialist"
|
||
|
||
# 所有可能的分析阶段
|
||
ALL_STAGES = list(CATALOG_STAGE_INFO.keys())
|
||
|
||
# 阶段描述(用于LLM理解)
|
||
STAGE_DESCRIPTIONS = {
|
||
"identify": "基因家族鉴定 - 使用HMM和BLAST从基因组中识别基因家族成员",
|
||
"phylogeny": "系统发育分析 - 构建进化树,分析基因家族的进化关系",
|
||
"reference_classification": "参考亚家族分类 - 根据参考物种已知亚家族标签和联合进化树,为目标物种成员迁移亚家族分类并标注置信度",
|
||
"subfamily_confirm": "确认亚家族分类 - 固化用户审核后的亚家族标签,供后续树图、结构、motif、domain、combo 和 promoter 复用",
|
||
"motif": "Motif分析 - 识别蛋白序列中的保守基序",
|
||
"domain": "结构域分析 - 分析蛋白结构域组成",
|
||
"structure": "基因结构分析 - 分析基因的外显子-内含子结构",
|
||
"promoter": "启动子分析 - 分析顺式作用元件和调控序列",
|
||
"synteny": "种内共线性分析 - 分析基因组内的共线性关系",
|
||
"karyotype": "核型分析 - 分析基因在染色体上的分布",
|
||
"interspecies_synteny": "物种间共线性 - 跨物种比较共线性",
|
||
"expression": "基因家族表达分析 - 基于已完成鉴定和进化树结果,分析基因家族成员的表达模式(热图等)",
|
||
"kaks": "Ka/Ks分析 - 计算进化速率和选择压力",
|
||
"ppi": "PPI分析 - 蛋白互作网络分析",
|
||
"pep": "蛋白特征分析 - 等电点、分子量等理化性质",
|
||
"identify_refine": "修订鉴定成员 - 根据人工修订ID列表重建final_members.id和final_members.pep.fa",
|
||
"genome_prepare": "数据准备总流程 - 多物种文件整理、GFF/FASTA标准化、最长转录本和参考序列合并",
|
||
"prepare_organize": "数据文件整理 - 根据config.xlsx组织文件、标准化命名、生成species_config",
|
||
"prepare_normalize_gff": "GFF ID规范化 - 修订JGI/Ensembl/NCBI GFF中的ID前缀和属性",
|
||
"prepare_longest_transcript": "提取最长转录本 - 按基因选择最长mRNA/CDS并输出过滤GFF和ID表",
|
||
"prepare_extract_pep": "提取PEP - 从genome+GFF使用gffread提取蛋白序列",
|
||
"prepare_extract_cds": "提取CDS - 从genome+GFF使用gffread提取CDS序列",
|
||
"sequence_extract_by_id": "按ID提取序列 - 从用户上传或指定的PEP/CDS/FASTA中提取一个或多个ID对应的序列",
|
||
"prepare_filter_fasta": "按ID提取FASTA - 使用ID列表从CDS/PEP/FASTA中提取子集",
|
||
"prepare_simplify_ncbi": "NCBI ID简化 - 规范化NCBI CDS header并简化GFF属性",
|
||
"rnaseq_qc": "RNA-seq质控 - FastQC质量评估(RNA-seq流程第1步)",
|
||
"wgs_qc": "WGS质控 - fastp paired-end quality control",
|
||
"wgs_index": "WGS参考索引 - samtools、GATK 和 BWA-MEM2 reference index",
|
||
"wgs_align": "WGS序列比对 - 使用BWA-MEM2比对clean reads并生成坐标排序BAM",
|
||
"rnaseq_align": "RNA-seq比对 - reads比对到参考基因组(RNA-seq流程第2步)",
|
||
"rnaseq_quant": "RNA-seq定量 - 基因表达量计算(RNA-seq流程第3步)",
|
||
"rnaseq_de": "RNA-seq差异表达分析 - 从原始FASTQ数据开始,进行质控→比对→定量→差异表达分析(DESeq2)",
|
||
"rnaseq_enrich": "RNA-seq富集分析 - GO/KEGG通路富集分析(RNA-seq流程最后一步)",
|
||
"rnaseq_candidate_family": "RNA-seq候选家族筛选 - 整合DEG、InterPro/Pfam和GO/KEGG富集,输出候选基因家族排名",
|
||
"rnaseq_report": "RNA-seq结果报告 - 从当前成功阶段生成可追溯的正式Markdown报告",
|
||
"enrichment": "富集分析 - 基于基因家族鉴定结果进行GO/KEGG通路富集分析",
|
||
"ncbi_fetch_sequences": "NCBI序列下载 - 根据GeneID、基因名或基因list下载protein/CDS序列"
|
||
}
|
||
|
||
# 阶段依赖关系
|
||
# 与 PlanningAgentV2 保持一致
|
||
STAGE_DEPENDENCIES = CATALOG_STAGE_DEPENDENCIES
|
||
|
||
# 推荐流程(当用户提到某个分析时,推荐执行的完整流程)
|
||
RECOMMENDED_FLOWS = {
|
||
"structure": {
|
||
"name": "基因结构分析",
|
||
"description": "完整的基因结构分析流程,包括鉴定、进化树构建和结构可视化",
|
||
"stages": ["identify", "phylogeny", "structure"],
|
||
"optional": ["motif", "domain"] # 可选的增强分析
|
||
},
|
||
"promoter": {
|
||
"name": "启动子分析",
|
||
"description": "启动子顺式作用元件分析,需要进化树分类",
|
||
"stages": ["identify", "phylogeny", "promoter"],
|
||
"optional": ["motif"]
|
||
},
|
||
"synteny": {
|
||
"name": "共线性分析",
|
||
"description": "基因组内共线性关系分析,需要进化树背景",
|
||
"stages": ["identify", "phylogeny", "synteny"],
|
||
"optional": ["karyotype"]
|
||
},
|
||
"kaks": {
|
||
"name": "进化分析",
|
||
"description": "Ka/Ks进化速率和选择压力分析,需要直系同源基因映射",
|
||
"stages": ["identify", "ortholog", "kaks"],
|
||
"optional": ["interspecies_synteny", "synteny"]
|
||
},
|
||
"complete": {
|
||
"name": "完整分析",
|
||
"description": "基因家族全部分析流程",
|
||
"stages": [
|
||
"identify", "phylogeny",
|
||
"motif", "domain", "structure", "promoter",
|
||
"synteny", "karyotype", "expression"
|
||
],
|
||
"optional": ["kaks", "ppi", "combo"]
|
||
},
|
||
"combo": {
|
||
"name": "组合图",
|
||
"description": "Tree + Motif + Structure + Domain 组合可视化",
|
||
"stages": ["identify", "phylogeny", "motif", "structure", "domain", "combo"],
|
||
"optional": []
|
||
},
|
||
"reference_classification": {
|
||
"name": "预测亚家族分类",
|
||
"description": "使用拟南芥等参考物种的已知亚家族标签和联合进化树,为目标物种成员预测亚家族与置信度;完成后前端会弹出亚家族确认界面",
|
||
"stages": ["identify", "phylogeny", "reference_classification"],
|
||
"optional": ["motif", "domain", "structure", "combo"]
|
||
},
|
||
"subfamily_confirm": {
|
||
"name": "确认亚家族分类",
|
||
"description": "在参考亚家族预测结果基础上,由用户结合 tree/structure/motif/domain/combo 证据确认最终亚家族标签",
|
||
"stages": ["subfamily_confirm"],
|
||
"optional": ["motif", "domain", "structure", "combo"]
|
||
},
|
||
"rnaseq_de": {
|
||
"name": "RNA-seq差异表达分析",
|
||
"description": "从原始FASTQ数据开始的完整RNA-seq差异表达分析流程",
|
||
"stages": ["rnaseq_qc", "rnaseq_align", "rnaseq_quant", "rnaseq_de"],
|
||
"optional": []
|
||
},
|
||
"rnaseq_enrich": {
|
||
"name": "RNA-seq富集分析",
|
||
"description": "从原始FASTQ数据开始的完整RNA-seq流程,包括差异表达和富集分析",
|
||
"stages": ["rnaseq_qc", "rnaseq_align", "rnaseq_quant", "rnaseq_de", "rnaseq_enrich"],
|
||
"optional": []
|
||
}
|
||
}
|
||
|
||
def __init__(self, llm_manager: LLMManager):
|
||
super().__init__(llm_manager)
|
||
|
||
async def execute(self, state: AgentState, message: AgentMessage) -> AgentState:
|
||
"""
|
||
执行意图识别
|
||
|
||
使用LLM理解用户输入,提取完整意图信息
|
||
"""
|
||
query = message.payload.get("query", "")
|
||
|
||
# 获取前端传递的 prefix(例如 "At.WRKY")
|
||
provided_prefix = message.payload.get("prefix", "")
|
||
print(f"[IntentAgentV2] Received prefix: {provided_prefix}")
|
||
|
||
# 如果提供了 prefix,优先从中解析 species 和 gene_family
|
||
parsed_from_prefix = {}
|
||
if provided_prefix and "." in provided_prefix:
|
||
parts = provided_prefix.split(".", 1)
|
||
parsed_from_prefix = {
|
||
"species": parts[0],
|
||
"gene_family": parts[1]
|
||
}
|
||
print(f"[IntentAgentV2] Parsed from prefix: species={parts[0]}, gene_family={parts[1]}")
|
||
|
||
# 获取已完成的阶段(从 state.results 中)
|
||
completed_stages = state.results.get("completed_stages", [])
|
||
print(f"[IntentAgentV2] Received completed_stages: {completed_stages}")
|
||
|
||
# 获取 workspace 和 job_id(用于后续 manifest 检查)
|
||
job_id = message.payload.get("job_id", "")
|
||
workspace = message.payload.get("workspace", "")
|
||
print(f"[IntentAgentV2] Received job_id: {job_id}, workspace: {workspace}")
|
||
|
||
# ===== 关键修改:使用公共模块的硬编码规则解析意图 =====
|
||
from gfa_core.mas.utils.intent_parser import parse_intent_from_query
|
||
|
||
base_intent = parse_intent_from_query(query)
|
||
print(f"[IntentAgentV2] Base intent from parser: {base_intent}")
|
||
|
||
# 如果硬编码规则解析成功(有明确阶段),直接使用
|
||
if base_intent["stages"] and base_intent["stages"] != ["identify"]:
|
||
print(f"[IntentAgentV2] Using hardcoded rule result")
|
||
# 如果提供了 prefix,优先使用 prefix 解析结果
|
||
final_species = parsed_from_prefix.get("species", base_intent["species"]) if parsed_from_prefix else base_intent["species"]
|
||
final_gene_family = parsed_from_prefix.get("gene_family", base_intent["gene_family"]) if parsed_from_prefix else base_intent["gene_family"]
|
||
|
||
# 关键修复:展开依赖关系
|
||
original_stages = base_intent["stages"]
|
||
expanded_stages = self._complete_dependencies(original_stages)
|
||
print(f"[IntentAgentV2] Stages expanded: {original_stages} -> {expanded_stages}")
|
||
|
||
intent_result = {
|
||
"gene_family": final_gene_family,
|
||
"species": final_species,
|
||
"stages": expanded_stages,
|
||
"primary_stage": base_intent["primary_stage"],
|
||
"description": base_intent.get("message") or f"执行{base_intent['primary_stage']}分析",
|
||
"requirements": [],
|
||
"confidence": base_intent["confidence"],
|
||
"task_kind": base_intent.get("task_kind", "pipeline"),
|
||
"taskType": base_intent.get("task_kind", "pipeline"),
|
||
}
|
||
if base_intent.get("utility_tool"):
|
||
intent_result["utility_tool"] = base_intent["utility_tool"]
|
||
if base_intent.get("params"):
|
||
intent_result["params"] = base_intent["params"]
|
||
if base_intent["params"].get("prefix"):
|
||
intent_result["prefix"] = base_intent["params"]["prefix"]
|
||
if base_intent["params"].get("ncbiSpecies"):
|
||
intent_result["species"] = base_intent["params"]["ncbiSpecies"]
|
||
else:
|
||
# 硬编码规则未命中,使用 LLM 细化 stages
|
||
print(f"[IntentAgentV2] Hardcoded rule not hit, using LLM")
|
||
intent_result = await self._recognize_intent_with_llm(query, completed_stages, provided_prefix)
|
||
|
||
# 关键修复:保护 Parser 已解析的 gene_family/species,不被 LLM 的"未知"覆盖
|
||
if base_intent.get("gene_family") and base_intent["gene_family"] not in ["未知", "待用户指定"]:
|
||
if intent_result.get("gene_family") in ["未知", "待用户指定", ""]:
|
||
intent_result["gene_family"] = base_intent["gene_family"]
|
||
print(f"[IntentAgentV2] Protected gene_family from parser: {base_intent['gene_family']}")
|
||
if base_intent.get("species") and base_intent["species"] not in ["未知", "待用户指定"]:
|
||
if intent_result.get("species") in ["未知", "待用户指定", ""]:
|
||
intent_result["species"] = base_intent["species"]
|
||
print(f"[IntentAgentV2] Protected species from parser: {base_intent['species']}")
|
||
intent_result.setdefault("task_kind", "pipeline")
|
||
intent_result.setdefault("taskType", intent_result.get("task_kind", "pipeline"))
|
||
|
||
# 保留原始 stages,不过滤(留给 PlanningAgentV2 处理)
|
||
original_stages = intent_result.get("stages", [])
|
||
print(f"[IntentAgentV2] Final stages: {original_stages}")
|
||
print(f"[IntentAgentV2] Leaving filter to PlanningAgentV2 based on manifest status")
|
||
|
||
# 添加 job_id 和 workspace 到结果(从 payload 中获取)
|
||
job_id = message.payload.get("job_id", "")
|
||
workspace = message.payload.get("workspace", "")
|
||
|
||
# 保留原始 stages,不过滤
|
||
intent_result["stages"] = original_stages
|
||
intent_result["all_completed"] = False
|
||
|
||
# 添加 completed_stages 信息,供 PlanningAgentV2 参考
|
||
intent_result["completed_stages_from_state"] = completed_stages
|
||
|
||
# 添加 workspace 和 job_id
|
||
intent_result["job_id"] = job_id
|
||
intent_result["workspace"] = workspace
|
||
|
||
# 更新状态
|
||
if "results" not in state.results:
|
||
state.results["results"] = {}
|
||
state.results["results"]["intent"] = intent_result
|
||
|
||
# 添加消息
|
||
stages_str = ", ".join(intent_result.get("stages", []))
|
||
state.messages.append({
|
||
"role": "intent",
|
||
"content": f"🎯 意图识别: {intent_result.get('description', '')}\n📋 分析阶段: {stages_str}",
|
||
})
|
||
|
||
return state
|
||
|
||
async def _recognize_intent_with_llm(self, query: str, completed_stages: List[str] = None, provided_prefix: str = "") -> Dict[str, Any]:
|
||
"""使用LLM识别用户意图
|
||
|
||
Args:
|
||
query: 用户查询
|
||
completed_stages: 已完成的阶段
|
||
provided_prefix: 前端传递的 prefix(例如 "At.WRKY")
|
||
"""
|
||
completed_stages = completed_stages or []
|
||
|
||
# 如果提供了 prefix,优先从中解析 species 和 gene_family
|
||
parsed_from_prefix = {}
|
||
if provided_prefix and "." in provided_prefix:
|
||
parts = provided_prefix.split(".", 1)
|
||
parsed_from_prefix = {
|
||
"species": parts[0],
|
||
"gene_family": parts[1]
|
||
}
|
||
print(f"[IntentAgentV2] Prefix override: species={parts[0]}, gene_family={parts[1]}")
|
||
|
||
# 强制规则匹配:当用户明确提到某个具体分析时,直接使用推荐流程
|
||
query_lower = query.lower()
|
||
|
||
gwas = resolve_gwas_intent(query)
|
||
population = resolve_population_intent(query)
|
||
if population:
|
||
return {
|
||
"gene_family": "", "species": "", "stages": population["stages"],
|
||
"primary_stage": population["target"],
|
||
"description": "执行群体遗传分析" if population["full_chain"] else "执行指定群体遗传分析阶段",
|
||
"requirements": ["需要群体VCF"], "confidence": 0.98,
|
||
"task_kind": "pipeline", "taskType": "pipeline",
|
||
"prefix": population["prefix"], "params": population["params"],
|
||
}
|
||
if gwas:
|
||
return {
|
||
"gene_family": "",
|
||
"species": "",
|
||
"stages": gwas["stages"],
|
||
"primary_stage": gwas["target"],
|
||
"description": "执行完整 GWAS 分析链" if gwas["full_chain"] else "执行指定 GWAS 分析阶段及其依赖",
|
||
"requirements": [],
|
||
"confidence": 0.98,
|
||
"task_kind": "pipeline",
|
||
"taskType": "pipeline",
|
||
}
|
||
|
||
# 核型分析 -> 返回 identify, synteny, karyotype(synteny依赖identify,karyotype依赖synteny)
|
||
if any(kw in query_lower for kw in ["核型", "karyotype", "染色体分布"]):
|
||
print(f"[IntentAgentV2] Force rule matched: karyotype -> ['identify', 'synteny', 'karyotype']")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["identify", "synteny", "karyotype"],
|
||
"primary_stage": "karyotype",
|
||
"description": "执行核型分析,需要鉴定、共线性和核型三个阶段",
|
||
"requirements": ["需要基因组文件和基因注释文件"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
# 共线性有种内/种间两类。用户只说“共线性”时不要擅自默认,交给前端二选一。
|
||
if any(kw in query_lower for kw in ["种间", "interspecies", "跨物种", "物种间共线性"]) and "核型" not in query_lower and "karyotype" not in query_lower:
|
||
print(f"[IntentAgentV2] Force rule matched: interspecies_synteny -> ['identify', 'interspecies_synteny']")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["identify", "interspecies_synteny"],
|
||
"primary_stage": "interspecies_synteny",
|
||
"description": "执行种间共线性分析,需要鉴定和种间共线性阶段",
|
||
"requirements": ["需要待比较物种的蛋白序列、CDS和注释文件"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
if any(kw in query_lower for kw in ["种内共线性", "intraspecies", "mcscanx"]) and "核型" not in query_lower and "karyotype" not in query_lower:
|
||
print(f"[IntentAgentV2] Force rule matched: synteny -> ['identify', 'synteny']")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["identify", "synteny"],
|
||
"primary_stage": "synteny",
|
||
"description": "执行种内共线性分析,需要鉴定和种内共线性阶段",
|
||
"requirements": ["需要基因组文件"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
if any(kw in query_lower for kw in ["共线性", "synteny"]) and "核型" not in query_lower and "karyotype" not in query_lower:
|
||
print("[IntentAgentV2] Ambiguous synteny request: requires user clarification")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": [],
|
||
"primary_stage": None,
|
||
"description": "共线性分析包含种内共线性和种间共线性,需要用户选择",
|
||
"requirements": [],
|
||
"confidence": 0.9,
|
||
"needs_clarification": True,
|
||
"clarification_type": "synteny_scope",
|
||
"clarification_question": "请选择要执行哪一种共线性分析",
|
||
"options": [
|
||
{
|
||
"id": "synteny",
|
||
"label": "种内共线性分析",
|
||
"description": "分析同一物种基因组内的共线性关系",
|
||
"stages": ["identify", "synteny"]
|
||
},
|
||
{
|
||
"id": "interspecies_synteny",
|
||
"label": "种间共线性分析",
|
||
"description": "比较不同物种之间的共线性关系",
|
||
"stages": ["identify", "interspecies_synteny"]
|
||
}
|
||
]
|
||
}
|
||
|
||
# 进化树分析 -> 只返回 identify, phylogeny
|
||
if any(kw in query_lower for kw in ["进化树", "phylogeny", "系统发育", "进化分析"]):
|
||
print(f"[IntentAgentV2] Force rule matched: phylogeny -> ['identify', 'phylogeny']")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["identify", "phylogeny"],
|
||
"primary_stage": "phylogeny",
|
||
"description": "执行进化树分析",
|
||
"requirements": ["需要基因组文件和基因注释文件"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
# 蛋白特征分析 (PEP) -> 只返回 pep
|
||
if any(kw in query_lower for kw in ["pep", "蛋白特征", "蛋白性质", "等电点", "分子量"]):
|
||
print(f"[IntentAgentV2] Force rule matched: pep -> ['pep']")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["pep"],
|
||
"primary_stage": "pep",
|
||
"description": "执行蛋白特征分析",
|
||
"requirements": ["需要identify阶段的蛋白序列结果"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
if is_gwas_fmt_intent(query):
|
||
return {
|
||
"gene_family": "",
|
||
"species": "",
|
||
"stages": ["gwas_fmt"],
|
||
"primary_stage": "gwas_fmt",
|
||
"description": "使用 TASSEL 排序外部 VCF 并转换为 HapMap",
|
||
"requirements": ["需要显式选择外部 VCF 文件"],
|
||
"confidence": 0.98,
|
||
}
|
||
|
||
if any(kw in query_lower for kw in ["背景注释", "功能注释", "基因组注释", "自动注释", "eggnog", "annotation"]):
|
||
print(f"[IntentAgentV2] Force rule matched: annotation -> ['annotation']")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["annotation"],
|
||
"primary_stage": "annotation",
|
||
"description": "执行背景注释分析,生成 RNA-seq GO/KEGG 富集所需背景文件",
|
||
"requirements": ["需要目标物种蛋白序列文件"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
# RNA-seq 差异表达分析 -> 返回完整 RNA-seq 流程
|
||
# 注意:区分 expression(基因家族表达分析)和 rnaseq_de(RNA-seq差异表达)
|
||
has_rnaseq_marker = any(kw in query_lower for kw in ["rnaseq", "rna-seq", "rna seq", "转录组"])
|
||
explicit_gene_family_context = any(kw in query_lower for kw in ["基因家族", "家族表达", "表达量", "热图", "heatmap"])
|
||
|
||
if has_rnaseq_marker and any(
|
||
keyword in query_lower
|
||
for keyword in ["生成结果报告", "打开结果报告", "结果报告", "生成报告", "打开报告"]
|
||
):
|
||
action = "open" if "打开" in query_lower else "generate"
|
||
return {
|
||
"gene_family": "",
|
||
"species": "",
|
||
"stages": ["rnaseq_report"],
|
||
"primary_stage": "rnaseq_report",
|
||
"description": "打开或生成当前RNA-seq正式结果报告",
|
||
"requirements": ["至少一个成功的RNA-seq阶段manifest"],
|
||
"params": {"rnaseqReportAction": action},
|
||
"confidence": 0.99,
|
||
}
|
||
|
||
stage_force_rules = [
|
||
("rnaseq_candidate_family", ["候选家族", "候选基因家族", "家族排名", "tf注释", "tf family", "tffamily", "pfam注释", "deg注释", "差异基因注释"], "执行RNA-seq候选家族筛选"),
|
||
("rnaseq_fgsea", ["fgsea", "gsea", "排序富集", "基因集富集"], "执行RNA-seq fgsea排序富集分析"),
|
||
("rnaseq_wgcna", ["wgcna", "共表达", "加权共表达", "模块性状"], "执行RNA-seq WGCNA共表达分析"),
|
||
("rnaseq_sample_qc", ["样本质量", "样本质量评估", "样本检测", "sample qc", "sample quality", "pca", "样本相关"], "执行RNA-seq样本质量评估"),
|
||
("rnaseq_qc", ["质控", "qc", "fastqc", "测序质量"], "执行RNA-seq质控分析"),
|
||
("rnaseq_quant", ["定量", "quant", "featurecounts", "count矩阵", "计数矩阵"], "执行RNA-seq定量分析"),
|
||
("rnaseq_align", ["比对", "align", "mapping", "hisat2", "star"], "执行RNA-seq比对分析"),
|
||
]
|
||
for stage_id, keywords, description in stage_force_rules:
|
||
if any(kw in query_lower for kw in keywords):
|
||
if stage_id == "rnaseq_align" and any(kw in query_lower for kw in ["多序列", "msa", "multiple alignment"]):
|
||
continue
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": [stage_id],
|
||
"primary_stage": stage_id,
|
||
"description": description,
|
||
"requirements": ["需要对应RNA-seq上游产物或原始输入文件"],
|
||
"confidence": 0.95
|
||
}
|
||
if any(kw in query_lower for kw in ["差异表达", "差异分析", "差异基因", "deg", "deseq2", "edger"]) and not explicit_gene_family_context:
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["rnaseq_de"],
|
||
"primary_stage": "rnaseq_de",
|
||
"description": "执行RNA-seq差异表达分析",
|
||
"requirements": ["需要RNA-seq定量结果和样本分组配置"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
if any(kw in query_lower for kw in ["富集", "enrich", "go", "kegg", "通路"]):
|
||
if "kegg" in query_lower:
|
||
enrich_stage = "rnaseq_kegg_enrich"
|
||
enrich_type = "kegg"
|
||
elif re.search(r"(^|[^a-z])go([^a-z]|$)", query_lower) or "go富集" in query_lower or "go分析" in query_lower:
|
||
enrich_stage = "rnaseq_go_enrich"
|
||
enrich_type = "go"
|
||
else:
|
||
enrich_stage = "rnaseq_enrich"
|
||
enrich_type = "go"
|
||
print(f"[IntentAgentV2] Force rule matched: {enrich_stage} -> RNA-seq enrichment")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": [enrich_stage],
|
||
"primary_stage": enrich_stage,
|
||
"description": "执行RNA-seq富集分析",
|
||
"requirements": ["需要已完成RNA-seq差异表达结果,并具备gene2go/gene2ko背景或可自动注释"],
|
||
"confidence": 0.95,
|
||
"params": {"enrichType": enrich_type}
|
||
}
|
||
|
||
if has_rnaseq_marker and any(kw in query_lower for kw in ["差异", "de", "deg"]):
|
||
print(f"[IntentAgentV2] Force rule matched: rnaseq_de -> full RNA-seq workflow")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["rnaseq_qc", "rnaseq_align", "rnaseq_quant", "rnaseq_de"],
|
||
"primary_stage": "rnaseq_de",
|
||
"description": "执行RNA-seq差异表达分析(完整流程:质控→比对→定量→差异表达)",
|
||
"requirements": ["需要RNA-seq原始FASTQ文件和样本分组配置文件"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
if has_rnaseq_marker and any(kw in query_lower for kw in ["定量", "quant", "count"]):
|
||
print(f"[IntentAgentV2] Force rule matched: rnaseq_quant -> qc + align + quant")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["rnaseq_qc", "rnaseq_align", "rnaseq_quant"],
|
||
"primary_stage": "rnaseq_quant",
|
||
"description": "执行RNA-seq定量分析(完整流程:质控→比对→定量)",
|
||
"requirements": ["需要RNA-seq原始FASTQ文件和基因组注释文件"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
if has_rnaseq_marker and any(kw in query_lower for kw in ["比对", "align", "mapping"]):
|
||
print(f"[IntentAgentV2] Force rule matched: rnaseq_align -> qc + align")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["rnaseq_qc", "rnaseq_align"],
|
||
"primary_stage": "rnaseq_align",
|
||
"description": "执行RNA-seq比对分析(完整流程:质控→比对)",
|
||
"requirements": ["需要RNA-seq原始FASTQ文件和参考基因组"],
|
||
"confidence": 0.95
|
||
}
|
||
|
||
# RNA-seq 富集分析 -> 返回完整 RNA-seq 流程 + 富集
|
||
if any(kw in query_lower for kw in ["rnaseq富集", "rna-seq富集", "rna富集分析", "rnaseq enrich"]) \
|
||
and any(kw in query_lower for kw in ["rnaseq", "rna-seq", "rna seq"]):
|
||
if "kegg" in query_lower:
|
||
enrich_stage = "rnaseq_kegg_enrich"
|
||
enrich_type = "kegg"
|
||
elif re.search(r"(^|[^a-z])go([^a-z]|$)", query_lower) or "go富集" in query_lower or "go分析" in query_lower:
|
||
enrich_stage = "rnaseq_go_enrich"
|
||
enrich_type = "go"
|
||
else:
|
||
enrich_stage = "rnaseq_enrich"
|
||
enrich_type = "go"
|
||
print(f"[IntentAgentV2] Force rule matched: {enrich_stage} -> full RNA-seq workflow + enrichment")
|
||
return {
|
||
"gene_family": self._extract_gene_family_fallback(query),
|
||
"species": "",
|
||
"stages": ["rnaseq_qc", "rnaseq_align", "rnaseq_quant", "rnaseq_de", enrich_stage],
|
||
"primary_stage": enrich_stage,
|
||
"description": "执行RNA-seq富集分析(完整流程:质控→比对→定量→差异表达→富集)",
|
||
"requirements": ["需要RNA-seq原始FASTQ文件和样本分组配置文件"],
|
||
"confidence": 0.95,
|
||
"params": {"enrichType": enrich_type}
|
||
}
|
||
|
||
# 构建推荐流程描述
|
||
flows_desc = "\n".join([
|
||
f"- {k}: {v['description']}\n 推荐流程: {' → '.join(v['stages'])}"
|
||
for k, v in self.RECOMMENDED_FLOWS.items()
|
||
])
|
||
|
||
# 构建提示词
|
||
system_prompt = """你是一个专业的基因家族分析意图理解专家。
|
||
你的任务是理解用户的自然语言输入,提取分析意图并推荐完整的分析流程。
|
||
|
||
可用的分析阶段:
|
||
{stages_desc}
|
||
|
||
阶段依赖关系:
|
||
{dependencies}
|
||
|
||
推荐分析流程:
|
||
{flows_desc}
|
||
|
||
输出格式(JSON):
|
||
{{
|
||
"gene_family": "基因家族名称,如SPL、NAC等",
|
||
"species": "物种代码,如Ft、At、Os等",
|
||
"stages": ["需要执行的分析阶段列表,按依赖顺序排列"],
|
||
"primary_stage": "用户主要想要的分析阶段",
|
||
"description": "用户意图的简要描述",
|
||
"requirements": ["特殊要求或注意事项"],
|
||
"confidence": 0.95
|
||
}}
|
||
|
||
规则:
|
||
1. 根据用户描述自动推断需要的分析阶段
|
||
2. 必须考虑阶段依赖关系(如phylogeny需要identify)
|
||
3. **重要**: 如果用户明确说"帮我执行核型分析",只返回 ["identify", "synteny", "karyotype"](种内核型依赖种内共线性)
|
||
4. **重要(共线性二选一)**:
|
||
- 用户明确「种间/物种间/跨物种/interspecies」共线性 → stages 必须包含 **interspecies_synteny**,典型前置为 identify + interspecies_synteny(按依赖展开)
|
||
- 用户明确「种内」或未写明跨物种、仅单一基因组语境 → **synteny**(MCScanX 种内),典型为 identify + synteny
|
||
- 禁止将「种间共线性」规划成仅 synteny
|
||
5. **重要**: 如果用户明确说"帮我执行进化树分析",只返回 ["identify", "phylogeny"]
|
||
6. **重要**: 如果用户只提到一个具体分析阶段,只返回该阶段及其直接依赖(不要返回完整流程)
|
||
7. 如果用户说"完整分析"或"全套分析",才返回所有主要阶段
|
||
8. **关键区分**: expression(基因家族表达分析)和 rnaseq_de(RNA-seq差异表达)是两种不同的分析:
|
||
- expression: 基于已完成鉴定和进化树结果,分析基因家族成员的表达模式
|
||
- rnaseq_de: 从RNA-seq原始FASTQ数据开始,进行质控→比对→定量→差异表达分析
|
||
如果用户提到"RNA-seq"、"rnaseq"、"转录组测序"等词汇,应该使用RNA-seq流程(rnaseq_qc→rnaseq_align→rnaseq_quant→rnaseq_de)
|
||
9. **关键**: 不要过度推断,用户明确提到的分析才是他们想要的
|
||
10. **关键**: 返回所有用户请求的阶段,即使它们可能在已完成列表中(后续会根据manifest状态决定是否执行)
|
||
|
||
参考信息 - 已完成的阶段(供参考,但仍需返回用户请求的阶段):
|
||
{completed_stages}
|
||
""".format(
|
||
completed_stages=', '.join(completed_stages) if completed_stages else '无',
|
||
stages_desc="\n".join([f"- {k}: {v}" for k, v in self.STAGE_DESCRIPTIONS.items()]),
|
||
dependencies="\n".join([f"- {k} 需要: {', '.join(v)}" for k, v in self.STAGE_DEPENDENCIES.items()]),
|
||
flows_desc=flows_desc
|
||
)
|
||
|
||
user_prompt = f"""用户输入: {query}
|
||
|
||
请识别用户的分析意图,并推荐完整的分析流程。
|
||
|
||
注意:
|
||
- 如果用户只上传了初始数据,还没有执行任何分析,请从identify开始规划
|
||
- 如果用户提到某个具体分析(如"基因结构"),请包含该分析需要的所有前置步骤
|
||
- 输出JSON格式结果。"""
|
||
|
||
# 调用LLM
|
||
try:
|
||
from gfa_core.mas.llm.base import LLMMessage
|
||
messages = [
|
||
LLMMessage(role="system", content=system_prompt),
|
||
LLMMessage(role="user", content=user_prompt)
|
||
]
|
||
response = await self.llm_manager.acall(messages, temperature=0.3)
|
||
|
||
# 解析LLM输出
|
||
content = response.content if hasattr(response, 'content') else str(response)
|
||
print(f"[IntentAgentV2] LLM raw response: {content[:200]}...")
|
||
|
||
# 尝试提取JSON(LLM可能返回markdown格式的JSON)
|
||
import re
|
||
json_match = re.search(r'\{.*\}', content, re.DOTALL)
|
||
if json_match:
|
||
content = json_match.group()
|
||
|
||
intent_data = json.loads(content)
|
||
print(f"[IntentAgentV2] LLM parsed intent: {intent_data}")
|
||
|
||
# 验证并补充必要字段(传递 provided_prefix)
|
||
intent_data = self._validate_intent(intent_data, query, provided_prefix)
|
||
|
||
return intent_data
|
||
|
||
except Exception as e:
|
||
print(f"[IntentAgentV2] LLM识别失败: {e}, 降级到fallback")
|
||
# 降级到规则匹配
|
||
return self._fallback_recognition(query)
|
||
|
||
def _validate_intent(self, intent_data: Dict, query: str, provided_prefix: str = "") -> Dict:
|
||
"""验证并补充意图数据
|
||
|
||
Args:
|
||
intent_data: LLM 返回的意图数据
|
||
query: 用户查询
|
||
provided_prefix: 前端传递的 prefix(例如 "At.WRKY")
|
||
"""
|
||
# 如果提供了 prefix,优先从中解析 species 和 gene_family
|
||
if provided_prefix and "." in provided_prefix:
|
||
parts = provided_prefix.split(".", 1)
|
||
intent_data["species"] = parts[0]
|
||
intent_data["gene_family"] = parts[1]
|
||
print(f"[IntentAgentV2] Prefix override in validation: species={parts[0]}, gene_family={parts[1]}")
|
||
else:
|
||
# 确保有基因家族;不能用全局默认家族,后续由 config/final 或澄清处理。
|
||
gene_family = intent_data.get("gene_family", "")
|
||
if not gene_family or "未知" in gene_family or "待用户" in gene_family:
|
||
intent_data["gene_family"] = self._extract_gene_family_fallback(query)
|
||
|
||
# 确保有物种;不能用全局默认物种。
|
||
species = intent_data.get("species", "")
|
||
if not species or "未知" in species or "待用户" in species:
|
||
intent_data["species"] = ""
|
||
|
||
# 确保有stages
|
||
if not intent_data.get("stages"):
|
||
# 尝试关键词匹配补充(而不是简单默认为identify)
|
||
fallback_stages = self._extract_stages_from_query(query)
|
||
if fallback_stages:
|
||
intent_data["stages"] = fallback_stages
|
||
print(f"[IntentAgentV2] Fallback keyword match: {fallback_stages}")
|
||
else:
|
||
query_lower = query.lower()
|
||
has_rnaseq_keyword = any(
|
||
kw in query_lower for kw in ["rnaseq", "rna-seq", "rna seq", "转录组", "fastq"]
|
||
)
|
||
intent_data["stages"] = (
|
||
["rnaseq_de", "rnaseq_go_enrich", "rnaseq_kegg_enrich", "rnaseq_fgsea", "rnaseq_wgcna"]
|
||
if has_rnaseq_keyword
|
||
else ["identify"]
|
||
)
|
||
|
||
# 根据依赖关系补充缺失的阶段(关键修复:确保依赖展开)
|
||
original_stages = intent_data["stages"]
|
||
intent_data["stages"] = self._complete_dependencies(original_stages)
|
||
print(f"[IntentAgentV2] Stages expanded: {original_stages} -> {intent_data['stages']}")
|
||
|
||
query_lower = query.lower()
|
||
selection = parse_gene_family_scope_selection(query)
|
||
params = intent_data.setdefault("params", {})
|
||
|
||
def as_species_list(value: Any) -> List[str]:
|
||
if isinstance(value, str):
|
||
values = [item.strip() for item in value.split(",")]
|
||
elif isinstance(value, (list, tuple, set)):
|
||
values = [str(item).strip() for item in value]
|
||
else:
|
||
values = []
|
||
return list(dict.fromkeys(item for item in values if item))
|
||
|
||
target_species = as_species_list(selection.get("targetSpecies"))
|
||
reference_species = as_species_list(selection.get("referenceSpecies"))
|
||
comparative_species = as_species_list(selection.get("comparativeSpecies"))
|
||
has_query_scope = bool(target_species or reference_species or comparative_species)
|
||
|
||
prefix_species = provided_prefix.split(".", 1)[0].strip() if provided_prefix and "." in provided_prefix else ""
|
||
if not target_species and prefix_species:
|
||
target_species = [prefix_species]
|
||
if not target_species:
|
||
target_species = as_species_list(params.get("targetSpecies") or intent_data.get("target_species"))
|
||
if not reference_species:
|
||
reference_species = as_species_list(params.get("referenceSpecies") or intent_data.get("reference_species"))
|
||
if not comparative_species:
|
||
comparative_species = as_species_list(params.get("comparativeSpecies") or intent_data.get("comparative_species"))
|
||
if not target_species and not has_query_scope:
|
||
llm_species = str(intent_data.get("species") or "").strip()
|
||
if llm_species and llm_species != "Multi":
|
||
target_species = [llm_species]
|
||
|
||
reference_species = [sp for sp in reference_species if sp not in target_species]
|
||
comparative_species = [
|
||
sp for sp in comparative_species
|
||
if sp not in target_species and sp not in reference_species
|
||
]
|
||
scope_mode = (
|
||
"target_plus_reference_plus_comparative"
|
||
if reference_species and comparative_species
|
||
else "target_plus_reference"
|
||
if reference_species
|
||
else "target_plus_comparative"
|
||
if comparative_species
|
||
else "target_only"
|
||
)
|
||
|
||
intent_data["target_species"] = target_species
|
||
intent_data["reference_species"] = reference_species
|
||
intent_data["comparative_species"] = comparative_species
|
||
intent_data["scopeMode"] = scope_mode
|
||
params.update({
|
||
"targetSpecies": target_species,
|
||
"referenceSpecies": reference_species,
|
||
"comparativeSpecies": comparative_species,
|
||
"scopeMode": scope_mode,
|
||
"target_species": target_species,
|
||
})
|
||
explicit_species = bool(target_species)
|
||
if target_species:
|
||
intent_data["species"] = target_species[0] if len(target_species) == 1 else "Multi"
|
||
elif has_query_scope:
|
||
intent_data["species"] = ""
|
||
generic_identify = (
|
||
"identify" in intent_data.get("stages", [])
|
||
and not provided_prefix
|
||
and not explicit_species
|
||
and any(kw in query_lower for kw in ["基因家族成员鉴定", "基因家族鉴定", "identify"])
|
||
)
|
||
if generic_identify and not intent_data.get("species"):
|
||
intent_data["species"] = "Multi"
|
||
|
||
# 生成prefix
|
||
if intent_data.get("species") and intent_data.get("gene_family"):
|
||
intent_data["prefix"] = f"{intent_data['species']}.{intent_data['gene_family']}"
|
||
else:
|
||
intent_data["prefix"] = intent_data.get("gene_family", "")
|
||
|
||
# 确保有描述
|
||
if not intent_data.get("description"):
|
||
stages_str = ", ".join(intent_data["stages"])
|
||
intent_data["description"] = f"执行{intent_data['gene_family']}基因家族的{stages_str}分析"
|
||
|
||
return intent_data
|
||
|
||
def _complete_dependencies(self, stages: List[str]) -> List[str]:
|
||
"""根据依赖关系补充缺失的阶段"""
|
||
return catalog_complete_dependencies(stages)
|
||
|
||
def _extract_gene_family_fallback(self, query: str) -> str:
|
||
"""降级:从查询中提取基因家族(复用 intent_parser 的过滤逻辑)"""
|
||
result = parse_gene_family(query)
|
||
return result if result else ""
|
||
|
||
def _extract_stages_from_query(self, query: str) -> List[str]:
|
||
"""从查询中通过关键词匹配提取分析阶段"""
|
||
query_lower = query.lower()
|
||
|
||
stages = []
|
||
if any(kw in query_lower for kw in ["鉴定", "识别", "identify"]):
|
||
stages.append("identify")
|
||
if any(kw in query_lower for kw in ["进化树", "系统发育", "phylogeny", "进化", "tree"]):
|
||
stages.append("phylogeny")
|
||
if any(kw in query_lower for kw in ["确认亚家族", "亚家族确认", "确认亚家族标签", "人工确认亚家族", "subfamily confirm", "confirmed subfamily"]):
|
||
stages.append("subfamily_confirm")
|
||
elif any(kw in query_lower for kw in ["预测亚家族", "亚家族分类", "参考亚家族", "参考分类", "亚家族", "subfamily", "reference classification"]):
|
||
stages.append("reference_classification")
|
||
if any(kw in query_lower for kw in ["motif", "基序", "保守"]):
|
||
stages.append("motif")
|
||
# 注意:"结构域" 包含 "结构",需要先检查 domain,避免同时匹配 structure
|
||
if any(kw in query_lower for kw in ["domain", "结构域", "pfam"]):
|
||
stages.append("domain")
|
||
elif any(kw in query_lower for kw in ["结构", "structure", "外显子", "内含子", "基因结构"]):
|
||
# 使用 elif 确保 "结构域" 不会同时匹配 structure
|
||
stages.append("structure")
|
||
if any(kw in query_lower for kw in ["启动子", "promoter", "顺式作用元件"]):
|
||
stages.append("promoter")
|
||
if any(kw in query_lower for kw in ["ppi", "蛋白互作", "protein interaction", "互作"]):
|
||
stages.append("ppi")
|
||
if any(kw in query_lower for kw in ["kaks", "ka/ks", "选择压力", "dnds", "ks", "ka"]):
|
||
stages.append("kaks")
|
||
# 注意:"种间共线性/物种间共线性" 含 "共线性",必须先匹配种间
|
||
if any(
|
||
kw in query_lower
|
||
for kw in [
|
||
"种间",
|
||
"interspecies",
|
||
"跨物种",
|
||
"种间共线性",
|
||
"物种间共线性",
|
||
"物种间",
|
||
]
|
||
):
|
||
stages.append("interspecies_synteny")
|
||
elif any(kw in query_lower for kw in ["种内共线性", "intraspecies"]):
|
||
stages.append("synteny")
|
||
elif any(
|
||
kw in query_lower for kw in ["共线性", "synteny", "mcscanx", "circos", "微共线性"]
|
||
):
|
||
# 未写明种间/种内时默认走种内 MCScanX(歧义由 clarify / 前端对话框处理)
|
||
stages.append("synteny")
|
||
if any(kw in query_lower for kw in ["核型", "karyotype", "染色体分布", "chromosome", "染色体"]):
|
||
stages.append("karyotype")
|
||
# 注意:区分 expression(基因家族表达分析)和 rnaseq_de(RNA-seq差异表达)
|
||
# expression 关键词:表达分析、expression、expression analysis
|
||
# rnaseq 关键词:rnaseq、rna-seq、转录组测序(这些应该匹配到 rnaseq 流程,不是 expression)
|
||
#
|
||
# 关键规则:如果查询中包含 RNA-seq 相关词汇,则跳过 expression,使用 RNA-seq 流程
|
||
has_rnaseq_keyword = any(
|
||
kw in query_lower for kw in ["rnaseq", "rna-seq", "rna seq", "转录组", "转录组测序", "fastq"]
|
||
)
|
||
explicit_gene_family_context = any(kw in query_lower for kw in ["基因家族", "家族表达", "表达量", "热图", "heatmap"])
|
||
extraction_context = any(kw in query_lower for kw in ["提取", "extract", "gffread", "筛选", "subseq"])
|
||
|
||
if not has_rnaseq_keyword:
|
||
# 只有不含 RNA-seq 关键词时,才考虑 expression
|
||
if any(kw in query_lower for kw in ["表达分析", "expression analysis", "基因表达", "热图", "heatmap"]):
|
||
stages.append("expression")
|
||
elif any(kw in query_lower for kw in ["表达", "expression"]):
|
||
stages.append("expression")
|
||
if not extraction_context and any(kw in query_lower for kw in ["pep", "蛋白特征", "理化性质", "蛋白性质", "蛋白理化特性", "蛋白理化特征"]):
|
||
stages.append("pep")
|
||
if any(kw in query_lower for kw in ["combo", "组合", "组合图"]):
|
||
stages.append("combo")
|
||
if any(kw in query_lower for kw in ["背景注释", "功能注释", "基因组注释", "自动注释", "eggnog", "annotation"]):
|
||
stages.append("annotation")
|
||
# MSA(多序列比对):需要排除 RNA-seq 相关查询
|
||
# "比对" 这个词在 RNA-seq 上下文中通常指 reads 比对,不是多序列比对
|
||
if any(kw in query_lower for kw in ["msa", "多序列比对", "multiple alignment"]):
|
||
stages.append("msa")
|
||
elif any(kw in query_lower for kw in ["比对", "alignment"]):
|
||
# 单独的 "比对" 或 "alignment":如果有 RNA-seq 关键词,则跳过 MSA
|
||
if not has_rnaseq_keyword:
|
||
stages.append("msa")
|
||
if any(kw in query_lower for kw in ["ortholog", "同源", "同源基因"]):
|
||
stages.append("ortholog")
|
||
# RNA-seq 流程
|
||
# 注意:这些关键词需要包含 RNA-seq 前缀,避免与基因家族分析阶段冲突
|
||
if has_rnaseq_keyword and any(kw in query_lower for kw in ["rnaseq_qc", "rna质控", "rna-seq质控", "质控", "qc"]):
|
||
stages.append("rnaseq_qc")
|
||
if has_rnaseq_keyword and any(kw in query_lower for kw in ["rnaseq_align", "rna比对", "rna-seq比对", "比对", "align", "mapping"]):
|
||
stages.append("rnaseq_align")
|
||
if has_rnaseq_keyword and any(kw in query_lower for kw in ["rnaseq_quant", "rna定量", "rna-seq定量", "定量", "quant", "count"]):
|
||
stages.append("rnaseq_quant")
|
||
# RNA-seq 差异表达:需要明确的 RNA-seq 前缀,或与其他 RNA-seq 关键词一起出现
|
||
# 单独的 "差异表达" 可能指代基因家族表达分析中的差异表达,不一定是 RNA-seq
|
||
# RNA-seq 差异表达和富集也需要 has_rnaseq_keyword
|
||
if has_rnaseq_keyword and any(kw in query_lower for kw in ["rnaseq_de", "rna差异", "rna-seq差异", "de", "deg", "差异基因"]):
|
||
stages.append("rnaseq_de")
|
||
elif has_rnaseq_keyword and "差异表达" in query_lower:
|
||
# "差异表达" + RNA-seq 关键词 = RNA-seq 差异表达
|
||
stages.append("rnaseq_de")
|
||
if not has_rnaseq_keyword:
|
||
if any(kw in query_lower for kw in ["候选家族", "候选基因家族", "家族排名", "tf注释", "tf family", "tffamily", "pfam注释", "deg注释", "差异基因注释"]):
|
||
stages.append("rnaseq_candidate_family")
|
||
if any(kw in query_lower for kw in ["fgsea", "gsea", "排序富集", "基因集富集"]):
|
||
stages.append("rnaseq_fgsea")
|
||
if any(kw in query_lower for kw in ["wgcna", "共表达", "加权共表达", "模块性状"]):
|
||
stages.append("rnaseq_wgcna")
|
||
if any(kw in query_lower for kw in ["样本质量", "样本质量评估", "样本检测", "sample qc", "sample quality", "pca", "样本相关性", "样本聚类"]):
|
||
stages.append("rnaseq_sample_qc")
|
||
if any(kw in query_lower for kw in ["差异表达", "差异分析", "差异基因", "deg", "deseq2", "edger"]) and not explicit_gene_family_context:
|
||
stages.append("rnaseq_de")
|
||
if any(kw in query_lower for kw in ["质控", "qc", "fastqc", "测序质量"]):
|
||
stages.append("rnaseq_qc")
|
||
if any(kw in query_lower for kw in ["定量", "quant", "featurecounts", "count矩阵", "计数矩阵"]):
|
||
stages.append("rnaseq_quant")
|
||
if any(kw in query_lower for kw in ["比对", "align", "mapping", "hisat2", "star"]) and not any(kw in query_lower for kw in ["多序列", "msa", "multiple alignment"]):
|
||
stages.append("rnaseq_align")
|
||
# 注意:区分 RNA-seq 富集 (rnaseq_enrich) 和基因家族富集 (enrichment)
|
||
if has_rnaseq_keyword and any(kw in query_lower for kw in ["rnaseq_enrich", "rna富集", "rna-seq富集"]):
|
||
if "kegg" in query_lower:
|
||
stages.append("rnaseq_kegg_enrich")
|
||
elif re.search(r"(^|[^a-z])go([^a-z]|$)", query_lower) or "go富集" in query_lower or "go分析" in query_lower:
|
||
stages.append("rnaseq_go_enrich")
|
||
else:
|
||
stages.append("rnaseq_enrich")
|
||
# 通用的富集分析:当前标准执行器使用 RNA-seq 富集子阶段
|
||
if any(kw in query_lower for kw in ["富集", "enrichment", "enrich", "go分析", "kegg", "通路分析", "pathway"]):
|
||
if "kegg" in query_lower:
|
||
stages.append("rnaseq_kegg_enrich")
|
||
elif re.search(r"(^|[^a-z])go([^a-z]|$)", query_lower) or "go富集" in query_lower or "go分析" in query_lower:
|
||
stages.append("rnaseq_go_enrich")
|
||
else:
|
||
stages.append("rnaseq_enrich")
|
||
# 基因组准备
|
||
if any(kw in query_lower for kw in ["genome_prepare", "基因组准备", "准备基因组"]):
|
||
stages.append("genome_prepare")
|
||
curated_member_context = (
|
||
any(kw in query_lower for kw in ["修订后的", "修订后", "人工修订", "curated", "edited"])
|
||
and any(kw in query_lower for kw in ["家族id", "成员id", "基因家族id", "member id", "members"])
|
||
)
|
||
if curated_member_context or any(kw in query_lower for kw in ["修订成员", "修订鉴定成员", "修改成员", "删除基因", "删除", "去除基因", "去掉基因", "移除基因", "补充基因", "遗漏基因", "重建蛋白", "人工修订id", "curated members", "refine identify"]):
|
||
stages.append("identify_refine")
|
||
if any(kw in query_lower for kw in ["整理文件", "组织文件", "整理数据", "标准化命名", "重命名文件", "config整理"]):
|
||
stages.append("prepare_organize")
|
||
if any(kw in query_lower for kw in ["修订gff", "规范化gff", "gff id", "gff整理"]):
|
||
stages.append("prepare_normalize_gff")
|
||
if any(kw in query_lower for kw in ["最长转录本", "最长cds", "primary transcript", "longest transcript", "提取最长"]):
|
||
stages.append("prepare_longest_transcript")
|
||
explicit_gff_extract_context = extraction_context and any(
|
||
kw in query_lower for kw in ["gff", "gff3", "gtf", "gffread", "genome", "基因组"]
|
||
)
|
||
if explicit_gff_extract_context and any(kw in query_lower for kw in ["提取pep", "提取蛋白", "提取蛋白序列", "gffread pep", "extract pep"]):
|
||
stages.append("prepare_extract_pep")
|
||
if explicit_gff_extract_context and any(kw in query_lower for kw in ["提取cds", "提取cds序列", "gffread cds", "extract cds"]):
|
||
stages.append("prepare_extract_cds")
|
||
if any(kw in query_lower for kw in ["按id提取", "按id筛选", "提取fasta", "筛选fasta", "subseq"]):
|
||
stages.append("prepare_filter_fasta")
|
||
if any(kw in query_lower for kw in ["ncbi id", "ncbi修订", "ncbi简化", "ncbi header", "简化ncbi"]):
|
||
stages.append("prepare_simplify_ncbi")
|
||
|
||
if not stages and has_rnaseq_keyword and any(
|
||
kw in query_lower for kw in ["分析", "执行", "运行", "开始", "流程", "pipeline"]
|
||
):
|
||
stages.extend(["rnaseq_de", "rnaseq_go_enrich", "rnaseq_kegg_enrich", "rnaseq_fgsea", "rnaseq_wgcna"])
|
||
|
||
if "identify_refine" in stages:
|
||
stages = [stage for stage in stages if stage not in {"identify", "prepare_extract_pep", "prepare_extract_cds"}]
|
||
if any(stage.startswith("rnaseq") for stage in stages):
|
||
stages = [stage for stage in stages if stage not in {"expression", "msa"}]
|
||
return list(dict.fromkeys(stages))
|
||
|
||
def _fallback_recognition(self, query: str) -> Dict[str, Any]:
|
||
"""降级:使用规则匹配识别意图"""
|
||
|
||
# 使用关键词匹配提取阶段
|
||
stages = self._extract_stages_from_query(query)
|
||
|
||
if not stages:
|
||
query_lower = query.lower()
|
||
has_rnaseq_keyword = any(
|
||
kw in query_lower for kw in ["rnaseq", "rna-seq", "rna seq", "转录组", "fastq"]
|
||
)
|
||
stages = (
|
||
["rnaseq_de", "rnaseq_go_enrich", "rnaseq_kegg_enrich", "rnaseq_fgsea", "rnaseq_wgcna"]
|
||
if has_rnaseq_keyword
|
||
else ["identify"]
|
||
)
|
||
|
||
# 补充依赖(使用 stage_catalog 的主规划依赖链)
|
||
original_stages = stages.copy()
|
||
stages = self._complete_dependencies(stages)
|
||
print(f"[_fallback_recognition] Stages: {original_stages} -> {stages}")
|
||
|
||
gene_family = self._extract_gene_family_fallback(query)
|
||
species = "Multi" if any(kw in query.lower() for kw in ["基因家族成员鉴定", "基因家族鉴定", "identify"]) else ""
|
||
|
||
return {
|
||
"gene_family": gene_family,
|
||
"species": species,
|
||
"prefix": f"{species}.{gene_family}",
|
||
"stages": stages,
|
||
"description": f"执行{gene_family}基因家族的{', '.join(stages)}分析",
|
||
"requirements": [],
|
||
"confidence": 0.7,
|
||
"task_kind": "pipeline",
|
||
"taskType": "pipeline",
|
||
"mode": "fallback"
|
||
}
|
||
|
||
|
||
def create_intent_agent_v2(llm_manager):
|
||
"""创建IntentAgentV2实例"""
|
||
return IntentAgentV2(llm_manager)
|