feat: 轻量拓展分析 + WGS2GWAS 阶段支持 + 结果区域重构 + 物种角色解析增强 + 会话 Job 隔离
【轻量拓展分析】 - 新增 gfa_core/extension_stage_lightweight/ 核心模块,支持用户上传代码/数据/原始命令 - 前端卡片式交互流程:候选核对 → 验证 → 结果审查 → 授权发布 → 运行 - Celery 不受限任务 + 轮询机制 + sessionStorage 操作恢复 - 启动脚本新增 BBAV5_LIGHTWEIGHT_* 环境变量 【WGS2GWAS 支持】 - 新增 13 个 WGS 阶段定义(QC → MAF/Missing Filter) - email_haplotype_region.py 新增 LTR-RT 分析功能 【结果区域重构】 - 单层折叠 → 三组双层折叠:RNA-seq / 基因家族 / WGS2GWAS - 结果按执行时间排序,保留最新尝试 【物种角色解析增强】 - 新增 _resolve_subfamily_request_species_roles() - 支持 reference 角色的识别 - 直接 stage 请求保留 intent 中的物种角色 【会话 Job 隔离】 - rebind_job() 跨 job 时创建新会话,返回新 conversation_id 【前端交互增强】 - RAG 文献问答模式 - NL 触发 config/samples 自动填写、亚家族确认表、基因家族成员确认表 - 执行状态从后端 resource_tasks 恢复 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1073,14 +1073,22 @@ def resolve_scoped_protein_fasta_from_phylogeny(
|
||||
)
|
||||
|
||||
|
||||
def resolve_scoped_tree_from_phylogeny(
|
||||
ws: Path,
|
||||
final_dir: Path,
|
||||
prefix: str,
|
||||
scope_path: Path,
|
||||
) -> tuple[Path, dict]:
|
||||
scope_data = read_scope(scope_path)
|
||||
manifest_candidates = stage_manifest_candidates(final_dir, "phylogeny", prefix, scope_data)
|
||||
def resolve_scoped_tree_from_phylogeny(
|
||||
ws: Path,
|
||||
final_dir: Path,
|
||||
prefix: str,
|
||||
scope_path: Path,
|
||||
explicit_tree_file: str = "",
|
||||
) -> tuple[Path, dict]:
|
||||
scope_data = read_scope(scope_path)
|
||||
if explicit_tree_file:
|
||||
selected_tree = Path(explicit_tree_file).expanduser()
|
||||
if not selected_tree.is_absolute():
|
||||
selected_tree = ws / selected_tree
|
||||
if selected_tree.exists() and selected_tree.stat().st_size > 0:
|
||||
return selected_tree.resolve(), scope_data
|
||||
raise FileNotFoundError(f"Specified scoped tree file not found: {explicit_tree_file}")
|
||||
manifest_candidates = stage_manifest_candidates(final_dir, "phylogeny", prefix, scope_data)
|
||||
|
||||
for manifest_file in manifest_candidates:
|
||||
if not manifest_file.exists():
|
||||
@@ -8261,7 +8269,13 @@ def run_stage(
|
||||
if member_set_id:
|
||||
cmd += ["--member-set-id", member_set_id]
|
||||
try:
|
||||
motif_tree_file, _motif_scope_data = resolve_scoped_tree_from_phylogeny(ws, final_dir, prefix, scope_path)
|
||||
motif_tree_file, _motif_scope_data = resolve_scoped_tree_from_phylogeny(
|
||||
ws,
|
||||
final_dir,
|
||||
prefix,
|
||||
scope_path,
|
||||
explicit_tree_file=tree_file,
|
||||
)
|
||||
cmd += ["--tree", str(motif_tree_file)]
|
||||
motif_scope_id = str(_motif_scope_data.get("scope_id") or "")
|
||||
except Exception as exc:
|
||||
|
||||
+1166
-29
File diff suppressed because it is too large
Load Diff
+173
-43
@@ -665,19 +665,10 @@ def _apply_scope_selection_to_intent(intent: Dict[str, Any], message: str) -> No
|
||||
params["geneFamily"] = family
|
||||
|
||||
|
||||
def _confirmed_config_species_scope(job_id: str) -> Dict[str, list[str]]:
|
||||
"""Read species roles only when the job has authoritative config confirmation."""
|
||||
def _configured_species_scope(job_id: str) -> Dict[str, list[str]]:
|
||||
"""Read the species roles declared by the current job config."""
|
||||
if not job_id:
|
||||
return {}
|
||||
try:
|
||||
from gfa_core.mas.confirmation_snapshot import build_confirmation_snapshot
|
||||
|
||||
confirmation = build_confirmation_snapshot(PROJECT_ROOT, job_id)
|
||||
except (OSError, ValueError):
|
||||
return {}
|
||||
if not confirmation.get("config_confirmed"):
|
||||
return {}
|
||||
|
||||
config_path = INPUT_ROOT / job_id / "config.xlsx"
|
||||
try:
|
||||
import pandas as pd
|
||||
@@ -705,6 +696,21 @@ def _confirmed_config_species_scope(job_id: str) -> Dict[str, list[str]]:
|
||||
return scope if any(scope.values()) else {}
|
||||
|
||||
|
||||
def _confirmed_config_species_scope(job_id: str) -> Dict[str, list[str]]:
|
||||
"""Read species roles only when the job has authoritative config confirmation."""
|
||||
if not job_id:
|
||||
return {}
|
||||
try:
|
||||
from gfa_core.mas.confirmation_snapshot import build_confirmation_snapshot
|
||||
|
||||
confirmation = build_confirmation_snapshot(PROJECT_ROOT, job_id)
|
||||
except (OSError, ValueError):
|
||||
return {}
|
||||
if not confirmation.get("config_confirmed"):
|
||||
return {}
|
||||
return _configured_species_scope(job_id)
|
||||
|
||||
|
||||
def _normalize_main_agent_analysis_intent(
|
||||
intent: Dict[str, Any],
|
||||
*,
|
||||
@@ -1137,7 +1143,7 @@ def _load_identify_species_selection(job_id: str) -> Dict[str, Any]:
|
||||
current_role = entry.get("role", "unspecified")
|
||||
if role == "target":
|
||||
entry["role"] = "target"
|
||||
elif role in {"ref", "seed_reference", "known_family_reference"} and current_role != "target":
|
||||
elif role in {"ref", "reference", "seed_reference", "known_family_reference"} and current_role != "target":
|
||||
entry["role"] = role
|
||||
elif role in {"comparative", "compactive", "outgroup"} and current_role not in {"target", "ref", "seed_reference", "known_family_reference"}:
|
||||
entry["role"] = role
|
||||
@@ -1185,7 +1191,7 @@ def _load_identify_species_selection(job_id: str) -> Dict[str, Any]:
|
||||
groups["target"].append(item)
|
||||
elif role in {"comparative", "compactive", "outgroup"}:
|
||||
groups["comparative"].append(item)
|
||||
elif role in {"ref", "seed_reference", "known_family_reference"}:
|
||||
elif role in {"ref", "reference", "seed_reference", "known_family_reference"}:
|
||||
groups["reference"].append(item)
|
||||
else:
|
||||
groups["other"].append(item)
|
||||
@@ -1344,6 +1350,53 @@ def _extract_species_codes_from_text_for_job(job_id: str, text: str) -> list[str
|
||||
return ordered
|
||||
|
||||
|
||||
def _resolve_subfamily_request_species_roles(
|
||||
job_id: str,
|
||||
message: str,
|
||||
intent: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, list[str]]:
|
||||
"""Preserve config roles while resolving species mentioned in a subfamily request."""
|
||||
routed_intent = intent or {}
|
||||
params = routed_intent.get("params") or {}
|
||||
structured_targets = _canonicalize_species_codes_for_job(
|
||||
job_id,
|
||||
_param_list(params.get("targetSpecies") or routed_intent.get("target_species")),
|
||||
)
|
||||
structured_refs = _canonicalize_species_codes_for_job(
|
||||
job_id,
|
||||
_param_list(params.get("referenceSpecies") or routed_intent.get("reference_species")),
|
||||
)
|
||||
structured_comps = _canonicalize_species_codes_for_job(
|
||||
job_id,
|
||||
_param_list(params.get("comparativeSpecies") or routed_intent.get("comparative_species")),
|
||||
)
|
||||
mentioned = _extract_species_codes_from_text_for_job(job_id, message or "")
|
||||
configured = _configured_species_scope(job_id)
|
||||
configured_targets = _canonicalize_species_codes_for_job(job_id, configured.get("targetSpecies") or [])
|
||||
configured_refs = _canonicalize_species_codes_for_job(job_id, configured.get("referenceSpecies") or [])
|
||||
configured_comps = _canonicalize_species_codes_for_job(job_id, configured.get("comparativeSpecies") or [])
|
||||
if configured:
|
||||
target_candidates = [*structured_targets, *mentioned]
|
||||
comp_candidates = [*structured_comps, *mentioned]
|
||||
ref_candidates = [*structured_refs, *mentioned]
|
||||
targets = [species for species in configured_targets if species in target_candidates]
|
||||
comps = [species for species in configured_comps if species in comp_candidates]
|
||||
refs = [species for species in configured_refs if species in ref_candidates]
|
||||
if not refs:
|
||||
refs = list(configured_refs)
|
||||
else:
|
||||
targets = structured_targets or mentioned
|
||||
refs = structured_refs
|
||||
comps = structured_comps
|
||||
classification_species = list(dict.fromkeys([*targets, *comps]))
|
||||
return {
|
||||
"target_species": targets,
|
||||
"reference_species": refs,
|
||||
"comparative_species": comps,
|
||||
"classification_species": classification_species,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def _species_groups_from_selection(selection: Dict[str, Any]) -> tuple[list[str], list[str], list[str]]:
|
||||
groups = selection.get("groups", {}) if isinstance(selection, dict) else {}
|
||||
@@ -1584,6 +1637,10 @@ def _split_subfamily_selected_species_by_config_roles(
|
||||
selected_set = set(selected)
|
||||
target_role = _subfamily_group_species(identify_selection or {}, ["target"])
|
||||
comparative_role = _subfamily_group_species(identify_selection or {}, ["comparative", "other"])
|
||||
if not target_role and not comparative_role:
|
||||
configured = _configured_species_scope(job_id)
|
||||
target_role = _canonicalize_species_codes_for_job(job_id, configured.get("targetSpecies") or [])
|
||||
comparative_role = _canonicalize_species_codes_for_job(job_id, configured.get("comparativeSpecies") or [])
|
||||
targets = [sp for sp in target_role if sp in selected_set]
|
||||
comps = [sp for sp in comparative_role if sp in selected_set and sp not in targets]
|
||||
if not targets:
|
||||
@@ -1726,18 +1783,47 @@ def _downstream_gene_family_stages_for_context(message: str) -> list[str]:
|
||||
return list(dict.fromkeys(stages))
|
||||
|
||||
|
||||
def _resolve_contextual_gene_family_request(job_id: str, message: str, request_prefix: str = "") -> Dict[str, Any]:
|
||||
def _resolve_contextual_gene_family_request(
|
||||
job_id: str,
|
||||
message: str,
|
||||
request_prefix: str = "",
|
||||
requested_stages: list[str] | None = None,
|
||||
request_intent: Dict[str, Any] | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Resolve post-subfamily/default phylogeny context from real project state.
|
||||
|
||||
This is deliberately rule-based: it decides scope/route boundaries from
|
||||
files and config, while LLM/intent parsing may still supply user wording.
|
||||
"""
|
||||
text = str(message or "")
|
||||
structured_stages = list(dict.fromkeys(
|
||||
str(stage).strip()
|
||||
for stage in (requested_stages or [])
|
||||
if str(stage).strip()
|
||||
))
|
||||
contextual_stage_request = bool(
|
||||
set(structured_stages) & {"phylogeny", "motif", "domain", "structure", "combo"}
|
||||
)
|
||||
routed_intent = request_intent or {}
|
||||
routed_params = routed_intent.get("params") or {}
|
||||
structured_targets = _canonicalize_species_codes_for_job(
|
||||
job_id,
|
||||
_param_list(routed_params.get("targetSpecies") or routed_intent.get("target_species")),
|
||||
)
|
||||
structured_refs = _canonicalize_species_codes_for_job(
|
||||
job_id,
|
||||
_param_list(routed_params.get("referenceSpecies") or routed_intent.get("reference_species")),
|
||||
)
|
||||
structured_comps = _canonicalize_species_codes_for_job(
|
||||
job_id,
|
||||
_param_list(routed_params.get("comparativeSpecies") or routed_intent.get("comparative_species")),
|
||||
)
|
||||
if not (
|
||||
_looks_like_phylogeny_query(text)
|
||||
or _looks_like_gene_family_workflow_query(text)
|
||||
or _looks_like_intraspecies_synteny_query(text)
|
||||
or _looks_like_tfbs_query(text)
|
||||
or contextual_stage_request
|
||||
):
|
||||
return {}
|
||||
selection = _load_identify_species_selection(job_id)
|
||||
@@ -1762,11 +1848,13 @@ def _resolve_contextual_gene_family_request(job_id: str, message: str, request_p
|
||||
if sp not in mentioned_targets:
|
||||
mentioned_targets.append(sp)
|
||||
|
||||
has_explicit_scope = bool(mentioned)
|
||||
has_explicit_scope = bool(mentioned or structured_targets or structured_refs or structured_comps)
|
||||
is_all_species_request = _looks_like_all_species_query(text) or _looks_like_all_subfamily_species_query(text)
|
||||
is_intraspecies_synteny_request = _looks_like_intraspecies_synteny_query(text)
|
||||
if has_explicit_scope:
|
||||
if mentioned:
|
||||
targets = mentioned_targets or [sp for sp in mentioned if sp not in mentioned_refs and sp not in mentioned_comps]
|
||||
elif structured_targets:
|
||||
targets = list(structured_targets)
|
||||
elif is_all_species_request and config_targets:
|
||||
targets = list(config_targets)
|
||||
elif is_intraspecies_synteny_request and config_targets:
|
||||
@@ -1780,8 +1868,8 @@ def _resolve_contextual_gene_family_request(job_id: str, message: str, request_p
|
||||
else:
|
||||
targets = []
|
||||
|
||||
refs = mentioned_refs or (list(config_refs) if is_all_species_request else [])
|
||||
comps = mentioned_comps or (list(config_comps) if is_all_species_request else [])
|
||||
refs = mentioned_refs or list(structured_refs) or (list(config_refs) if is_all_species_request else [])
|
||||
comps = mentioned_comps or list(structured_comps) or (list(config_comps) if is_all_species_request else [])
|
||||
if refs and comps:
|
||||
scope_mode = "target_plus_reference_plus_comparative"
|
||||
elif refs:
|
||||
@@ -1793,7 +1881,7 @@ def _resolve_contextual_gene_family_request(job_id: str, message: str, request_p
|
||||
|
||||
if not family or not targets:
|
||||
return {}
|
||||
stages = _downstream_gene_family_stages_for_context(text)
|
||||
stages = structured_stages or _downstream_gene_family_stages_for_context(text)
|
||||
subfamily_label_stages = {"phylogeny", "motif", "domain", "structure", "combo"}
|
||||
needs_subfamily_labels_for_stage = any(stage in subfamily_label_stages for stage in stages)
|
||||
prefix = f"{targets[0]}.{family}" if len(targets) == 1 else f"Target.{family}"
|
||||
@@ -1826,6 +1914,7 @@ def _resolve_contextual_gene_family_request(job_id: str, message: str, request_p
|
||||
or _requires_confirmed_subfamily_query(text)
|
||||
or _looks_like_gene_family_workflow_query(text)
|
||||
or _looks_like_phylogeny_query(text)
|
||||
or contextual_stage_request
|
||||
)
|
||||
)
|
||||
requires_confirmed = wants_subfamily_context
|
||||
@@ -3870,6 +3959,9 @@ def _attach_agent_manifest_status(task: Dict) -> None:
|
||||
print(f"[API DEBUG] Failed to attach agent manifest status: {e}")
|
||||
|
||||
app = FastAPI(title="BotanicalBaseAgent API")
|
||||
from gfa_core.extension_stage_lightweight.api import create_lightweight_extension_router
|
||||
|
||||
app.include_router(create_lightweight_extension_router())
|
||||
# MainAgent runtime state. Analysis planning and execution remain owned by
|
||||
# the original IntentAgent/PlanningAgent/frontend flow below.
|
||||
llm_manager = None
|
||||
@@ -4115,11 +4207,15 @@ async def link_agent_conversation_to_job(
|
||||
store = globals().get("agent_conversation_store")
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="会话存储尚未初始化")
|
||||
store.rebind_job(
|
||||
conversation_id = store.rebind_job(
|
||||
session_id=request.conversation_id,
|
||||
job_id=request.job_id,
|
||||
)
|
||||
return {"success": True, "message": "会话已关联 Working Directory"}
|
||||
return {
|
||||
"success": True,
|
||||
"conversation_id": conversation_id,
|
||||
"message": "会话已关联 Working Directory",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6014,6 +6110,13 @@ def _api_semantic_preflight(job_id: str, stage: str, prefix: str, params: Dict[s
|
||||
decisions[decision_key] = decision.to_dict() if decision else None
|
||||
|
||||
if decision and decision.resolved:
|
||||
if (
|
||||
need.role == "tree"
|
||||
and decision.selected
|
||||
and decision.selected.path
|
||||
and not str(params.get("treeFile") or "").strip()
|
||||
):
|
||||
materialized_params["treeFile"] = decision.selected.path
|
||||
if (
|
||||
stage == "wgs_index"
|
||||
and need.role == "reference_fasta"
|
||||
@@ -12598,7 +12701,7 @@ def _get_celery_overview() -> Dict[str, Any]:
|
||||
|
||||
resource = snapshot(include_terminal=False)
|
||||
overview["resource_usage"] = resource.get("usage")
|
||||
overview["resource_tasks"] = resource.get("tasks", [])[:20]
|
||||
overview["resource_tasks"] = resource.get("tasks", [])
|
||||
except Exception as exc:
|
||||
overview["errors"].append(f"Resource governor snapshot failed: {exc}")
|
||||
overview["thresholds"] = {
|
||||
@@ -13925,13 +14028,6 @@ async def get_results(
|
||||
def _add_manifest_result_group(manifest_file: Path, manifest: dict):
|
||||
if "combo_panel_test" in manifest_file.name.lower():
|
||||
return
|
||||
if _is_subfamily_intermediate_manifest(manifest_file, manifest):
|
||||
# These manifests are intermediate evidence generated inside the
|
||||
# reference_classification branch. Final combo/circular tree/MSA
|
||||
# artifacts are exposed under 2.5 亚家族确认 instead.
|
||||
_remember_hidden_manifest_artifacts(manifest_file, manifest)
|
||||
hidden_intermediate_artifact_names.add(manifest_file.name)
|
||||
return
|
||||
group_name = (
|
||||
manifest.get("result_group")
|
||||
or (manifest.get("parameters") or {}).get("result_group")
|
||||
@@ -14206,6 +14302,12 @@ async def get_results(
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Results are a persisted job view. Apply manifests by execution time so
|
||||
# the latest attempt determines the visible stage status.
|
||||
manifest_payloads.sort(
|
||||
key=lambda item: (item[0].stat().st_mtime, item[0].name)
|
||||
)
|
||||
|
||||
# A refreshed GWAS format result request may not carry the genotype scope.
|
||||
# Rehydrate it only when the requested prefix (if any) identifies exactly
|
||||
# one successful scientific scope; otherwise remain fail-closed.
|
||||
@@ -16729,6 +16831,7 @@ async def _run_agent_chat(
|
||||
{
|
||||
"taskType": task_type,
|
||||
"message": outcome_message,
|
||||
"ui_action": outcome.get("ui_action"),
|
||||
"job_id": request.job_id,
|
||||
"workspace": (
|
||||
str(PROJECT_ROOT / "workspace" / request.job_id)
|
||||
@@ -17904,7 +18007,7 @@ def _build_contextual_gene_family_plan_response(request: Any, session_id: str, c
|
||||
# Subfamily review happens after reference_classification.
|
||||
if "identify" in stage_ids:
|
||||
detail["dependencies"] = ["identify"]
|
||||
if contextual_request.get("scopeMode") != "target_only" and contextual_request.get("context_route") in {"custom_phylogeny_scope", "plain_phylogeny"}:
|
||||
if not needs_subfamily_evidence_chain and contextual_request.get("scopeMode") != "target_only" and contextual_request.get("context_route") in {"custom_phylogeny_scope", "plain_phylogeny"}:
|
||||
detail_params["analysisRoute"] = "custom_phylogeny"
|
||||
detail["params"] = enrich_stage_params_with_context(stage_id, plan_prefix, detail_params)
|
||||
stages_detail.append(detail)
|
||||
@@ -19652,8 +19755,12 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
request.message or "",
|
||||
subfamily=True,
|
||||
)
|
||||
explicit_species_for_subfamily = _extract_species_codes_from_text_for_job(request.job_id, request.message or "")
|
||||
targets_for_subfamily = explicit_species_for_subfamily or _target_species_fallback_from_prefix(requested_prefix_for_subfamily)
|
||||
request_roles_for_subfamily = _resolve_subfamily_request_species_roles(
|
||||
request.job_id,
|
||||
request.message or "",
|
||||
request.intent,
|
||||
)
|
||||
targets_for_subfamily = request_roles_for_subfamily["classification_species"] or _target_species_fallback_from_prefix(requested_prefix_for_subfamily)
|
||||
if not targets_for_subfamily and prefix_species and prefix_species not in {"Target", "Multi"}:
|
||||
targets_for_subfamily = [prefix_species]
|
||||
scope_defaults_for_subfamily = _latest_gene_family_scope_defaults(request.job_id, family_for_subfamily, requested_prefix_for_subfamily)
|
||||
@@ -19675,7 +19782,7 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
*(scope_defaults_for_subfamily.get("target_species") or []),
|
||||
*(scope_defaults_for_subfamily.get("comparative_species") or []),
|
||||
])) or targets_for_subfamily
|
||||
refs_for_subfamily = scope_defaults_for_subfamily.get("reference_species") or [
|
||||
refs_for_subfamily = request_roles_for_subfamily["reference_species"] or scope_defaults_for_subfamily.get("reference_species") or [
|
||||
item.get("species")
|
||||
for item in ((identify_selection_for_subfamily.get("groups") or {}).get("reference") or [])
|
||||
if item.get("species")
|
||||
@@ -19828,6 +19935,8 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
request.job_id,
|
||||
request.message,
|
||||
request.prefix,
|
||||
requested_stages=list((request.intent or {}).get("stages") or []),
|
||||
request_intent=request.intent,
|
||||
)
|
||||
if contextual_request_pre_mas and not subfamily_prediction_query_for_context and not subfamily_confirm_query_for_context:
|
||||
response = _build_contextual_gene_family_plan_response(request, session_id, contextual_request_pre_mas)
|
||||
@@ -19978,12 +20087,16 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
and not _requires_confirmed_subfamily_query(query_lower_early)
|
||||
)
|
||||
if subfamily_prediction_query_early:
|
||||
explicit_species = _extract_species_codes_from_text_for_job(request.job_id, request.message or "")
|
||||
request_roles_for_subfamily = _resolve_subfamily_request_species_roles(
|
||||
request.job_id,
|
||||
request.message or "",
|
||||
incoming_intent,
|
||||
)
|
||||
current_targets = _canonicalize_species_codes_for_job(
|
||||
request.job_id,
|
||||
_param_list(incoming_params.get("targetSpecies")) or _param_list(incoming_intent.get("target_species")),
|
||||
)
|
||||
target_species_for_subfamily = explicit_species or current_targets
|
||||
target_species_for_subfamily = request_roles_for_subfamily["classification_species"] or current_targets
|
||||
family_for_subfamily = _resolve_authoritative_gene_family(
|
||||
request.job_id,
|
||||
incoming_params.get("geneFamily") or incoming_intent.get("gene_family"),
|
||||
@@ -19996,7 +20109,7 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
if not target_species_for_subfamily:
|
||||
target_species_for_subfamily = scope_defaults_for_subfamily.get("target_species") or _target_species_fallback_from_prefix(effective_plan_prefix or requested_prefix)
|
||||
identify_selection_for_subfamily = _load_identify_species_selection(request.job_id) if request.job_id else {}
|
||||
reference_defaults_for_subfamily = _primary_reference_species_from_scope(request.job_id, family_for_subfamily, effective_plan_prefix or requested_prefix)
|
||||
reference_defaults_for_subfamily = request_roles_for_subfamily["reference_species"] or _primary_reference_species_from_scope(request.job_id, family_for_subfamily, effective_plan_prefix or requested_prefix)
|
||||
if not reference_defaults_for_subfamily:
|
||||
reference_defaults_for_subfamily = scope_defaults_for_subfamily.get("reference_species") or []
|
||||
if not reference_defaults_for_subfamily:
|
||||
@@ -20014,8 +20127,8 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
incoming_intent["reference_species"] = reference_defaults_for_subfamily
|
||||
incoming_params["scopeMode"] = "target_plus_reference"
|
||||
incoming_intent["scopeMode"] = "target_plus_reference"
|
||||
incoming_params["comparativeSpecies"] = []
|
||||
incoming_intent["comparative_species"] = []
|
||||
incoming_params["comparativeSpecies"] = request_roles_for_subfamily["comparative_species"]
|
||||
incoming_intent["comparative_species"] = request_roles_for_subfamily["comparative_species"]
|
||||
incoming_params["geneFamily"] = family_for_subfamily
|
||||
incoming_intent["gene_family"] = family_for_subfamily
|
||||
if target_species_for_subfamily and family_for_subfamily:
|
||||
@@ -20036,14 +20149,19 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
request.message or "",
|
||||
subfamily=True,
|
||||
)
|
||||
targets_for_subfamily = _canonicalize_species_codes_for_job(
|
||||
request_roles_for_subfamily = _resolve_subfamily_request_species_roles(
|
||||
request.job_id,
|
||||
_param_list(incoming_params.get("targetSpecies")) or _param_list(incoming_intent.get("target_species")) or _extract_species_codes_from_text_for_job(request.job_id, request.message or ""),
|
||||
request.message or "",
|
||||
incoming_intent,
|
||||
)
|
||||
targets_for_subfamily = request_roles_for_subfamily["classification_species"] or _canonicalize_species_codes_for_job(
|
||||
request.job_id,
|
||||
_param_list(incoming_params.get("targetSpecies")) or _param_list(incoming_intent.get("target_species")),
|
||||
)
|
||||
scope_defaults_for_subfamily = _latest_gene_family_scope_defaults(request.job_id, family_for_subfamily, effective_plan_prefix or requested_prefix)
|
||||
if not targets_for_subfamily:
|
||||
targets_for_subfamily = scope_defaults_for_subfamily.get("target_species") or _target_species_fallback_from_prefix(effective_plan_prefix or requested_prefix)
|
||||
refs_for_subfamily = _canonicalize_species_codes_for_job(
|
||||
refs_for_subfamily = request_roles_for_subfamily["reference_species"] or _canonicalize_species_codes_for_job(
|
||||
request.job_id,
|
||||
_param_list(incoming_params.get("referenceSpecies")) or _param_list(incoming_intent.get("reference_species")) or scope_defaults_for_subfamily.get("reference_species"),
|
||||
)
|
||||
@@ -20246,7 +20364,13 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
print(f"[Agent Plan] Deterministic subfamily intent plan: targets={targets_for_subfamily}, refs={refs_for_subfamily}, stages={deterministic_stages}")
|
||||
return response
|
||||
|
||||
contextual_request_early = _resolve_contextual_gene_family_request(request.job_id, request.message, request.prefix)
|
||||
contextual_request_early = _resolve_contextual_gene_family_request(
|
||||
request.job_id,
|
||||
request.message,
|
||||
request.prefix,
|
||||
requested_stages=list(incoming_intent.get("stages") or []),
|
||||
request_intent=incoming_intent,
|
||||
)
|
||||
if contextual_request_early and not subfamily_prediction_query_early and not subfamily_confirm_query_early:
|
||||
response = _build_contextual_gene_family_plan_response(request, session_id, contextual_request_early)
|
||||
plan_for_log = response.get('plan') or {}
|
||||
@@ -20939,7 +21063,13 @@ async def agent_plan(request: AgentPlanRequest):
|
||||
for key in ("targetSpecies", "referenceSpecies", "comparativeSpecies", "scopeMode", "geneFamily"):
|
||||
if scope_params.get(key):
|
||||
plan_params[key] = scope_params[key]
|
||||
contextual_request = _resolve_contextual_gene_family_request(request.job_id, request.message, request.prefix)
|
||||
contextual_request = _resolve_contextual_gene_family_request(
|
||||
request.job_id,
|
||||
request.message,
|
||||
request.prefix,
|
||||
requested_stages=list(intent.get("stages") or []),
|
||||
request_intent=intent,
|
||||
)
|
||||
if contextual_request and not subfamily_prediction_query and not subfamily_confirm_query:
|
||||
stage_ids = list(contextual_request.get("stages") or [])
|
||||
raw_stages = stage_ids
|
||||
|
||||
Reference in New Issue
Block a user