核心组件: - Orchestrator: 意图理解、任务拆分、竞标管理、结果验证 - Worker: 竞标任务、执行交付 - TaskBoard: 状态管理、信息存储 - BidEvaluator: 竞标评估算法 - ExecutionMonitor: 执行监控、超时处理 - LLMClient: 大模型接口调用 功能特性: - 竞标机制:Agent主动竞争任务 - 动态调度:串行/并行任务智能调度 - 智能容错:超时切换、验证重试 - 质量保证:结果验证、历史追踪 Web界面:首页、请求列表、任务列表、Agent管理 API接口:请求/任务/Agent管理、测试接口 端口:19015
501 lines
16 KiB
Python
501 lines
16 KiB
Python
"""
|
|
规划Agent (Orchestrator) - 意图理解、任务拆分、竞标管理、结果验证
|
|
"""
|
|
|
|
import time
|
|
import json
|
|
from typing import Dict, List, Optional, Any
|
|
from .models import (
|
|
Task, TaskType, TaskStatus, TaskGraph, Bid, Execution,
|
|
Attempt, ValidationResult, UserRequest, AgentProfile
|
|
)
|
|
from .task_board import TaskBoard
|
|
from .bid_evaluator import BidEvaluator
|
|
from .execution_monitor import ExecutionMonitorManager
|
|
from .llm_client import LLMClient, default_client
|
|
|
|
|
|
class Orchestrator:
|
|
"""规划Agent - 核心调度引擎"""
|
|
|
|
def __init__(
|
|
self,
|
|
task_board: TaskBoard,
|
|
llm_client: LLMClient = None
|
|
):
|
|
self.task_board = task_board
|
|
self.llm = llm_client or default_client
|
|
|
|
self.bid_evaluator = BidEvaluator()
|
|
self.execution_manager = ExecutionMonitorManager()
|
|
|
|
# 配置
|
|
self.bidding_window = 30 # 竞标窗口时间(秒)
|
|
self.max_retry_per_agent = 1 # 每个Agent最大重试次数
|
|
self.max_total_attempts = 5 # 总尝试次数上限
|
|
self.max_total_time = 1800 # 最大总执行时间(秒)
|
|
|
|
# === 意图理解 ===
|
|
|
|
def understand_intent(self, user_request: str) -> Dict:
|
|
"""
|
|
意图理解
|
|
|
|
Args:
|
|
user_request: 用户原始请求
|
|
|
|
Returns:
|
|
意图分析结果
|
|
"""
|
|
try:
|
|
intent = self.llm.analyze_intent(user_request)
|
|
return intent
|
|
except Exception as e:
|
|
# 简化处理
|
|
return {
|
|
"intent": user_request,
|
|
"keywords": [],
|
|
"need_clarification": False,
|
|
"questions": []
|
|
}
|
|
|
|
# === 任务拆分 ===
|
|
|
|
def split_tasks(self, intent: Dict, user_request: str) -> TaskGraph:
|
|
"""
|
|
任务拆分
|
|
|
|
Args:
|
|
intent: 意图分析结果
|
|
user_request: 用户原始请求
|
|
|
|
Returns:
|
|
TaskGraph
|
|
"""
|
|
try:
|
|
task_list = self.llm.split_tasks(intent, user_request)
|
|
|
|
graph = TaskGraph()
|
|
|
|
for t in task_list:
|
|
task_type = TaskType.PARALLEL if t.get('type') == 'parallel' else TaskType.SERIAL
|
|
task = Task(
|
|
type=task_type,
|
|
description=t.get('description', ''),
|
|
input_data=t.get('input_schema', {}),
|
|
output_schema=t.get('output_schema', {}),
|
|
dependencies=t.get('dependencies', [])
|
|
)
|
|
graph.add_task(task)
|
|
|
|
return graph
|
|
|
|
except Exception as e:
|
|
# 简化:创建单个任务
|
|
graph = TaskGraph()
|
|
task = Task(
|
|
type=TaskType.INDEPENDENT,
|
|
description=user_request
|
|
)
|
|
graph.add_task(task)
|
|
return graph
|
|
|
|
# === 任务发布 ===
|
|
|
|
def publish_task(self, task: Task) -> List[AgentProfile]:
|
|
"""
|
|
发布任务
|
|
|
|
Args:
|
|
task: 任务
|
|
|
|
Returns:
|
|
能处理该任务的Agent列表
|
|
"""
|
|
# 更新状态
|
|
task.status = TaskStatus.PUBLISHED
|
|
self.task_board.add_task(task)
|
|
|
|
# 找能处理的Agent
|
|
capable_agents = self.task_board.find_capable_agents(task)
|
|
|
|
return capable_agents
|
|
|
|
# === 竞标收集 ===
|
|
|
|
def collect_bids(
|
|
self,
|
|
task: Task,
|
|
agents: List[AgentProfile],
|
|
window_seconds: int = None
|
|
) -> List[Bid]:
|
|
"""
|
|
收集竞标
|
|
|
|
Args:
|
|
task: 任务
|
|
agents: Agent列表
|
|
window_seconds: 竞标窗口时间
|
|
|
|
Returns:
|
|
竞标列表
|
|
"""
|
|
window = window_seconds or self.bidding_window
|
|
task.status = TaskStatus.BIDDING
|
|
|
|
bids = []
|
|
|
|
# 让每个Agent生成竞标
|
|
for agent in agents:
|
|
try:
|
|
bid_data = self.llm.generate_bid(task.to_dict(), agent.to_dict())
|
|
|
|
bid = Bid(
|
|
task_id=task.id,
|
|
agent_id=agent.id,
|
|
capability_match=float(bid_data.get('capability_match', 0.5)),
|
|
estimated_time=int(bid_data.get('estimated_time', 60)),
|
|
confidence=float(bid_data.get('confidence', 0.5)),
|
|
approach=bid_data.get('approach', ''),
|
|
prerequisites=bid_data.get('prerequisites', []),
|
|
alternative_approaches=bid_data.get('alternative_approaches', [])
|
|
)
|
|
|
|
bids.append(bid)
|
|
self.task_board.add_bid(bid)
|
|
|
|
except Exception as e:
|
|
# Agent无法竞标,跳过
|
|
pass
|
|
|
|
return bids
|
|
|
|
# === 竞标评估 ===
|
|
|
|
def evaluate_bids(self, task: Task, bids: List[Bid]) -> tuple:
|
|
"""
|
|
评估竞标
|
|
|
|
Args:
|
|
task: 任务
|
|
bids: 竞标列表
|
|
|
|
Returns:
|
|
(selected_agent, selected_bid, backup_agents)
|
|
"""
|
|
agents_dict = {
|
|
a.id: a for a in self.task_board.list_agents()
|
|
}
|
|
|
|
best_bid, best_agent, scores = self.bid_evaluator.select_best_bid(bids, agents_dict)
|
|
|
|
if best_agent:
|
|
backup_agents = self.bid_evaluator.get_backup_agents(
|
|
bids, agents_dict, best_agent.id
|
|
)
|
|
|
|
return (best_agent, best_bid, backup_agents)
|
|
|
|
return (None, None, [])
|
|
|
|
# === 任务执行 ===
|
|
|
|
def assign_and_execute(
|
|
self,
|
|
task: Task,
|
|
agent: AgentProfile,
|
|
bid: Bid,
|
|
backup_agents: List[AgentProfile] = None
|
|
) -> Execution:
|
|
"""
|
|
分配并执行任务
|
|
|
|
Args:
|
|
task: 任务
|
|
agent: 执行Agent
|
|
bid: 竞标书
|
|
backup_agents: 备选Agent
|
|
|
|
Returns:
|
|
Execution对象
|
|
"""
|
|
task.status = TaskStatus.ASSIGNED
|
|
|
|
# 定义执行函数
|
|
def execute_func(t, a, b):
|
|
try:
|
|
result = self.llm.execute_task(t.to_dict(), b.approach)
|
|
return result.get('result') or result
|
|
except Exception as e:
|
|
raise e
|
|
|
|
# 创建监控器
|
|
monitor = self.execution_manager.create_monitor(
|
|
task=task,
|
|
agent=agent,
|
|
bid=bid,
|
|
callbacks={
|
|
'complete': lambda exec: self._on_execution_complete(exec, task),
|
|
'timeout': lambda exec: self._on_execution_timeout(exec, task, backup_agents),
|
|
'error': lambda exec, e: self._on_execution_error(exec, task, e)
|
|
}
|
|
)
|
|
|
|
# 添加备选
|
|
if backup_agents:
|
|
for backup in backup_agents:
|
|
monitor.add_backup_agent(backup)
|
|
|
|
# 启动执行
|
|
task.status = TaskStatus.EXECUTING
|
|
execution = monitor.start(execute_func)
|
|
|
|
self.task_board.add_execution(execution)
|
|
|
|
return execution
|
|
|
|
def _on_execution_complete(self, execution: Execution, task: Task):
|
|
"""执行完成回调"""
|
|
task.status = TaskStatus.VALIDATING
|
|
|
|
# 验证结果
|
|
validation = self.validate_result(task, execution.result)
|
|
|
|
if validation.passed:
|
|
task.status = TaskStatus.COMPLETED
|
|
self.task_board.update_task_status(task.id, task.status)
|
|
|
|
# 更新Agent统计
|
|
duration = execution.end_time - execution.start_time
|
|
self.task_board.update_agent_stats(
|
|
execution.agent_id,
|
|
success=True,
|
|
duration=duration,
|
|
quality_score=validation.score
|
|
)
|
|
else:
|
|
# 验证失败,尝试重试
|
|
self._handle_validation_failure(task, execution, validation)
|
|
|
|
def _on_execution_timeout(self, execution: Execution, task: Task, backup_agents: List[AgentProfile]):
|
|
"""执行超时回调"""
|
|
# 记录失败
|
|
attempt = Attempt(
|
|
task_id=task.id,
|
|
agent_id=execution.agent_id,
|
|
execution=execution,
|
|
success=False,
|
|
failure_type='timeout',
|
|
duration=execution.end_time - execution.start_time
|
|
)
|
|
self.task_board.add_attempt(attempt)
|
|
|
|
# 尝试备选Agent
|
|
if backup_agents and self.task_board.get_attempt_count(task.id) < self.max_total_attempts:
|
|
# 找一个备选Agent重新执行
|
|
for backup in backup_agents:
|
|
attempts = [a for a in self.task_board.get_attempts(task.id) if a.agent_id == backup.id]
|
|
if len(attempts) < self.max_retry_per_agent:
|
|
# 创建新的竞标
|
|
bid = Bid(
|
|
task_id=task.id,
|
|
agent_id=backup.id,
|
|
capability_match=0.5,
|
|
estimated_time=60,
|
|
confidence=0.5,
|
|
approach="备选执行"
|
|
)
|
|
self.assign_and_execute(task, backup, bid, [])
|
|
return
|
|
|
|
# 无备选或已达上限,任务失败
|
|
task.status = TaskStatus.FAILED
|
|
self.task_board.update_task_status(task.id, task.status)
|
|
|
|
def _on_execution_error(self, execution: Execution, task: Task, error: Exception):
|
|
"""执行错误回调"""
|
|
# 记录失败
|
|
attempt = Attempt(
|
|
task_id=task.id,
|
|
agent_id=execution.agent_id,
|
|
execution=execution,
|
|
success=False,
|
|
failure_type='error',
|
|
duration=time.time() - execution.start_time,
|
|
feedback=str(error)
|
|
)
|
|
self.task_board.add_attempt(attempt)
|
|
|
|
task.status = TaskStatus.FAILED
|
|
self.task_board.update_task_status(task.id, task.status)
|
|
|
|
def _handle_validation_failure(self, task: Task, execution: Execution, validation: ValidationResult):
|
|
"""处理验证失败"""
|
|
# 记录失败
|
|
attempt = Attempt(
|
|
task_id=task.id,
|
|
agent_id=execution.agent_id,
|
|
execution=execution,
|
|
validation=validation,
|
|
success=False,
|
|
failure_type='validation',
|
|
duration=execution.end_time - execution.start_time,
|
|
feedback=json.dumps(validation.issues)
|
|
)
|
|
self.task_board.add_attempt(attempt)
|
|
|
|
# 检查重试次数
|
|
agent_attempts = [a for a in self.task_board.get_attempts(task.id) if a.agent_id == execution.agent_id]
|
|
|
|
if len(agent_attempts) < self.max_retry_per_agent:
|
|
# 给予重试机会
|
|
task.status = TaskStatus.RETRY
|
|
self.task_board.update_task_status(task.id, task.status)
|
|
|
|
# 重新执行(带上反馈)
|
|
# 简化处理,直接失败
|
|
task.status = TaskStatus.FAILED
|
|
self.task_board.update_task_status(task.id, task.status)
|
|
else:
|
|
# 已达重试上限,任务失败
|
|
task.status = TaskStatus.FAILED
|
|
self.task_board.update_task_status(task.id, task.status)
|
|
|
|
# === 结果验证 ===
|
|
|
|
def validate_result(self, task: Task, result: Any) -> ValidationResult:
|
|
"""
|
|
验证结果
|
|
|
|
Args:
|
|
task: 任务
|
|
result: 执行结果
|
|
|
|
Returns:
|
|
ValidationResult
|
|
"""
|
|
try:
|
|
validation_data = self.llm.validate_result(task.to_dict(), result)
|
|
|
|
validation = ValidationResult(
|
|
passed=validation_data.get('passed', False),
|
|
issues=validation_data.get('issues', []),
|
|
score=float(validation_data.get('score', 0.5))
|
|
)
|
|
|
|
return validation
|
|
|
|
except Exception as e:
|
|
# 简化:假设通过
|
|
return ValidationResult(passed=True, score=0.5)
|
|
|
|
# === 主流程 ===
|
|
|
|
def process_request(self, user_request: str) -> UserRequest:
|
|
"""
|
|
处理用户请求(完整流程)
|
|
|
|
Args:
|
|
user_request: 用户原始请求
|
|
|
|
Returns:
|
|
UserRequest对象
|
|
"""
|
|
# 1. 创建请求记录
|
|
request = self.task_board.create_request(user_request)
|
|
|
|
try:
|
|
# 2. 意图理解
|
|
intent = self.understand_intent(user_request)
|
|
|
|
# 3. 如果需要澄清
|
|
if intent.get('need_clarification'):
|
|
request.status = 'clarification_needed'
|
|
request.error = json.dumps(intent.get('questions', []))
|
|
self.task_board.update_request(request)
|
|
return request
|
|
|
|
# 4. 任务拆分
|
|
task_graph = self.split_tasks(intent, user_request)
|
|
request.task_graph = task_graph
|
|
request.status = 'processing'
|
|
self.task_board.update_request(request)
|
|
|
|
# 5. 按执行顺序处理任务
|
|
execution_order = task_graph.get_execution_order()
|
|
completed = set()
|
|
results = {}
|
|
|
|
for layer_tasks in execution_order:
|
|
# 并行处理当前层的任务
|
|
layer_results = {}
|
|
|
|
for task in layer_tasks:
|
|
# 发布任务
|
|
capable_agents = self.publish_task(task)
|
|
|
|
if not capable_agents:
|
|
task.status = TaskStatus.FAILED
|
|
continue
|
|
|
|
# 收集竞标
|
|
bids = self.collect_bids(task, capable_agents)
|
|
|
|
if not bids:
|
|
task.status = TaskStatus.FAILED
|
|
continue
|
|
|
|
# 评估竞标
|
|
agent, bid, backup = self.evaluate_bids(task, bids)
|
|
|
|
if not agent:
|
|
task.status = TaskStatus.FAILED
|
|
continue
|
|
|
|
# 执行任务
|
|
execution = self.assign_and_execute(task, agent, bid, backup)
|
|
|
|
# 等待执行完成(简化:直接等待)
|
|
# 实际应该用异步等待或轮询
|
|
import time
|
|
max_wait = task.timeout + 10
|
|
waited = 0
|
|
while waited < max_wait:
|
|
exec_record = self.task_board.get_execution(execution.id)
|
|
if exec_record and exec_record.status in ['completed', 'failed', 'timeout']:
|
|
break
|
|
time.sleep(1)
|
|
waited += 1
|
|
|
|
# 获取结果
|
|
if task.status == TaskStatus.COMPLETED:
|
|
layer_results[task.id] = execution.result
|
|
completed.add(task.id)
|
|
else:
|
|
# 任务失败,整个请求失败
|
|
request.status = 'failed'
|
|
request.error = f"任务 {task.id} 执行失败"
|
|
self.task_board.update_request(request)
|
|
return request
|
|
|
|
results.update(layer_results)
|
|
|
|
# 6. 整合结果
|
|
request.final_result = results
|
|
request.status = 'completed'
|
|
self.task_board.update_request(request)
|
|
|
|
return request
|
|
|
|
except Exception as e:
|
|
request.status = 'failed'
|
|
request.error = str(e)
|
|
self.task_board.update_request(request)
|
|
return request
|
|
|
|
def get_request_status(self, request_id: str) -> Dict:
|
|
"""获取请求状态"""
|
|
request = self.task_board.get_request(request_id)
|
|
if request:
|
|
return request.to_dict()
|
|
return None |