Files

1188 lines
62 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
IntentAgent V2 - LLM驱动的智能意图识别
使用大模型理解用户自然语言,提取分析意图
"""
import json
import re
from typing import Any, Dict, List, Mapping, Optional
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 (
extract_negated_target_stages,
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)
@staticmethod
def _current_request_text(query: str) -> str:
"""Return the user request without Router-appended confirmed context."""
text = str(query or "")
boundaries = [
index
for marker in ("\n已确认上下文:", "\nConfirmed context:")
if (index := text.find(marker)) >= 0
]
request = text[:min(boundaries)] if boundaries else text
return request.strip() or text.strip()
@staticmethod
def _apply_structured_goal_context(
intent: Dict[str, Any],
goal_context: Mapping[str, Any] | None,
) -> Dict[str, Any]:
if not isinstance(goal_context, Mapping):
return intent
terms = goal_context.get("user_terms")
if not isinstance(terms, Mapping):
return intent
def species_list(value: Any) -> List[str]:
if isinstance(value, str):
values = value.split(",")
elif isinstance(value, (list, tuple, set)):
values = value
else:
values = ()
return list(
dict.fromkeys(str(item).strip() for item in values if str(item).strip())
)
normalized = dict(intent)
params = dict(normalized.get("params") or {})
targets = species_list(
params.get("targetSpecies") or normalized.get("target_species")
) or species_list(terms.get("target_species"))
references = species_list(
params.get("referenceSpecies") or normalized.get("reference_species")
) or species_list(terms.get("reference_species"))
comparatives = species_list(
params.get("comparativeSpecies") or normalized.get("comparative_species")
) or species_list(terms.get("comparative_species"))
references = [species for species in references if species not in targets]
comparatives = [
species
for species in comparatives
if species not in targets and species not in references
]
family = str(
normalized.get("gene_family") or terms.get("gene_family") or ""
).strip()
scope_mode = (
"target_plus_reference_plus_comparative"
if references and comparatives
else "target_plus_reference"
if references
else "target_plus_comparative"
if comparatives
else "target_only"
)
params.update(
{
"targetSpecies": targets,
"referenceSpecies": references,
"comparativeSpecies": comparatives,
"target_species": targets,
"scopeMode": scope_mode,
}
)
normalized.update(
{
"params": params,
"gene_family": family,
"target_species": targets,
"reference_species": references,
"comparative_species": comparatives,
"scopeMode": scope_mode,
}
)
if targets:
normalized["species"] = targets[0] if len(targets) == 1 else "Multi"
if normalized.get("species") and family:
normalized["prefix"] = f"{normalized['species']}.{family}"
return normalized
async def execute(self, state: AgentState, message: AgentMessage) -> AgentState:
"""
执行意图识别
使用LLM理解用户输入,提取完整意图信息
"""
query = message.payload.get("query", "")
request_query = self._current_request_text(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
request_intent = parse_intent_from_query(request_query)
context_intent = parse_intent_from_query(query)
base_intent = dict(request_intent)
for key in (
"gene_family", "species", "target_species", "reference_species",
"comparative_species", "scopeMode", "params", "prefix",
):
if context_intent.get(key):
base_intent[key] = context_intent[key]
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(request_query, completed_stages, provided_prefix)
excluded_stages = set(extract_negated_target_stages(request_query))
if excluded_stages:
intent_result["stages"] = [
stage for stage in intent_result.get("stages", [])
if stage not in excluded_stages
]
if intent_result.get("primary_stage") in excluded_stages:
intent_result["primary_stage"] = intent_result["stages"][0] if intent_result["stages"] else None
if not intent_result["stages"]:
intent_result.update({
"description": "未检测到需要执行的正向分析阶段",
"needs_clarification": True,
"clarification_type": "analysis_stage",
"clarification_question": "请说明要执行的具体分析阶段",
})
# 关键修复:保护 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"))
intent_result = self._apply_structured_goal_context(
intent_result,
message.payload.get("goal_context"),
)
# 保留原始 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 []
)
if not intent_data["stages"]:
intent_data.update(
{
"description": "未能可靠识别要执行的分析阶段",
"needs_clarification": True,
"clarification_type": "analysis_stage",
"clarification_question": "请说明要执行的具体分析阶段",
}
)
excluded_stages = set(extract_negated_target_stages(query))
if excluded_stages:
intent_data["stages"] = [
stage for stage in intent_data.get("stages", [])
if stage not in excluded_stages
]
if intent_data.get("primary_stage") in excluded_stages:
intent_data["primary_stage"] = intent_data["stages"][0] if intent_data["stages"] else None
if not intent_data["stages"]:
intent_data.update({
"description": "未检测到需要执行的正向分析阶段",
"needs_clarification": True,
"clarification_type": "analysis_stage",
"clarification_question": "请说明要执行的具体分析阶段",
})
# 根据依赖关系补充缺失的阶段(关键修复:确保依赖展开)
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"}]
excluded_stages = set(extract_negated_target_stages(query))
stages = [stage for stage in stages if stage not in excluded_stages]
return list(dict.fromkeys(stages))
def _fallback_recognition(self, query: str) -> Dict[str, Any]:
"""降级:使用规则匹配识别意图"""
# 使用关键词匹配提取阶段
stages = self._extract_stages_from_query(query)
excluded_stages = set(extract_negated_target_stages(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"]
)
if has_rnaseq_keyword:
stages = [
stage for stage in (
"rnaseq_de", "rnaseq_go_enrich", "rnaseq_kegg_enrich",
"rnaseq_fgsea", "rnaseq_wgcna",
)
if stage not in excluded_stages
]
# 补充依赖(使用 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 ""
result = {
"gene_family": gene_family,
"species": species,
"prefix": f"{species}.{gene_family}",
"stages": stages,
"description": (
f"执行{gene_family}基因家族的{', '.join(stages)}分析"
if stages
else "未能可靠识别要执行的分析阶段"
),
"requirements": [],
"confidence": 0.7 if stages else 0.0,
"task_kind": "pipeline",
"taskType": "pipeline",
"mode": "fallback"
}
if not stages:
result.update(
{
"needs_clarification": True,
"clarification_type": "analysis_stage",
"clarification_question": "请说明要执行的具体分析阶段",
}
)
return result
def create_intent_agent_v2(llm_manager):
"""创建IntentAgentV2实例"""
return IntentAgentV2(llm_manager)