From 2e6459a5248ec3e4bb613c15c628f90fb6e06226 Mon Sep 17 00:00:00 2001 From: shenyue_ustc Date: Mon, 29 Sep 2025 16:57:47 +0800 Subject: [PATCH 1/3] add client --- src/client/DockerRegistry.py | 187 ++++++ src/client/MCPClient.py | 584 ++++++++++++++++++ src/client/Session.py | 148 +++++ .../DockerRegistry.cpython-311.pyc | Bin 0 -> 10715 bytes .../__pycache__/MCPClient.cpython-311.pyc | Bin 0 -> 29073 bytes .../__pycache__/Session.cpython-311.pyc | Bin 0 -> 8379 bytes 6 files changed, 919 insertions(+) create mode 100644 src/client/DockerRegistry.py create mode 100644 src/client/MCPClient.py create mode 100644 src/client/Session.py create mode 100644 src/client/__pycache__/DockerRegistry.cpython-311.pyc create mode 100644 src/client/__pycache__/MCPClient.cpython-311.pyc create mode 100644 src/client/__pycache__/Session.cpython-311.pyc diff --git a/src/client/DockerRegistry.py b/src/client/DockerRegistry.py new file mode 100644 index 0000000..5eb0506 --- /dev/null +++ b/src/client/DockerRegistry.py @@ -0,0 +1,187 @@ +import logging +import asyncio +import atexit +from typing import Set +import signal +import subprocess +import sys +import os + +class DockerContainerRegistry: + """全局Docker容器注册表,确保程序退出时清理所有容器""" + _instance = None + _containers: Set[str] = set() + _cleanup_lock = asyncio.Lock() + _initialized = False + _cleanup_in_progress = False # 添加清理状态标志 + _signal_count = 0 # 信号计数器 + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + @classmethod + def initialize(cls): + """初始化全局清理机制""" + if cls._initialized: + return + + instance = cls() + + atexit.register(instance._sync_cleanup_all) + + def signal_handler(signum, frame): + instance._handle_signal(signum) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + cls._initialized = True + logging.info("Docker容器注册表已初始化") + + def _handle_signal(self, signum): + """处理信号,避免重复清理""" + self._signal_count += 1 + + if self._cleanup_in_progress: + if self._signal_count <= 2: + logging.info(f"清理正在进行中,请稍候... (信号计数: {self._signal_count})") + return + elif self._signal_count <= 5: + logging.warning(f"强制中断清理过程... (信号计数: {self._signal_count})") + return + else: + logging.error("多次中断信号,强制退出程序") + os._exit(1) + + logging.info(f"接收到信号 {signum},开始清理Docker容器...") + self._cleanup_in_progress = True + + try: + self._sync_cleanup_all() + except Exception as e: + logging.error(f"清理过程中出错: {e}") + finally: + logging.info("程序退出") + sys.exit(0) + + def register_container(self, container_name: str): + """注册容器""" + self._containers.add(container_name) + logging.debug(f"注册Docker容器: {container_name}") + + def unregister_container(self, container_name: str): + """注销容器""" + self._containers.discard(container_name) + logging.debug(f"注销Docker容器: {container_name}") + + def _sync_cleanup_all(self): + """同步清理所有注册的容器""" + if not self._containers or self._cleanup_in_progress: + return + + self._cleanup_in_progress = True + + try: + logging.info(f"开始清理 {len(self._containers)} 个Docker容器...") + containers_to_clean = self._containers.copy() + + for container_name in containers_to_clean: + try: + result = subprocess.run( + ["docker", "kill", container_name], + capture_output=True, + text=True, + timeout=3 # 减少超时时间 + ) + if result.returncode == 0: + logging.info(f"成功清理容器: {container_name}") + self._containers.discard(container_name) + else: + logging.warning(f"清理容器失败: {container_name}, {result.stderr}") + except subprocess.TimeoutExpired: + logging.warning(f"清理容器 {container_name} 超时,跳过") + except Exception as e: + logging.error(f"清理容器 {container_name} 出错: {e}") + + # 如果还有容器未清理,尝试强制清理 + if self._containers: + logging.info("尝试强制清理剩余容器...") + for container_name in list(self._containers): + try: + subprocess.run( + ["docker", "rm", "-f", container_name], + capture_output=True, + text=True, + timeout=2 + ) + self._containers.discard(container_name) + logging.info(f"强制清理容器: {container_name}") + except Exception as e: + logging.error(f"强制清理容器 {container_name} 失败: {e}") + + logging.info("Docker容器清理完成") + + finally: + self._cleanup_in_progress = False + + async def async_cleanup_all(self): + """异步清理所有容器""" + if not self._containers: + return + + async with self._cleanup_lock: + if self._cleanup_in_progress: + return + + self._cleanup_in_progress = True + + try: + containers_to_clean = list(self._containers) + + # 并发清理所有容器,但添加超时限制 + tasks = [] + for container_name in containers_to_clean: + task = asyncio.create_task(self._async_kill_container(container_name)) + tasks.append(task) + + if tasks: + # 设置总体超时时间 + try: + results = await asyncio.wait_for( + asyncio.gather(*tasks, return_exceptions=True), + timeout=10 # 总体超时10秒 + ) + for container_name, result in zip(containers_to_clean, results): + if isinstance(result, Exception): + logging.error(f"异步清理容器 {container_name} 失败: {result}") + else: + self.unregister_container(container_name) + except asyncio.TimeoutError: + logging.warning("异步清理容器超时") + finally: + self._cleanup_in_progress = False + + async def _async_kill_container(self, container_name: str): + """异步终止单个容器""" + loop = asyncio.get_event_loop() + try: + result = await loop.run_in_executor( + None, + lambda: subprocess.run( + ["docker", "kill", container_name], + capture_output=True, + text=True, + timeout=5 # 减少单个容器的超时时间 + ) + ) + if result.returncode == 0: + logging.info(f"异步清理容器成功: {container_name}") + return True + else: + logging.warning(f"异步清理容器失败: {container_name}, {result.stderr}") + return False + except Exception as e: + logging.error(f"异步清理容器 {container_name} 出错: {e}") + return False \ No newline at end of file diff --git a/src/client/MCPClient.py b/src/client/MCPClient.py new file mode 100644 index 0000000..b46ba64 --- /dev/null +++ b/src/client/MCPClient.py @@ -0,0 +1,584 @@ +import asyncio +import sys +import logging +import subprocess +import os +import time +import shutil +import docker +from docker.errors import NotFound, APIError +from contextlib import AsyncExitStack +from typing import Any, Optional +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client +from ..utils.parse_json import parse_evaluation_json +from .DockerRegistry import DockerContainerRegistry + +class MCPClient: + """改进的MCP服务器管理器,支持可靠的Docker生命周期管理""" + + def __init__(self, name: str, config: dict[str, Any], env_script: str = "", use_docker: bool = False) -> None: + self.name: str = name + self.config: dict[str, Any] = config + self.session: Optional[ClientSession] = None + self._cleanup_lock: asyncio.Lock = asyncio.Lock() + self.exit_stack: AsyncExitStack = AsyncExitStack() + + self.env_script = env_script + + + # 状态管理 + self._is_initialized = False + self._is_cleaning_up = False + self._cleanup_completed = asyncio.Event() + + # Docker相关配置 + self.use_docker = use_docker + self.abs_script_path = self.get_command_script_path() + self.host_mcp_path = self.abs_script_path.split('src')[0] if self.abs_script_path else "" + self.container_mcp_path = "/app/" + self.server_port = config.get("port", 8080) + + # Docker进程管理 + self.docker_process = None + self.container_id = None + self.container_name = None + + self.client = docker.from_env() + + # 初始化全局容器注册表 + if use_docker: + DockerContainerRegistry.initialize() + self._registry = DockerContainerRegistry() + + def get_command_script_path(self) -> str: + """获取命令脚本路径""" + try: + server_args = self.config['args'] + source_file = None + work_dir = os.getcwd() + for i, arg in enumerate(server_args): + if arg == "--directory" and i + 1 < len(server_args): + work_dir = server_args[i + 1] + work_dir = os.path.abspath(work_dir) + break + + for arg in server_args: + if arg.endswith(".py"): + source_file = arg + break + + if not source_file: + logging.warning("未在 args 中找到 .py 源代码文件") + return None + + if os.path.isabs(source_file): + absolute_path = source_file + else: + absolute_path = os.path.join(work_dir, source_file) + + if os.path.exists(absolute_path): + return absolute_path + else: + logging.error(f"源代码文件不存在:{absolute_path}") + return None + except Exception as e: + logging.error(f"获取脚本路径出错: {e}") + return None + + async def initialize(self) -> None: + """初始化服务器""" + if self._is_initialized: + logging.warning(f"服务器 {self.name} 已经初始化") + return + + try: + logging.info(f"开始初始化服务器 {self.name}") + + if self.use_docker: + await self._initialize_docker() + else: + await self._initialize_host_server() + + self._is_initialized = True + + except Exception as e: + logging.error(f"初始化失败: {e}") + await self.cleanup() + raise + + async def _initialize_host_server(self) -> None: + """在主机上启动MCP服务器""" + command = shutil.which("npx") if self.config["command"] == "npx" else self.config["command"] + if command is None: + raise ValueError(f"主机命令不存在: {self.config['command']}") + + server_params = StdioServerParameters( + command=command, + args=self.config["args"], + env={**os.environ, **self.config["env"]} if self.config.get("env") else None, + ) + + try: + stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) + read, write = stdio_transport + session = await self.exit_stack.enter_async_context(ClientSession(read, write)) + await session.initialize() + self.session = session + logging.info(f"主机上的MCP服务器 {self.name} 已初始化") + except Exception as e: + logging.error(f"主机服务器初始化失败: {e}") + raise + + def _build_docker_command(self) -> list[str]: + """构建Docker运行命令""" + self.container_name = f"mcp-server-{self.name}-{int(time.time())}" + + docker_cmd = [ + "docker", "run", + "--rm", + "-i", + "--name", self.container_name, + "--workdir", self.container_mcp_path, + ] + + # 挂载主机MCP代码目录到容器 + docker_cmd.extend([ + "-v", f"{self.host_mcp_path}:{self.container_mcp_path}" + ]) + + # 添加环境变量 + env_vars = { + "PYTHONPATH": self.container_mcp_path, + "PYTHONUNBUFFERED": "1", + "PIP_ROOT_USER_ACTION": "ignore", + } + env_vars.update(self.config.get("env", {})) + + for key, value in env_vars.items(): + docker_cmd.extend(["-e", f"{key}={value}"]) + + docker_cmd.extend(["-a", "stdout", "-a", "stderr"]) + + # 添加Docker镜像 + self.docker_image = "val:latest" + docker_cmd.append(self.docker_image) + + startup_script = self._build_correct_bash_script() + docker_cmd.extend(["bash", "-c", startup_script]) + + return docker_cmd + + def _build_correct_bash_script(self) -> str: + """构建启动脚本""" + container_command = self._get_container_command() + script = f'''set -e + + echo "=== 快速环境检查 ===" + echo "Python版本: $(python --version)" + echo "工作目录: $(pwd)" + echo "文件列表:" + ls -la + echo "" + + # 只安装项目特定的新依赖 + echo "=== 检查并安装项目依赖 ===" + if [ -f requirements.txt ]; then + echo "发现requirements.txt文件" + pip install -qq --upgrade-strategy only-if-needed -r requirements.txt + if [ $? -eq 0 ]; then + echo "依赖安装完成(无异常)" + else + echo "依赖安装失败!(可去掉 -qq 参数重新执行查看详细错误)" + fi + else + echo "未找到requirements.txt文件" + fi + + echo "=== 执行自定义环境部署 ===" + {self.env_script} + + echo "=== 启动MCP服务器 ===" + echo "执行命令: {container_command}" + exec {container_command}''' + + return script + + def _get_container_command(self) -> str: + """获取容器内的命令字符串""" + command = self.config.get("command", "python") + if command == "uv": + command = "uv run" + script_rel_path = 'src'+self.abs_script_path.split('src')[-1] + return command + " " + script_rel_path + + + async def _initialize_docker(self): + """初始化Docker中的MCP服务器,支持输出显示""" + original_docker_command = self._build_docker_command() + + # 修改Docker命令,使用tee同时输出到终端和MCP + docker_command = self._add_output_redirection(original_docker_command) + + logging.info(f"启动Docker命令: {' '.join(docker_command)}") + + # 注册容器到全局注册表 + if self.container_name: + self._registry.register_container(self.container_name) + + # 清理可能存在的同名容器 + await self._cleanup_existing_container() + + # 使用修改后的命令建立MCP连接(只启动一个进程) + server_params = StdioServerParameters( + command=docker_command[0], + args=docker_command[1:], + env=None + ) + + try: + stdio_transport = await self.exit_stack.enter_async_context( + stdio_client(server_params)) + + read, write = stdio_transport + session = await self.exit_stack.enter_async_context( + ClientSession(read, write) + ) + + # 等待容器稳定 + await asyncio.sleep(3) + self.search_for_container() + + # 启动监控任务 + monitor_task = await self._start_container_monitoring(session) + + # 注册清理回调 + self._register_cleanup_callback(monitor_task) + + self.session = session + logging.info(f"Docker MCP服务器 {self.name} 已初始化") + + return session + + except Exception as e: + logging.error(f"Docker MCP服务器初始化失败: {str(e)}") + raise + + def _add_output_redirection(self, docker_command): + """为Docker命令添加输出重定向""" + # 找到bash脚本部分 + if len(docker_command) >= 3 and docker_command[-2] == "-c": + # 修改bash脚本,添加tee命令 + original_script = docker_command[-1] + + # 将输出同时发送到stderr(显示在终端)和stdout(给MCP) + modified_script = f''' + # 设置输出重定向 + exec > >(tee /dev/stderr) + exec 2>&1 + + # 原始脚本 + {original_script} + ''' + new_command = docker_command[:-1] + [modified_script] + return new_command + + return docker_command + + async def _cleanup_existing_container(self): + """清理可能存在的同名容器""" + if not self.container_name: + return + + try: + import subprocess + stop_cmd = f"docker stop {self.container_name} 2>/dev/null || true" + remove_cmd = f"docker rm {self.container_name} 2>/dev/null || true" + + subprocess.run(stop_cmd, shell=True, capture_output=True) + subprocess.run(remove_cmd, shell=True, capture_output=True) + + logging.debug(f"已清理可能存在的容器: {self.container_name}") + except Exception as e: + logging.warning(f"清理现有容器时出错: {str(e)}") + + async def _start_container_monitoring(self, session): + """启动容器监控和会话初始化""" + try: + # 创建监控任务 + async def monitor(): + """容器状态监控循环""" + try: + while True: + await asyncio.sleep(2) + self.search_for_container() + except asyncio.CancelledError: + logging.debug("监控任务被取消") + raise + except Exception as e: + logging.error(f"监控任务错误: {str(e)}") + raise + + # 启动初始化和监控任务 + init_task = asyncio.create_task(session.initialize()) + monitor_task = asyncio.create_task(monitor()) + + try: + # 等待任务完成 + done, pending = await asyncio.wait( + [init_task, monitor_task], + return_when=asyncio.FIRST_COMPLETED + ) + + # 取消还在运行的任务 + for task in pending: + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + # 检查完成的任务是否有异常 + for task in done: + await task + + # 返回监控任务引用 + return monitor_task + + except Exception as e: + # 清理任务 + for task in [init_task, monitor_task]: + if not task.done(): + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + raise + + except Exception as e: + logging.error(f"启动容器监控失败: {str(e)}") + raise + + def _register_cleanup_callback(self, monitor_task): + """注册清理回调函数""" + def cleanup(): + """清理所有资源""" + try: + # 清理监控任务 + if monitor_task and not monitor_task.done(): + monitor_task.cancel() + except Exception as cleanup_error: + logging.warning(f"清理过程中出现错误: {str(cleanup_error)}") + + self.exit_stack.callback(cleanup) + + def search_for_container(self): + try: + container = self.client.containers.get(self.container_name) + if container.status != "running": + raise RuntimeError(f"容器{self.container_name}未处于运行状态,当前状态: {container.status}") + except NotFound: + raise RuntimeError(f"容器{self.container_name}不存在") + except APIError as e: + raise RuntimeError(f"Docker API错误: {str(e)}") + + async def list_tools(self) -> list[Any]: + """列出可用工具""" + if not self.session: + raise RuntimeError(f"Server {self.name} not initialized") + + tools_response = await self.session.list_tools() + tools = [] + + for item in tools_response: + if isinstance(item, tuple) and item[0] == "tools": + tools.extend(Tool(tool.name, tool.description, tool.inputSchema) for tool in item[1]) + + return tools + + async def execute_tool( + self, + tool_name: str, + arguments: dict[str, Any], + retries: int = 1, + delay: float = 1.0, + ) -> list[Any]: + """执行工具""" + if not self.session: + raise RuntimeError(f"Server {self.name} not initialized") + + attempt = 0 + while attempt < retries: + try: + logging.info(f"Executing {tool_name}...") + result = await self.session.call_tool(tool_name, arguments) + tool_result = [] + for rc in result.content: + if rc.type == "text": + if '{' and '}' in rc.text: + try: + # 假设parse_evaluation_json函数存在 + rc_text_json = parse_evaluation_json(rc.text) + tool_result.append(rc_text_json) + except: + tool_result.append(rc.text) + else: + tool_result.append(rc.text) + elif rc.type == "image": + logging.warning("Image result is not supported yet") + elif rc.type == "resource": + logging.warning("Resource result is not supported yet") + return tool_result + except Exception as e: + attempt += 1 + logging.warning(f"Error executing tool: {e}. Attempt {attempt} of {retries}.") + if attempt < retries: + await asyncio.sleep(delay) + else: + logging.error("Max retries reached. Failing.") + raise + + async def _force_kill_docker_container_async(self) -> bool: + """异步强制终止Docker容器""" + if not self.container_name: + return True + + loop = asyncio.get_event_loop() + try: + # 使用线程池执行同步的docker命令 + result = await loop.run_in_executor( + None, + lambda: subprocess.run( + ["docker", "kill", self.container_name], + capture_output=True, + text=True, + timeout=10 + ) + ) + + if result.returncode == 0: + logging.info(f"成功强制终止容器: {self.container_name}") + return True + else: + logging.warning(f"终止容器失败: {result.stderr}") + return False + + except Exception as e: + logging.error(f"强制终止容器出错: {e}") + return False + + async def cleanup(self) -> None: + """清理服务器资源""" + async with self._cleanup_lock: + if self._is_cleaning_up: + # 等待之前的清理完成 + await self._cleanup_completed.wait() + return + + self._is_cleaning_up = True + self._cleanup_completed.clear() + + try: + logging.info(f"开始清理服务器 {self.name}") + + # 1. 标记为未初始化 + self._is_initialized = False + + # 2. 如果是Docker模式,先强制终止容器 + if self.use_docker and self.container_name: + success = await self._force_kill_docker_container_async() + if success: + # 从注册表中移除 + self._registry.unregister_container(self.container_name) + await asyncio.sleep(0.5) + + self.session = None + self.stdio_context = None + + try: + await self.exit_stack.aclose() + logging.debug("exit_stack清理完成") + except Exception as e: + logging.warning(f"exit_stack清理出错: {e}") + finally: + self.exit_stack = AsyncExitStack() + + self.docker_process = None + self.container_id = None + if self.use_docker: + self.container_name = None # 重置容器名,允许下次重新创建 + + logging.info(f"服务器 {self.name} 清理完成") + + except Exception as e: + logging.error(f"清理过程出错: {e}") + finally: + self._is_cleaning_up = False + self._cleanup_completed.set() + + async def wait_for_cleanup(self) -> None: + """等待清理完成""" + if self._is_cleaning_up: + await self._cleanup_completed.wait() + + def is_ready_for_reuse(self) -> bool: + """检查是否可以重新使用""" + return not self._is_cleaning_up and not self._is_initialized + + def __del__(self): + """析构函数,确保Docker容器被清理""" + if self.use_docker and self.container_name: + try: + subprocess.run( + ["docker", "kill", self.container_name], + capture_output=True, + timeout=5 + ) + # 从注册表中移除 + self._registry.unregister_container(self.container_name) + except: + pass + +class Tool: + """Represents a tool with its properties and formatting.""" + + def __init__( + self, + name: str, + description: str, + input_schema: dict[str, Any], + title: str | None = None, + ) -> None: + self.name: str = name + self.title: str | None = title + self.description: str = description + self.input_schema: dict[str, Any] = input_schema + + def format_for_llm(self) -> str: + """Format tool information for LLM. + + Returns: + A formatted string describing the tool. + """ + args_desc = [] + if "properties" in self.input_schema: + for param_name, param_info in self.input_schema["properties"].items(): + arg_desc = f"- {param_name}: {param_info.get('description', 'No description')}" + if param_name in self.input_schema.get("required", []): + arg_desc += " (required)" + args_desc.append(arg_desc) + + # Build the formatted output with title as a separate field + output = f"Tool: {self.name}\n" + + # Add human-readable title if available + if self.title: + output += f"User-readable title: {self.title}\n" + + output += f"""Description: {self.description} +Arguments: +{chr(10).join(args_desc)} +""" + + return output \ No newline at end of file diff --git a/src/client/Session.py b/src/client/Session.py new file mode 100644 index 0000000..87f0141 --- /dev/null +++ b/src/client/Session.py @@ -0,0 +1,148 @@ +import json +import logging +import os +from typing import Any +from dotenv import load_dotenv +from ..llm.LLM import LLMClient +from .MCPClient import MCPClient +from ..utils.parse_json import parse_evaluation_json + +# Configure logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") + + +class Configuration: + """Manages configuration and environment variables for the MCP client.""" + + def __init__(self) -> None: + """Initialize configuration with environment variables.""" + self.load_env() + self.api_key = os.getenv("LLM_API_KEY") + + @staticmethod + def load_env() -> None: + """Load environment variables from .env file.""" + load_dotenv() + + @staticmethod + def load_config(file_path: str) -> dict[str, Any]: + """Load server configuration from JSON file. + + Args: + file_path: Path to the JSON configuration file. + + Returns: + Dict containing server configuration. + + Raises: + FileNotFoundError: If configuration file doesn't exist. + JSONDecodeError: If configuration file is invalid JSON. + """ + with open(file_path, "r") as f: + return json.load(f) + + @property + def llm_api_key(self) -> str: + """Get the LLM API key. + + Returns: + The API key as a string. + + Raises: + ValueError: If the API key is not found in environment variables. + """ + if not self.api_key: + raise ValueError("LLM_API_KEY not found in environment variables") + return self.api_key + + +class ChatSession: + def __init__(self, server: MCPClient, llm_client: LLMClient) -> None: + self.server = server + self.llm_client = llm_client + + async def process_llm_response(self, llm_response: str) -> str: + """Process the LLM response and execute tools if needed. + + Args: + llm_response: The response from the LLM. + + Returns: + The result of tool execution or the original response. + """ + tool_info = { + "tool_name": "", + "arguments": {}, + } + try: + tool_call = parse_evaluation_json(llm_response) + if tool_call and "tool" in tool_call and "arguments" in tool_call: + print(f"Executing tool: {tool_call['tool']}") + print(f"With arguments: {tool_call['arguments']}") + tool_info["tool_name"] = tool_call["tool"] + tool_info["arguments"] = tool_call["arguments"] + + tools = await self.server.list_tools() + if any(tool.name == tool_call["tool"] for tool in tools): + try: + result = await self.server.execute_tool(tool_call["tool"], tool_call["arguments"]) + result_str = f"{result}" + if len(result_str) > 500: + logging.info(f"The output of tool execution is too long. Only show part of it: {result[:400]}... {result[-100:]}") + else: + logging.info(f"Tool execution result: {result}") + return tool_info, f"Tool execution result: {result}" ###这里有问题,把输出变成str了 + except Exception as e: + error_msg = f"Error executing tool: {str(e)}" + print(error_msg) + return tool_info, error_msg + + return tool_info, f"No server found with tool: {tool_call['tool']}" + return tool_info, f"tool call json decode error: {llm_response}" + except json.JSONDecodeError: + return tool_info, llm_response + + async def handle_query(self, query) -> None: + all_tools = [] + # for server in self.servers: + tools = await self.server.list_tools() + all_tools.extend(tools) + + tools_description = "\n".join([tool.format_for_llm() for tool in all_tools]) + + system_message = f"""You are a helpful assistant with access to these tools: +{tools_description} +Choose the appropriate tool based on the user's question. If no tool is needed, reply directly. +IMPORTANT: When you need to use a tool, you must ONLY respond with the exact JSON object format below, nothing else: +```json +{{ + "tool": "tool-name", + "arguments": {{ + "argument-name": "value" + }} +}} +``` +After receiving a tool's response: +1. Transform the raw data into a natural, conversational response +2. Keep responses concise but informative +3. Focus on the most relevant information +4. Use appropriate context from the user's question +5. Avoid simply repeating the raw data + +Please use only the tools that are explicitly defined above.""" + + + messages = [{"role": "system", "content": system_message}] + messages.append({"role": "user", "content": "User query:"+query}) + + llm_response = self.llm_client.get_response(messages) + print("\nAssistant: %s", llm_response) + + tool_info, tool_result = await self.process_llm_response(llm_response) + tool_included_or_not = True if tool_result != llm_response else False + if tool_included_or_not: + return tool_included_or_not, tool_info, tool_result + else: + return tool_included_or_not, tool_info, 'No tool was used, here is the direct response: '+ llm_response + + diff --git a/src/client/__pycache__/DockerRegistry.cpython-311.pyc b/src/client/__pycache__/DockerRegistry.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ebf760b2b819e43c0e19e67831bb0fb013f3196 GIT binary patch literal 10715 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 M&HrYBx9Z6M2dc%$`Tzg` literal 0 HcmV?d00001 diff --git a/src/client/__pycache__/MCPClient.cpython-311.pyc b/src/client/__pycache__/MCPClient.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16c5d1b4c1b4f3b25c5860f2e186f7eabfc92b53 GIT binary patch literal 29073 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 literal 0 HcmV?d00001 diff --git a/src/client/__pycache__/Session.cpython-311.pyc b/src/client/__pycache__/Session.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8401cb4e37cefd6ea925dd24acc65bc44e972690 GIT binary patch literal 8379 zcma)BU2GFsmcC_|?Xu(iKtd8AkRly0hTt#|*qIQ1!cUqsiJ?0zy@YO=!c|TR%C1aR zB_VF7JDOp41428r#E8r`t#J*Nbcb>npm zaS@5R87w^z%JrqP=>M0%S1v-nfxn%w_q#-_yFo;&PU(BqrZI5O^qdavXYT@okxB!9xS zwcM6qMc=yq0jUo9`VuaYTh|s8-5{=jRewfJCFF@|jjl9(W>iSQ8gxEhUCj$Akp}_F znwm<2*!USilZ6`!^n(TR#-zl90QoqTX~gnsk}<8NqH&aNDY>D5|CiT+JR%vg4Eg%x zTrxup98*j;%NimwUjgrL%KS4VR_-UdU}5fb-*8GcWI>VBQq5v!Wn*&V)kLg1P>tB& zrQz6xb62Bb*5au8z|t+3s$1R(3H8YG2~%?HmW2An>XMSsaU%Sif!;|qDfQ};Qfh8m z>P^O{4(O6LBWVW=NjD&ynlmQVRIjeZdu>_uj)BgAhDE04EG`z4Val;sy4A5Kky8C8 zc!=H&WPv>Ag1@Q1SAVzhe&b@}THDr#bIUujZN0g+-bMC)a1nklQD$)@rG&zx8_)%X zN5m)#kC0lS(^tur-Zuj8&c>4B6PlXjBOnYuAuCcOs-X?}nrPb~)7p`222#6}k~CYP zO~>^tkpJ-#f6Lwa`}K?TM;TatWEz_ij7f(Md0_-yg8k}LFChaq%V`mgka=cf?a9<= zPuwNapL0Az#>hM5XGCXbUANs=$t=T=tAv4{tzp7ANRUd%-sBoBqXK$w>|)dwP$0k; z)1Wq?_m}Umvb@-S{t_&iH&psc(ZSC5kjlEk>I3?o)o?p2#|@l|A%HriCisnJ7pdYQ z$huU+VhE%kRgEEaIwhXdG*#>8hZD8ygFd{dN_r|}@X~ErHzJNj;q1>!aaEMw%0bq7 zIR%zh7I_>|;bAdaH0-umbxKNEELvcTMfC~0v>l)Z7L(9Xqj{ccThY$j(n@c2zCcv# z?56E7L|*^`iq}CJHa`e2jb-b1iyqHhc1IfFLvbxofmlB&_I`}hi#xXSHNLb?KZlF z5);_wlxl#t0F8nT-`J>BHa{Jyvcvz6k)RZSX^0i5OhKbIh8pIQhA7Ec(QvQRkV;bz z@X_}GSs;ZL($sQ4gPV4dS=-!U?ikH(j^;K;&0sX&*nB@@a@(oB`r4&6!iZ?N9<4Y+ zf>_K7#9~QRoCZ4&AZElm_pDX3S|Jtm{Bhv9=9(iv<5Vf`*0iFVBve>}Y{?dkz+elLUx&bHU~E zv=&H-yl_L^osaBVj=_$9qSAX_$`oO#;(s~rLh8m ztU|C`!=1mOjg?w^9XRti{L2Z=J*JoaK&p|FBif)ljco8R!BQElaZpmxopm87I zC%`=8oXAp?Q+H-kFp9p`^4b>B3MjDawjZ{)FOI@4r5bmcffp-QY9kpJ+?5@u2%aL$ zL_rA;Ll3PFNZ75lVYytPBX6+vwr|9ZPq$P}s_f+Ph1XkvERcL_2YB zQaM>coVO6$P4q&4_U-+h95XJ_hjMU^gIX4A z!N8T*yj;Cw&}JtmocT2oxli0z$RqdPvGZ=jU+P83B9~x9{~a#lK3fBUoa@p4)9-w$ zr^2IBa<0KScPFxN%!tjfUyyl^Q9cjB3O&x}i-B7#K1bVcpqVMAo4DIFiFFxPh-D{a81&Zh^!R1x)*bv(l2Tm&nuO{dO0D9qU;0(bRPhzSjPsk}j zDLAL5az+_|6EegPew z=R4F;w06{3|CX_BPlkNFWuD0}75H)&q6igRaErdl@NO~;x!-CZJG5iS8_wY@JzKji zSL7hAE6zMFC`!?qE>LUw#y>D2rKH3DeQ_~x541gaH zB(JD|p!ka^WscV;)ma|G479gw^z-rqWWz=x5ng`$}tpFcns1lVc2kY{7fIUww}9ph1K zv{jJ~_Slbt&;hQ~3fOrJfe<5Eemc`wQlHRH!ayC3I5<<9PTBCTWux6r^Z%DVoI-a0 z0BTs=e87C$I`Uh$7d-AQ0YFPz0*n3vx6QwQt^L3gCEI>1*M4kqIN#X*f^4b3%q(45 zo-=nG%d1N$uWOS`*i`hExWb4zTxo|&};L0`~&ov!4n~u}AL1;Uc3!gMw z2jR*#oz69#Hk(f8!-wdQ2mYn~*`_^si@l1s&v$k`zV&Qp-|Ei3?9QXPokz{~+UtR5 zt+};YLsxdgUAFan4rufFT=V$?32y2G@U^8cAKL%>Po9NNu7*yo_`lziJN3Rf_L2GV zWNu8(hHmCUH_ctO*OE8CH?n+fDX_ME=TiE2Cm)}DGPb(?pt=3vTE{z&M+$`Oi@Hoj zGxb^1m<5_sZrhb*`WbW11vf*k_w4LKDSJi8XUquR0S}wLAY{iy7c>Bcnlk-we3%)& zuyPa#T-lD1T*rvnF_Q1xYwjCa83KY==ZM)p@_KE1H|}Czh2*=>{rlL;raxTy;}x^} z99&EOwe}rLa<+YMu6?iBz8CmB37egV&Gy<0PIa#fKH!FVfVA&2yT(e=>l%X_vu(_5 z8p}6#m^(+lRe<2tJYoi6B8|6jv+Vs zkI?D5vxmu_4hPQlxc_v#`&`KV-7W^o-*vIj{9VX{<(@_?4|sw8+1)&})&1vo2B-rb zB5a}Xa0QHwZJ;N}JV4osd0vK=^=TcTA{9WDaV?kOSoO$^yVj|T%*Jp{blm}`{dNd8 z4`?6Zh{%5Fp7)5J3?Tl;t_fz|oAEAFa8&(lEQ>yo%P?syLC@N`z?#b-Zn<>MaImO_ zEdW`nz?bL;&;skO4q8JmxD&p!MTcbid1Rzdkv4^>f$# zwlVuy3BI?%4qV3|L-z?sn-jo*y5Z`u%bUOe`iZ!joH~*2skXI~5Hg0N?=Pn%ZLZ4a zmV2wft{BWT_l()sO3SkGEY!am>d%G-a-o581NF4&pI=p{0fb1rz)wobRAO3z*n|#` z7a$@{y`2!ZQ5%h)=|zOr9}w<=lmllbRTb;NQwvjgmWB{^5ykO01YHt&aE#bG4d=s< z&coDobY>BZeWidUx=@T`P_)+r@M;P?xhQK=+)yB*GCX?e;s=)pqnG>n4Uuz{?r>cfj5TO;Tr;fLsHb)9t?&| zVpva7bg4gZ{rYuu=z&jY?5zu*(ACe=`~bqIt{%IgjNPEs?u)*&t5j|Gg@O3ZL+YYq z<^%J{J}{Vo&^636F3B_aV0#@Ppb~=Z5Bw~`U)F?_jx(hq)Pz}H6bu1EJ+OL#PYLir zK!C+V6b|CyI{nyC1yKWsBK!qOnkw_8A0XlondEN(q(X#%GLUDaz~Kl#q{gT9(rzVH z5Tz!;_ZC!xa!-&+Uxfc_lzY`?!{ZI+Uf5&_o{KOG z_NKI~K;V)hfy&T*RT0o(D@8C+g1tg51wg9EaT#<2;8sFTfhGty)ENoBkLYvoIRIWN zi`7&`vV3$}DZ^q>_0n}PYYo*;o9jIZ zY?g;k2#ySjV#7l10y5l0R~-{2J+8?V{8V49bMU- z?R(d3KMz-y8_sdVCO7=NZTrK|p0yobZ9Dw*e75a)uI>0@aE+@s8#<`9hi0y+^9rzPDeujQRwb@=NMo^+bdD^GaSdF4r;>AVVF!n7>-a-?}F^ti|D u9XH9fEV-5=*Pu}F?qeXFyPmwjx>uEkappsY!F~AY?1kO->MdR9M*T0$jfOV> literal 0 HcmV?d00001 -- Gitee From c90ccd1f82feb0d1c2f44a50780b94c34a026885 Mon Sep 17 00:00:00 2001 From: shenyue_ustc Date: Mon, 29 Sep 2025 16:59:32 +0800 Subject: [PATCH 2/3] add client fix bug --- .../__pycache__/DockerRegistry.cpython-311.pyc | Bin 10715 -> 0 bytes .../__pycache__/MCPClient.cpython-311.pyc | Bin 29073 -> 0 bytes src/client/__pycache__/Session.cpython-311.pyc | Bin 8379 -> 0 bytes src/test_generator/TestGenerator.py | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/client/__pycache__/DockerRegistry.cpython-311.pyc delete mode 100644 src/client/__pycache__/MCPClient.cpython-311.pyc delete mode 100644 src/client/__pycache__/Session.cpython-311.pyc diff --git a/src/client/__pycache__/DockerRegistry.cpython-311.pyc b/src/client/__pycache__/DockerRegistry.cpython-311.pyc deleted file mode 100644 index 1ebf760b2b819e43c0e19e67831bb0fb013f3196..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10715 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 M&HrYBx9Z6M2dc%$`Tzg` diff --git a/src/client/__pycache__/MCPClient.cpython-311.pyc b/src/client/__pycache__/MCPClient.cpython-311.pyc deleted file mode 100644 index 16c5d1b4c1b4f3b25c5860f2e186f7eabfc92b53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29073 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 diff --git a/src/client/__pycache__/Session.cpython-311.pyc b/src/client/__pycache__/Session.cpython-311.pyc deleted file mode 100644 index 8401cb4e37cefd6ea925dd24acc65bc44e972690..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8379 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 diff --git a/src/test_generator/TestGenerator.py b/src/test_generator/TestGenerator.py index 9e97fab..2f17094 100644 --- a/src/test_generator/TestGenerator.py +++ b/src/test_generator/TestGenerator.py @@ -32,7 +32,7 @@ class TestGenerator: # load config servers = [MCPClient(name, srv_config) for name, srv_config in self.config["mcpServers"].items()] - tests_per_tool = self.config["numTestsPerTool"] + tests_per_tool = self.config.get("numTestsPerTool",2) for server in servers: # connect server -- Gitee From 62a1a6637581f12e5c27899cb18932cb001981e4 Mon Sep 17 00:00:00 2001 From: shenyue_ustc Date: Tue, 30 Sep 2025 10:01:46 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E5=BA=93=E9=A1=BA=E5=BA=8F=EF=BC=8C=E5=B0=86=E5=8D=95=E7=8B=AC?= =?UTF-8?q?=E7=9A=84=E7=B1=BB=E6=8F=90=E6=88=90=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client/DockerRegistry.py | 8 ++--- src/client/MCPClient.py | 13 +++---- src/client/MakeConfig.py | 53 +++++++++++++++++++++++++++++ src/client/Session.py | 5 ++- src/test_generator/TestGenerator.py | 2 +- 5 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 src/client/MakeConfig.py diff --git a/src/client/DockerRegistry.py b/src/client/DockerRegistry.py index 5eb0506..97516d8 100644 --- a/src/client/DockerRegistry.py +++ b/src/client/DockerRegistry.py @@ -1,11 +1,11 @@ -import logging -import asyncio import atexit -from typing import Set +import asyncio +import logging +import os import signal import subprocess import sys -import os +from typing import Set class DockerContainerRegistry: """全局Docker容器注册表,确保程序退出时清理所有容器""" diff --git a/src/client/MCPClient.py b/src/client/MCPClient.py index b46ba64..8f10172 100644 --- a/src/client/MCPClient.py +++ b/src/client/MCPClient.py @@ -1,14 +1,15 @@ import asyncio -import sys import logging -import subprocess import os -import time import shutil -import docker -from docker.errors import NotFound, APIError -from contextlib import AsyncExitStack +import subprocess +import time from typing import Any, Optional +from contextlib import AsyncExitStack + +import docker +from docker.errors import NotFound, APIError + from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from ..utils.parse_json import parse_evaluation_json diff --git a/src/client/MakeConfig.py b/src/client/MakeConfig.py new file mode 100644 index 0000000..9c84bac --- /dev/null +++ b/src/client/MakeConfig.py @@ -0,0 +1,53 @@ +import os +import logging +import json +from typing import Any +from dotenv import load_dotenv + +# Configure logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") + + +class Configuration: + """Manages configuration and environment variables for the MCP client.""" + + def __init__(self) -> None: + """Initialize configuration with environment variables.""" + self.load_env() + self.api_key = os.getenv("LLM_API_KEY") + + @staticmethod + def load_env() -> None: + """Load environment variables from .env file.""" + load_dotenv() + + @staticmethod + def load_config(file_path: str) -> dict[str, Any]: + """Load server configuration from JSON file. + + Args: + file_path: Path to the JSON configuration file. + + Returns: + Dict containing server configuration. + + Raises: + FileNotFoundError: If configuration file doesn't exist. + JSONDecodeError: If configuration file is invalid JSON. + """ + with open(file_path, "r") as f: + return json.load(f) + + @property + def llm_api_key(self) -> str: + """Get the LLM API key. + + Returns: + The API key as a string. + + Raises: + ValueError: If the API key is not found in environment variables. + """ + if not self.api_key: + raise ValueError("LLM_API_KEY not found in environment variables") + return self.api_key diff --git a/src/client/Session.py b/src/client/Session.py index 87f0141..1b99863 100644 --- a/src/client/Session.py +++ b/src/client/Session.py @@ -1,12 +1,11 @@ import json import logging -import os -from typing import Any -from dotenv import load_dotenv + from ..llm.LLM import LLMClient from .MCPClient import MCPClient from ..utils.parse_json import parse_evaluation_json + # Configure logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") diff --git a/src/test_generator/TestGenerator.py b/src/test_generator/TestGenerator.py index 2f17094..920ce33 100644 --- a/src/test_generator/TestGenerator.py +++ b/src/test_generator/TestGenerator.py @@ -8,7 +8,7 @@ from ..llm.LLM import LLMClient from ..type.types_def import ToolDefinition, TestCase from ..prompts.tool_prompt import tool_prompt from ..prompts.eval_prompt import eval_prompt -from ..client.Client import Configuration +from ..client.MakeConfig import Configuration from ..client.MCPClient import MCPClient from ..utils.read_source_code import ReadSourceCode -- Gitee