ForcePilot/src/agents/chatbot/graph.py

144 lines
5.3 KiB
Python
Raw Normal View History

import os
2025-03-24 23:00:14 +08:00
import uuid
2025-05-16 23:46:25 +08:00
from pathlib import Path
from typing import Any, cast
2025-03-24 19:07:51 +08:00
from langchain_core.messages import AIMessage, ToolMessage
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver, aiosqlite
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-05-16 23:46:25 +08:00
from src import config as sys_config
from src.agents.common.base import BaseAgent
from src.agents.common.mcp import get_mcp_tools
from src.agents.common.models import load_chat_model
from src.utils import logger
from .context import Context
from .state import State
from .tools import get_tools
2025-03-24 19:07:51 +08:00
class ChatbotAgent(BaseAgent):
name = "智能体助手"
description = "基础的对话机器人,可以回答问题,默认不使用任何工具,可在配置中启用需要的工具。"
2025-03-24 23:00:14 +08:00
2025-03-28 11:40:46 +08:00
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.graph = None
self.context_schema = Context
self.workdir = Path(sys_config.save_dir) / "agents" / self.module_name
2025-05-16 23:46:25 +08:00
self.workdir.mkdir(parents=True, exist_ok=True)
self.agent_tools = None
def get_tools(self):
return get_tools()
2025-03-25 05:40:07 +08:00
async def _get_invoke_tools(self, selected_tools: list[str], selected_mcps: list[str]):
"""根据配置获取工具。
默认不使用任何工具
如果配置为列表则使用列表中的工具
"""
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:
# 使用配置中指定的工具
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
async def llm_call(self, state: State, runtime: Runtime[Context] = None) -> dict[str, Any]:
"""调用 llm 模型 - 异步版本以支持异步工具"""
model = load_chat_model(runtime.context.model)
2025-04-02 13:00:25 +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)
# 使用异步调用
response = cast(
AIMessage,
await model.ainvoke([{"role": "system", "content": runtime.context.system_prompt}, *state.messages]),
2025-03-29 17:33:09 +08:00
)
return {"messages": [response]}
async def dynamic_tools_node(self, state: State, runtime: Runtime[Context]) -> dict[str, list[ToolMessage]]:
"""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
available_tools = await self._get_invoke_tools(runtime.context.tools, runtime.context.mcps)
# 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
async def get_graph(self, **kwargs):
2025-03-24 23:00:14 +08:00
"""构建图"""
# if self.graph:
# return self.graph
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,
)
builder.add_edge("tools", "chatbot")
builder.add_edge("chatbot", END)
2025-03-25 05:40:07 +08:00
# 创建数据库连接并确保设置 checkpointer
try:
sqlite_checkpointer = AsyncSqliteSaver(await self.get_async_conn())
graph = builder.compile(checkpointer=sqlite_checkpointer, name=self.name)
self.graph = graph
return graph
except Exception as e:
logger.error(f"构建 Graph 设置 checkpointer 时出错: {e}, 尝试使用内存存储")
# 即使出错也返回一个可用的图实例,只是无法保存历史
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer, name=self.name)
self.graph = graph
return graph
2025-03-24 23:00:14 +08:00
2025-05-16 23:46:25 +08:00
async def get_async_conn(self) -> aiosqlite.Connection:
"""获取异步数据库连接"""
return await aiosqlite.connect(os.path.join(self.workdir, "aio_history.db"))
2025-05-16 23:46:25 +08:00
async def get_aio_memory(self) -> AsyncSqliteSaver:
"""获取异步存储实例"""
return AsyncSqliteSaver(await self.get_async_conn())
2025-03-24 23:00:14 +08:00
2025-03-24 23:00:14 +08:00
def main():
agent = ChatbotAgent(Context)
2025-03-24 19:07:51 +08:00
2025-03-24 23:00:14 +08:00
thread_id = str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}
2025-03-24 19:07:51 +08:00
2025-03-25 05:40:07 +08:00
from src.agents.utils import agent_cli
2025-03-24 23:00:14 +08:00
agent_cli(agent, config)
2025-03-24 19:07:51 +08:00
2025-03-24 23:00:14 +08:00
if __name__ == "__main__":
main()
# asyncio.run(main())