Merge branch 'master' of http://192.168.31.232:3000/admin/PlantAI-dev
This commit is contained in:
@@ -1365,6 +1365,7 @@
|
||||
<el-tooltip content="上传已跑通过的代码和测试数据;执行通过自然语言触发。" placement="top">
|
||||
<h4 style="margin:0; color:#303133; cursor:help;">拓展分析阶段</h4>
|
||||
</el-tooltip>
|
||||
<el-button size="small" @click="loadExtensionRuns(); extensionResultDialogVisible = true">运行结果</el-button>
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns:72px 1fr; gap:7px; align-items:center; font-size:12px;">
|
||||
<span>已写阶段</span>
|
||||
@@ -1425,7 +1426,6 @@
|
||||
<el-button v-if="isLightweightExtensionSelected" size="small" type="success" @click="openLightweightRuntimeCard()">
|
||||
运行已发布
|
||||
</el-button>
|
||||
<el-button size="small" @click="loadExtensionRuns(); extensionResultDialogVisible = true">运行结果</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+22
-11
@@ -777,18 +777,12 @@ def _reconcile_confirmed_gene_family_species(
|
||||
job_id,
|
||||
_param_list(routed_params.get("comparativeSpecies") or routed_intent.get("comparative_species")),
|
||||
)
|
||||
explicit_reference_set = set(explicit["referenceSpecies"])
|
||||
explicit_assigned_set = set(
|
||||
explicit["targetSpecies"]
|
||||
+ explicit["referenceSpecies"]
|
||||
+ explicit["comparativeSpecies"]
|
||||
)
|
||||
explicit_unassigned_set = set(explicit["unassigned"])
|
||||
for species in structured_targets:
|
||||
config_role = config_role_by_species.get(species)
|
||||
if (
|
||||
config_role in {None, "reference"}
|
||||
and species not in explicit_reference_set
|
||||
and species not in explicit_assigned_set
|
||||
and species in explicit_unassigned_set
|
||||
):
|
||||
conflicts.append({
|
||||
"code": "analysis_target_config_role_conflict",
|
||||
@@ -865,10 +859,24 @@ def _species_role_conflict_message(conflicts: list[Dict[str, str]]) -> str:
|
||||
config_role = role_labels.get(conflict.get("config_role") or "", conflict.get("config_role") or "未知")
|
||||
requested_role = role_labels.get(conflict.get("requested_role") or "", conflict.get("requested_role") or "配置内部重复角色")
|
||||
details.append(f"{species}:config.xlsx 为{config_role},本轮为{requested_role}")
|
||||
conflict_codes = {str(conflict.get("code") or "") for conflict in conflicts}
|
||||
detail_text = ";".join(details)
|
||||
if "config_species_role_conflict" in conflict_codes:
|
||||
return (
|
||||
"检测到 config.xlsx 内部物种角色冲突:"
|
||||
+ detail_text
|
||||
+ "。请发送“打开 config.xlsx”,修正并重新确认物种角色后再继续。"
|
||||
)
|
||||
if "analysis_target_config_role_conflict" in conflict_codes:
|
||||
return (
|
||||
"检测到系统意图解析发生角色漂移:"
|
||||
+ detail_text
|
||||
+ "。config.xlsx 未被修改,系统已阻止按错误范围执行,请重新提交当前分析请求。"
|
||||
)
|
||||
return (
|
||||
"检测到物种角色不一致:"
|
||||
+ ";".join(details)
|
||||
+ "。为避免按错误范围执行,请发送“打开 config.xlsx”,核对并重新确认物种角色后再继续。"
|
||||
"检测到本轮请求与项目物种角色不一致:"
|
||||
+ detail_text
|
||||
+ "。系统已阻止执行,请按 config.xlsx 中已确认的角色重新描述分析范围。"
|
||||
)
|
||||
|
||||
|
||||
@@ -4191,6 +4199,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
|
||||
|
||||
@@ -12,7 +12,13 @@ from gfa_core.mas.stage_catalog import (
|
||||
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, resolve_gwas_intent, resolve_population_intent
|
||||
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):
|
||||
@@ -696,18 +702,68 @@ class IntentAgentV2(BaseAgent):
|
||||
print(f"[IntentAgentV2] Stages expanded: {original_stages} -> {intent_data['stages']}")
|
||||
|
||||
query_lower = query.lower()
|
||||
species_codes = re.findall(r"(?<![A-Za-z0-9])(?:Ft|At|Os|Sl|Zm|Gm|Br|Vv|Pt|Sb|Lc|Dl|Py|Cp|Lg)(?![A-Za-z0-9])", query, flags=re.IGNORECASE)
|
||||
species_map = {code.lower(): code for code in ["Ft", "At", "Os", "Sl", "Zm", "Gm", "Br", "Vv", "Pt", "Sb", "Lc", "Dl", "Py", "Cp", "Lg"]}
|
||||
target_species = []
|
||||
for item in species_codes:
|
||||
sp = species_map.get(item.lower())
|
||||
if sp and sp not in target_species:
|
||||
target_species.append(sp)
|
||||
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["target_species"] = target_species
|
||||
intent_data.setdefault("params", {})["target_species"] = 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
|
||||
|
||||
Executable
+185
@@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
# PlantAI-dev 9000 服务管理脚本
|
||||
# 用法: bash manage_plantai.sh {start|stop|restart|status} [--port PORT]
|
||||
# 默认端口: 9000
|
||||
#
|
||||
# 项目: /home/xzq/5090/teamWorkspace/zh/PlantAI-dev
|
||||
# 端口: 9000 (web), Redis DB 14 (queue prefix: plantai_dev_9002)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 解析命令行参数:第一个参数是命令,后面可选 --port / -p
|
||||
CMD=""
|
||||
WEB_PORT="9000"
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
start|stop|restart|status)
|
||||
CMD="$arg"
|
||||
;;
|
||||
--port|-p)
|
||||
WEB_PORT=""
|
||||
;;
|
||||
*)
|
||||
if [ -z "$WEB_PORT" ] && [[ "$arg" =~ ^[0-9]+$ ]]; then
|
||||
WEB_PORT="$arg"
|
||||
elif [ -z "$CMD" ]; then
|
||||
echo "错误: 未知命令 '$arg'"
|
||||
echo "用法: bash manage_plantai.sh {start|stop|restart|status} [--port PORT]"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
CMD="${CMD:-status}"
|
||||
|
||||
# Conda 环境:启动脚本内部也依赖这个环境
|
||||
CONDA_ENV="${CONDA_ENV:-/home/xzq/miniconda3/envs/test4py311}"
|
||||
export PATH="$CONDA_ENV/bin:$PATH"
|
||||
|
||||
# 项目配置(与 .env 和启动脚本保持一致)
|
||||
PROJECT_DIR="/home/xzq/5090/teamWorkspace/zh/PlantAI-dev"
|
||||
QUEUE_PREFIX="plantai_dev_9002"
|
||||
WEB_LOG="/tmp/plantai_${WEB_PORT}.log"
|
||||
CELERY_LOG="/tmp/celery_${WEB_PORT}.log"
|
||||
|
||||
red() { echo -e "\033[31m$*\033[0m"; }
|
||||
green(){ echo -e "\033[32m$*\033[0m"; }
|
||||
cyan() { echo -e "\033[36m$*\033[0m"; }
|
||||
|
||||
status() {
|
||||
echo ""
|
||||
cyan "=== PlantAI-dev ${WEB_PORT} 服务状态 ==="
|
||||
echo ""
|
||||
|
||||
# Web 服务
|
||||
WEB_PID=$(ss -tlnp 2>/dev/null | grep ":$WEB_PORT " | grep -oP 'pid=\K\d+' || true)
|
||||
if [ -n "${WEB_PID:-}" ]; then
|
||||
green "[✓] Web 服务运行中 (端口 $WEB_PORT, PID $WEB_PID)"
|
||||
else
|
||||
red "[✗] Web 服务未启动 (端口 $WEB_PORT)"
|
||||
fi
|
||||
|
||||
# Redis
|
||||
if redis-cli ping > /dev/null 2>&1; then
|
||||
green "[✓] Redis 运行中 (localhost:6379)"
|
||||
else
|
||||
red "[✗] Redis 未启动"
|
||||
fi
|
||||
|
||||
# Celery workers
|
||||
CELERY_COUNT=$(pgrep -fc "$QUEUE_PREFIX" || echo 0)
|
||||
if [ "$CELERY_COUNT" -gt 0 ]; then
|
||||
green "[✓] Celery workers 运行中 (${CELERY_COUNT} 进程, 队列前缀: $QUEUE_PREFIX)"
|
||||
echo " Worker 列表:"
|
||||
ps aux | grep "$QUEUE_PREFIX" | grep -v grep | grep -oP "queues=\K\S+" | sort | while read q; do
|
||||
echo " - $q"
|
||||
done
|
||||
else
|
||||
red "[✗] Celery workers 未启动 (队列前缀: $QUEUE_PREFIX)"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo ""
|
||||
cyan "=== 停止 PlantAI-dev ${WEB_PORT} 服务 ==="
|
||||
echo ""
|
||||
|
||||
# 停止 Web 服务
|
||||
WEB_PID=$(ss -tlnp 2>/dev/null | grep ":$WEB_PORT " | grep -oP 'pid=\K\d+' || true)
|
||||
if [ -n "${WEB_PID:-}" ]; then
|
||||
echo "正在停止 Web 服务 (PID $WEB_PID)..."
|
||||
kill "$WEB_PID" 2>/dev/null || true
|
||||
sleep 1
|
||||
# 如果还没死,强制 kill
|
||||
if ss -tlnp 2>/dev/null | grep -q ":$WEB_PORT"; then
|
||||
kill -9 "$WEB_PID" 2>/dev/null || true
|
||||
fi
|
||||
green "[✓] Web 服务已停止"
|
||||
else
|
||||
echo "[ ] Web 服务未运行"
|
||||
fi
|
||||
|
||||
# 停止 Celery workers
|
||||
CELERY_PIDS=$(pgrep -f "$QUEUE_PREFIX" 2>/dev/null) || true
|
||||
if [ -n "${CELERY_PIDS:-}" ]; then
|
||||
echo "正在停止 Celery workers..."
|
||||
echo "$CELERY_PIDS" | xargs -r kill 2>/dev/null || true
|
||||
sleep 2
|
||||
# 清理残留
|
||||
REMAIN=$(pgrep -f "$QUEUE_PREFIX" 2>/dev/null) || true
|
||||
if [ -n "${REMAIN:-}" ]; then
|
||||
echo " 强制清理残留进程..."
|
||||
echo "$REMAIN" | xargs -r kill -9 2>/dev/null || true
|
||||
fi
|
||||
green "[✓] Celery workers 已停止"
|
||||
else
|
||||
echo "[ ] Celery workers 未运行"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
start() {
|
||||
echo ""
|
||||
cyan "=== 启动 PlantAI-dev ${WEB_PORT} 服务 ==="
|
||||
echo ""
|
||||
|
||||
# 检查是否已在运行
|
||||
if ss -tlnp 2>/dev/null | grep -q ":$WEB_PORT"; then
|
||||
red "[!] 端口 $WEB_PORT 已被占用,请先停止服务"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! redis-cli ping > /dev/null 2>&1; then
|
||||
red "[!] Redis 未运行,请先启动 Redis"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# 启动 Celery workers
|
||||
echo "正在启动 Celery workers..."
|
||||
nohup bash start_celery_9000.sh > "$CELERY_LOG" 2>&1 &
|
||||
sleep 3
|
||||
CELERY_COUNT=$(pgrep -fc "$QUEUE_PREFIX" || echo 0)
|
||||
if [ "$CELERY_COUNT" -gt 0 ]; then
|
||||
green " [✓] Celery workers 已启动 (${CELERY_COUNT} 进程)"
|
||||
else
|
||||
red " [✗] Celery workers 启动失败,查看 $CELERY_LOG"
|
||||
fi
|
||||
|
||||
# 启动 Web 服务
|
||||
echo "正在启动 Web 服务..."
|
||||
nohup bash start_plantai_9000.sh --port "$WEB_PORT" > "$WEB_LOG" 2>&1 &
|
||||
sleep 3
|
||||
if ss -tlnp 2>/dev/null | grep -q ":$WEB_PORT"; then
|
||||
green " [✓] Web 服务已启动 (http://0.0.0.0:$WEB_PORT)"
|
||||
else
|
||||
red " [✗] Web 服务启动失败,查看 $WEB_LOG"
|
||||
fi
|
||||
|
||||
# 最终状态
|
||||
echo ""
|
||||
echo "日志文件:"
|
||||
echo " Web: $WEB_LOG"
|
||||
echo " Celery: $CELERY_LOG"
|
||||
echo " Worker: $PROJECT_DIR/logs/celery_${QUEUE_PREFIX}/"
|
||||
echo ""
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
||||
case "$CMD" in
|
||||
start) start ;;
|
||||
stop) stop ;;
|
||||
restart) restart ;;
|
||||
status) status ;;
|
||||
*)
|
||||
echo "用法: bash manage_plantai.sh {start|stop|restart|status} [--port PORT]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Project restart script — called by /api/admin/restart-project
|
||||
# 用法: bash restart_plantai_9000.sh [--port PORT]
|
||||
# 从环境变量 PLANTAI_WEB_PORT 或参数获取端口,默认 9000
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
PORT="${PLANTAI_WEB_PORT:-9000}"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--port|-p)
|
||||
PORT="${2:?错误: --port 需要指定端口号}"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
exec bash manage_plantai.sh restart --port "$PORT"
|
||||
@@ -25,13 +25,13 @@ fi
|
||||
if [ -n "$EXTERNAL_BBAV5_WORKER_NAME_PREFIX" ]; then
|
||||
BBAV5_WORKER_NAME_PREFIX="$EXTERNAL_BBAV5_WORKER_NAME_PREFIX"
|
||||
fi
|
||||
if [ -z "$EXTERNAL_BBAV5_QUEUE_PREFIX" ]; then
|
||||
if [ -z "${BBAV5_QUEUE_PREFIX-}" ]; then
|
||||
BBAV5_QUEUE_PREFIX="plantai_dev_9000"
|
||||
fi
|
||||
if [ -z "$EXTERNAL_BBAV5_REDIS_URL" ]; then
|
||||
if [ -z "${BBAV5_REDIS_URL-}" ]; then
|
||||
BBAV5_REDIS_URL="redis://localhost:6379/10"
|
||||
fi
|
||||
if [ -z "$EXTERNAL_BBAV5_CELERY_LOG_DIR" ]; then
|
||||
if [ -z "${BBAV5_CELERY_LOG_DIR-}" ]; then
|
||||
BBAV5_CELERY_LOG_DIR="logs/celery_plantai_dev_9000"
|
||||
fi
|
||||
if [ -z "$EXTERNAL_BBAV5_WORKER_NAME_PREFIX" ]; then
|
||||
|
||||
+28
-4
@@ -1,8 +1,26 @@
|
||||
#!/bin/bash
|
||||
# Start the PlantAI-dev web service on port 9000.
|
||||
# Start the PlantAI-dev web service.
|
||||
# 用法: bash start_plantai_9000.sh [--port PORT]
|
||||
# 默认端口: 9000
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 解析命令行参数
|
||||
PORT="9000"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--port|-p)
|
||||
PORT="${2:?错误: --port 需要指定端口号}"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1"
|
||||
echo "用法: bash start_plantai_9000.sh [--port PORT]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
@@ -15,13 +33,19 @@ export PATH="$CONDA_ENV/bin:$PATH"
|
||||
# the caller's shell; GWAS/other sandboxes have dedicated restart scripts.
|
||||
export BBAV5_PROJECT_ROOT="$SCRIPT_DIR"
|
||||
export PLANTAI_PROJECT_ROOT="$SCRIPT_DIR"
|
||||
if [ -f "$SCRIPT_DIR/.env" ]; then
|
||||
set -a
|
||||
. "$SCRIPT_DIR/.env"
|
||||
set +a
|
||||
fi
|
||||
export BBAV5_PROJECT_ENV_FILE="${BBAV5_PROJECT_ENV_FILE:-$SCRIPT_DIR/.env}"
|
||||
export BBAV5_LIGHTWEIGHT_EXTENSION_STORE="${BBAV5_LIGHTWEIGHT_EXTENSION_STORE:-$SCRIPT_DIR/.runtime/lightweight-extension/store}"
|
||||
export BBAV5_LIGHTWEIGHT_INPUT="${BBAV5_LIGHTWEIGHT_INPUT:-$SCRIPT_DIR/input}"
|
||||
export BBAV5_LIGHTWEIGHT_WORKSPACE="${BBAV5_LIGHTWEIGHT_WORKSPACE:-$SCRIPT_DIR/workspace}"
|
||||
export BBAV5_QUEUE_PREFIX="plantai_dev_9000"
|
||||
export BBAV5_REDIS_URL="redis://localhost:6379/10"
|
||||
export PLANTAI_WEB_PORT="9000"
|
||||
export BBAV5_QUEUE_PREFIX="${BBAV5_QUEUE_PREFIX:-plantai_dev_9000}"
|
||||
export BBAV5_REDIS_URL="${BBAV5_REDIS_URL:-redis://localhost:6379/10}"
|
||||
export PLANTAI_WEB_PORT="$PORT"
|
||||
export PLANTAI_RESTART_SCRIPT="$SCRIPT_DIR/restart_plantai_9000.sh"
|
||||
export GENE_FAMILY_MAS_API="http://127.0.0.1:7926"
|
||||
export DUAL_KB_QA_BASE_URL="http://127.0.0.1:7926"
|
||||
export RAG_API_URL="http://127.0.0.1:7926/api/v1/qa-v1-1-73/chat"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from gfa_core.mas.agents.intent_v2 import IntentAgentV2
|
||||
from gfa_core.mas.utils.intent_parser import (
|
||||
parse_explicit_species_role_mentions,
|
||||
parse_gene_family_scope_selection,
|
||||
@@ -30,3 +33,58 @@ def test_explicit_role_parser_keeps_unassigned_mentions_separate() -> None:
|
||||
"comparativeSpecies": [],
|
||||
"unassigned": ["Ppy"],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("species", ["Gm", "Br", "Pt", "Sb"])
|
||||
def test_scope_parser_preserves_species_supported_by_legacy_intent(species: str) -> None:
|
||||
scope = parse_gene_family_scope_selection(f"分析 {species} 的 SPL 基因家族")
|
||||
|
||||
assert scope["targetSpecies"] == [species]
|
||||
|
||||
|
||||
def test_scope_parser_accepts_explicit_role_bound_custom_species_code() -> None:
|
||||
scope = parse_gene_family_scope_selection("目标物种 Sv;参考物种 At")
|
||||
|
||||
assert scope["targetSpecies"] == ["Sv"]
|
||||
assert scope["referenceSpecies"] == ["At"]
|
||||
|
||||
|
||||
def test_scope_parser_does_not_treat_gene_family_as_species() -> None:
|
||||
scope = parse_gene_family_scope_selection("目标物种 Ft;分析 SPL 基因家族")
|
||||
|
||||
assert scope["targetSpecies"] == ["Ft"]
|
||||
|
||||
|
||||
def test_scope_parser_does_not_bind_custom_code_to_generic_goal_word() -> None:
|
||||
scope = parse_gene_family_scope_selection("分析目标 SPL 基因家族")
|
||||
|
||||
assert scope["targetSpecies"] == []
|
||||
|
||||
|
||||
def test_intent_validation_keeps_confirmed_target_and_reference_separate() -> None:
|
||||
agent = object.__new__(IntentAgentV2)
|
||||
|
||||
intent = agent._validate_intent(
|
||||
{"gene_family": "SPL", "stages": ["identify"], "params": {}},
|
||||
(
|
||||
"请按照你规划的步骤来帮我执行\n"
|
||||
"已确认上下文:基因家族 SPL;目标物种 Ft;参考物种 At。"
|
||||
),
|
||||
)
|
||||
|
||||
assert intent["target_species"] == ["Ft"]
|
||||
assert intent["reference_species"] == ["At"]
|
||||
assert intent["species"] == "Ft"
|
||||
|
||||
|
||||
def test_intent_validation_preserves_unlabelled_multi_target_request() -> None:
|
||||
agent = object.__new__(IntentAgentV2)
|
||||
|
||||
intent = agent._validate_intent(
|
||||
{"gene_family": "SPL", "stages": ["identify"], "params": {}},
|
||||
"分析 Ft 和 Os 的 SPL 基因家族",
|
||||
)
|
||||
|
||||
assert intent["target_species"] == ["Ft", "Os"]
|
||||
assert intent["reference_species"] == []
|
||||
assert intent["species"] == "Multi"
|
||||
|
||||
@@ -426,6 +426,64 @@ def test_confirmed_multi_species_roles_remain_separate(monkeypatch):
|
||||
assert resolved["classification_species"] == ["Ft", "Ppy"]
|
||||
|
||||
|
||||
def test_confirmation_ack_normalizes_structured_role_drift(monkeypatch):
|
||||
main = _load_vue3_main()
|
||||
monkeypatch.setattr(
|
||||
main,
|
||||
"_confirmed_config_species_scope",
|
||||
lambda _job_id: {
|
||||
"targetSpecies": ["Ft"],
|
||||
"referenceSpecies": ["At"],
|
||||
"comparativeSpecies": [],
|
||||
},
|
||||
)
|
||||
|
||||
resolved = main._reconcile_confirmed_gene_family_species(
|
||||
"job-confirmed-ack",
|
||||
"是",
|
||||
{
|
||||
"gene_family": "SPL",
|
||||
"stages": ["identify"],
|
||||
"target_species": ["Ft", "At"],
|
||||
"params": {"targetSpecies": ["Ft", "At"]},
|
||||
},
|
||||
)
|
||||
|
||||
assert resolved["conflicts"] == []
|
||||
assert resolved["target_species"] == ["Ft"]
|
||||
assert resolved["reference_species"] == ["At"]
|
||||
|
||||
|
||||
def test_unassigned_reference_species_analysis_request_remains_blocked(monkeypatch):
|
||||
main = _load_vue3_main()
|
||||
monkeypatch.setattr(
|
||||
main,
|
||||
"_confirmed_config_species_scope",
|
||||
lambda _job_id: {
|
||||
"targetSpecies": ["Ft"],
|
||||
"referenceSpecies": ["At"],
|
||||
"comparativeSpecies": [],
|
||||
},
|
||||
)
|
||||
|
||||
resolved = main._reconcile_confirmed_gene_family_species(
|
||||
"job-unassigned-reference",
|
||||
"分析 At 的 SPL 基因家族",
|
||||
{
|
||||
"gene_family": "SPL",
|
||||
"stages": ["identify"],
|
||||
"target_species": ["At"],
|
||||
"params": {"targetSpecies": ["At"]},
|
||||
},
|
||||
)
|
||||
|
||||
assert any(
|
||||
conflict["code"] == "analysis_target_config_role_conflict"
|
||||
and conflict["species"] == "At"
|
||||
for conflict in resolved["conflicts"]
|
||||
)
|
||||
|
||||
|
||||
def test_confirmed_config_duplicate_roles_are_blocked(monkeypatch):
|
||||
main = _load_vue3_main()
|
||||
monkeypatch.setattr(
|
||||
|
||||
Reference in New Issue
Block a user