diff --git a/src/agents/chatbot/configuration.py b/src/agents/chatbot/configuration.py index af94792e..fbdb68bc 100644 --- a/src/agents/chatbot/configuration.py +++ b/src/agents/chatbot/configuration.py @@ -1,62 +1,36 @@ from dataclasses import dataclass, field -from datetime import datetime, timezone - -from langchain_community.tools.tavily_search import TavilySearchResults from src.agents.registry import Configuration -from src.agents.tools_factory import multiply, add, subtract, divide - - - -def get_default_tools(): - return ["TavilySearchResults", "multiply", "add", "subtract", "divide"] @dataclass(kw_only=True) class ChatbotConfiguration(Configuration): - """Chatbot 的配置""" + """Chatbot 的配置 + + 配置说明: + + metadata 中 configurable 为 True 的配置项可以被用户配置, + configurable 为 False 的配置项不能被用户配置,只能由开发者预设。 + """ system_prompt: str = field( - default=f"You are a helpful assistant. Now is {datetime.now(tz=timezone.utc).isoformat()}", + default="You are a helpful assistant.", metadata={ - "description": "The system prompt to use for the agent's interactions. " - "This prompt sets the context and behavior for the agent." + "name": "系统提示词", + "configurable": True, + "description": "用来描述智能体的角色和行为" }, ) model: str = field( default="zhipu/glm-4-plus", metadata={ - "description": "The name of the language model to use for the agent's main interactions. " - "Should be in the form: provider/model-name." + "name": "智能体模型", + "configurable": True, + "options": [ + "zhipu/glm-4-plus", + "siliconflow/Qwen/QwQ-32B", + "siliconflow/deepseek-ai/DeepSeek-V3", + ], + "description": "智能体的驱动模型" }, ) - - tools: list = field( - default_factory=get_default_tools, - metadata={ - "description": "The tools to use for the agent's interactions. " - "Should be in the form: provider/model-name." - }, - ) - - temperature: float = field( - default=0.7, - metadata={ - "description": "控制模型生成结果的随机性,值越大随机性越高,建议范围 0.0-1.0" - }, - ) - - use_tools: bool = field( - default=True, - metadata={ - "description": "是否启用工具调用功能" - }, - ) - - max_iterations: int = field( - default=10, - metadata={ - "description": "智能体最大执行步数,防止无限循环" - }, - ) - diff --git a/src/agents/chatbot/graph.py b/src/agents/chatbot/graph.py index e2df88fe..1d4162ce 100644 --- a/src/agents/chatbot/graph.py +++ b/src/agents/chatbot/graph.py @@ -1,7 +1,7 @@ import asyncio import uuid from typing import Any -from datetime import datetime +from datetime import datetime, timezone from langchain_core.runnables import RunnableConfig from langgraph.graph import StateGraph, START, END @@ -9,8 +9,9 @@ from langgraph.prebuilt import ToolNode, tools_condition from langgraph.checkpoint.memory import MemorySaver # 实际上没有起作用 +from src.utils import logger from src.agents.registry import State, BaseAgent -from src.agents.utils import load_chat_model +from src.agents.utils import load_chat_model, get_cur_time_with_utc from src.agents.chatbot.configuration import ChatbotConfiguration from src.agents.tools_factory import _TOOLS_REGISTRY @@ -18,27 +19,38 @@ class ChatbotAgent(BaseAgent): name = "chatbot" description = "A chatbot that can answer questions and help with tasks." requirements = ["TAVILY_API_KEY", "ZHIPUAI_API_KEY"] - _graph_cache = None + all_tools = ["TavilySearchResults", "multiply", "add", "subtract", "divide"] config_schema = ChatbotConfiguration def __init__(self, **kwargs): super().__init__(**kwargs) def _get_tools(self, config_schema: RunnableConfig): - """根据配置获取工具""" - default_tools_names = config_schema.get("tools", []) - default_tools = [_TOOLS_REGISTRY[tool] for tool in default_tools_names] - return default_tools + """根据配置获取工具,如果配置为空,则使用所有工具,如果配置为列表,则使用列表中的工具, + 如果配置为其他类型,则抛出错误""" + conf_tools = config_schema.get("tools") + if conf_tools == None: + tool_names = self.all_tools + elif isinstance(conf_tools, list): + tool_names = [tool for tool in self.all_tools if tool in conf_tools] + else: + raise ValueError(f"tools 配置错误: {conf_tools}") + + logger.info(f"Tools: {tool_names}") + return [_TOOLS_REGISTRY[tool] for tool in tool_names] def llm_call(self, state: State, config: RunnableConfig = None) -> dict[str, Any]: """调用 llm 模型""" config_schema = config or {} conf = self.config_schema.from_runnable_config(config_schema) - model = load_chat_model(conf.model, temperature=conf.temperature) + + system_prompt = f"{conf.system_prompt} Now is {get_cur_time_with_utc()}" + model = load_chat_model(conf.model) model_with_tools = model.bind_tools(self._get_tools(config_schema)) + logger.info(f"llm_call with config: {conf}, {conf.model}") res = model_with_tools.invoke( - [{"role": "system", "content": conf.system_prompt}, *state["messages"]] + [{"role": "system", "content": system_prompt}, *state["messages"]] ) return {"messages": [res]} diff --git a/src/agents/registry.py b/src/agents/registry.py index 7d3c89bf..dc974bd7 100644 --- a/src/agents/registry.py +++ b/src/agents/registry.py @@ -12,7 +12,7 @@ from langgraph.graph.state import CompiledStateGraph from langgraph.graph.message import add_messages from src.config import SimpleConfig - +from src.utils import logger class State(TypedDict): """ @@ -26,7 +26,7 @@ class State(TypedDict): @dataclass(kw_only=True) -class Configuration(SimpleConfig): +class Configuration(dict): """ 定义一个基础 Configuration 供 各类 graph 继承 """ @@ -44,7 +44,26 @@ class Configuration(SimpleConfig): def to_dict(cls): # 创建一个实例来处理 default_factory instance = cls() - return {f.name: getattr(instance, f.name) for f in fields(cls) if f.init} + confs = {} + configurable_items = {} + for f in fields(cls): + if f.init and not f.metadata.get("hide", False): + value = getattr(instance, f.name) + if callable(value) and hasattr(value, "__call__"): + confs[f.name] = value() + else: + confs[f.name] = value + + if f.metadata.get("configurable"): + configurable_items[f.name] = { + "type": f.type.__name__, + "name": f.metadata.get("name", f.name), + "options": f.metadata.get("options", []), + "default": f.default, + "description": f.metadata.get("description", ""), + } + confs["configurable_items"] = configurable_items + return confs @@ -69,6 +88,7 @@ class BaseAgent(): "description": cls.description, "config_schema": cls.config_schema.to_dict(), "requirements": cls.requirements if hasattr(cls, "requirements") else [], + "all_tools": cls.all_tools if hasattr(cls, "all_tools") else [], } def check_requirements(self): @@ -85,13 +105,8 @@ class BaseAgent(): def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs): graph = self.get_graph(config_schema=config_schema, **kwargs) - conf = self.config_schema.from_runnable_config(config_schema) for msg, metadata in graph.stream({"messages": messages}, stream_mode="messages", config=config_schema): - # msg_type = msg.type - # return_keys = conf.get("return_keys", []) - # if not return_keys or msg_type in return_keys: - # yield msg, metadata yield msg, metadata @abstractmethod diff --git a/src/agents/utils.py b/src/agents/utils.py index b9401860..1d08636d 100644 --- a/src/agents/utils.py +++ b/src/agents/utils.py @@ -1,3 +1,5 @@ +from datetime import datetime, timezone + from src.models import select_model from src.agents.registry import BaseAgent from langchain_core.language_models import BaseChatModel @@ -17,12 +19,6 @@ def load_chat_model(fully_specified_name: str, **kwargs) -> BaseChatModel: provider, model = fully_specified_name.split("/", maxsplit=1) model_instance = select_model(model_name=model, model_provider=provider) - # 配置额外参数,如temperature - if kwargs and hasattr(model_instance, 'chat_open_ai'): - for key, value in kwargs.items(): - if value is not None: - setattr(model_instance.chat_open_ai, key, value) - return model_instance.chat_open_ai @@ -58,3 +54,7 @@ def agent_cli(agent: BaseAgent, config: RunnableConfig = None): if isinstance(msg, ToolMessage): print(f"Tool: {msg.content}") + +def get_cur_time_with_utc(): + return datetime.now(tz=timezone.utc).isoformat() + diff --git a/src/config/__init__.py b/src/config/__init__.py index 372e6305..b9fc17e2 100644 --- a/src/config/__init__.py +++ b/src/config/__init__.py @@ -28,7 +28,7 @@ class SimpleConfig(dict): return self.get(self.__key(key)) def __getitem__(self, key): - return super().get(self.__key(key)) + return self.get(self.__key(key)) def __setitem__(self, key, value): return super().__setitem__(self.__key(key), value) diff --git a/src/routers/chat_router.py b/src/routers/chat_router.py index 3c7c6d25..d2d0becf 100644 --- a/src/routers/chat_router.py +++ b/src/routers/chat_router.py @@ -131,20 +131,22 @@ async def get_agent(): def chat_agent(agent_name: str, query: str = Body(...), history: list = Body(...), - config: dict = Body({})): + config: dict = Body({}), + meta: dict = Body({})): + + meta.update({ + "query": query, + "agent_name": agent_name, + "server_model_name": config["model"] , + "thread_id": config.get("thread_id"), + }) # 将meta和thread_id整合到config中 def make_chunk(content=None, **kwargs): - chat_metadata = { - "agent_name": agent_name, - "thread_id": config.get("thread_id"), - } - if update_metadata := kwargs.get("chat_metadata"): - chat_metadata.update(update_metadata) return json.dumps({ + "request_id": meta.get("request_id"), "response": content, - "chat_metadata": chat_metadata, **kwargs }, ensure_ascii=False).encode('utf-8') + b"\n" @@ -186,7 +188,9 @@ def chat_agent(agent_name: str, metadata=metadata, status="loading") - yield make_chunk(status="finished", history=history_manager.update_ai(content)) + yield make_chunk(status="finished", + history=history_manager.update_ai(content), + meta=meta) return StreamingResponse(stream_messages(), media_type='application/json') diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 5db16ca7..83c27765 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -26,15 +26,16 @@

{{ currentAgent ? currentAgent.description : '不同的智能体有不同的专长和能力' }}

-
+
-
{{ message }}
+
{{ message }}