Files
2026-08-03 18:42:47 +08:00

297 lines
13 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.
"""
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()]