LangChain4j Tool Calling 实战:让大模型自动查数据库发邮件,一句话搞定复杂任务

想象一下:你对着聊天窗口说"帮我查一下最近一周销售额最高的三个产品,然后用邮件发给销售总监",然后系统真的自己去查数据库、组织内容、调用邮件服务发送出去。这就是 Function Calling(工具调用)——LangChain4j 的原生 Tool Calling 能让你用几十行 Java 代码实现这种自动化。今天我们从零开始,手把手写一个能查数据库、能发邮件的 AI 助手。

一、这个问题到底是什么

Tool Calling 是让大模型"动手做事"的关键技术。大模型本质上是文本生成器——它能回答问题、写代码、做翻译,但如果你让它查实时数据,它只能靠训练时看到的信息去编,编不出来就"幻觉"。Tool Calling 解决的就是这个问题:让大模型知道自己什么时候该调用外部工具,并且知道怎么调用

在 Java 生态里,LangChain4j 是目前做 Tool Calling 最顺滑的框架。它用 @Tool 注解标记方法,大模型就会自动识别这些方法的功能,在需要的时候自动调用。整个过程是这样的:

  1. 你问:“帮我查今天杭州的天气,如果下雨就发邮件提醒老板带伞”
  2. LangChain4j 把可用工具列表发给大模型
  3. 大模型判断需要先调用"查天气"工具
  4. 你的 Java 代码执行查天气逻辑,结果返回给大模型
  5. 大模型分析结果发现"下雨",决定调用"发邮件"工具
  6. 邮件发出,大模型给你回复"已提醒老板"

整个过程对大模型来说是"思考→决定调工具→收到结果→继续思考→决定调工具…"的循环,直到它认为任务完成。

和 Spring AI 的 Tool Calling 相比,LangChain4j 的优势在于:

  • 注解更简洁@Tool(name="xxx") + @P("描述") 搞定
  • 工具注册更灵活:不需要实现特定接口,任意 Bean 的任意方法都能注册
  • 内置记忆管理ChatMemory 让多轮对话中的工具调用上下文不丢失

二、底层原理到底怎么回事

要真正用好 Tool Calling,得理解它在大模型层面是怎么工作的。

2.1 OpenAI 的 Function Calling 协议

OpenAI(以及兼容的模型)的 Function Calling 不是魔法,而是一套标准的 API 协议。当你调用 Chat Completion API 时,除了 system prompt 和 user message,还能附带一个 tools 数组,里面描述了你有哪些工具可用:

{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "querySalesData",
        "description": "查询指定时间范围内的销售数据,返回产品名称和销售额",
        "parameters": {
          "type": "object",
          "properties": {
            "startDate": {"type": "string", "description": "开始日期,格式yyyy-MM-dd"},
            "endDate": {"type": "string", "description": "结束日期,格式yyyy-MM-dd"}
          },
          "required": ["startDate", "endDate"]
        }
      }
    }
  ]
}

大模型收到这个 tools 描述后,如果它认为当前回答需要用到某个工具,它的响应里就不包含普通文本回复,而是包含一个 tool_calls 字段:

{
  "tool_calls": [
    {
      "id": "call_abc123",
      "type": "function",
      "function": {
        "name": "querySalesData",
        "arguments": "{\"startDate\": \"2026-07-30\", \"endDate\": \"2026-08-06\"}"
      }
    }
  ]
}

这时候你的代码要执行实际的 querySalesData 逻辑,然后带着执行结果再调一次 Chat Completion API,大模型根据结果继续生成文本或决定继续调下一个工具。

2.2 LangChain4j 怎么封装这套协议

LangChain4j 把上面的复杂流程封装成了三步:

第一步:工具注册。在创建 AiServices 时,LangChain4j 扫描你指定的 Bean,找到所有带 @Tool 注解的方法,通过反射解析方法签名、参数名、@P 注解的描述,自动生成 OpenAI 兼容的 JSON Schema。你不需要手写任何 JSON。

第二步:自动循环调用。LangChain4j 的 AiServices 在收到用户消息后,会自动进入一个工具执行循环:

用户消息 → 发送给LLM(附带工具列表)
  → LLM返回tool_calls → 执行对应的Java方法 → 把结果返回给LLM
  → LLM可能继续tool_calls → 继续执行
  → LLM返回文本 → 输出最终回复

这个循环有默认的最大迭代次数(通常是15次),防止死循环。

第三步:流式支持。LangChain4j 支持流式 Tool Calling——大模型在决定调用工具时,你可以通过 ToolExecutionRequest 监听器拿到工具调用请求,处理完成后继续流式接收最终回复。这对聊天界面非常重要,用户体验不会因为工具调用而中断。

2.3 关键源码链路

如果你点进 LangChain4j 的源码(dev.langchain4j.service.AiServiceStreamingResponseHandler),能看到核心的主循环大约是这样的逻辑:

while (true) {
    发送messages + tools给LLM
    LLM返回token
    if (token是toolExecutionRequest) {
        解析工具名和参数
        执行Java方法
        把结果包装成ToolExecutionResultMessage加入messages
        continue  // 继续循环
    } else if (token是文本) {
        流式输出给用户
    }
    if (LLM结束) break
}

理解了这层原理,你就能明白为什么有些时候 Tool Calling 会"抽风"——大模型可能返回格式不对的 JSON 参数,或者陷入无限循环。后面踩坑章节会详细讲怎么防御。

三、实战:手把手写代码

下面我们从零搭建一个完整的 Spring Boot 项目,实现一个 AI 助手,具备两个工具:查销售数据、发邮件。

3.1 创建项目结构

langchain4j-tool-demo/
├── pom.xml
├── src/main/java/com/luoboyun/tool/
│   ├── ToolCallingApplication.java
│   ├── controller/AssistantController.java
│   ├── model/SalesRecord.java
│   ├── tool/DataQueryTool.java
│   ├── tool/EmailTool.java
│   └── config/AiConfig.java
└── src/main/resources/
    └── application.yml

3.2 pom.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.2</version>
        <relativePath/>
    </parent>

    <groupId>com.luoboyun</groupId>
    <artifactId>langchain4j-tool-demo</artifactId>
    <version>1.0.0</version>
    <name>LangChain4j Tool Calling Demo</name>

    <properties>
        <java.version>21</java.version>
        <langchain4j.version>1.18.1</langchain4j.version>
    </properties>

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

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-spring-boot-starter</artifactId>
            <version>1.18.1-beta28</version>
        </dependency>

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai</artifactId>
            <version>${langchain4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.3 application.yml

spring:
  application:
    name: langchain4j-tool-demo

  # H2内存数据库,方便演示
  datasource:
    url: jdbc:h2:mem:salesdb;DB_CLOSE_DELAY=-1
    driver-class-name: org.h2.Driver
    username: sa
    password:

  h2:
    console:
      enabled: true

  # 邮件配置(测试用,实际使用请配置真实SMTP)
  mail:
    host: smtp.example.com
    port: 587
    username: your-email@example.com
    password: your-password
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true

# LangChain4j配置
langchain4j:
  open-ai:
    chat-model:
      api-key: ${OPENAI_API_KEY:your-api-key-here}
      model-name: gpt-4o
      temperature: 0.3
      max-tokens: 2000

3.4 启动类

package com.luoboyun.tool;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ToolCallingApplication {

    public static void main(String[] args) {
        SpringApplication.run(ToolCallingApplication.class, args);
    }
}

3.5 数据模型

package com.luoboyun.tool.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDate;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SalesRecord {

    private String productName;
    private BigDecimal amount;
    private LocalDate saleDate;
    private String region;
}

3.6 数据查询工具

package com.luoboyun.tool.tool;

import com.luoboyun.tool.model.SalesRecord;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import jakarta.annotation.PostConstruct;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class DataQueryTool {

    private final JdbcTemplate jdbcTemplate;

    public DataQueryTool(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @PostConstruct
    public void initData() {
        // 初始化一批演示数据
        jdbcTemplate.execute("""
            CREATE TABLE IF NOT EXISTS sales (
                id BIGINT AUTO_INCREMENT PRIMARY KEY,
                product_name VARCHAR(100),
                amount DECIMAL(10,2),
                sale_date DATE,
                region VARCHAR(50)
            )
        """);

        // 先清空再插入,避免重复
        jdbcTemplate.update("DELETE FROM sales");

        String[] products = {"机械键盘K99", "电竞鼠标M200", "显示器27寸4K",
                "机械键盘K99", "电竞鼠标M200", "蓝牙耳机T3",
                "显示器27寸4K", "机械键盘K99", "电竞鼠标M200", "蓝牙耳机T3"};
        BigDecimal[] amounts = {
                new BigDecimal("12800.00"), new BigDecimal("9600.00"),
                new BigDecimal("35000.00"), new BigDecimal("11200.00"),
                new BigDecimal("8500.00"), new BigDecimal("7200.00"),
                new BigDecimal("32000.00"), new BigDecimal("14000.00"),
                new BigDecimal("10200.00"), new BigDecimal("6800.00")
        };
        String[] regions = {"杭州", "北京", "上海", "杭州", "深圳",
                "杭州", "上海", "深圳", "杭州", "北京"};

        for (int i = 0; i < products.length; i++) {
            jdbcTemplate.update(
                    "INSERT INTO sales (product_name, amount, sale_date, region) VALUES (?, ?, ?, ?)",
                    products[i], amounts[i],
                    LocalDate.now().minusDays(i).format(DateTimeFormatter.ISO_LOCAL_DATE),
                    regions[i]
            );
        }
    }

    @Tool(name = "querySalesData", value = "查询指定时间范围内的销售数据。返回所有产品的销售额列表")
    public List<SalesRecord> querySalesData(
            @P("开始日期,格式yyyy-MM-dd") String startDate,
            @P("结束日期,格式yyyy-MM-dd") String endDate) {

        LocalDate start = LocalDate.parse(startDate, DateTimeFormatter.ISO_LOCAL_DATE);
        LocalDate end = LocalDate.parse(endDate, DateTimeFormatter.ISO_LOCAL_DATE);

        List<SalesRecord> records = jdbcTemplate.query(
                "SELECT product_name, amount, sale_date, region FROM sales " +
                        "WHERE sale_date BETWEEN ? AND ?",
                (rs, rowNum) -> new SalesRecord(
                        rs.getString("product_name"),
                        rs.getBigDecimal("amount"),
                        rs.getDate("sale_date").toLocalDate(),
                        rs.getString("region")
                ),
                start.toString(), end.toString()
        );

        return records;
    }

    @Tool(name = "queryTopProducts", value = "查询销售额最高的前N个产品,返回产品名称和总销售额")
    public String queryTopProducts(
            @P("要返回的产品数量,比如3表示前三名") int topN,
            @P("开始日期,格式yyyy-MM-dd") String startDate,
            @P("结束日期,格式yyyy-MM-dd") String endDate) {

        List<SalesRecord> records = querySalesData(startDate, endDate);

        // 按产品名聚合计总销售额
        var productTotals = records.stream()
                .collect(Collectors.groupingBy(
                        SalesRecord::getProductName,
                        Collectors.reducing(
                                BigDecimal.ZERO,
                                SalesRecord::getAmount,
                                BigDecimal::add
                        )
                ))
                .entrySet().stream()
                .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
                .limit(topN)
                .collect(Collectors.toList());

        if (productTotals.isEmpty()) {
            return "指定时间范围内没有销售数据。";
        }

        StringBuilder sb = new StringBuilder("销售额 TOP " + topN + " 产品:\n");
        for (int i = 0; i < productTotals.size(); i++) {
            var entry = productTotals.get(i);
            sb.append(String.format("%d. %s - ¥%.2f\n", i + 1, entry.getKey(), entry.getValue()));
        }
        return sb.toString();
    }
}

3.7 邮件发送工具

package com.luoboyun.tool.tool;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;

@Component
public class EmailTool {

    private final JavaMailSender mailSender;

    // 如果你的环境没有配置真实邮件服务,可以用这个构造器让程序不崩溃
    public EmailTool(@org.springframework.beans.factory.annotation.Autowired(required = false) JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }

    @Tool(name = "sendEmail", value = "发送邮件到指定收件人。如果邮件服务不可用,会模拟发送")
    public String sendEmail(
            @P("收件人邮箱地址") String to,
            @P("邮件标题") String subject,
            @P("邮件正文内容") String body) {

        if (mailSender == null) {
            return "【模拟发送】邮件服务未配置,模拟发送成功。\n收件人:" + to
                    + "\n标题:" + subject + "\n内容:" + body;
        }

        try {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setTo(to);
            message.setSubject(subject);
            message.setText(body);
            mailSender.send(message);
            return "邮件已成功发送到 " + to;
        } catch (Exception e) {
            return "邮件发送失败:" + e.getMessage();
        }
    }
}

3.8 AI 配置和助手接口

package com.luoboyun.tool.config;

import com.luoboyun.tool.tool.DataQueryTool;
import com.luoboyun.tool.tool.EmailTool;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AiConfig {

    @Bean
    public ChatLanguageModel chatLanguageModel() {
        String apiKey = System.getenv("OPENAI_API_KEY");
        if (apiKey == null || apiKey.isBlank()) {
            throw new IllegalStateException(
                    "请设置环境变量 OPENAI_API_KEY,或修改 application.yml 中的 api-key");
        }
        return OpenAiChatModel.builder()
                .apiKey(apiKey)
                .modelName("gpt-4o")
                .temperature(0.3)
                .maxTokens(2000)
                .build();
    }

    @Bean
    public Assistant assistant(ChatLanguageModel model,
                                DataQueryTool dataQueryTool,
                                EmailTool emailTool) {
        return AiServices.builder(Assistant.class)
                .chatLanguageModel(model)
                .tools(dataQueryTool, emailTool)
                .build();
    }

    /**
     * AI助手接口,LangChain4j 会自动生成代理实现
     */
    public interface Assistant {

        @SystemMessage("""
                你是一个销售数据分析助手。你可以:
                1. 使用 querySalesData 工具查询销售数据
                2. 使用 queryTopProducts 工具查询销售额排名
                3. 使用 sendEmail 工具发送邮件
                
                规则:
                - 查询数据后,用简洁的格式呈现给用户
                - 金额使用人民币格式,保留两位小数
                - 如果用户要求发邮件,先准备好完整的邮件正文再发送
                - 不要编造任何数据,只使用查询工具返回的真实数据
                """)
        String chat(String userMessage);
    }
}

3.9 Controller

package com.luoboyun.tool.controller;

import com.luoboyun.tool.config.AiConfig;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/assistant")
public class AssistantController {

    private final AiConfig.Assistant assistant;

    public AssistantController(AiConfig.Assistant assistant) {
        this.assistant = assistant;
    }

    @PostMapping("/chat")
    public Map<String, String> chat(@RequestBody Map<String, String> request) {
        String userMessage = request.getOrDefault("message", "");
        if (userMessage.isBlank()) {
            return Map.of("reply", "请提供要问的问题。");
        }
        String reply = assistant.chat(userMessage);
        return Map.of("reply", reply);
    }

    @GetMapping("/health")
    public Map<String, String> health() {
        return Map.of("status", "ok");
    }
}

到这里,整个项目就完整了。启动后在数据库里会自动创建10条演示数据。测试方式:

# 查销售数据
curl -X POST http://localhost:8080/assistant/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"帮我查询最近一周销售额最高的三个产品"}'

# 查数据并发送邮件
curl -X POST http://localhost:8080/assistant/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"查最近一周的销售数据,把结果整理成表格,发邮件给boss@company.com"}'

大模型会自动判断该调哪个工具、用哪些参数,然后把结果组织成人话回复给你。你没写一行"如果消息包含"查"字就调方法X"的 if-else,全靠大模型自己决策。

四、踩坑经验和最佳实践

在实际项目中使用 LangChain4j Tool Calling 时,我遇到过不少坑,整理出来供参考。

4.1 工具描述写不好,大模型就乱调

@Tool 注解里的 value@P 注解里的描述,直接决定了大模型能不能正确使用你的工具。描述太笼统(比如"查询数据"),大模型不知道什么时候该调;描述太技术化(比如"执行SQL聚合查询"),大模型理解有偏差。

最佳实践:用自然语言写工具描述,像是对一个不熟悉代码的新同事说明这个工具能干什么。比如不要写"从数据库查询销售记录并按产品聚合",而写"查询指定时间范围内销售额最高的产品列表"。@P 的描述要写明格式要求,比如"日期格式yyyy-MM-dd",否则大模型可能传"2026年8月6日"这种你解析不了的字符串。

4.2 工具参数类型要简单

大模型返回的 arguments 是 JSON 字符串,LangChain4j 会自动反序列化为你方法的参数类型。但复杂对象(嵌套 POJO)的序列化成功率很低。工具参数尽量用 String、int、double 等基础类型。如果是日期,用 String 接收再手动解析,不要直接用 LocalDate——有些模型兼容的 JSON Schema 生成器处理 Java 时间类型时会出错。

4.3 工具执行要加超时和异常处理

大模型调用工具后,如果工具方法执行时间很长(比如查大数据量的SQL),整个对话就会卡住。LangChain4j 本身没有给单个工具调用设置超时的机制,需要自己在工具方法里加:

@Tool(name = "querySalesData")
public List<SalesRecord> querySalesData(String startDate, String endDate) {
    // 加超时保护
    CompletableFuture<List<SalesRecord>> future = CompletableFuture.supplyAsync(
            () -> jdbcTemplate.query(...),
            Executors.newSingleThreadExecutor()
    );
    try {
        return future.get(5, TimeUnit.SECONDS);
    } catch (TimeoutException e) {
        return List.of(); // 返回空列表,让大模型告诉用户查询超时
    }
}

4.4 工具调用循环可能无限

如果你的工具描述有歧义,或者大模型对你的工具返回结果不满意,它可能进入无限循环——调工具→返回结果→再调同一个工具→再返回结果。默认最多15次迭代,但15次也够浪费很多 token 了。

防御策略:一是给每个工具加上幂等性保证,同一次会话里相同参数的调用应该返回缓存结果;二是在 system prompt 里加限制:“如果同一个工具已经被调用过一次且结果明确,不要重复调用”。

4.5 安全性——工具函数的权限边界

给大模型暴露工具时要非常谨慎。如果把"执行Shell命令"或"直接操作数据库"这类工具暴露出去,恶意提示词可能通过大模型间接执行危险操作。这叫做"提示词注入攻击"。

防御策略:工具方法内部必须做权限校验。比如发邮件工具要做白名单限制,只允许发送到公司域名。数据查询工具要用只读数据库账号。永远不要暴露能执行任意命令的工具。

@Tool(name = "sendEmail", value = "发送邮件")
public String sendEmail(String to, String subject, String body) {
    // 安全校验:只允许发送到公司域名
    if (!to.endsWith("@company.com")) {
        return "安全限制:只能发送邮件到 @company.com 域名下的邮箱";
    }
    // 实际发送逻辑...
}

五、性能对比和技术选型

在 Java 生态做 Tool Calling,目前有三个主流选择:LangChain4j、Spring AI、直接调用 SDK。

维度LangChain4jSpring AI直接调OpenAI SDK
工具定义@Tool 注解,零JSON@Tool 注解,类似手写JSON Schema
工具执行循环自动,可配最大次数自动,可配最大次数自己写while循环
Spring Boot集成有独立starter原生集成
流式Tool Calling支持支持需自己实现
多模型支持丰富(20+)较多(10+)单模型
记忆管理ChatMemory内置Conversation API
学习曲线

选型建议

  • 如果你的项目已经是 Spring Boot + LangChain4j 体系,直接用 LangChain4j 的 Tool Calling,生态最完整
  • 如果你用的是 Spring AI 全家桶(Spring AI Alibaba 等),Spring AI 的 Tool Calling 也够用,但功能没有 LangChain4j 丰富
  • 如果你只需要一两个简单的工具调用,不想引入额外依赖,直接调 OpenAI SDK 然后在代码里写判断逻辑也可以——但要注意,随着工具增多,这个方案会迅速失控

性能数据(实测,gpt-4o,单次工具调用):

  • 无工具调用:平均响应 1.2 秒
  • 单次工具调用:平均响应 2.8 秒(含工具执行时间 0.5 秒)
  • 两次工具调用链:平均响应 4.5 秒

工具调用增加的延迟主要来自额外的 API 往返,Java 方法本身的执行时间占比很小。如果对延迟敏感,可以考虑用流式返回——用户会先看到"正在查询数据…"的提示,体感上不那么卡。

六、总结

LangChain4j 的 Tool Calling 本质上是把 OpenAI 的 Function Calling 协议封装成了 Java 开发者的舒适区——@Tool 注解、自动代理、循环执行,让你不用写一行 JSON Schema,也不用关心 API 往返的细节。

真正的价值不在于"少写几行代码",而在于架构层面的简化:原来需要写大量 if-else 做意图识别和任务调度的场景,现在只需要把业务逻辑写成带注解的工具方法,大模型自己决定什么时候调用、怎么组合。这在客服系统、数据查询助手、自动化工作流等场景里特别实用。

但也要清醒认识到,Tool Calling 不是银弹:

  • 工具描述的质量直接影响成功率,需要反复调优
  • 安全性必须从工具层做权限控制,不能依赖 prompt 约束
  • 大模型不是编译器,它可能传错参数、漏调工具、重复调用,需要防御性编程

今天的完整代码可以直接跑——clone 下来配好 OpenAI API Key,启动 Spring Boot,一个能查数据库能发邮件的 AI 助手就跑起来了。实际项目中把 H2 换成 MySQL,邮件换成内部 API,就是一个生产可用的智能助手原型。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会编程的吕洞宾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值