从零开发AI诊断Agent:拆解LLM+Tools+Prompt三大核心

引言

不知道你有没有这种感觉------现在用 Claude Code 这类 AI 工具时,总觉得它像变魔术一样。你丢一个问题进去,它就能自动定位代码 bug、修改文件、甚至操作服务器。前阵子我做了一个 MySQL 诊断 Agent,用户只需要敲一行命令,它就能自己查慢查询、看系统负载、分析锁等待,最后给你一份完整的诊断报告。

今天这篇文章,我就把这个项目拆解开,跟你聊聊 Agent 开发的那些事儿。相信我,理解了背后的原理,你会发现 AI 工具其实没那么神秘。

智能体(Agent)

Agent(智能体/代理)是一种基于大语言模型(LLM)的自主AI系统。它能感知环境、规划目标、自主决策,并通过调用外部工具(API、数据库、浏览器)执行任务,将传统的"人机交互"提升为能独立完成复杂工作的"数字员工"或合作伙伴。

其实开发 Agent 说复杂也复杂,说简单也简单。拆开来看,核心就是三个部分:

  1. LLM - 大脑,负责做决策
  2. Tools - 手脚,负责执行具体操作
  3. Prompt - 灵魂,告诉 LLM 它是谁、能做什么

80% 的开发工作在写 Tools。 LLM 和 Prompt 通常很快就定了。

MySQL-Agent开发实战

作为开发者或 DBA,你是不是也遇到过这些情况:

  • 数据库慢了,要查一堆命令:先看慢查询,再看 QPS,然后查进程列表,还要看系统负载...
  • 每次排查都要重复同样的操作,记不住那么多命令和指标
  • 新手想排查问题,根本不知道从何下手
  • 想做自动化诊断,但硬编码的规则又太死板

这就是我开发这个 Agent 的初衷------让 AI 来帮我们完成这些繁琐的排查工作。

LLM:Agent 的大脑

LLM 是整个 Agent 的决策中心。在这个项目里,我用的是 SiliconFlow 的 DeepSeek-V3.2 API。

为什么用 API 而不是直接用 Claude Code 界面?因为通过 API,我们可以把 LLM 集成到我们的程序里,让它按我们的逻辑来工作。

调用 LLM 的代码其实很简单:

python 复制代码
def _call_llm(self, user_message: str) -> ToolCall:
    # 构建请求消息
    self.messages.append({"role": "user", "content": user_message})

    # 调用 API
    payload = {
        "model": self.model,
        "messages": [{"role": "system", "content": self.SYSTEM_PROMPT}] + self.messages,
        "temperature": 0.3,  # 温度低一点,输出更稳定
        "max_tokens": 3000,
    }

    with httpx.Client(timeout=120) as client:
        response = client.post(url, headers=headers, json=payload)
        result = response.json()

    # 解析返回的工具调用决策
    return parse_tool_call(result)

就这么简单------把上下文发给 LLM,它返回一个决策,告诉我们下一步该用什么工具、做什么操作。

但这里有个关键点:LLM 本身不会直接操作数据库或服务器,它只会"说"要做什么。真正执行的是我们接下来要讲的 Tools。

Tools:Agent 的手脚(这才是 80% 的工作)

很多人以为开发 Agent 的重点是调 LLM、写 Prompt。其实我的体会正好相反------80% 的工作都在写 Tools

为什么?因为 LLM 虽然聪明,但它"说"的东西不一定能直接执行。你需要把它的想法转换成真正安全、可靠的代码。

在这个项目里,我写了三个核心工具:

工具一:SSHTool - 安全的 Linux 命令执行

第一个工具是 SSH 执行器。但这里有个大问题------你敢让 LLM 随便执行 Linux 命令吗?要是它说个 rm -rf / 怎么办?

所以安全是第一位的。我的做法是白名单机制

python 复制代码
class SSHTool:
    # 只允许这些只读命令
    ALLOWED_COMMANDS = {
        "top", "free", "df", "du", "ps", "netstat", "ss", "uptime",
        "vmstat", "iostat", "mpstat", "sar", "dmesg", "ls", "cat",
        # ... 更多只读命令
    }

    # 绝对禁止这些危险子串
    BLOCKED_SUBSTRINGS = [
        ">", ">>", "<", "<<", "|", ";", "&&", "||",  # 重定向和管道
        "rm -rf", "mkfs", "dd if=",                   # 危险操作
        "exec", "eval", "sudo", "su ",                 # 权限提升
        # ... 更多
    ]

    def _validate_command(self, command: str) -> Tuple[bool, str]:
        # 先检查有没有危险子串
        for blocked in self.BLOCKED_SUBSTRINGS:
            if blocked in command.lower():
                return False, f"命令包含禁止的子串: {blocked}"

        # 再检查命令名是否在白名单里
        cmd_name = command.strip().split()[0]
        if "/" in cmd_name:
            cmd_name = cmd_name.split("/")[-1]

        if cmd_name not in self.ALLOWED_COMMANDS:
            return False, f"命令不在允许列表中: {cmd_name}"

        return True, ""

这样一来,LLM 只能执行我们允许的只读命令,完全不用担心安全问题。

工具二:MySQLTool - 只读的数据库查询

第二个工具是 MySQL 查询器。同样,我们也需要严格的安全控制。

除了白名单,我还加了一个实用的功能------支持多条 SQL 执行。因为 LLM 有时候会想一次查多个指标,比如:

sql 复制代码
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW VARIABLES LIKE 'long_query_time';
SHOW GLOBAL STATUS LIKE 'Questions';

默认情况下,PyMySQL 不支持一次执行多条 SQL。所以我写了一个解析器:

python 复制代码
def _split_multi_query(self, sql: str) -> List[str]:
    """智能拆分多条 SQL,正确处理字符串中的分号"""
    queries = []
    current_query = []
    in_string = False
    string_char = None
    escape_next = False

    for char in sql:
        if escape_next:
            current_query.append(char)
            escape_next = False
            continue

        if char == "\\":
            current_query.append(char)
            escape_next = True
            continue

        if char in ("'", '"'):
            # 处理字符串边界
            if not in_string:
                in_string = True
                string_char = char
            elif char == string_char:
                in_string = False
                string_char = None
            current_query.append(char)
            continue

        if char == ";" and not in_string:
            # 只有不在字符串中的分号才是语句分隔符
            query = "".join(current_query).strip()
            if query:
                queries.append(query)
            current_query = []
            continue

        current_query.append(char)

    # 处理最后一条语句
    query = "".join(current_query).strip()
    if query:
        queries.append(query)

    return queries

这个函数会正确处理 SQL 字符串中的分号,不会把 SELECT 'a;b' FROM t 拆成两条语句。

工具三:ReportWriter - 报告生成器

第三个工具是报告生成器。它的工作是把每一轮的观察结果记录下来,最后生成一份漂亮的 Markdown 报告。

python 复制代码
class ReportWriter:
    def add_observation(self, tool: str, action: str,
                       input_data: str, observation: str, round_num: int):
        """记录一轮观察"""
        self.observations.append({
            "round": round_num,
            "tool": tool,
            "action": action,
            "input": input_data,
            "observation": observation,
            "timestamp": datetime.now().isoformat(),
        })

    def generate_markdown(self, final_answer: str,
                         metadata: Optional[Dict] = None) -> str:
        """生成完整的 Markdown 报告"""
        # ... 组织报告内容
        # 包含:配置信息、最终诊断、每一轮的详细观察
        # ...

        with open(filepath, "w", encoding="utf-8") as f:
            f.write(content)

        return filepath

好的工具设计应该是这样的:每个工具只做一件事,但把这件事做扎实

Prompt:Agent 的灵魂

有了 LLM 和 Tools,还需要告诉 LLM 怎么用它们。这就是 Prompt 的作用。

Prompt 是 Agent 的"使用说明书",它要回答三个问题:

  1. 你是谁?(角色定位)
  2. 你能做什么?(工具说明)
  3. 你应该怎么做?(任务指南)

在这个项目里,我的 System Prompt 是全中文的,因为目标用户是中文用户。我把它分成了几个部分:

第一部分:角色定位
python 复制代码
SYSTEM_PROMPT = """你是一位专业的 MySQL 数据库和 Linux 系统性能诊断专家。
你的任务是通过收集和分析信息,诊断数据库和系统的性能问题。
"""

先给 LLM 一个清晰的角色------你是专家,不是助手。这样它会更专业、更自信地做决策。

第二部分:工具说明

这部分要详细,因为 LLM 需要准确理解每个工具的能力:

python 复制代码
【可用工具】

你有以下三个工具可以使用:

1. mysql_query - 执行只读 MySQL 查询
   - 输入: SQL 查询语句(仅允许 SELECT, SHOW, DESCRIBE, EXPLAIN)
   - 用途: 检查 MySQL 状态、变量、进程列表、慢查询、锁信息等
   - 提示: 可以一次执行多条用分号分隔的查询
   - 常用查询示例:
     * SHOW GLOBAL STATUS LIKE 'Slow_queries'
     * SHOW VARIABLES LIKE 'long_query_time'
     ...

注意,我还特意加了"常用查询示例"。这很重要,因为 LLM 有时候不知道具体该用什么命令,给几个例子能帮它快速上手。

第三部分:诊断流程建议

我还给了 LLM 一个诊断流程的建议,但强调"可以根据实际情况调整":

python 复制代码
【诊断流程建议】

请按以下思路进行诊断,但可以根据实际情况调整:

第1阶段 - 基础检查(建议第1-3轮):
1. 慢查询统计 - Slow_queries, long_query_time
2. MySQL 运行时间 - Uptime
3. 查询量统计 - Questions, 计算 QPS
...

这就像给新手医生一个检查清单------既提供指导,又不限制灵活处理。

第四部分:响应格式

最后,必须明确告诉 LLM 如何输出它的决策:

python 复制代码
【响应格式】

你的回答必须是纯 JSON 格式,不要包含其他文字:

{
  "tool": "mysql_query|ssh_exec|final_answer",
  "action": "具体动作描述",
  "input": "要执行的查询或命令"
}

格式约定是 Agent 开发中最容易出问题的地方。LLM 有时候会在 JSON 外面加一些解释,或者用 Markdown 代码块包裹。所以在代码里,我加了一些清理逻辑:

python 复制代码
# 清理响应 - 有时会有额外的文本或 markdown 格式
assistant_message = assistant_message.strip()
if assistant_message.startswith("```json"):
    assistant_message = assistant_message[7:]
if assistant_message.startswith("```"):
    assistant_message = assistant_message[3:]
if assistant_message.endswith("```"):
    assistant_message = assistant_message[:-3]
assistant_message = assistant_message.strip()

parsed = json.loads(assistant_message)

这些细节虽然琐碎,但能让你的 Agent 稳定很多。

Agent 的主循环

现在三大组件都有了,怎么把它们拼起来呢?核心就是一个主循环

python 复制代码
def run(self) -> str:
    """运行完整的诊断流程"""
    logger.info("诊断 Agent 启动...")
    self.current_round = 0

    # 初始上下文
    user_message = self._build_initial_context()

    while self.current_round < self.MAX_ROUNDS:
        self.current_round += 1
        logger.info(f"--- 第 {self.current_round} 轮诊断 ---")

        # 步骤 1:让 LLM 决定下一步
        tool_call = self._call_llm(user_message)
        logger.info(f"决策: {tool_call.tool} - {tool_call.action}")

        # 步骤 2:检查是否结束诊断
        if tool_call.tool == "final_answer":
            final_answer = tool_call.input_data
            self.report_writer.add_observation(
                tool="agent",
                action="final_answer",
                input_data="",
                observation=final_answer,
                round_num=self.current_round,
            )
            logger.info("已得出最终诊断。")
            return final_answer

        # 步骤 3:执行工具
        observation = self._execute_tool(tool_call)

        # 步骤 4:记录观察
        self.report_writer.add_observation(
            tool=tool_call.tool,
            action=tool_call.action,
            input_data=tool_call.input_data,
            observation=observation,
            round_num=self.current_round,
        )

        # 步骤 5:准备下一轮消息
        user_message = f"""来自 {tool_call.tool}.{tool_call.action} 的观察结果:
...
"""

这个循环就是 Agent 的核心。每一轮都是:观察 → 决策 → 行动 → 再观察,直到 LLM 认为信息足够了。


完整代码:

mian.py

python 复制代码
#!/usr/bin/env python3
"""
MySQL/Linux 远程只读诊断 Agent

这是一个自主的 MySQL 和 Linux 系统性能诊断工具,使用 LLM 自动决策
需要收集哪些信息,最终生成综合诊断报告。

【功能特性】
- 自主诊断: LLM 自动决定下一步查询什么
- 只读安全: 严格的白名单机制,只允许只读操作
- 双端检查: 同时检查 MySQL 数据库和 Linux 系统
- 详细报告: 生成完整的 Markdown 格式诊断报告

【使用方式】
    python main.py
"""
import os
import sys
import logging
from dotenv import load_dotenv

from src.mysql_diagnostic_agent import (
    SSHTool,
    MySQLTool,
    ReportWriter,
    DiagnosticAgent,
)


def setup_logging() -> None:
    """
    配置日志系统

    设置日志格式和输出方式,便于调试和监控。
    """
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
        handlers=[
            logging.StreamHandler(sys.stdout),
        ],
    )


def load_config() -> dict:
    """
    从环境变量加载配置

    读取 .env 文件中的配置信息。

    Returns:
        配置字典
    """
    load_dotenv()

    return {
        # SiliconFlow API 配置
        "api_key": os.getenv("SILICONFLOW_API_KEY"),
        "base_url": os.getenv("SILICONFLOW_BASE_URL", "https://api.siliconflow.cn/v1/"),
        "model": os.getenv("SILICONFLOW_MODEL", "deepseek-ai/DeepSeek-V3.2"),
        # SSH 连接配置
        "ssh_host": os.getenv("SSH_HOST"),
        "ssh_port": int(os.getenv("SSH_PORT", "22")),
        "ssh_user": os.getenv("SSH_USER", "root"),
        "ssh_password": os.getenv("SSH_PASSWORD"),
        "ssh_key_path": os.getenv("SSH_KEY_PATH"),
        # MySQL 连接配置
        "mysql_host": os.getenv("MYSQL_HOST"),
        "mysql_port": int(os.getenv("MYSQL_PORT", "3306")),
        "mysql_user": os.getenv("MYSQL_USER", "root"),
        "mysql_password": os.getenv("MYSQL_PASSWORD"),
        "mysql_database": os.getenv("MYSQL_DATABASE"),
        # 诊断配置
        "slow_query_threshold": float(os.getenv("SLOW_QUERY_THRESHOLD", "0.5")),
    }


def validate_config(config: dict) -> None:
    """
    验证必需的配置是否存在

    Args:
        config: 配置字典

    Raises:
        ValueError: 当缺少必需配置时
    """
    required = [
        ("api_key", "SILICONFLOW_API_KEY"),
        ("ssh_host", "SSH_HOST"),
        ("mysql_host", "MYSQL_HOST"),
    ]

    missing = []
    for key, env_var in required:
        if not config.get(key):
            missing.append(env_var)

    if missing:
        raise ValueError(f"缺少必需的环境变量: {', '.join(missing)}")


def main() -> int:
    """
    主入口函数

    协调整个诊断流程:
    1. 加载配置
    2. 初始化工具
    3. 测试连接
    4. 运行 Agent 诊断
    5. 生成报告

    Returns:
        退出码 (0=成功, 非0=失败)
    """
    setup_logging()
    logger = logging.getLogger(__name__)

    try:
        # ==================== 欢迎信息 ====================
        logger.info("=" * 60)
        logger.info("   MySQL/Linux 远程只读诊断 Agent")
        logger.info("=" * 60)

        # ==================== 加载配置 ====================
        config = load_config()
        validate_config(config)

        # ==================== 初始化工具 ====================
        logger.info("正在初始化工具...")

        ssh_tool = SSHTool(
            host=config["ssh_host"],
            port=config["ssh_port"],
            username=config["ssh_user"],
            password=config["ssh_password"],
            key_path=config["ssh_key_path"],
        )

        mysql_tool = MySQLTool(
            host=config["mysql_host"],
            port=config["mysql_port"],
            username=config["mysql_user"],
            password=config["mysql_password"],
            database=config["mysql_database"],
        )

        report_writer = ReportWriter(output_dir="reports")

        # ==================== 测试连接 ====================
        logger.info("正在测试连接...")
        ssh_tool.connect()
        logger.info("SSH 连接: 正常")

        mysql_tool.connect()
        logger.info("MySQL 连接: 正常")

        # ==================== 初始化并运行 Agent ====================
        logger.info("正在初始化诊断 Agent...")

        agent = DiagnosticAgent(
            ssh_tool=ssh_tool,
            mysql_tool=mysql_tool,
            report_writer=report_writer,
            api_key=config["api_key"],
            base_url=config["base_url"],
            model=config["model"],
            slow_query_threshold=config["slow_query_threshold"],
        )

        # 运行诊断
        logger.info("开始自主诊断...")
        final_answer = agent.run()

        # ==================== 生成报告 ====================
        logger.info("正在生成报告...")
        report_path = report_writer.generate_markdown(
            final_answer=final_answer,
            metadata={
                "目标 SSH": f"{config['ssh_host']}:{config['ssh_port']}",
                "目标 MySQL": f"{config['mysql_host']}:{config['mysql_port']}",
                "使用模型": config["model"],
                "慢查询阈值": f"{config['slow_query_threshold']}秒",
            },
        )

        # ==================== 输出摘要 ====================
        print()
        print(report_writer.generate_summary_text(final_answer))
        print()
        print(f"完整报告已保存至: {report_path}")
        print()

        # ==================== 清理 ====================
        ssh_tool.disconnect()
        mysql_tool.disconnect()

        logger.info("诊断完成!")
        return 0

    except KeyboardInterrupt:
        logger.info("诊断被用户中断。")
        return 130
    except Exception as e:
        logger.error(f"诊断失败: {e}", exc_info=True)
        return 1


if __name__ == "__main__":
    sys.exit(main())

.env

复制代码
# SiliconFlow API Configuration
SILICONFLOW_API_KEY=sk-xxxxxxxx
SILICONFLOW_BASE_URL=https://api.siliconflow.cn/v1/
SILICONFLOW_MODEL=deepseek-ai/DeepSeek-V3.2

# SSH Configuration
SSH_HOST=172.20.20.15
SSH_PORT=22
SSH_USER=root
SSH_PASSWORD=xxxxxx# or use SSH_KEY_PATH
#SSH_KEY_PATH=~/.ssh/id_rsa

# MySQL Configuration
MYSQL_HOST=172.20.20.15
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=xxxxx@
MYSQL_DATABASE=ownit

# Diagnostic Configuration
SLOW_QUERY_THRESHOLD=0.5

src\mysql_diagnostic_agent\diagnostic_agent.py

python 复制代码
"""
诊断 Agent - 核心编排逻辑

使用 LLM 自主决定需要收集哪些信息,
何时可以得出诊断结论。提供中文界面和提示词。
"""
import os
import logging
from typing import Dict, Any, List, Optional
from dotenv import load_dotenv
import httpx

from .ssh_tool import SSHTool
from .mysql_tool import MySQLTool
from .report_writer import ReportWriter

logger = logging.getLogger(__name__)

load_dotenv()


class ToolCall:
    """
    工具调用决策

    表示从 LLM 返回的工具调用决策。
    """

    def __init__(self, tool: str, action: str, input_data: str):
        """
        初始化工具调用

        Args:
            tool: 工具名称 (mysql_query, ssh_exec, final_answer)
            action: 动作描述
            input_data: 输入数据(SQL 或命令)
        """
        self.tool = tool
        self.action = action
        self.input_data = input_data

    def to_dict(self) -> Dict[str, str]:
        """转换为字典格式"""
        return {
            "tool": self.tool,
            "action": self.action,
            "input": self.input_data,
        }


class DiagnosticAgent:
    """
    自主 MySQL/Linux 诊断 Agent

    使用 LLM 来决定收集什么信息,何时可以结束诊断。
    会自动检查 MySQL 和系统两方面的指标。
    """

    # 最大诊断轮次,防止无限循环
    MAX_ROUNDS = 30

    # ==================== 系统提示词(中文) ====================

    SYSTEM_PROMPT = """你是一位专业的 MySQL 数据库和 Linux 系统性能诊断专家。
你的任务是通过收集和分析信息,诊断数据库和系统的性能问题。

【可用工具】

你有以下三个工具可以使用:

1. mysql_query - 执行只读 MySQL 查询
   - 输入: SQL 查询语句(仅允许 SELECT, SHOW, DESCRIBE, EXPLAIN)
   - 用途: 检查 MySQL 状态、变量、进程列表、慢查询、锁信息等
   - 提示: 可以一次执行多条用分号分隔的查询
   - 常用查询示例:
     * SHOW GLOBAL STATUS LIKE 'Slow_queries'
     * SHOW VARIABLES LIKE 'long_query_time'
     * SHOW GLOBAL STATUS LIKE 'Questions'
     * SHOW GLOBAL STATUS LIKE 'Uptime'
     * SHOW FULL PROCESSLIST
     * SHOW ENGINE INNODB STATUS
     * SHOW GLOBAL STATUS LIKE 'Threads_%'
     * SHOW GLOBAL STATUS LIKE 'Connections'
     * SHOW GLOBAL STATUS LIKE 'Aborted_%'
     * SHOW GLOBAL VARIABLES
     * SHOW OPEN TABLES WHERE In_use > 0

2. ssh_exec - 通过 SSH 执行只读 Linux 命令
   - 输入: Linux 命令(仅允许只读命令)
   - 用途: 检查系统资源:CPU、内存、磁盘、网络、进程等
   - 常用命令示例:
     * top -bn1 | head -50
     * free -h
     * df -h
     * uptime
     * vmstat 1 3
     * iostat -x 1 3
     * netstat -tuln
     * netstat -an | grep ESTABLISHED | wc -l
     * ps aux --sort=-%cpu | head -20
     * ps aux --sort=-%mem | head -20
     * dmesg | tail -50
     * ss -s

3. final_answer - 结束诊断并给出结论
   - 输入: 综合诊断报告和优化建议
   - 使用时机: 当你收集了足够的信息,可以给出完整诊断时

【诊断流程建议】

请按以下思路进行诊断,但可以根据实际情况调整:

第1阶段 - 基础检查(建议第1-3轮):
1. 慢查询统计 - Slow_queries, long_query_time
2. MySQL 运行时间 - Uptime
3. 查询量统计 - Questions, 计算 QPS
4. 连接数统计 - Threads_connected, Max_used_connections
5. 系统基本信息 - uptime, free, df

第2阶段 - 深入检查(建议第4-10轮):
1. 进程列表 - SHOW FULL PROCESSLIST
2. InnoDB 状态 - SHOW ENGINE INNODB STATUS
3. 锁等待信息
4. CPU 使用率 - top, vmstat
5. 内存使用 - free, ps
6. 磁盘 I/O - iostat, df
7. 网络连接 - netstat, ss

第3阶段 - 针对性分析(按需进行):
1. 表缓存状态
2. 查询缓存(如果启用)
3. 临时表使用情况
4. 排序统计
5. 特定慢查询的 EXPLAIN

【重要规则】

1. 只能使用上面列出的三个工具
2. 所有 MySQL 查询必须是只读的(SELECT, SHOW, DESCRIBE, EXPLAIN)
3. 所有 SSH 命令必须是只读的(不能修改系统)
4. 从基础检查开始,然后根据发现深入挖掘
5. 必须全面检查:既要检查 MySQL,也要检查系统资源
6. 当你认为已经收集了足够信息时,使用 final_answer

【响应格式】

你的回答必须是纯 JSON 格式,不要包含其他文字:

{
  "tool": "mysql_query|ssh_exec|final_answer",
  "action": "具体动作描述",
  "input": "要执行的查询或命令"
}

【最终诊断报告要求】

当使用 final_answer 时,请提供包含以下内容的综合报告:

1. 总体评价 - 健康状态评分(0-100分)
2. 慢查询分析 - 数量、阈值、占比
3. QPS 和负载分析
4. 连接和线程状态
5. 锁等待分析
6. 内存使用状况
7. CPU 使用状况
8. 磁盘空间和 I/O
9. 主要问题列表(按严重程度排序)
10. 具体的优化建议

现在,请开始诊断工作!"""

    # ==================== 初始化 ====================

    def __init__(
        self,
        ssh_tool: SSHTool,
        mysql_tool: MySQLTool,
        report_writer: ReportWriter,
        api_key: str,
        base_url: str,
        model: str,
        slow_query_threshold: float = 0.5,
    ):
        """
        初始化诊断 Agent

        Args:
            ssh_tool: SSH 工具实例
            mysql_tool: MySQL 工具实例
            report_writer: 报告生成器实例
            api_key: API 密钥
            base_url: API 基础 URL
            model: 模型名称
            slow_query_threshold: 慢查询阈值(秒)
        """
        self.ssh_tool = ssh_tool
        self.mysql_tool = mysql_tool
        self.report_writer = report_writer
        self.api_key = api_key
        self.base_url = base_url
        self.model = model
        self.slow_query_threshold = slow_query_threshold
        self.messages: List[Dict[str, str]] = []
        self.current_round = 0

    # ==================== LLM 交互 ====================

    def _build_initial_context(self) -> str:
        """
        构建初始上下文

        Returns:
            初始上下文消息
        """
        return f"""MySQL/Linux 诊断会话开始

【配置信息】
- 慢查询阈值: {self.slow_query_threshold} 秒
- 目标: MySQL 和 Linux 系统健康检查

请开始诊断过程。建议按以下顺序进行:
1. 检查慢查询统计信息
2. 检查 MySQL 状态和配置变量
3. 检查系统资源使用情况

请确保收集足够全面的信息后再给出最终诊断。
"""

    def _call_llm(self, user_message: str) -> ToolCall:
        """
        调用 LLM 获取下一步工具决策

        Args:
            user_message: 用户消息

        Returns:
            工具调用决策
        """
        self.messages.append({"role": "user", "content": user_message})

        # 构建 API 请求
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
        }

        payload = {
            "model": self.model,
            "messages": [
                {"role": "system", "content": self.SYSTEM_PROMPT}
            ] + self.messages,
            "temperature": 0.3,  # 较低的温度使输出更稳定
            "max_tokens": 3000,
        }

        url = f"{self.base_url.rstrip('/')}/chat/completions"

        logger.debug(f"调用 LLM API: {url}")

        try:
            with httpx.Client(timeout=120) as client:
                response = client.post(url, headers=headers, json=payload)
                response.raise_for_status()
                result = response.json()

            # 解析响应
            assistant_message = result["choices"][0]["message"]["content"]
            self.messages.append({"role": "assistant", "content": assistant_message})

            # 解析工具调用 JSON
            import json

            # 清理响应 - 有时会有额外的文本或 markdown 格式
            assistant_message = assistant_message.strip()
            if assistant_message.startswith("```json"):
                assistant_message = assistant_message[7:]
            if assistant_message.startswith("```"):
                assistant_message = assistant_message[3:]
            if assistant_message.endswith("```"):
                assistant_message = assistant_message[:-3]
            assistant_message = assistant_message.strip()

            parsed = json.loads(assistant_message)
            return ToolCall(
                tool=parsed["tool"],
                action=parsed.get("action", parsed["tool"]),
                input_data=parsed["input"],
            )

        except Exception as e:
            logger.error(f"LLM 调用失败: {e}", exc_info=True)
            # 降级处理 - 如果无法解析,直接结束诊断
            return ToolCall(
                tool="final_answer",
                action="结束诊断",
                input_data=f"诊断因错误中断: {e}\n\n请查看报告中的观察数据进行手动分析。",
            )

    # ==================== 工具执行 ====================

    def _execute_tool(self, tool_call: ToolCall) -> str:
        """
        执行工具调用并返回结果

        Args:
            tool_call: 工具调用决策

        Returns:
            工具执行结果
        """
        try:
            if tool_call.tool == "mysql_query":
                result = self.mysql_tool.query(tool_call.input_data)
                # 格式化结果以便阅读
                if result:
                    # 尝试格式化为表格
                    lines = []
                    headers = list(result[0].keys())
                    # 表头
                    lines.append(" | ".join(str(h) for h in headers))
                    # 分隔线
                    lines.append("-+-".join("-" * len(str(h)) for h in headers))
                    # 数据行
                    for row in result:
                        lines.append(" | ".join(str(row.get(h, "")) for h in headers))
                    return "\n".join(lines)
                return "查询执行成功,无结果返回。"

            elif tool_call.tool == "ssh_exec":
                return self.ssh_tool.execute(tool_call.input_data)

            elif tool_call.tool == "final_answer":
                return "收到最终诊断结果。"

            else:
                return f"未知工具: {tool_call.tool}"

        except Exception as e:
            logger.error(f"工具执行失败: {e}", exc_info=True)
            return f"错误: {str(e)}"

    # ==================== 主诊断流程 ====================

    def run(self) -> str:
        """
        运行完整的诊断流程

        Returns:
            最终诊断字符串
        """
        logger.info("诊断 Agent 启动...")
        self.current_round = 0

        # 初始上下文
        user_message = self._build_initial_context()

        while self.current_round < self.MAX_ROUNDS:
            self.current_round += 1
            logger.info(f"--- 第 {self.current_round} 轮诊断 ---")

            # 获取 LLM 决策
            tool_call = self._call_llm(user_message)
            logger.info(f"决策: {tool_call.tool} - {tool_call.action}")

            # 检查是否结束诊断
            if tool_call.tool == "final_answer":
                final_answer = tool_call.input_data
                self.report_writer.add_observation(
                    tool="agent",
                    action="final_answer",
                    input_data="",
                    observation=final_answer,
                    round_num=self.current_round,
                )
                logger.info("已得出最终诊断。")
                return final_answer

            # 执行工具
            observation = self._execute_tool(tool_call)

            # 记录观察
            self.report_writer.add_observation(
                tool=tool_call.tool,
                action=tool_call.action,
                input_data=tool_call.input_data,
                observation=observation,
                round_num=self.current_round,
            )

            # 准备下一轮消息
            user_message = f"""来自 {tool_call.tool}.{tool_call.action} 的观察结果:

【输入】
{tool_call.input_data}

【结果】
{observation}

接下来你想做什么?如果已经收集了足够的信息,请使用 final_answer 给出诊断结论。
"""

        # 达到最大轮次,强制结束
        final_answer = "诊断已达到最大轮次限制仍未结束。请查看已收集的观察数据进行手动分析。"
        logger.warning("已达到最大诊断轮次。")
        return final_answer

src\mysql_diagnostic_agent\mysql_tool.py

python 复制代码
"""
MySQL 工具模块 - 只读 MySQL 查询

提供安全的 MySQL 连接和只读查询功能,
包含 SQL 白名单和安全检查机制,同时支持批量查询。
"""
import logging
from typing import Optional, List, Tuple, Any
import pymysql
from pymysql.cursors import DictCursor

logger = logging.getLogger(__name__)


class MySQLTool:
    """
    MySQL 工具类,用于执行只读查询

    该类提供安全的 MySQL 连接管理和查询执行功能,
    所有执行的 SQL 都会经过白名单验证。
    支持一次执行多条用分号分隔的查询。
    """

    # ==================== 安全配置 ====================

    # 允许的只读语句类型
    ALLOWED_STATEMENTS = {
        "SELECT", "SHOW", "DESCRIBE", "DESC", "EXPLAIN", "USE",
        "HELP", "CHECKSUM", "CHECK", "ANALYZE"
    }

    # 需要阻止的关键字(用于只读安全保护)
    BLOCKED_KEYWORDS = [
        # 数据修改
        "INSERT", "UPDATE", "DELETE", "REPLACE",
        # 数据定义
        "DROP", "TRUNCATE", "ALTER", "CREATE", "RENAME",
        # 权限管理
        "GRANT", "REVOKE",
        # 变量设置
        "SET",
        # 数据加载
        "LOAD",
        # 锁操作
        "LOCK", "UNLOCK",
        # 事务控制
        "START", "COMMIT", "ROLLBACK", "SAVEPOINT", "RELEASE",
        # 预处理语句
        "PREPARE", "EXECUTE", "DEALLOCATE",
        # 表维护
        "OPTIMIZE", "REPAIR", "USE FRM",
        # 备份恢复
        "BACKUP", "RESTORE", "IMPORT", "EXPORT",
        # 服务器控制
        "FLUSH", "RESET", "SHUTDOWN", "KILL", "PURGE", "CHANGE",
        # 插件管理
        "INSTALL", "UNINSTALL", "PLUGIN"
    ]

    # ==================== 初始化和连接管理 ====================

    def __init__(
        self,
        host: str,
        port: int = 3306,
        username: str = "root",
        password: str = "",
        database: Optional[str] = None,
    ):
        """
        初始化 MySQL 工具

        Args:
            host: MySQL 服务器地址
            port: MySQL 端口,默认 3306
            username: MySQL 用户名,默认 root
            password: MySQL 密码
            database: 默认数据库(可选)
        """
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.database = database
        self._connection: Optional[pymysql.Connection] = None

    def connect(self) -> None:
        """
        建立 MySQL 连接

        如果已有连接则不重复建立。
        """
        if self._connection:
            return

        logger.info(f"正在连接 MySQL: {self.username}@{self.host}:{self.port}")

        self._connection = pymysql.connect(
            host=self.host,
            port=self.port,
            user=self.username,
            password=self.password,
            database=self.database,
            cursorclass=DictCursor,
            charset="utf8mb4",
            connect_timeout=30,
            read_timeout=60,
            write_timeout=60,
        )

    def disconnect(self) -> None:
        """
        关闭 MySQL 连接

        安全地关闭连接并清理资源。
        """
        if self._connection:
            self._connection.close()
            self._connection = None
            logger.info("MySQL 连接已断开")

    # ==================== SQL 验证 ====================

    def _validate_query(self, query: str) -> Tuple[bool, str]:
        """
        验证单条查询是否为只读且安全

        Args:
            query: 待验证的 SQL 查询

        Returns:
            (是否允许, 错误信息) 的元组
        """
        query_upper = query.strip().upper()

        if not query_upper:
            return False, "空查询"

        # ==================== 清理注释 ====================
        lines = []
        for line in query_upper.split("\n"):
            # 移除行内注释
            line = line.split("--", 1)[0].split("#", 1)[0].strip()
            if line:
                lines.append(line)
        clean_query = " ".join(lines)

        # ==================== 检查禁止关键字 ====================
        for keyword in self.BLOCKED_KEYWORDS:
            # 检查是否为完整单词
            if f" {keyword} " in f" {clean_query} ":
                return False, f"查询包含禁止的关键字: {keyword}"
            if clean_query.startswith(f"{keyword} "):
                return False, f"查询以禁止关键字开头: {keyword}"

        # ==================== 获取第一个单词 ====================
        first_word = clean_query.split()[0] if clean_query else ""

        # ==================== 特殊处理 ====================

        # ANALYZE TABLE 特殊处理 - 允许用于诊断(只读分析)
        if first_word == "ANALYZE" and "TABLE" in clean_query:
            return True, ""

        # ==================== 白名单检查 ====================
        if first_word not in self.ALLOWED_STATEMENTS:
            return False, f"语句类型不在允许列表中: {first_word}"

        return True, ""

    def _split_multi_query(self, sql: str) -> List[str]:
        """
        将多条用分号分隔的 SQL 拆分为单条语句

        Args:
            sql: 包含多条 SQL 的字符串

        Returns:
            SQL 语句列表
        """
        queries = []
        current_query = []
        in_string = False
        string_char = None
        escape_next = False

        for char in sql:
            if escape_next:
                current_query.append(char)
                escape_next = False
                continue

            if char == "\\":
                current_query.append(char)
                escape_next = True
                continue

            if char in ("'", '"'):
                if not in_string:
                    in_string = True
                    string_char = char
                elif char == string_char:
                    in_string = False
                    string_char = None
                current_query.append(char)
                continue

            if char == ";" and not in_string:
                query = "".join(current_query).strip()
                if query:
                    queries.append(query)
                current_query = []
                continue

            current_query.append(char)

        # 处理最后一条
        query = "".join(current_query).strip()
        if query:
            queries.append(query)

        return queries

    # ==================== 查询执行 ====================

    def query(self, sql: str, params: Optional[Tuple] = None) -> List[dict]:
        """
        执行只读 MySQL 查询

        支持一次执行多条用分号分隔的查询,会返回所有查询的合并结果。

        Args:
            sql: SQL 查询(可以包含多条用分号分隔的语句)
            params: 查询参数(仅用于单条查询)

        Returns:
            查询结果列表,每行是一个字典

        Raises:
            ValueError: 查询验证失败
            RuntimeError: 查询执行失败
        """
        # 分割多条查询
        queries = self._split_multi_query(sql)

        if not queries:
            return []

        # 如果是多条查询,分别执行
        if len(queries) > 1:
            all_results = []
            for i, single_query in enumerate(queries, 1):
                logger.info(f"执行多查询 [{i}/{len(queries)}]: {single_query}")
                results = self._execute_single_query(single_query)
                if results:
                    all_results.extend(results)
            return all_results

        # 单条查询
        return self._execute_single_query(queries[0], params)

    def _execute_single_query(self, sql: str, params: Optional[Tuple] = None) -> List[dict]:
        """
        执行单条查询(内部方法)

        Args:
            sql: 单条 SQL 查询
            params: 查询参数

        Returns:
            查询结果列表
        """
        # 验证查询安全性
        is_valid, error_msg = self._validate_query(sql)
        if not is_valid:
            raise ValueError(f"查询验证失败: {error_msg}")

        # 确保已连接
        if not self._connection:
            self.connect()

        logger.info(f"执行 MySQL 查询: {sql}")

        try:
            with self._connection.cursor() as cursor:
                cursor.execute(sql, params or ())
                result = cursor.fetchall()
                return list(result) if result else []
        except Exception as e:
            logger.error(f"查询执行失败: {e}")
            raise RuntimeError(f"执行查询失败: {e}")

    def query_one(self, sql: str, params: Optional[Tuple] = None) -> Optional[dict]:
        """
        执行查询并返回单行结果

        Args:
            sql: SQL 查询
            params: 查询参数

        Returns:
            单行结果字典,如果没有结果返回 None
        """
        results = self.query(sql, params)
        return results[0] if results else None

    def query_value(self, sql: str, params: Optional[Tuple] = None) -> Any:
        """
        执行查询并返回第一列的单个值

        Args:
            sql: SQL 查询
            params: 查询参数

        Returns:
            第一列的值,如果没有结果返回 None
        """
        row = self.query_one(sql, params)
        if row:
            return next(iter(row.values()))
        return None

    # ==================== 便捷方法 ====================

    def show_variables(self, like: Optional[str] = None) -> dict:
        """
        获取 MySQL 系统变量

        Args:
            like: 可选的 LIKE 过滤条件

        Returns:
            变量名到值的字典
        """
        if like:
            sql = "SHOW VARIABLES LIKE %s"
            rows = self.query(sql, (like,))
        else:
            rows = self.query("SHOW VARIABLES")
        return {row["Variable_name"]: row["Value"] for row in rows}

    def show_status(self, like: Optional[str] = None) -> dict:
        """
        获取 MySQL 状态信息

        Args:
            like: 可选的 LIKE 过滤条件

        Returns:
            状态名到值的字典
        """
        if like:
            sql = "SHOW GLOBAL STATUS LIKE %s"
            rows = self.query(sql, (like,))
        else:
            rows = self.query("SHOW GLOBAL STATUS")
        return {row["Variable_name"]: row["Value"] for row in rows}

    def show_processlist(self) -> List[dict]:
        """
        获取当前进程列表

        Returns:
            进程列表
        """
        return self.query("SHOW FULL PROCESSLIST")

    def show_engine_status(self) -> Optional[dict]:
        """
        获取 InnoDB 引擎状态

        Returns:
            InnoDB 状态信息
        """
        rows = self.query("SHOW ENGINE INNODB STATUS")
        if rows:
            return rows[0]
        return None

    # ==================== 上下文管理器支持 ====================

    def __enter__(self):
        """进入上下文时建立连接"""
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """退出上下文时关闭连接"""
        self.disconnect()

src\mysql_diagnostic_agent\report_writer.py

python 复制代码
"""
报告生成器模块 - 生成诊断报告

负责收集诊断过程中的观察结果,并生成
详细的 Markdown 格式诊断报告。
"""
import os
import logging
from datetime import datetime
from typing import Dict, Any, List, Optional

logger = logging.getLogger(__name__)


class ReportWriter:
    """
    诊断报告生成器

    用于记录诊断过程中的每一轮观察,并最终生成
    完整的 Markdown 格式报告。
    """

    def __init__(self, output_dir: str = "reports"):
        """
        初始化报告生成器

        Args:
            output_dir: 报告输出目录,默认为 "reports"
        """
        self.output_dir = output_dir
        self.observations: List[Dict[str, Any]] = []
        self.start_time = datetime.now()
        self._ensure_output_dir()

    def _ensure_output_dir(self) -> None:
        """
        确保输出目录存在

        如果目录不存在则创建。
        """
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)
            logger.info(f"已创建输出目录: {self.output_dir}")

    def add_observation(
        self,
        tool: str,
        action: str,
        input_data: str,
        observation: str,
        round_num: int,
    ) -> None:
        """
        添加一条观察记录到报告

        Args:
            tool: 使用的工具 (ssh, mysql, agent)
            action: 执行的动作描述
            input_data: 工具的输入数据
            observation: 观察结果/输出
            round_num: 诊断轮次编号
        """
        self.observations.append({
            "round": round_num,
            "tool": tool,
            "action": action,
            "input": input_data,
            "observation": observation,
            "timestamp": datetime.now().isoformat(),
        })
        logger.info(f"[第 {round_num} 轮] {tool}.{action} - 观察已记录")

    def _generate_filename(self) -> str:
        """
        生成唯一的报告文件名

        基于时间戳生成唯一的文件名。

        Returns:
            报告文件名
        """
        timestamp = self.start_time.strftime("%Y%m%d_%H%M%S")
        return f"diagnostic_report_{timestamp}.md"

    def _format_code_block(self, content: str, language: str = "") -> str:
        """
        将内容格式化为 Markdown 代码块

        如果内容过长会自动截断。

        Args:
            content: 要格式化的内容
            language: 代码语言(可选)

        Returns:
            Markdown 代码块字符串
        """
        if not content:
            return ""
        # 截断过长的内容以保持报告可读性
        if len(content) > 5000:
            content = content[:5000] + "\n... [已截断]"
        return f"```{language}\n{content}\n```"

    def generate_markdown(
        self,
        final_answer: str,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> str:
        """
        生成 Markdown 格式的完整报告

        Args:
            final_answer: 最终诊断结论
            metadata: 要包含的额外元数据(可选)

        Returns:
            生成的报告文件路径
        """
        end_time = datetime.now()
        duration = (end_time - self.start_time).total_seconds()

        filename = self._generate_filename()
        filepath = os.path.join(self.output_dir, filename)

        # ==================== 构建 Markdown 内容 ====================
        lines = []

        # 标题
        lines.append("# MySQL/Linux 诊断报告")
        lines.append("")
        lines.append(f"- **生成时间**: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
        lines.append(f"- **总耗时**: {duration:.2f} 秒")
        lines.append(f"- **诊断轮次**: {max((o['round'] for o in self.observations), default=0)}")
        lines.append("")

        # 元数据部分
        if metadata:
            lines.append("## 配置信息")
            lines.append("")
            for key, value in metadata.items():
                lines.append(f"- **{key}**: {value}")
            lines.append("")

        # 最终诊断结论
        lines.append("## 最终诊断")
        lines.append("")
        lines.append(final_answer)
        lines.append("")

        # 按轮次组织的观察记录
        lines.append("## 诊断过程")
        lines.append("")

        if not self.observations:
            lines.append("*没有记录观察数据。*")
            lines.append("")
        else:
            # 按轮次分组
            rounds: Dict[int, List[Dict[str, Any]]] = {}
            for obs in self.observations:
                r = obs["round"]
                if r not in rounds:
                    rounds[r] = []
                rounds[r].append(obs)

            # 为每一轮生成内容
            for round_num in sorted(rounds.keys()):
                lines.append(f"### 第 {round_num} 轮")
                lines.append("")

                for obs in rounds[round_num]:
                    lines.append(f"**工具**: `{obs['tool']}.{obs['action']}`")
                    lines.append("")
                    lines.append("**输入**:")
                    lines.append("")
                    lines.append(self._format_code_block(str(obs["input"])))
                    lines.append("")
                    lines.append("**观察结果**:")
                    lines.append("")
                    lines.append(self._format_code_block(str(obs["observation"])))
                    lines.append("")

        # 页脚
        lines.append("---")
        lines.append("")
        lines.append("*由 MySQL 诊断 Agent 自动生成*")

        # ==================== 写入文件 ====================
        content = "\n".join(lines)
        with open(filepath, "w", encoding="utf-8") as f:
            f.write(content)

        logger.info(f"报告已写入: {filepath}")
        return filepath

    def generate_summary_text(self, final_answer: str) -> str:
        """
        生成简洁的文本摘要

        Args:
            final_answer: 最终诊断结论

        Returns:
            摘要文本
        """
        lines = []
        lines.append("=" * 60)
        lines.append("        MySQL/Linux 诊断报告摘要")
        lines.append("=" * 60)
        lines.append("")
        lines.append("最终诊断:")
        lines.append("-" * 40)
        lines.append(final_answer)
        lines.append("")
        lines.append("=" * 60)
        lines.append(f"总诊断轮次: {max((o['round'] for o in self.observations), default=0)}")
        return "\n".join(lines)

src\mysql_diagnostic_agent\ssh_tool.py

python 复制代码
"""
SSH 工具模块 - 只读 Linux 命令执行

提供安全的 SSH 连接和只读命令执行功能,
包含命令白名单和安全检查机制。
"""
import os
import logging
from typing import Optional, Tuple
import paramiko

# 获取模块日志记录器
logger = logging.getLogger(__name__)


class SSHTool:
    """
    SSH 工具类,用于执行只读的 Linux 命令

    该类提供安全的 SSH 连接管理和命令执行功能,
    所有执行的命令都会经过白名单验证。
    """

    # ==================== 安全配置 ====================

    # 允许执行的只读命令白名单
    ALLOWED_COMMANDS = {
        # 系统信息
        "top", "uptime", "uname", "hostname", "date", "ls", "cat",
        # 资源监控
        "free", "df", "du", "vmstat", "iostat", "mpstat", "sar",
        # 进程管理
        "ps", "pstree", "pgrep", "pidof", "lsof", "fuser",
        # 网络工具
        "netstat", "ss", "ip", "ifconfig", "ping", "traceroute",
        "nslookup", "dig", "nethogs", "iftop",
        # 系统日志
        "dmesg", "journalctl",
        # 系统状态
        "lscpu", "lsmem", "lsblk",
        # MySQL 相关只读命令
        "mysqladmin", "mysqldump",
        # 文本处理
        "grep", "awk", "sed", "cut", "sort", "uniq", "wc", "head", "tail",
        # 性能监控
        "iotop", "htop", "atop",
        # 服务管理
        "systemctl",
    }

    # 需要阻止的危险子串列表
    BLOCKED_SUBSTRINGS = [
        # 重定向和管道
        ">", ">>", "<", "<<", "|",
        # 命令分隔符
        ";", "&&", "||",
        # 命令替换
        "`", "$(", "${",
        # 危险操作
        "rm -rf", "mkfs", "dd if=",
        # Fork 炸弹
        ":(){ :|:& };:",
        # 权限修改
        "chmod 777", "chown -R",
        # 破坏性操作
        "mv /", "cp /dev/null", "> /dev/sd",
        # 代码执行
        "exec", "eval", "source", ". ",
        # 权限提升
        "sudo", "su ",
        # 网络下载
        "wget ", "curl ",
        # 包管理
        "apt-get", "yum ", "dnf ", "pip ",
        # 版本控制
        "git ",
        # 容器/虚拟化
        "docker ", "kube", "virsh",
    ]

    # ==================== 初始化和连接管理 ====================

    def __init__(
        self,
        host: str,
        port: int = 22,
        username: str = "root",
        password: Optional[str] = None,
        key_path: Optional[str] = None,
    ):
        """
        初始化 SSH 工具

        Args:
            host: SSH 服务器地址
            port: SSH 端口,默认 22
            username: SSH 用户名,默认 root
            password: SSH 密码(可选)
            key_path: SSH 私钥路径(可选)
        """
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.key_path = key_path
        self._client: Optional[paramiko.SSHClient] = None

    def connect(self) -> None:
        """
        建立 SSH 连接

        如果已有连接则不重复建立。
        优先使用密钥认证,其次使用密码认证。
        """
        if self._client:
            return

        # 创建 SSH 客户端并配置自动添加主机密钥
        self._client = paramiko.SSHClient()
        self._client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        # 构建连接参数
        connect_kwargs = {
            "hostname": self.host,
            "port": self.port,
            "username": self.username,
        }

        # 优先使用密钥认证
        if self.key_path and os.path.exists(self.key_path):
            connect_kwargs["key_filename"] = self.key_path
        elif self.password:
            connect_kwargs["password"] = self.password

        logger.info(f"正在连接 SSH: {self.username}@{self.host}:{self.port}")
        self._client.connect(**connect_kwargs, timeout=30)

    def disconnect(self) -> None:
        """
        关闭 SSH 连接

        安全地关闭连接并清理资源。
        """
        if self._client:
            self._client.close()
            self._client = None
            logger.info("SSH 连接已断开")

    # ==================== 命令验证 ====================

    def _validate_command(self, command: str) -> Tuple[bool, str]:
        """
        验证命令是否为只读且安全

        Args:
            command: 待验证的命令字符串

        Returns:
            (是否允许, 错误信息) 的元组
        """
        cmd_lower = command.lower()

        # 检查是否包含危险子串
        for blocked in self.BLOCKED_SUBSTRINGS:
            if blocked in cmd_lower:
                return False, f"命令包含禁止的子串: {blocked}"

        # 提取命令名
        cmd_parts = command.strip().split()
        if not cmd_parts:
            return False, "空命令"

        cmd_name = cmd_parts[0]
        # 处理包含路径的情况,如 /usr/bin/top
        if "/" in cmd_name:
            cmd_name = cmd_name.split("/")[-1]

        # ==================== 特殊命令处理 ====================

        # mysqladmin 特殊处理 - 只允许只读子命令
        if cmd_name == "mysqladmin":
            allowed_mysqladmin = {
                "ping", "status", "version", "extended-status",
                "processlist", "variables", "info"
            }
            for subcmd in allowed_mysqladmin:
                if subcmd in command:
                    return True, ""
            return False, "mysqladmin 子命令不在允许列表中"

        # mysqldump 特殊处理 - 只允许无数据导出
        if cmd_name == "mysqldump":
            if "--no-data" not in command and "-d" not in command:
                return False, "mysqldump 必须使用 --no-data 或 -d 参数"
            return True, ""

        # ==================== 白名单检查 ====================

        if cmd_name not in self.ALLOWED_COMMANDS:
            return False, f"命令不在允许列表中: {cmd_name}"

        return True, ""

    # ==================== 命令执行 ====================

    def execute(self, command: str, timeout: int = 60) -> str:
        """
        执行只读命令

        Args:
            command: 要执行的命令
            timeout: 命令超时时间(秒),默认 60

        Returns:
            命令输出结果

        Raises:
            ValueError: 命令验证失败
            RuntimeError: 命令执行失败
        """
        # 首先验证命令安全性
        is_valid, error_msg = self._validate_command(command)
        if not is_valid:
            raise ValueError(f"命令验证失败: {error_msg}")

        # 确保已连接
        if not self._client:
            self.connect()

        logger.info(f"执行 SSH 命令: {command}")

        try:
            # 执行命令
            stdin, stdout, stderr = self._client.exec_command(command, timeout=timeout)

            # 读取输出(处理编码问题)
            output = stdout.read().decode("utf-8", errors="replace")
            error = stderr.read().decode("utf-8", errors="replace")

            # 获取退出码
            exit_code = stdout.channel.recv_exit_status()

            if exit_code != 0:
                logger.warning(f"命令退出码 {exit_code}: {error}")

            # 合并输出和错误信息
            result = output
            if error and not output:
                result = error
            elif error:
                result = output + "\n\nSTDERR:\n" + error

            return result.strip()

        except Exception as e:
            logger.error(f"命令执行失败: {e}")
            raise RuntimeError(f"执行命令失败: {e}")

    # ==================== 上下文管理器支持 ====================

    def __enter__(self):
        """进入上下文时建立连接"""
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """退出上下文时关闭连接"""
        self.disconnect()

生成的日志

复制代码
# MySQL/Linux 诊断报告

- **生成时间**: 2026-03-16 23:12:23
- **总耗时**: 125.44 秒
- **诊断轮次**: 16

## 配置信息

- **目标 SSH**: 172.20.20.15:22
- **目标 MySQL**: 172.20.20.15:3306
- **使用模型**: deepseek-ai/DeepSeek-V3.2
- **慢查询阈值**: 0.5秒

## 最终诊断

根据收集的信息,以下是MySQL和Linux系统的综合诊断报告:

1. 总体评价 - 健康状态评分:95/100
   - 系统运行正常,负载极低,资源充足
   - MySQL配置合理,无慢查询,连接数正常

2. 慢查询分析
   - 慢查询数量:0(Slow_queries = 0)
   - 慢查询阈值:10秒(long_query_time = 10.000000)
   - 注意:当前阈值设置为10秒,远高于目标阈值0.5秒,建议调整为0.5秒以捕获更多潜在慢查询

3. QPS和负载分析
   - MySQL运行时间:1623秒(约27分钟)
   - 总查询数:Questions = 35
   - 平均QPS:35/1623 ≈ 0.022 QPS(极低负载)
   - 系统负载:0.00, 0.01, 0.05(非常健康)

4. 连接和线程状态
   - 当前连接数:Threads_connected = 2
   - 最大使用连接数:Max_used_connections = 2
   - 最大允许连接数:max_connections = 151
   - 线程运行数:Threads_running = 2
   - 连接利用率:2/151 ≈ 1.3%(非常低)

5. 锁等待分析
   - 表锁等待:Table_locks_waited = 0(无等待)
   - InnoDB行锁等待:Innodb_row_lock_current_waits = 0(无等待)
   - InnoDB行锁等待时间:Innodb_row_lock_time = 0
   - 无锁争用问题

6. 内存使用状况
   - 总内存:3.7G
   - 已用内存:544M(14.7%)
   - 可用内存:2.9G(78.4%)
   - 缓存/缓冲:1.6G
   - InnoDB缓冲池:16MB(1001页 * 16KB),使用率约12.2%
   - 内存使用非常健康

7. CPU使用状况
   - vmstat显示:用户态1%,系统态1%,空闲98%
   - 无I/O等待(wa = 0%),无steal时间
   - CPU资源充足

8. 磁盘空间和I/O
   - 根分区:36G容量,14G已用(39%),22G可用
   - 其他分区空间充足
   - 无磁盘I/O统计数据(iostat未安装)
   - 从vmstat看,bi/bo较低,I/O压力小

9. 其他重要观察
   - 临时表:Created_tmp_disk_tables = 12,Created_tmp_tables = 25(磁盘临时表占比48%)
   - 表缓存:Table_open_cache_hits = 3605,Table_open_cache_misses = 206(命中率94.6%)
   - 连接错误:Aborted_clients = 1,Aborted_connects = 1(可忽略)
   - InnoDB缓冲池命中率:(30229-845)/30229 ≈ 97.2%(良好)

10. 主要问题列表(按严重程度排序)
    a) 慢查询阈值设置过高(10秒 vs 目标0.5秒)
    b) 磁盘临时表使用比例较高(48%)
    c) 系统监控工具不全(缺少iostat)

11. 具体的优化建议
    a) 调整慢查询阈值:SET GLOBAL long_query_time = 0.5;(需在my.cnf中永久设置)
    b) 优化查询以减少磁盘临时表:检查相关查询,增加tmp_table_size和max_heap_table_size
    c) 安装系统监控工具:yum install sysstat(用于iostat等工具)
    d) 考虑降低max_connections(当前151过高,可降至100左右)
    e) 定期监控InnoDB缓冲池使用情况,考虑适当增加innodb_buffer_pool_size
    f) 启用慢查询日志:SET GLOBAL slow_query_log = ON;

总体而言,系统处于非常健康的状态,负载极低,资源充足。主要需要调整监控配置以更好地捕获潜在问题。

## 诊断过程

### 第 1 轮

**工具**: `mysql_query.检查慢查询统计、MySQL运行状态和连接信息`

**输入**:

```
SHOW GLOBAL STATUS LIKE 'Slow_queries'; SHOW VARIABLES LIKE 'long_query_time'; SHOW GLOBAL STATUS LIKE 'Uptime'; SHOW GLOBAL STATUS LIKE 'Questions'; SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Max_used_connections'; SHOW GLOBAL VARIABLES LIKE 'max_connections';
```

**观察结果**:

```
Variable_name | Value
--------------+------
Slow_queries | 0
long_query_time | 10.000000
Uptime | 1553
Questions | 27
Threads_connected | 2
Max_used_connections | 2
max_connections | 151
```

### 第 2 轮

**工具**: `ssh_exec.检查系统负载、内存、磁盘和网络连接概览`

**输入**:

```
uptime; free -h; df -h; netstat -an | grep ESTABLISHED | wc -l; ss -s;
```

**观察结果**:

```
错误: 命令验证失败: 命令包含禁止的子串: |
```

### 第 3 轮

**工具**: `ssh_exec.分别检查系统负载、内存、磁盘和网络连接`

**输入**:

```
uptime
```

**观察结果**:

```
23:12:42 up 33 min,  2 users,  load average: 0.00, 0.01, 0.05
```

### 第 4 轮

**工具**: `ssh_exec.检查内存使用情况`

**输入**:

```
free -h
```

**观察结果**:

```
total        used        free      shared  buff/cache   available
Mem:           3.7G        544M        1.6G         11M        1.6G        2.9G
Swap:          3.9G          0B        3.9G
```

### 第 5 轮

**工具**: `ssh_exec.检查磁盘空间使用情况`

**输入**:

```
df -h
```

**观察结果**:

```
文件系统                 容量  已用  可用 已用% 挂载点
/dev/mapper/centos-root   36G   14G   22G   39% /
devtmpfs                 1.9G     0  1.9G    0% /dev
tmpfs                    1.9G     0  1.9G    0% /dev/shm
tmpfs                    1.9G   12M  1.9G    1% /run
tmpfs                    1.9G     0  1.9G    0% /sys/fs/cgroup
/dev/sda1               1014M  142M  873M   14% /boot
tmpfs                    378M     0  378M    0% /run/user/0
```

### 第 6 轮

**工具**: `ssh_exec.检查网络连接统计`

**输入**:

```
ss -s
```

**观察结果**:

```
Total: 589 (kernel 1020)
TCP:   13 (estab 4, closed 0, orphaned 0, synrecv 0, timewait 0/0), ports 0

Transport Total     IP        IPv6
*	  1020      -         -        
RAW	  0         0         0        
UDP	  6         3         3        
TCP	  13        7         6        
INET	  19        10        9        
FRAG	  0         0         0
```

### 第 7 轮

**工具**: `mysql_query.检查MySQL进程列表和InnoDB状态`

**输入**:

```
SHOW FULL PROCESSLIST; SHOW ENGINE INNODB STATUS;
```

**观察结果**:

```
Id | User | Host | db | Command | Time | State | Info
---+------+------+----+---------+------+-------+-----
5 | event_scheduler | localhost | None | Daemon | 1585 | Waiting on empty queue | None
9 | root | localhost | None | Sleep | 1368 |  | None
12 | root | 172.20.20.1:5111 | ownit | Query | 0 | init | SHOW FULL PROCESSLIST
 |  |  |  |  |  |  | 
```

### 第 8 轮

**工具**: `ssh_exec.检查CPU使用率和进程信息`

**输入**:

```
top -bn1 | head -20
```

**观察结果**:

```
错误: 命令验证失败: 命令包含禁止的子串: |
```

### 第 9 轮

**工具**: `ssh_exec.检查系统CPU和进程状态`

**输入**:

```
vmstat 1 2
```

**观察结果**:

```
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 1653292   2108 1649372    0    0    70   371  177  230  1  1 98  0  0
 2  0      0 1653292   2108 1649404    0    0     0     0  349  427  0  0 100  0  0
```

### 第 10 轮

**工具**: `ssh_exec.检查磁盘I/O统计`

**输入**:

```
iostat -x 1 2
```

**观察结果**:

```
bash: iostat: 未找到命令
```

### 第 11 轮

**工具**: `mysql_query.检查MySQL的锁等待和表使用情况`

**输入**:

```
SHOW OPEN TABLES WHERE In_use > 0; SHOW GLOBAL STATUS LIKE 'Table_locks_%'; SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%';
```

**观察结果**:

```
错误: 执行查询失败: not enough arguments for format string
```

### 第 12 轮

**工具**: `mysql_query.分别检查MySQL的锁等待和表使用情况`

**输入**:

```
SHOW OPEN TABLES WHERE In_use > 0
```

**观察结果**:

```
查询执行成功,无结果返回。
```

### 第 13 轮

**工具**: `mysql_query.检查表锁和行锁状态`

**输入**:

```
SHOW GLOBAL STATUS LIKE 'Table_locks_%'; SHOW GLOBAL STATUS LIKE 'Innodb_row_lock%';
```

**观察结果**:

```
错误: 执行查询失败: not enough arguments for format string
```

### 第 14 轮

**工具**: `mysql_query.检查表锁状态`

**输入**:

```
SHOW GLOBAL STATUS LIKE 'Table_locks_%'
```

**观察结果**:

```
错误: 执行查询失败: not enough arguments for format string
```

### 第 15 轮

**工具**: `mysql_query.检查MySQL全局状态变量`

**输入**:

```
SHOW GLOBAL STATUS
```

**观察结果**:

```
Variable_name | Value
--------------+------
Aborted_clients | 1
Aborted_connects | 1
Acl_cache_items_count | 0
Binlog_cache_disk_use | 0
Binlog_cache_use | 4
Binlog_stmt_cache_disk_use | 0
Binlog_stmt_cache_use | 1
Bytes_received | 2761
Bytes_sent | 10275
Caching_sha2_password_rsa_public_key | -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3zcuZv9VYFb51NWQa0Cj
PumXey9VY1mcnBpWbQhCRoltqMoec0pnxNV0D8xCirnlEcUhb7X8PtHdewOLqkEo
ZotxjdT+mQIERUTr45pImh6Wo2TcgyJVQzS0cGFoYCIRKczb/NgeGUCgucUcbpNy
uIJk1n1JYKVVwu/B4KxDphyPUgyY17Nn9y0anH6uAN7FLCvwuigIfRByENjV71MZ
SFjCsQImrJu+BU2/q+wJZhIIo+w5l2nE7OL3vslDW1sosrNrTwDNfKD89+xDpg9Q
JDu+KbVV+FgOrbku+jaE8JDqvHdMhMUDLs7y2ZzdKLCEKePF9PbwJ8UeqY86gMU5
bwIDAQAB
-----END PUBLIC KEY-----

Com_admin_commands | 0
Com_assign_to_keycache | 0
Com_alter_db | 0
Com_alter_event | 0
Com_alter_function | 0
Com_alter_instance | 0
Com_alter_procedure | 0
Com_alter_resource_group | 0
Com_alter_server | 0
Com_alter_table | 0
Com_alter_tablespace | 0
Com_alter_user | 4
Com_alter_user_default_role | 0
Com_analyze | 0
Com_begin | 0
Com_binlog | 0
Com_call_procedure | 0
Com_change_db | 1
Com_change_master | 0
Com_change_repl_filter | 0
Com_change_replication_source | 0
Com_check | 0
Com_checksum | 0
Com_clone | 0
Com_commit | 0
Com_create_db | 2
Com_create_event | 0
Com_create_function | 0
Com_create_index | 0
Com_create_procedure | 0
Com_create_role | 0
Com_create_server | 0
Com_create_table | 35
Com_create_resource_group | 0
Com_create_trigger | 0
Com_create_udf | 0
Com_create_user | 1
Com_create_view | 0
Com_create_spatial_reference_system | 0
Com_dealloc_sql | 0
Com_delete | 0
Com_delete_multi | 0
Com_do | 0
Com_drop_db | 0
Com_drop_event | 0
Com_drop_function | 0
Com_drop_index | 0
Com_drop_procedure | 0
Com_drop_resource_group | 0
Com_drop_role | 0
Com_drop_server | 0
Com_drop_spatial_reference_system | 0
Com_drop_table | 0
Com_drop_trigger | 0
Com_drop_user | 0
Com_drop_view | 0
Com_empty_query | 0
Com_execute_sql | 0
Com_explain_other | 0
Com_flush | 2
Com_get_diagnostics | 0
Com_grant | 1
Com_grant_roles | 0
Com_ha_close | 0
Com_ha_open | 0
Com_ha_read | 0
Com_help | 0
Com_import | 0
Com_insert | 0
Com_insert_select | 0
Com_install_component | 0
Com_install_plugin | 0
Com_kill | 0
Com_load | 0
Com_lock_instance | 0
Com_lock_tables | 0
Com_optimize | 0
Com_preload_keys | 0
Com_prepare_sql | 0
Com_purge | 0
Com_purge_before_date | 0
Com_release_savepoint | 0
Com_rename_table | 0
Com_rename_user | 0
Com_repair | 0
Com_replace | 0
Com_replace_select | 0
Com_reset | 0
Com_resignal | 0
Com_restart | 0
Com_revoke | 0
Com_revoke_all | 0
Com_revoke_roles | 0
Com_rollback | 0
Com_rollback_to_savepoint | 0
Com_savepoint | 0
Com_select | 0
Com_set_option | 10
Com_set_password | 0
Com_set_resource_group | 0
Com_set_role | 0
Com_signal | 0
Com_show_binlog_events | 0
Com_show_binlogs | 0
Com_show_charsets | 0
Com_show_collations | 0
Com_show_create_db | 0
Com_show_create_event | 0
Com_show_create_func | 0
Com_show_create_proc | 0
Com_show_create_table | 0
Com_show_create_trigger | 0
Com_show_databases | 0
Com_show_engine_logs | 0
Com_show_engine_mutex | 0
Com_show_engine_status | 1
Com_show_events | 0
Com_show_errors | 0
Com_show_fields | 0
Com_show_function_code | 0
Com_show_function_status | 0
Com_show_grants | 0
Com_show_keys | 0
Com_show_master_status | 0
Com_show_open_tables | 2
Com_show_plugins | 0
Com_show_privileges | 0
Com_show_procedure_code | 0
Com_show_procedure_status | 0
Com_show_processlist | 1
Com_show_profile | 0
Com_show_profiles | 0
Com_show_relaylog_events | 0
Com_show_replicas | 0
Com_show_slave_hosts | 0
Com_show_replica_status | 0
Com_show_slave_status | 0
Com_show_status | 8
Com_show_storage_engines | 0
Com_show_table_status | 0
Com_show_tables | 0
Com_show_triggers | 0
Com_show_variables | 3
Com_show_warnings | 0
Com_show_create_user | 0
Com_shutdown | 0
Com_replica_start | 0
Com_slave_start | 0
Com_replica_stop | 0
Com_slave_stop | 0
Com_group_replication_start | 0
Com_group_replication_stop | 0
Com_stmt_execute | 0
Com_stmt_close | 0
Com_stmt_fetch | 0
Com_stmt_prepare | 0
Com_stmt_reset | 0
Com_stmt_send_long_data | 0
Com_truncate | 0
Com_uninstall_component | 0
Com_uninstall_plugin | 0
Com_unlock_instance | 0
Com_unlock_tables | 0
Com_update | 0
Com_update_multi | 0
Com_xa_commit | 0
Com_xa_end | 0
Com_xa_prepare | 0
Com_xa_recover | 0
Com_xa_rollback | 0
Com_xa_start | 0
Com_stmt_reprepare | 0
Connection_errors_accept | 0
Connection_errors_internal | 0
Connection_errors_max_connections | 0
Connection_errors_peer_address | 0
Connection_errors_select | 0
Connection_errors_tcpwrap | 0
Connections | 12
Created_tmp_disk_tables | 12
Created_tmp_files | 5
Created_tmp_tables | 25
Current_tls_ca | ca.pem
Current_tls_capath | 
Current_tls_cert | server-cert.pem
Current_tls_cipher | 
Current_tls_ciphersuites | 
Current_tls_crl | 
Current_tls_crlpath | 
Current_tls_key | server-key.pem
Current_tls_version | TLSv1.2
Delayed_errors | 0
Delayed_insert_threads | 0
Dela
... [已截断]
```

### 第 16 轮

**工具**: `agent.final_answer`

**输入**:



**观察结果**:

```
根据收集的信息,以下是MySQL和Linux系统的综合诊断报告:

1. 总体评价 - 健康状态评分:95/100
   - 系统运行正常,负载极低,资源充足
   - MySQL配置合理,无慢查询,连接数正常

2. 慢查询分析
   - 慢查询数量:0(Slow_queries = 0)
   - 慢查询阈值:10秒(long_query_time = 10.000000)
   - 注意:当前阈值设置为10秒,远高于目标阈值0.5秒,建议调整为0.5秒以捕获更多潜在慢查询

3. QPS和负载分析
   - MySQL运行时间:1623秒(约27分钟)
   - 总查询数:Questions = 35
   - 平均QPS:35/1623 ≈ 0.022 QPS(极低负载)
   - 系统负载:0.00, 0.01, 0.05(非常健康)

4. 连接和线程状态
   - 当前连接数:Threads_connected = 2
   - 最大使用连接数:Max_used_connections = 2
   - 最大允许连接数:max_connections = 151
   - 线程运行数:Threads_running = 2
   - 连接利用率:2/151 ≈ 1.3%(非常低)

5. 锁等待分析
   - 表锁等待:Table_locks_waited = 0(无等待)
   - InnoDB行锁等待:Innodb_row_lock_current_waits = 0(无等待)
   - InnoDB行锁等待时间:Innodb_row_lock_time = 0
   - 无锁争用问题

6. 内存使用状况
   - 总内存:3.7G
   - 已用内存:544M(14.7%)
   - 可用内存:2.9G(78.4%)
   - 缓存/缓冲:1.6G
   - InnoDB缓冲池:16MB(1001页 * 16KB),使用率约12.2%
   - 内存使用非常健康

7. CPU使用状况
   - vmstat显示:用户态1%,系统态1%,空闲98%
   - 无I/O等待(wa = 0%),无steal时间
   - CPU资源充足

8. 磁盘空间和I/O
   - 根分区:36G容量,14G已用(39%),22G可用
   - 其他分区空间充足
   - 无磁盘I/O统计数据(iostat未安装)
   - 从vmstat看,bi/bo较低,I/O压力小

9. 其他重要观察
   - 临时表:Created_tmp_disk_tables = 12,Created_tmp_tables = 25(磁盘临时表占比48%)
   - 表缓存:Table_open_cache_hits = 3605,Table_open_cache_misses = 206(命中率94.6%)
   - 连接错误:Aborted_clients = 1,Aborted_connects = 1(可忽略)
   - InnoDB缓冲池命中率:(30229-845)/30229 ≈ 97.2%(良好)

10. 主要问题列表(按严重程度排序)
    a) 慢查询阈值设置过高(10秒 vs 目标0.5秒)
    b) 磁盘临时表使用比例较高(48%)
    c) 系统监控工具不全(缺少iostat)

11. 具体的优化建议
    a) 调整慢查询阈值:SET GLOBAL long_query_time = 0.5;(需在my.cnf中永久设置)
    b) 优化查询以减少磁盘临时表:检查相关查询,增加tmp_table_size和max_heap_table_size
    c) 安装系统监控工具:yum install sysstat(用于iostat等工具)
    d) 考虑降低max_connections(当前151过高,可降至100左右)
    e) 定期监控InnoDB缓冲池使用情况,考虑适当增加innodb_buffer_pool_size
    f) 启用慢查询日志:SET GLOBAL slow_query_log = ON;

总体而言,系统处于非常健康的状态,负载极低,资源充足。主要需要调整监控配置以更好地捕获潜在问题。
```

---

*由 MySQL 诊断 Agent 自动生成*
相关推荐
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章36-骨架提取
图像处理·人工智能·opencv·算法·计算机视觉
輕華2 小时前
OpenCV 图像金字塔全解析:高斯金字塔上下采样 + 拉普拉斯金字塔图像复原 | 附完整实战代码
人工智能·opencv·计算机视觉
ZHANG13HAO2 小时前
物理信息神经网络(PINN):融合物理规律与深度学习的工程建模范式
人工智能
xjf77112 小时前
Vue转TypeDOM的AI训练方案
前端·vue.js·人工智能·typedom
人工智能AI技术2 小时前
GTC 2026首日:C#对接NVIDIA物理AI,工业仿真开发全流程
人工智能·c#
bst@微胖子2 小时前
OpenCV 案例六【道路裂缝检测】
人工智能·opencv·计算机视觉
集芯微电科技有限公司2 小时前
PC5204集成700V/7.5A 400mΩ增强型氮化镓GaN HEMT驱动器具有高功率密度运行
数据结构·人工智能·单片机·嵌入式硬件·神经网络·机器学习·生成对抗网络
熊猫钓鱼>_>2 小时前
Puppeteer深度解析:Chrome自动化的艺术与实践
前端·人工智能·chrome·自动化·云计算·puppeteer·mcp
GIOTTO情2 小时前
Infoseek字节探索舆情处置技术落地实战——以2026年3月连锁餐饮舆情事件为例
人工智能