1+ from abc import abstractmethod
2+ from typing import Any , cast
3+
4+ from langchain .messages import AIMessage , ToolMessage
5+ from langgraph .prebuilt import ToolNode
6+ from langgraph .runtime import Runtime
7+
8+ from src .agents .common .base import BaseAgent
9+ from src .agents .common .mcp import get_mcp_tools
10+ from src .agents .common .models import load_chat_model
11+ from src .utils import logger
12+
13+ from .state import BaseState
14+ from .context import BaseContext
15+
16+ class ToolAgent (BaseAgent ):
17+ name = "ToolAgent"
18+ description = "具有工具调用能力的Agent"
19+
20+ def __init__ (self , ** kwargs ):
21+ super ().__init__ (** kwargs )
22+ self .graph = None
23+ self .checkpointer = None
24+ self .context_schema = BaseContext
25+ self .agent_tools = None
26+
27+
28+ # TODO:[修改建议] _get_invoke_tools,llm_call,dynamic_tools_node这类针对工具调用的功能大多数Agent都能用得到
29+ # 可以通过一个ToolAgent类继承BaseAgent,通过重写抽象方法获取tools,通过继承BaseState和BaseContext获取配置
30+ # 必要时可通过重写以下方法实现其他逻辑
31+ @abstractmethod
32+ def get_tools (self ):
33+ logger .error (f"get_tools() is not implemented in { self .__class__ .__name__ } " )
34+ return []
35+
36+
37+ async def _get_invoke_tools (self , selected_tools : list [str ], selected_mcps : list [str ]):
38+ """根据配置获取工具。
39+ 默认不使用任何工具。
40+ 如果配置为列表,则使用列表中的工具。
41+ """
42+ enabled_tools = []
43+ self .agent_tools = self .agent_tools or self .get_tools ()
44+ if selected_tools and isinstance (selected_tools , list ) and len (selected_tools ) > 0 :
45+ # 使用配置中指定的工具
46+ enabled_tools = [tool for tool in self .agent_tools if tool .name in selected_tools ]
47+
48+ if selected_mcps and isinstance (selected_mcps , list ) and len (selected_mcps ) > 0 :
49+ for mcp in selected_mcps :
50+ enabled_tools .extend (await get_mcp_tools (mcp ))
51+
52+ return enabled_tools
53+
54+ async def llm_call (self , state : BaseState , runtime : Runtime [BaseContext ] = None ) -> dict [str , Any ]:
55+ """调用 llm 模型 - 异步版本以支持异步工具"""
56+ model = load_chat_model (runtime .context .model )
57+
58+ # 这里要根据配置动态获取工具
59+ available_tools = await self ._get_invoke_tools (runtime .context .tools , runtime .context .mcps )
60+ logger .info (f"LLM binded ({ len (available_tools )} ) available_tools: { [tool .name for tool in available_tools ]} " )
61+
62+ if available_tools :
63+ model = model .bind_tools (available_tools )
64+
65+ # 使用异步调用
66+ response = cast (
67+ AIMessage ,
68+ await model .ainvoke ([{"role" : "system" , "content" : runtime .context .system_prompt }, * state .messages ]),
69+ )
70+ return {"messages" : [response ]}
71+
72+ async def dynamic_tools_node (self , state : BaseState , runtime : Runtime [BaseContext ]) -> dict [str , list [ToolMessage ]]:
73+ """Execute tools dynamically based on configuration.
74+
75+ This function gets the available tools based on the current configuration
76+ and executes the requested tool calls from the last message.
77+ """
78+ # Get available tools based on configuration
79+ available_tools = await self ._get_invoke_tools (runtime .context .tools , runtime .context .mcps )
80+
81+ # Create a ToolNode with the available tools
82+ tool_node = ToolNode (available_tools )
83+
84+ # Execute the tool node
85+ result = await tool_node .ainvoke (state )
86+
87+ return cast (dict [str , list [ToolMessage ]], result )
0 commit comments