[add] multi agent handoff

This commit is contained in:
Mark
2025-12-29 12:17:32 +08:00
parent 667c3393bc
commit de714d0422
8 changed files with 3555 additions and 1401 deletions

View File

@@ -0,0 +1,481 @@
"""动态 Handoff 工具创建器
展示如何在运行时动态创建 Agent 切换工具,并将其注入到 LLM 的工具列表中
"""
import json
from typing import Dict, Any, List, Optional, Callable
from pydantic import BaseModel, Field
from app.core.logging_config import get_business_logger
logger = get_business_logger()
class DynamicHandoffToolCreator:
"""动态 Handoff 工具创建器
核心功能:
1. 根据可用 Agent 动态生成工具定义
2. 将工具转换为 LLM 可理解的 schema
3. 处理工具调用并执行 handoff
"""
def __init__(self, current_agent_id: str, available_agents: Dict[str, Any]):
"""初始化工具创建器
Args:
current_agent_id: 当前 Agent ID
available_agents: 可用的 Agent 字典
"""
self.current_agent_id = current_agent_id
self.available_agents = available_agents
self.tools = []
self.tool_handlers = {}
# 动态创建工具
self._create_handoff_tools()
def _create_handoff_tools(self):
"""动态创建所有 handoff 工具"""
for agent_id, agent_data in self.available_agents.items():
if agent_id == self.current_agent_id:
continue # 不创建切换到自己的工具
# 创建工具
tool_def = self._create_single_tool(agent_id, agent_data)
self.tools.append(tool_def)
# 创建工具处理器
handler = self._create_tool_handler(agent_id, agent_data)
self.tool_handlers[tool_def["function"]["name"]] = handler
logger.info(
f"为 Agent {self.current_agent_id} 创建了 {len(self.tools)} 个 handoff 工具"
)
def _create_single_tool(self, target_agent_id: str, agent_data: Dict[str, Any]) -> Dict[str, Any]:
"""创建单个 handoff 工具定义
Args:
target_agent_id: 目标 Agent ID
agent_data: Agent 数据
Returns:
工具定义OpenAI function calling 格式)
"""
agent_info = agent_data.get("info", {})
name = agent_info.get("name", "未命名")
role = agent_info.get("role", "")
capabilities = agent_info.get("capabilities", [])
# 生成工具名称(符合函数命名规范)
tool_name = f"transfer_to_{self._sanitize_name(target_agent_id)}"
# 生成描述
description = f"切换到 {name}"
if role:
description += f"{role}"
if capabilities:
cap_str = "".join(capabilities[:3])
description += f"。擅长: {cap_str}"
description += "。当用户的问题更适合该 Agent 处理时调用此工具。"
# 构建工具定义OpenAI function calling 格式)
tool_def = {
"type": "function",
"function": {
"name": tool_name,
"description": description,
"parameters": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "为什么要切换到该 Agent请简要说明原因。"
},
"context_summary": {
"type": "string",
"description": "需要传递给目标 Agent 的上下文摘要(可选)。例如:之前的计算结果、用户的具体需求等。"
}
},
"required": ["reason"]
}
}
}
return tool_def
def _create_tool_handler(
self,
target_agent_id: str,
agent_data: Dict[str, Any]
) -> Callable:
"""创建工具处理器函数
Args:
target_agent_id: 目标 Agent ID
agent_data: Agent 数据
Returns:
工具处理器函数
"""
def handler(reason: str, context_summary: Optional[str] = None) -> Dict[str, Any]:
"""处理 handoff 工具调用
Args:
reason: 切换原因
context_summary: 上下文摘要
Returns:
Handoff 请求
"""
agent_info = agent_data.get("info", {})
logger.info(
f"Handoff 工具被调用: {self.current_agent_id}{target_agent_id}",
extra={
"reason": reason,
"has_context": bool(context_summary)
}
)
return {
"type": "handoff",
"target_agent_id": target_agent_id,
"target_agent_name": agent_info.get("name", ""),
"reason": reason,
"context_summary": context_summary,
"from_agent_id": self.current_agent_id
}
return handler
def _sanitize_name(self, name: str) -> str:
"""清理名称,使其符合函数命名规范
Args:
name: 原始名称
Returns:
清理后的名称
"""
# 替换特殊字符为下划线
sanitized = name.replace("-", "_").replace(" ", "_")
# 移除其他非法字符
sanitized = "".join(c for c in sanitized if c.isalnum() or c == "_")
return sanitized.lower()
def get_tools_for_llm(self) -> List[Dict[str, Any]]:
"""获取用于 LLM 的工具列表
Returns:
工具定义列表OpenAI function calling 格式)
"""
return self.tools
def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""处理 LLM 的工具调用
Args:
tool_name: 工具名称
arguments: 工具参数
Returns:
Handoff 请求或 None
"""
handler = self.tool_handlers.get(tool_name)
if not handler:
logger.warning(f"未找到工具处理器: {tool_name}")
return None
try:
return handler(**arguments)
except Exception as e:
logger.error(f"工具调用失败: {tool_name}, 错误: {str(e)}")
return None
def get_tool_names(self) -> List[str]:
"""获取所有工具名称
Returns:
工具名称列表
"""
return [tool["function"]["name"] for tool in self.tools]
# ==================== 使用示例 ====================
def example_usage():
"""展示如何使用动态工具创建器"""
# 1. 准备可用的 Agent 信息
available_agents = {
"math-agent-uuid": {
"info": {
"name": "数学助手",
"role": "数学专家",
"capabilities": ["数学计算", "方程求解", "几何问题"]
}
},
"creative-agent-uuid": {
"info": {
"name": "创意助手",
"role": "创意专家",
"capabilities": ["写作", "诗歌", "故事创作"]
}
},
"code-agent-uuid": {
"info": {
"name": "代码助手",
"role": "编程专家",
"capabilities": ["代码编写", "调试", "代码审查"]
}
}
}
# 2. 为当前 Agent 创建工具
current_agent_id = "general-agent-uuid"
tool_creator = DynamicHandoffToolCreator(current_agent_id, available_agents)
# 3. 获取工具定义(用于 LLM
tools = tool_creator.get_tools_for_llm()
print("=" * 60)
print("动态创建的 Handoff 工具:")
print("=" * 60)
for tool in tools:
print(f"\n工具名称: {tool['function']['name']}")
print(f"描述: {tool['function']['description']}")
print(f"参数: {json.dumps(tool['function']['parameters'], indent=2, ensure_ascii=False)}")
# 4. 模拟 LLM 调用工具
print("\n" + "=" * 60)
print("模拟 LLM 工具调用:")
print("=" * 60)
# LLM 决定切换到数学 Agent
tool_call = {
"name": "transfer_to_math_agent_uuid",
"arguments": {
"reason": "用户问题涉及数学计算",
"context_summary": "用户想解方程 x^2 + 5x + 6 = 0"
}
}
print(f"\nLLM 调用: {tool_call['name']}")
print(f"参数: {json.dumps(tool_call['arguments'], indent=2, ensure_ascii=False)}")
# 5. 处理工具调用
handoff_request = tool_creator.handle_tool_call(
tool_call["name"],
tool_call["arguments"]
)
if handoff_request:
print(f"\n✓ Handoff 请求已创建:")
print(f" 类型: {handoff_request['type']}")
print(f" 从: {handoff_request['from_agent_id']}")
print(f" 到: {handoff_request['target_agent_id']} ({handoff_request['target_agent_name']})")
print(f" 原因: {handoff_request['reason']}")
print(f" 上下文: {handoff_request['context_summary']}")
# ==================== 与 LLM 集成示例 ====================
async def integrate_with_llm_example():
"""展示如何将动态工具集成到 LLM 调用中"""
from app.core.models import RedBearLLM
from app.core.models.base import RedBearModelConfig
# 1. 准备 Agent 信息
available_agents = {
"math-agent": {
"info": {
"name": "数学助手",
"role": "数学专家",
"capabilities": ["计算", "方程"]
}
}
}
# 2. 创建工具
tool_creator = DynamicHandoffToolCreator("general-agent", available_agents)
tools = tool_creator.get_tools_for_llm()
# 3. 构建 LLM 配置
model_config = RedBearModelConfig(
model_name="gpt-4",
provider="openai",
api_key="your-api-key",
extra_params={
"temperature": 0.7,
"tools": tools, # 注入动态创建的工具
"tool_choice": "auto" # 让 LLM 自动决定是否调用工具
}
)
# 4. 创建 LLM 实例
llm = RedBearLLM(model_config)
# 5. 构建消息(包含系统提示)
system_prompt = """你是一个智能助手。当用户的问题超出你的能力范围时,你可以使用工具切换到专业的 Agent。
可用的切换工具:
- transfer_to_math_agent: 当遇到数学问题时使用
请根据用户问题判断是否需要切换。"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": "帮我计算 3*6+15 的结果"}
]
# 6. 调用 LLM
response = await llm.ainvoke(messages)
# 7. 检查是否有工具调用
if hasattr(response, 'tool_calls') and response.tool_calls:
for tool_call in response.tool_calls:
print(f"\n✓ LLM 调用了工具: {tool_call.name}")
print(f" 参数: {tool_call.arguments}")
# 处理工具调用
handoff_request = tool_creator.handle_tool_call(
tool_call.name,
tool_call.arguments
)
if handoff_request:
print(f"\n✓ 执行 Handoff:")
print(f" 切换到: {handoff_request['target_agent_name']}")
print(f" 原因: {handoff_request['reason']}")
# 这里可以执行实际的 Agent 切换逻辑
# await execute_handoff(handoff_request)
else:
print(f"\n✓ LLM 直接回复: {response.content}")
# ==================== 完整的 Agent 执行流程 ====================
class AgentExecutorWithHandoffs:
"""支持 Handoffs 的 Agent 执行器"""
def __init__(self, agent_id: str, agent_config: Any, available_agents: Dict[str, Any]):
"""初始化执行器
Args:
agent_id: 当前 Agent ID
agent_config: Agent 配置
available_agents: 可用的其他 Agent
"""
self.agent_id = agent_id
self.agent_config = agent_config
self.available_agents = available_agents
# 创建工具创建器
self.tool_creator = DynamicHandoffToolCreator(agent_id, available_agents)
async def execute(
self,
message: str,
conversation_history: List[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""执行 Agent支持 handoff
Args:
message: 用户消息
conversation_history: 会话历史
Returns:
执行结果,可能包含 handoff_request
"""
from app.core.models import RedBearLLM
from app.core.models.base import RedBearModelConfig
# 1. 获取动态工具
tools = self.tool_creator.get_tools_for_llm()
# 2. 构建系统提示(包含工具说明)
system_prompt = self._build_system_prompt_with_tools()
# 3. 构建消息
messages = [{"role": "system", "content": system_prompt}]
# 添加历史消息
if conversation_history:
messages.extend(conversation_history)
# 添加当前消息
messages.append({"role": "user", "content": message})
# 4. 配置 LLM注入工具
model_config = RedBearModelConfig(
model_name=self.agent_config.model_name,
provider=self.agent_config.provider,
api_key=self.agent_config.api_key,
extra_params={
"temperature": 0.7,
"tools": tools, # 动态工具
"tool_choice": "auto"
}
)
llm = RedBearLLM(model_config)
# 5. 调用 LLM
response = await llm.ainvoke(messages)
# 6. 处理响应
result = {
"message": response.content if hasattr(response, 'content') else str(response),
"agent_id": self.agent_id
}
# 7. 检查工具调用
if hasattr(response, 'tool_calls') and response.tool_calls:
for tool_call in response.tool_calls:
# 检查是否是 handoff 工具
if tool_call.name in self.tool_creator.get_tool_names():
# 处理 handoff
handoff_request = self.tool_creator.handle_tool_call(
tool_call.name,
tool_call.arguments
)
if handoff_request:
result["handoff_request"] = handoff_request
result["is_final_answer"] = False
break
else:
# 处理其他业务工具
pass
return result
def _build_system_prompt_with_tools(self) -> str:
"""构建包含工具说明的系统提示"""
base_prompt = self.agent_config.system_prompt or "你是一个智能助手。"
# 添加工具说明
tool_names = self.tool_creator.get_tool_names()
if tool_names:
tools_desc = "\n\n你可以使用以下工具切换到专业的 Agent\n"
for tool in self.tool_creator.get_tools_for_llm():
tools_desc += f"- {tool['function']['name']}: {tool['function']['description']}\n"
tools_desc += "\n当用户的问题超出你的能力范围时,请使用相应的工具切换到专业 Agent。"
return base_prompt + tools_desc
return base_prompt
if __name__ == "__main__":
# 运行示例
print("\n🚀 动态 Handoff 工具创建示例\n")
example_usage()
print("\n\n" + "=" * 60)
print("完成!查看上面的输出了解工具创建流程。")
print("=" * 60)