2025-09-01 22:37:03 +08:00
|
|
|
|
from typing import Any, cast
|
2025-03-24 19:07:51 +08:00
|
|
|
|
|
2025-10-23 01:39:01 +08:00
|
|
|
|
from langchain.messages import AIMessage, ToolMessage
|
2025-09-01 22:37:03 +08:00
|
|
|
|
from langgraph.graph import END, START, StateGraph
|
|
|
|
|
|
from langgraph.prebuilt import ToolNode, tools_condition
|
|
|
|
|
|
from langgraph.runtime import Runtime
|
2025-03-24 19:07:51 +08:00
|
|
|
|
|
2025-08-31 00:34:26 +08:00
|
|
|
|
from src.agents.common.base import BaseAgent
|
2025-09-01 03:38:46 +08:00
|
|
|
|
from src.agents.common.mcp import get_mcp_tools
|
2025-09-01 22:37:03 +08:00
|
|
|
|
from src.agents.common.models import load_chat_model
|
|
|
|
|
|
from src.utils import logger
|
2025-09-01 03:38:46 +08:00
|
|
|
|
|
2025-08-31 00:34:26 +08:00
|
|
|
|
from .context import Context
|
2025-09-01 22:37:03 +08:00
|
|
|
|
from .state import State
|
2025-08-31 00:34:26 +08:00
|
|
|
|
from .tools import get_tools
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-24 19:07:51 +08:00
|
|
|
|
class ChatbotAgent(BaseAgent):
|
2025-08-11 21:29:33 +08:00
|
|
|
|
name = "智能体助手"
|
2025-04-05 17:27:52 +08:00
|
|
|
|
description = "基础的对话机器人,可以回答问题,默认不使用任何工具,可在配置中启用需要的工具。"
|
2025-03-24 23:00:14 +08:00
|
|
|
|
|
2025-03-28 11:40:46 +08:00
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
|
|
super().__init__(**kwargs)
|
2025-05-15 22:34:32 +08:00
|
|
|
|
self.graph = None
|
2025-10-12 16:04:46 +08:00
|
|
|
|
self.checkpointer = None
|
2025-08-31 00:34:26 +08:00
|
|
|
|
self.context_schema = Context
|
2025-09-01 03:38:46 +08:00
|
|
|
|
self.agent_tools = None
|
|
|
|
|
|
|
|
|
|
|
|
def get_tools(self):
|
|
|
|
|
|
return get_tools()
|
2025-03-25 05:40:07 +08:00
|
|
|
|
|
2025-09-01 03:38:46 +08:00
|
|
|
|
async def _get_invoke_tools(self, selected_tools: list[str], selected_mcps: list[str]):
|
2025-04-05 17:27:52 +08:00
|
|
|
|
"""根据配置获取工具。
|
|
|
|
|
|
默认不使用任何工具。
|
|
|
|
|
|
如果配置为列表,则使用列表中的工具。
|
|
|
|
|
|
"""
|
2025-09-01 03:38:46 +08:00
|
|
|
|
enabled_tools = []
|
|
|
|
|
|
self.agent_tools = self.agent_tools or self.get_tools()
|
|
|
|
|
|
if selected_tools and isinstance(selected_tools, list) and len(selected_tools) > 0:
|
2025-04-05 17:27:52 +08:00
|
|
|
|
# 使用配置中指定的工具
|
2025-09-01 03:38:46 +08:00
|
|
|
|
enabled_tools = [tool for tool in self.agent_tools if tool.name in selected_tools]
|
|
|
|
|
|
|
|
|
|
|
|
if selected_mcps and isinstance(selected_mcps, list) and len(selected_mcps) > 0:
|
|
|
|
|
|
for mcp in selected_mcps:
|
|
|
|
|
|
enabled_tools.extend(await get_mcp_tools(mcp))
|
|
|
|
|
|
|
|
|
|
|
|
return enabled_tools
|
2025-03-25 05:40:07 +08:00
|
|
|
|
|
2025-08-31 00:34:26 +08:00
|
|
|
|
async def llm_call(self, state: State, runtime: Runtime[Context] = None) -> dict[str, Any]:
|
2025-06-27 01:49:31 +08:00
|
|
|
|
"""调用 llm 模型 - 异步版本以支持异步工具"""
|
2025-08-31 00:34:26 +08:00
|
|
|
|
model = load_chat_model(runtime.context.model)
|
2025-04-02 13:00:25 +08:00
|
|
|
|
|
2025-08-31 00:34:26 +08:00
|
|
|
|
# 这里要根据配置动态获取工具
|
2025-09-01 03:38:46 +08:00
|
|
|
|
available_tools = await self._get_invoke_tools(runtime.context.tools, runtime.context.mcps)
|
|
|
|
|
|
logger.info(f"LLM binded ({len(available_tools)}) available_tools: {[tool.name for tool in available_tools]}")
|
|
|
|
|
|
|
|
|
|
|
|
if available_tools:
|
|
|
|
|
|
model = model.bind_tools(available_tools)
|
2025-06-24 00:13:11 +08:00
|
|
|
|
|
2025-06-27 01:49:31 +08:00
|
|
|
|
# 使用异步调用
|
2025-08-31 00:34:26 +08:00
|
|
|
|
response = cast(
|
|
|
|
|
|
AIMessage,
|
2025-09-01 22:37:03 +08:00
|
|
|
|
await model.ainvoke([{"role": "system", "content": runtime.context.system_prompt}, *state.messages]),
|
2025-03-29 17:33:09 +08:00
|
|
|
|
)
|
2025-08-31 00:34:26 +08:00
|
|
|
|
return {"messages": [response]}
|
|
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
async def dynamic_tools_node(self, state: State, runtime: Runtime[Context]) -> dict[str, list[ToolMessage]]:
|
2025-08-31 00:34:26 +08:00
|
|
|
|
"""Execute tools dynamically based on configuration.
|
|
|
|
|
|
|
|
|
|
|
|
This function gets the available tools based on the current configuration
|
|
|
|
|
|
and executes the requested tool calls from the last message.
|
|
|
|
|
|
"""
|
|
|
|
|
|
# Get available tools based on configuration
|
2025-09-01 03:38:46 +08:00
|
|
|
|
available_tools = await self._get_invoke_tools(runtime.context.tools, runtime.context.mcps)
|
2025-08-31 00:34:26 +08:00
|
|
|
|
|
|
|
|
|
|
# Create a ToolNode with the available tools
|
|
|
|
|
|
tool_node = ToolNode(available_tools)
|
|
|
|
|
|
|
|
|
|
|
|
# Execute the tool node
|
|
|
|
|
|
result = await tool_node.ainvoke(state)
|
|
|
|
|
|
|
|
|
|
|
|
return cast(dict[str, list[ToolMessage]], result)
|
2025-03-24 23:00:14 +08:00
|
|
|
|
|
2025-08-31 00:34:26 +08:00
|
|
|
|
async def get_graph(self, **kwargs):
|
2025-03-24 23:00:14 +08:00
|
|
|
|
"""构建图"""
|
2025-10-11 21:10:39 +08:00
|
|
|
|
if self.graph:
|
|
|
|
|
|
return self.graph
|
2025-07-26 03:36:54 +08:00
|
|
|
|
|
2025-08-31 00:34:26 +08:00
|
|
|
|
builder = StateGraph(State, context_schema=self.context_schema)
|
|
|
|
|
|
builder.add_node("chatbot", self.llm_call)
|
|
|
|
|
|
builder.add_node("tools", self.dynamic_tools_node)
|
|
|
|
|
|
builder.add_edge(START, "chatbot")
|
|
|
|
|
|
builder.add_conditional_edges(
|
2025-03-25 05:40:07 +08:00
|
|
|
|
"chatbot",
|
|
|
|
|
|
tools_condition,
|
|
|
|
|
|
)
|
2025-08-31 00:34:26 +08:00
|
|
|
|
builder.add_edge("tools", "chatbot")
|
|
|
|
|
|
builder.add_edge("chatbot", END)
|
2025-03-25 05:40:07 +08:00
|
|
|
|
|
2025-10-12 16:04:46 +08:00
|
|
|
|
self.checkpointer = await self._get_checkpointer()
|
2025-10-11 21:10:39 +08:00
|
|
|
|
graph = builder.compile(checkpointer=self.checkpointer, name=self.name)
|
|
|
|
|
|
self.graph = graph
|
|
|
|
|
|
return graph
|
2025-03-24 23:00:14 +08:00
|
|
|
|
|
2025-09-01 22:37:03 +08:00
|
|
|
|
|
2025-03-24 23:00:14 +08:00
|
|
|
|
def main():
|
2025-10-12 16:04:46 +08:00
|
|
|
|
pass
|
2025-03-24 19:07:51 +08:00
|
|
|
|
|
2025-10-12 23:45:17 +08:00
|
|
|
|
|
2025-03-24 23:00:14 +08:00
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|
|
|
|
|
|
# asyncio.run(main())
|