Files
PlantAI-dev/gfa_core/mas/agents/execution.py
T

297 lines
13 KiB
Python
Raw Normal View History

2026-08-03 18:42:47 +08:00
"""
ExecutionAgent V2 - 集成StageToolRegistry
支持执行bbav20311分析阶段脚本
"""
import asyncio
from typing import Dict, List, Optional
from .base import AgentMessage, AgentState, BaseAgent
from gfa_core.mas.llm import LLMManager
from gfa_core.project_paths import WORKSPACE_ROOT
class ExecutionAgent(BaseAgent):
"""
任务执行Agent V2
使用StageToolRegistry执行实际的分析阶段
"""
name = "execution"
role = "Task Execution Specialist"
def __init__(self, llm_manager: LLMManager, stage_registry=None):
super().__init__(llm_manager)
self.stage_registry = stage_registry
async def execute(self, state: AgentState, message: AgentMessage) -> AgentState:
"""
执行分析任务
从state中提取任务信息,使用StageToolRegistry执行
"""
print(f"[ExecutionAgent] 开始执行,session_id: {state.session_id}")
# 获取任务信息
task_plan = state.results.get("task_plan", {})
stages_detail = task_plan.get("stages", [])
# 优先使用用户传入的 job_id,其次使用 session_id
job_id = state.results.get("job_id") or state.session_id or "default_job"
prefix = state.results.get("prefix", "")
print(f"[ExecutionAgent] task_plan: {task_plan}")
print(f"[ExecutionAgent] stages_detail: {stages_detail}")
print(f"[ExecutionAgent] job_id: {job_id}, prefix: {prefix}")
# 提取 stage_id 列表(支持 dict 或 string)
stages_to_run = []
for stage in stages_detail:
if isinstance(stage, dict):
stages_to_run.append(stage.get("id", "unknown"))
else:
stages_to_run.append(stage)
print(f"[ExecutionAgent] stages_to_run: {stages_to_run}")
# 获取意图信息(用于参数)
intent = state.results.get("results", {}).get("intent", {}) or state.results.get("intent", {})
print(f"[ExecutionAgent] intent: {intent}")
if not stages_to_run:
# 检查是否是因为PreFlight验证失败导致没有可执行的阶段
# preflight_result在task_plan中,key是"preflight_result"
preflight_result = task_plan.get("preflight_result", {})
task_plan_valid = task_plan.get("valid", True)
task_plan_error = task_plan.get("error", "")
if not task_plan_valid:
# PreFlight验证失败,返回错误而不是加载旧结果
error_msg = task_plan_error or "执行前验证失败"
print(f"[ExecutionAgent] ❌ PreFlight验证失败,无法执行: {error_msg}")
state.messages.append({
"role": "execution",
"content": f"❌ 执行前验证失败: {error_msg}"
})
# AgentState使用results字典
state.results["execution_status"] = "error"
state.results["execution_error"] = error_msg
state.results["execution_results"] = []
return state
# 如果没有需要执行的阶段(可能都已完成了),从manifest加载已有结果
print(f"[ExecutionAgent] ✅ 没有需要执行的阶段(可能都已完成了),从manifest加载结果")
# 从manifest加载已完成阶段的结果
completed_results = []
job_id = state.results.get("job_id") or state.session_id
prefix = state.results.get("prefix", "")
try:
from pathlib import Path
import json
workspace = WORKSPACE_ROOT / job_id / "final"
if workspace.exists():
for manifest_file in workspace.glob(f"{prefix}.*.manifest.json"):
try:
with open(manifest_file, 'r') as f:
manifest = json.load(f)
stage = manifest.get("stage", "unknown")
completed_results.append({
"stage": stage,
"result": {
"status": manifest.get("status", "success"),
"manifest": manifest,
"manifest_path": str(manifest_file),
"message": f"✅ {stage.capitalize()} 完成(从manifest加载)"
}
})
print(f"[ExecutionAgent] 加载manifest: {manifest_file.name}, stage: {stage}")
except Exception as e:
print(f"[ExecutionAgent] 加载manifest失败: {e}")
except Exception as e:
print(f"[ExecutionAgent] 扫描manifest目录失败: {e}")
state.messages.append({
"role": "execution",
"content": f"✅ 所有阶段已完成,加载 {len(completed_results)} 个已有结果"
})
state.results["execution_status"] = "success"
state.results["execution_results"] = completed_results
state.results["execution_error"] = None
return state
# 检查stage_registry是否可用
print(f"[ExecutionAgent] stage_registry: {self.stage_registry}")
if self.stage_registry is None:
print("[ExecutionAgent] ❌ Stage registry not available")
state.messages.append({
"role": "execution",
"content": "⚠️ Stage registry not available, cannot execute analysis"
})
state.results["execution_status"] = "error"
state.results["execution_error"] = "Stage registry not initialized"
return state
print(f"[ExecutionAgent] ✅ Stage registry available, stages: {self.stage_registry.list_all()}")
# 执行每个阶段
execution_results = []
overall_status = "success"
print(f"[ExecutionAgent] 开始执行 {len(stages_to_run)} 个阶段")
for stage_id in stages_to_run:
print(f"[ExecutionAgent] 处理阶段: {stage_id}")
# 检查阶段是否存在
try:
stage_tool = self.stage_registry.get(stage_id)
print(f"[ExecutionAgent] stage_tool: {stage_tool}")
except Exception as e:
print(f"[ExecutionAgent] ❌ 获取 stage_tool 失败: {e}")
import traceback
traceback.print_exc()
state.messages.append({
"role": "execution",
"content": f"❌ Stage '{stage_id}' 获取失败: {str(e)}"
})
overall_status = "error"
continue
if stage_tool is None:
print(f"[ExecutionAgent] ❌ Stage '{stage_id}' not found")
state.messages.append({
"role": "execution",
"content": f"❌ Stage '{stage_id}' not found in registry"
})
overall_status = "error"
continue
# 通知开始执行
print(f"[ExecutionAgent] 🚀 开始执行 {stage_tool.name} ({stage_id})")
state.messages.append({
"role": "execution",
"content": f"🚀 Starting {stage_tool.name} ({stage_id})..."
})
# 准备参数
extra_params = state.results.get("params", {}).copy()
extra_params.update(intent.get("params", {}))
print(f"[ExecutionAgent] extra_params: {extra_params}")
# 执行阶段
try:
print(f"[ExecutionAgent] 调用 stage_registry.execute({stage_id}, {job_id}, {prefix})")
# stage_registry.execute 现在已经是 async,直接 await
result = await self.stage_registry.execute(
stage_id=stage_id,
job_id=job_id,
prefix=prefix,
extra_params=extra_params
)
print(f"[ExecutionAgent] ✅ 执行完成,结果: {result}")
execution_results.append({
"stage": stage_id,
"result": result
})
if result.get("status") in ["success", "partial_success"]:
state.messages.append({
"role": "execution",
"content": f"✅ {stage_tool.name} completed successfully"
})
else:
# 提取错误信息(优先使用error字段,否则使用message字段)
error_msg = result.get('error') or result.get('message', 'Unknown error')
# 从日志文件读取详细错误信息
stderr = result.get('stderr', '')
stdout = result.get('stdout', '')
# 如果stderr为空,尝试从日志文件读取
if not stderr and result.get('manifest_path'):
try:
from pathlib import Path
manifest_path = Path(result['manifest_path'])
logs_dir = manifest_path.parent.parent / "logs"
if logs_dir.exists():
import glob
log_files = glob.glob(str(logs_dir / f"*_{stage_id}.log"))
if log_files:
latest_log = max(log_files, key=lambda x: Path(x).stat().st_mtime)
with open(latest_log, 'r', encoding='utf-8', errors='ignore') as f:
log_content = f.read()
stderr = log_content[-2000:] # 最后2000字符
except Exception as e:
print(f"[ExecutionAgent] 读取日志文件失败: {e}")
state.messages.append({
"role": "execution",
"content": f"❌ {stage_tool.name} failed: {error_msg}"
})
overall_status = "error"
# 更新最后一个execution_result,添加错误详情
if execution_results:
execution_results[-1]["error_details"] = {
"error": error_msg,
"stderr": stderr,
"stdout": stdout,
"message": result.get('message', '')
}
break
except Exception as e:
execution_results.append({
"stage": stage_id,
"result": {"status": "error", "error": str(e)}
})
state.messages.append({
"role": "execution",
"content": f"❌ Exception in {stage_tool.name}: {str(e)}"
})
overall_status = "error"
break
# 更新状态 - 保存到AgentState.results(供其他Agent使用)
state.results["execution_results"] = execution_results
state.results["execution_status"] = overall_status
if overall_status == "error":
for item in execution_results:
result = item.get("result", {}) if isinstance(item, dict) else {}
error_msg = result.get("error") or result.get("message")
if error_msg:
state.results["execution_error"] = error_msg
break
return state
async def execute_single_stage(self, state: AgentState, stage_id: str) -> dict:
"""
执行单个分析阶段
Args:
state: 当前Agent状态
stage_id: 阶段ID
Returns:
执行结果字典
"""
if self.stage_registry is None:
return {"status": "error", "error": "Stage registry not initialized"}
job_id = state.session_id or state.results.get("job_id", "default_job")
prefix = state.results.get("prefix", "")
params = state.results.get("params", {})
# stage_registry.execute 现在已经是 async,直接 await
return await self.stage_registry.execute(
stage_id=stage_id,
job_id=job_id,
prefix=prefix,
extra_params=params
)
def get_available_stages(self) -> List[str]:
"""获取可用的分析阶段列表"""
if self.stage_registry is None:
return []
return [s.id for s in self.stage_registry.list_all()]