Spring AI Tool 实现自然语言操作 MySQL 数据库详解

目录

  • [1. 为什么需要自然语言操作数据库](#1. 为什么需要自然语言操作数据库)
  • [2. 核心原理:Spring AI Tool Calling](#2. 核心原理:Spring AI Tool Calling)
    • [2.1 工作流程](#2.1 工作流程)
    • [2.2 为什么不用 LLM 直出 SQL](#2.2 为什么不用 LLM 直出 SQL)
  • [3. 环境准备](#3. 环境准备)
  • [4. 项目搭建](#4. 项目搭建)
    • [4.1 引入依赖](#4.1 引入依赖)
    • [4.2 编写配置文件](#4.2 编写配置文件)
  • [5. 定义实体与 Repository](#5. 定义实体与 Repository)
    • [5.1 员工实体](#5.1 员工实体)
    • [5.2 Repository 接口](#5.2 Repository 接口)
  • [6. 定义数据库 Tool 方法](#6. 定义数据库 Tool 方法)
    • [6.1 自动注册说明](#6.1 自动注册说明)
  • [7. 配置 ChatClient 与对外接口](#7. 配置 ChatClient 与对外接口)
    • [7.1 配置类(可选)](#7.1 配置类(可选))
    • [7.2 Controller](#7.2 Controller)
  • [8. 运行与验证](#8. 运行与验证)
    • [8.1 初始化测试数据](#8.1 初始化测试数据)
    • [8.2 测试查询](#8.2 测试查询)
    • [8.3 测试写入](#8.3 测试写入)
  • [9. 进阶优化与实践建议](#9. 进阶优化与实践建议)
    • [9.1 控制工具返回数据量](#9.1 控制工具返回数据量)
    • [9.2 提高工具选择的准确率](#9.2 提高工具选择的准确率)
    • [9.3 安全与权限](#9.3 安全与权限)
    • [9.4 可观测性](#9.4 可观测性)
  • [10. 总结](#10. 总结)

1. 为什么需要自然语言操作数据库

在传统业务系统里,用户查询数据必须依赖固定的界面表单、固定的接口或手写 SQL,业务人员无法用"查一下 IT 部薪资超过一万的员工"这样自然的表述直接拿到结果。大模型(LLM)虽然能理解自然语言,但它本身并不直接访问数据库,更不能可靠地生成并执行业务 SQL------这存在严重的安全与正确性风险。

Spring AI 提供的 Tool Calling(工具调用) 机制正好解决了这个问题:我们把对 MySQL 的访问能力封装成一组"受控的工具方法",由大模型理解用户意图后,自动选择并调用合适的工具,再把查询结果组织成自然语言返回给用户。整个过程模型并不直接拼接 SQL,而是执行我们预先定义好的、参数化的数据库操作。

本文将带你从零搭建一个 Spring Boot + Spring AI + MySQL 的项目,实现:

  • 用自然语言查询员工信息(按姓名、部门、薪资范围等);
  • 用自然语言新增员工记录;
  • 理解 Tool Calling 的工作原理与安全边界。

2. 核心原理:Spring AI Tool Calling

2.1 工作流程

Spring AI 的 Tool Calling 流程可以概括为下图:
#mermaid-svg-9LLhQjfNFdOWlxHm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9LLhQjfNFdOWlxHm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9LLhQjfNFdOWlxHm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9LLhQjfNFdOWlxHm .error-icon{fill:#552222;}#mermaid-svg-9LLhQjfNFdOWlxHm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9LLhQjfNFdOWlxHm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9LLhQjfNFdOWlxHm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9LLhQjfNFdOWlxHm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9LLhQjfNFdOWlxHm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9LLhQjfNFdOWlxHm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9LLhQjfNFdOWlxHm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9LLhQjfNFdOWlxHm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9LLhQjfNFdOWlxHm .marker.cross{stroke:#333333;}#mermaid-svg-9LLhQjfNFdOWlxHm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9LLhQjfNFdOWlxHm p{margin:0;}#mermaid-svg-9LLhQjfNFdOWlxHm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9LLhQjfNFdOWlxHm .cluster-label text{fill:#333;}#mermaid-svg-9LLhQjfNFdOWlxHm .cluster-label span{color:#333;}#mermaid-svg-9LLhQjfNFdOWlxHm .cluster-label span p{background-color:transparent;}#mermaid-svg-9LLhQjfNFdOWlxHm .label text,#mermaid-svg-9LLhQjfNFdOWlxHm span{fill:#333;color:#333;}#mermaid-svg-9LLhQjfNFdOWlxHm .node rect,#mermaid-svg-9LLhQjfNFdOWlxHm .node circle,#mermaid-svg-9LLhQjfNFdOWlxHm .node ellipse,#mermaid-svg-9LLhQjfNFdOWlxHm .node polygon,#mermaid-svg-9LLhQjfNFdOWlxHm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9LLhQjfNFdOWlxHm .rough-node .label text,#mermaid-svg-9LLhQjfNFdOWlxHm .node .label text,#mermaid-svg-9LLhQjfNFdOWlxHm .image-shape .label,#mermaid-svg-9LLhQjfNFdOWlxHm .icon-shape .label{text-anchor:middle;}#mermaid-svg-9LLhQjfNFdOWlxHm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9LLhQjfNFdOWlxHm .rough-node .label,#mermaid-svg-9LLhQjfNFdOWlxHm .node .label,#mermaid-svg-9LLhQjfNFdOWlxHm .image-shape .label,#mermaid-svg-9LLhQjfNFdOWlxHm .icon-shape .label{text-align:center;}#mermaid-svg-9LLhQjfNFdOWlxHm .node.clickable{cursor:pointer;}#mermaid-svg-9LLhQjfNFdOWlxHm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9LLhQjfNFdOWlxHm .arrowheadPath{fill:#333333;}#mermaid-svg-9LLhQjfNFdOWlxHm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9LLhQjfNFdOWlxHm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9LLhQjfNFdOWlxHm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9LLhQjfNFdOWlxHm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9LLhQjfNFdOWlxHm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9LLhQjfNFdOWlxHm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9LLhQjfNFdOWlxHm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9LLhQjfNFdOWlxHm .cluster text{fill:#333;}#mermaid-svg-9LLhQjfNFdOWlxHm .cluster span{color:#333;}#mermaid-svg-9LLhQjfNFdOWlxHm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9LLhQjfNFdOWlxHm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9LLhQjfNFdOWlxHm rect.text{fill:none;stroke-width:0;}#mermaid-svg-9LLhQjfNFdOWlxHm .icon-shape,#mermaid-svg-9LLhQjfNFdOWlxHm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9LLhQjfNFdOWlxHm .icon-shape p,#mermaid-svg-9LLhQjfNFdOWlxHm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9LLhQjfNFdOWlxHm .icon-shape .label rect,#mermaid-svg-9LLhQjfNFdOWlxHm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9LLhQjfNFdOWlxHm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9LLhQjfNFdOWlxHm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9LLhQjfNFdOWlxHm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 需要
不需要
用户输入自然语言
ChatClient 组装 Prompt 与工具元数据
大模型理解意图
是否需要调用工具
模型返回工具调用请求(方法名+参数)
Spring AI 定位并执行 @Tool 方法
通过 JPA/JDBC 访问 MySQL
工具执行结果回传给模型
模型组织自然语言回答
返回最终答复给用户

关键点在于:大模型并不自己写 SQL,也不直接连数据库 。它只会根据工具描述(description)判断该调哪个方法、传什么参数;真正的 SQL 由我们写的 @Tool 方法内部通过 Spring Data JPA 生成,参数经过预处理与绑定,天然规避 SQL 注入。

2.2 为什么不用 LLM 直出 SQL

让模型直接生成 SQL 再执行,存在三个明显问题:

  1. 安全风险 :用户可通过 Prompt 注入诱导模型执行 DROP TABLE、全表删除等危险语句;
  2. 正确性难以保证:模型对表结构、字段名的把握不稳定,容易生成错误 SQL;
  3. 难以审计:SQL 由模型临时生成,排查问题困难。

而 Tool Calling 把数据库能力收敛为白名单方法,模型只能调用我们允许的操作,参数类型严格约束,既安全又可控。


3. 环境准备

开始编码前,请准备以下环境:

组件 版本/说明
JDK 17 及以上
Maven 3.8+
Spring Boot 3.4.x(本文以 3.4.5 为例)
Spring AI 1.0.0(GA 版本,基于 Tool Calling 新 API)
MySQL 8.x
大模型 API OpenAI 兼容接口(可替换为通义千问、DeepSeek 等)

说明:本文示例使用 spring-ai-starter-model-openai 接入 OpenAI 兼容接口;如果你使用国产模型,只需替换为对应的 starter(如 spring-ai-alibaba-starter-dashscope)并修改配置,其余代码完全一致。


4. 项目搭建

4.1 引入依赖

pom.xml 中引入 Spring AI BOM 及所需依赖:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.5</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-ai-mysql-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-ai-mysql-demo</name>

    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0</spring-ai.version>
    </properties>

    <dependencies>
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring AI:OpenAI 兼容模型 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>

        <!-- 数据库:JPA + MySQL -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

4.2 编写配置文件

application.yml 中配置模型与数据源:

yaml 复制代码
spring:
  application:
    name: spring-ai-mysql-demo

  # 大模型配置(OpenAI 兼容)
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      base-url: https://api.openai.com   # 可替换为兼容网关
      chat:
        options:
          model: gpt-4o-mini
          temperature: 0.2

  # MySQL 数据库
  datasource:
    url: jdbc:mysql://localhost:3306/spring_ai_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: ${MYSQL_PASSWORD}
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update   # 演示环境自动建表,生产请改为 validate/none
    show-sql: true
    properties:
      hibernate:
        format_sql: true

在 MySQL 中手动创建数据库:

sql 复制代码
CREATE DATABASE IF NOT EXISTS spring_ai_demo
    DEFAULT CHARACTER SET utf8mb4
    COLLATE utf8mb4_unicode_ci;

5. 定义实体与 Repository

5.1 员工实体

java 复制代码
package com.example.demo.entity;

import jakarta.persistence.*;

@Entity
@Table(name = "employee")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 64)
    private String name;

    @Column(length = 64)
    private String department;

    @Column(nullable = false)
    private Double salary;

    // 无参构造
    public Employee() {
    }

    public Employee(String name, String department, Double salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    // getter / setter 略
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getDepartment() { return department; }
    public void setDepartment(String department) { this.department = department; }
    public Double getSalary() { return salary; }
    public void setSalary(Double salary) { this.salary = salary; }

    @Override
    public String toString() {
        return "Employee{id=" + id + ", name='" + name + "', department='"
                + department + "', salary=" + salary + "}";
    }
}

5.2 Repository 接口

java 复制代码
package com.example.demo.repository;

import com.example.demo.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {

    List<Employee> findByNameContaining(String name);

    List<Employee> findByDepartment(String department);

    List<Employee> findBySalaryBetween(Double minSalary, Double maxSalary);

    List<Employee> findByDepartmentAndSalaryGreaterThanEqual(String department, Double minSalary);
}

6. 定义数据库 Tool 方法

这是整篇文章的核心。我们通过 @Tool 注解把数据库访问方法暴露给大模型,@ToolParam 注解用来描述参数含义,帮助模型正确传参。

java 复制代码
package com.example.demo.tool;

import com.example.demo.entity.Employee;
import com.example.demo.repository.EmployeeRepository;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class DatabaseTools {

    private final EmployeeRepository employeeRepository;

    public DatabaseTools(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    @Tool(description = "查询全部员工信息")
    public List<Employee> listAllEmployees() {
        return employeeRepository.findAll();
    }

    @Tool(description = "根据姓名关键词模糊查询员工,例如输入"张"可查到所有名字含张的员工")
    public List<Employee> searchEmployeesByName(
            @ToolParam(description = "员工姓名关键词") String name) {
        return employeeRepository.findByNameContaining(name);
    }

    @Tool(description = "根据部门名称查询该部门的所有员工")
    public List<Employee> getEmployeesByDepartment(
            @ToolParam(description = "部门名称,如研发部、市场部") String department) {
        return employeeRepository.findByDepartment(department);
    }

    @Tool(description = "查询薪资在指定区间内的员工,左闭右闭")
    public List<Employee> getEmployeesBySalaryRange(
            @ToolParam(description = "最低薪资(包含)") Double minSalary,
            @ToolParam(description = "最高薪资(包含)") Double maxSalary) {
        return employeeRepository.findBySalaryBetween(minSalary, maxSalary);
    }

    @Tool(description = "查询指定部门中薪资大于等于某个值的员工")
    public List<Employee> getEmployeesByDepartmentAndSalary(
            @ToolParam(description = "部门名称") String department,
            @ToolParam(description = "最低薪资") Double minSalary) {
        return employeeRepository.findByDepartmentAndSalaryGreaterThanEqual(department, minSalary);
    }

    @Tool(description = "新增一名员工,需要姓名、部门与薪资,返回保存后的员工信息")
    public Employee addEmployee(
            @ToolParam(description = "员工姓名") String name,
            @ToolParam(description = "所属部门") String department,
            @ToolParam(description = "薪资") Double salary) {
        Employee employee = new Employee(name, department, salary);
        return employeeRepository.save(employee);
    }
}

6.1 自动注册说明

在 Spring AI 1.0 中,@Tool 注解的方法会被 ToolCallingAutoConfiguration 自动扫描并注册为 ToolCallback。默认情况下,ChatClient 会自动加载这些工具,无需手动绑定。

但为了代码更直观、可控,我们也可以在构建 ChatClient 时显式指定。两种方式任选其一即可。


7. 配置 ChatClient 与对外接口

7.1 配置类(可选)

如果需要自定义系统提示词,可以写一个配置类:

java 复制代码
package com.example.demo.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatConfig {

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                .defaultSystem("你是一个友好的员工信息助手,请根据用户问题调用合适的数据库工具查询或新增员工,并用简洁的中文回答。")
                .build();
    }
}

7.2 Controller

java 复制代码
package com.example.demo.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @GetMapping("/chat")
    public String chat(@RequestParam String message) {
        return chatClient.prompt(message).call().content();
    }
}

到这里,一个完整的最小可用版本就写好了。启动项目后,大模型即可根据自然语言自动调用 DatabaseTools 中的方法操作 MySQL。


8. 运行与验证

8.1 初始化测试数据

启动项目后,JPA 会自动创建 employee 表。我们先插入几条基础数据:

sql 复制代码
INSERT INTO employee (name, department, salary) VALUES
('张三', '研发部', 15000),
('李四', '研发部', 12000),
('王五', '市场部', 9000),
('赵六', '市场部', 11000),
('钱七', '财务部', 13000);

8.2 测试查询

模糊查询

请求:

bash 复制代码
curl "http://localhost:8080/chat?message=帮我查一下名字里带张的员工"

大模型内部会调用 searchEmployeesByName("张"),返回类似:

名字里带"张"的员工有:张三,研发部,薪资 15000。

部门查询

请求:

bash 复制代码
curl "http://localhost:8080/chat?message=研发部都有哪些人"

模型调用 getEmployeesByDepartment("研发部") 并组织回答。

组合条件查询

请求:

bash 复制代码
curl "http://localhost:8080/chat?message=查询研发部薪资大于等于13000的员工"

模型会调用 getEmployeesByDepartmentAndSalary("研发部", 13000)

8.3 测试写入

请求:

bash 复制代码
curl "http://localhost:8080/chat?message=帮我新增一个员工,叫孙悟空,属于研发部,薪资18000"

模型会调用 addEmployee("孙悟空", "研发部", 18000),保存后返回成功信息:

已成功新增员工:孙悟空,研发部,薪资 18000。

此时再去 MySQL 里 SELECT * FROM employee,可以看到新记录已经落库。


9. 进阶优化与实践建议

9.1 控制工具返回数据量

当表数据量很大时,listAllEmployees 这类全量查询可能把大量数据塞进模型上下文,既浪费 Token 又可能超限。建议:

  • 增加分页参数 pagesize,或限制默认返回条数;
  • 对于大数据量统计分析,优先使用聚合查询(如 AVGSUM),只把统计结果返回给模型。

9.2 提高工具选择的准确率

  • 写好 @Tool 的 description:说明方法用途、适用场景与边界;
  • 写好 @ToolParam 的 description:说明参数格式、枚举范围、示例值;
  • 必要时在系统提示词中补充业务规则(如"部门名称必须使用规范全称")。

9.3 安全与权限

  • Tool 方法即能力白名单,不要提供 executeNativeSql(String sql) 这类直接执行 SQL 的工具,否则相当于把数据库钥匙交给模型;
  • 对写入类工具(新增、修改、删除)增加业务校验与权限控制,必要时要求二次确认;
  • 生产环境关闭 ddl-auto=update,数据库账号只授予最小权限;
  • 对敏感字段(如薪资)做脱敏或在 Tool 层过滤。

9.4 可观测性

建议记录每次工具调用的入参与返回结果(脱敏后),便于排查模型"答非所问"或"调用错误工具"的问题。可以结合 Spring AI 的 Advisor 或 AOP 统一拦截 @Tool 方法。


10. 总结

本文完整演示了如何用 Spring AI Tool Calling 实现自然语言操作 MySQL:

  1. @Tool 把数据库查询/写入能力封装为白名单方法;
  2. 大模型理解用户意图后自动选择并调用合适工具;
  3. 工具方法通过 Spring Data JPA 生成参数化 SQL,安全可控;
  4. 模型将结果组织成自然语言返回给用户。

相比让 LLM 直接生成 SQL,这种方式在安全性、可控性和可维护性上都有明显优势,非常适合企业内部的智能问答、智能报表等场景。你可以在此基础上继续扩展更多工具(分页、统计、关联查询、更新、删除等),构建一个完整的自然语言数据库助手。

相关推荐
hh9501 小时前
Agent Plan x DeepSeek Harness:Agent 供应链安全与第三方插件审计
java·开发语言·安全·agent plan·adg成都社区·adg社区
Zentceh1 小时前
AI-ISP在夜视机芯中的应用:从传统ISP到PixelClean全彩夜视的进化
人工智能·科技·算法·计算机视觉·车载系统·视频·智能硬件
wshzd1 小时前
LLM之Agent(五十七)|Claude Code 智能体循环:从入门到精通的实战指南
人工智能
AI英德西牛仔1 小时前
文心 Excel 与“AI 导出鸭”:PC 端批量导出方案的技术解构
人工智能·excel·deepseek·ai导出鸭
人民新视野1 小时前
党建引领促交流 互学互鉴共提升 —— 赴启信创格(北京)营销科技有限公司交流学习活动总结
大数据·人工智能·科技
阿伟玩不懂1 小时前
用 AI 写周报,我把时间从 2 小时压到 10 分钟
人工智能
「QT(C++)开发工程师」1 小时前
C++ std::move 详解
开发语言·c++
matlab代码1 小时前
基于matlab的玉米种子计数设计(GUI界面)【源码58期】
开发语言·matlab
c#上位机1 小时前
C#上位机项目实战——C#的dll项目编译时拷贝到指定目录下
开发语言·c#