有了 Spring AI ,Java 开发AI应用也就简单多了


前言

Spring 官方自从发布了 Spring AI,AI 界的门槛儿算是被彻底踹飞了!为什么?因为这就意味着整天只会 CRUD 的 Javaer 们也能开发AI应用了,而且简单到让你怀疑人生。那么本文就基于 Spring AI Alibaba 开发一个简单的 AI 对话应用。

效果展示

Spring AI 实现的AI对话

实现步骤

基于 Spring AI Alibaba 实现这样一个AI对话非常简单,只需要几行代码就可以完成。可以跟着下面的步骤操作:

创建一个 SpringBoot 项目

Spring AI Alibaba 提供了 SpringBoot starter。所以,第一步就是创建一个SpringBoot项目引入依赖以及配置。

pom.xml文件添加spring-ai-alibaba-starter依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter</artifactId>
        <version>1.0.0-M3.1</version>
    </dependency>
</dependencies>

application.yml添加

server:
  port: 8080

spring:
  application:
    name: spring-ai-alibaba-chat
  ai:
    dashscope:
      api-key: xxxx

配置中的api-key是阿里云百炼大模型平台的凭证,AI的实现最终会调用该模型。

api-key 申请

这里有两个事项需要注意:

  1. Spring AI Alibaba 基于 Spring Boot 3.x 开发,所以JDK 版本要求为 17 及以上。
  2. spring-ai 相关依赖包还没有发布到中央仓库,本地 Maven 仓库要添加以下配置。
<repositories>
  <repository>
    <id>spring-milestones</id>
    <name>Spring Milestones</name>
    <url>https://repo.spring.io/milestone</url>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
  </repository>
</repositories>

编写与大模型交互的 Controller

项目创建成功之后,只需要写一个Controller就可以实现与AI对话,代码如下:

@RestController
@RequestMapping("/ai")
public class AIChatController {

    private final ChatClient chatClient;
    
    public AIChatController(ChatClient.Builder builder) {
        this.chatClient = builder
                .build();
    }


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

代码中的ChatClient类似于应用程序开发中的服务层,只不过这里由spring-ai-alibaba-starter自动初始化了实例,可以直接提供 AI 服务,最终会调用百炼大模型。

此时就可以启动应用,通过浏览器访问localhost:8080/ai/chat?message= 进行对话。

编写通过页面进行对话的 html

为了直观的展示效果,简单实现一个前端和接口交互,通过页面进行AI对话。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <script src="js/marked.min.js"></script>
    <title></title>
    <style>
        body {
            background-color: #f8f9fa;
            font-family: Arial, sans-serif;
        }

        .container {
            margin: 50px auto;
            width: 800px;
            background-color: #fff;
            padding: 20px;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        }

        h1 {
            text-align: center;
            margin-bottom: 30px;
        }

        label {
            display: block;
            margin-bottom: 10px;
            color: #333;
        }

        input[type="text"] {
            width: 85%;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 3px;
        }

        input[type="submit"] {
            background-color: #2ecc71;
            color: #fff;
            border: none;
            padding: 10px 20px;
            border-radius: 3px;
            cursor: pointer;
            width: 10%;
        }

        .chat-box {
            width: 100%;
            height: 500px;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 3px;
            overflow-y: scroll;
        }

        .message {
            margin-bottom: 10px;
            padding: 10px;
            background-color: #f1f1f1;
            border-radius: 3px;
        }

        .user-message {
            background-color: #2ecc71;
            color: #fff;
        }

        .bot-message {
            background-color: #e6aa6b;
            color: #fff;
        }

        .loader {
            text-align: center;
        }

        .loader::after {
            content: "";
            display: inline-block;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            border: 2px solid #ccc;
            border-top-color: #2ecc71;
            animation: spin 1s infinite linear;
        }

        @keyframes spin {
            to {
                transform: rotate(360deg);
            }
        }
    </style>
</head>
<body>
<div class="container">
    <h1>AI 对话</h1>
    <form id="form" style="width: 47%;position: absolute;bottom: 150px;margin-left:15px">
        <input type="text" id="message" name="message" placeholder="输入你的问题">
        <input type="submit" value="发送">
    </form>
    <br>
    <div id="loader" class="loader" style="display: none;"></div>
    <div id="chat-box" class="chat-box"></div>
</div>

<script>
    var loader = document.getElementById("loader");

    document.getElementById("form").addEventListener("submit", function(event) {
        event.preventDefault();

        var messageInput = document.getElementById("message");
        var message = messageInput.value;
        messageInput.value = "";

        var chatBox = document.getElementById("chat-box");

        var userMessage = document.createElement("div");
        userMessage.className = "message";
        userMessage.textContent = "我: " + message;
        chatBox.appendChild(userMessage);
        chatBox.scrollTop = chatBox.scrollHeight;

        loader.style.display = "block";

        var xhr = new XMLHttpRequest();
        xhr.open("GET", "http://localhost:8080/ai/chat?message=" + encodeURIComponent(message), true);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                loader.style.display = "none";

                if (xhr.status === 200) {
                    var response = xhr.responseText;

                    var botMessage = document.createElement("div");
                    botMessage.className = "message bot-message";

                    var botMessageText = document.createElement("span");
                    botMessageText.className = "message-text";
                    botMessage.appendChild(botMessageText);
                    botMessageText.innerHTML = marked.marked(response);

                    chatBox.appendChild(botMessage);
                    chatBox.scrollTop = chatBox.scrollHeight;
                } else if (xhr.status === 400) {
                    var error = JSON.parse(xhr.responseText);

                    var errorMessage = document.createElement("div");
                    errorMessage.className = "message bot-message";
                    errorMessage.textContent = "Bot: " + error.message;
                    chatBox.appendChild(errorMessage);
                    chatBox.scrollTop = chatBox.scrollHeight;
                } else {
                    var errorMessage = document.createElement("div");
                    errorMessage.className = "message bot-message";
                    errorMessage.textContent = "Bot: Failed to connect to the backend service. Please make sure the backend service is running.";
                    chatBox.appendChild(errorMessage);
                    chatBox.scrollTop = chatBox.scrollHeight;
                }
            }
        };

        xhr.onloadstart = function() {
            loader.style.display = "block";
        };

        xhr.onloadend = function() {
            loader.style.display = "none";
        };

        xhr.send();
    });
</script>
</body>
</html>

js 引入的 marked.min.js 可以去GitHub或者联系我获取。

至此,就实现了文中开头的AI对话效果。

需要了解的基本知识

上面的示例其实就是一个SpringBoot项目,对于Javaer肯定不陌生。而对于没有人工智能相关背景的人来说,如果想要基于 Spring AI 开发一个AI应用产品,还是需要了解一些基本概念的。

  • AI 模型:AI 模型是旨在处理和生成信息的算法,通常模仿人类的认知功能。我们现在基本上听到的大模型都属于生成式AI模型,例如ChatGPT、文心一言、通义千问、混元、豆包等。
  • Prompt:提示词作为生成式大模型的输入,可以指导模型生成特定的输出。如果搭建过智能体就会知道,创建一个智能体首先要设置一些提示词。
  • Token:token作为 AI 模型工作原理的基石,输入时,模型将单词转换为token,输出时,将token转换回单词。在现在的AI模型背景下,token就相当于金钱,一次调用的费用就越高。
  • 模型的认知:一个模型的诞生需要在海量的训练集中进行学习,所以它的认知中没有未来的知识。如果想让模型对现有知识产生认知,有微调、检索增强生成 (RAG)、函数调用这几种方式。

对于本文示例中来说,每一次的对话就是模型的一次token输入和输出。

实现有记忆的对话

如果运行了上面的示例,并且进行了几轮对话,会发现大模型并不会理解上下文,也无法实现连贯的对话。

就如上面所说的“模型的认知”,大模型不会记录对话,所以不会产生上下文关联。例如我接下来的问答中,并没有按照我的提问生成“冒险的”故事,只是“冒险的”进行了说明。

Spring AI 实现的AI对话

那如何让大模型能够追踪、理解并利用先前对话上下文的能力?

spring-ai 支持基于chat memory的对话记忆。在上面的代码中,只需要在构建chatClient时添加两行代码。

private ChatMemory chatMemory = new InMemoryChatMemory();                                            
                                                                                                     
public AIChatController(ChatClient.Builder builder) {                                                
    this.chatClient = builder                                                                        
            .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory, UUID.randomUUID().toString(), 10))
            .build();                                                                                
}                                                                                                    

UUID 会作为此次对话的唯一标识,ChatMemory会将之前的对话内容记录下来,每次对模型的输入就是历史的对话记录,最终实现对话记忆。

这种做法最终会影响使用大模型的成本,就如上面所说,输入和输出都会影响总token数量,而token就相当于金钱。

最后看一下对话记忆效果

总结

Spring AI解决了AI集成的核心难题:即无缝连接企业数据、API与AI模型,同时也简化了Java 开发人员对AI应用的编程复杂度,还提供检索增强生成 (RAG)、函数调用等关键功能。对于Java 开发人员来讲,通过 Spring AI 可以探索更多的 AI 应用场景。

<think>嗯,用户想知道如何用Java集成通义千问实现轮对话,需要示例代码和API集成。首先,我得回忆一下通义千问的API文档,看看他们的接口是什么样的。通常,这类API需要API密钥,通过HTTP请求发送,可能使用WebSocket或者普通的RESTful接口。轮对话的话,可能需要维护一个对话历史,每次请求都带上之前的上下文。 用户提到Java,所以需要提供一个Java的示例。可能需要用到OkHttp或者Apache HttpClient这些库来处理HTTP请求。另外,数据格式可能是JSON,所以还需要JSON处理库,比如Gson或者Jackson。 首先,应该创建一个服务类,比如ThousandQuestionsClient,用来封装API的交互逻辑。这个类需要初始化API密钥和接口地址。然后,每次调用的时候,构造请求体,包含当前的输入和之前的对话历史。发送POST请求,解析返回的JSON,提取回复内容,并更新对话历史。 需要注意的是,轮对话的关键在于维护对话状态,所以客户端需要保存之前的对话记录。可能还需要处理一些异常情况,比如网络错误或者API返回的错误信息。 然后,用户可能需要示例代码。代码结构应该包括构建请求、发送请求、处理响应这几个步骤。在Java中,使用OkHttp的话,需要构建RequestBody,设置正确的Content-Type为application/json,添加Authorization头,里面包含API密钥。 响应解析部分,需要从JSON中提取出回复内容。例如,通义千问的响应可能有一个output字段,里面包含回答。同时,可能还有其他字段,比如请求ID或者状态码,需要处理。 另外,考虑到用户可能对如何维护对话历史有疑问,应该在代码中展示如何将每次的输入和输出添加到历史列表中。这样,每次请求都会带上所有历史记录,实现轮对话的上下文保持。 可能还需要考虑性能优化,比如使用单例的HTTP客户端,或者异步请求处理,但这些可能超出了示例代码的范围。用户的问题主要是基础集成,所以保持代码简单明了更好。 最后,要检查是否有引用到站内的资料。根据用户提供的引用,需要引用[^1]和[^2]。比如,ThousandQuestionsClient这个类名来自引用,而Chainlit集成Langchain的部分来自引用,但用户的问题是关于Java的,可能引用[^2]中的链(Chains)概念,说明工作流模块化,但在代码示例中可能不需要直接涉及,但可以在解释时提到。 总结步骤:创建客户端类,初始化配置,维护对话历史,构造请求体,发送请求,解析响应,更新历史。确保代码符合Java标准,使用常见的库,代码注释清晰,用户容易理解。</think>以下是使用Java集成通义千问实现轮对话的示例方案: ### 1. 创建HTTP客户端服务类 ```java import okhttp3.*; public class ThousandQuestionsClient { private static final String API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"; private final OkHttpClient client = new OkHttpClient(); private final String apiKey; private final List<Message> conversationHistory = new ArrayList<>(); public ThousandQuestionsClient(String apiKey) { this.apiKey = apiKey; } public String sendMessage(String userInput) throws IOException { // 添加用户输入到对话历史 conversationHistory.add(new Message("user", userInput)); // 构建请求体 RequestBody body = RequestBody.create( buildRequestBody(), MediaType.parse("application/json") ); Request request = new Request.Builder() .url(API_URL) .post(body) .addHeader("Authorization", "Bearer " + apiKey) .build(); try (Response response = client.newCall(request).execute()) { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); String responseBody = response.body().string(); String assistantReply = parseResponse(responseBody); // 添加助手回复到对话历史 conversationHistory.add(new Message("assistant", assistantReply)); return assistantReply; } } private String buildRequestBody() { // 构建包含对话历史的请求体 JSONObject messages = new JSONObject(); // 此处需要根据实际API要求构建JSON结构 return messages.toString(); } private String parseResponse(String jsonResponse) { // 解析API返回的JSON数据 JSONObject responseObj = new JSONObject(jsonResponse); return responseObj.getJSONObject("output").getString("text"); } static class Message { String role; String content; Message(String role, String content) { this.role = role; this.content = content; } } } ``` ### 2. 实现轮对话 ```java public class ChatApplication { public static void main(String[] args) { ThousandQuestionsClient client = new ThousandQuestionsClient("your-api-key-here"); try (Scanner scanner = new Scanner(System.in)) { while (true) { System.out.print("用户:"); String input = scanner.nextLine(); if ("exit".equalsIgnoreCase(input)) break; String response = client.sendMessage(input); System.out.println("助手:" + response); } } catch (IOException e) { e.printStackTrace(); } } } ``` 关键实现要点: 1. **对话历史维护**:通过`conversationHistory`列表保存完整的对话上下文 2. **请求构建**:每次请求需要包含完整的对话历史记录 3. **状态管理**:客户端类封装了对话状态,实现轮对话的连续性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

王二蛋!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值