springai 简易聊天机器人设计

1. 引言

**Spring AI Alibaba 开源项目基于 Spring AI 构建,是阿里云通义系列模型及服务在 Java AI 应用开发领域的最佳实践,提供高层次的 AI API 抽象与云原生基础设施集成方案,帮助开发者快速构建 AI 应用。**

![image-20241112230716389](https://picgo-clouddz.oss-cn-fuzhou.aliyuncs.com/note/image-20241112230716389.png)

2. 效果展示

![20241112_223517](https://picgo-clouddz.oss-cn-fuzhou.aliyuncs.com/note/20241112_223517.gif)

**源代码 **[simple-chatboot: 一个简易的聊天机器人,使用spring ai aibaba (gitee.com)](https://gitee.com/DailySmileStart/simple-chatboot)

3. 代码实现

**依赖**

```

<dependency>

<groupId>com.alibaba.cloud.ai</groupId>

<artifactId>spring-ai-alibaba-starter</artifactId>

<version>1.0.0-M2</version>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

```

**注意:由于 spring-ai 相关依赖包还没有发布到中央仓库,如出现 spring-ai-core 等相关依赖解析问题,请在您项目的 pom.xml 依赖中加入如下仓库配置。**

```

<repositories>

<repository>

<id>spring-milestones</id>

<name>Spring Milestones</name>

<url><https://repo.spring.io/milestone\></url>

<snapshots>

<enabled>false</enabled>

</snapshots>

</repository>

</repositories>

@SpringBootApplication

public class SimpleChatbootApplication {

public static void main(String[] args) {

SpringApplication.run(SimpleChatbootApplication.class, args);

}

}

```

**配置自定义ChatClient**

```

import org.springframework.ai.chat.client.ChatClient;

import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;

import org.springframework.ai.chat.memory.ChatMemory;

import org.springframework.ai.chat.memory.InMemoryChatMemory;

import org.springframework.ai.chat.model.ChatModel;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class ChatClientConfig {

static ChatMemory chatMemory = new InMemoryChatMemory();

@Bean

public ChatClient chatClient(ChatModel chatModel) {

return ChatClient.builder(chatModel)

.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))

.build();

}

}

```

**controller类**

```

import ch.qos.logback.core.util.StringUtil;

import com.hbduck.simplechatboot.demos.function.WeatherService;

import org.springframework.ai.chat.client.ChatClient;

import org.springframework.ai.chat.model.ChatModel;

import org.springframework.ai.chat.model.ChatResponse;

import org.springframework.http.MediaType;

import org.springframework.http.codec.ServerSentEvent;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Flux;

import java.util.UUID;

import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;

import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;

@RestController

@RequestMapping("/ai")

public class ChatModelController {

private final ChatModel chatModel;

private final ChatClient chatClient;

public ChatModelController(ChatModel chatModel, ChatClient chatClient) {

this.chatClient = chatClient;

this.chatModel = chatModel;

}

@GetMapping("/stream")

public String stream(String input) {

StringBuilder res = new StringBuilder();

Flux<ChatResponse> stream = chatModel.stream(new Prompt(input));

stream.toStream().toList().forEach(resp -> {

res.append(resp.getResult().getOutput().getContent());

});

return res.toString();

}

@GetMapping(value = "/memory", produces = MediaType.TEXT_EVENT_STREAM_VALUE)

public Flux<ServerSentEvent<String>> memory(@RequestParam("conversantId") String conversantId, @RequestParam("input") String input) {

if (StringUtil.isNullOrEmpty(conversantId)) {

conversantId = UUID.randomUUID().toString();

}

String finalConversantId = conversantId;

Flux<ChatResponse> chatResponseFlux = chatClient

.prompt()

.function("getWeather", "根据城市查询天气", new WeatherService())

.user(input)

.advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, finalConversantId)

.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))

.stream().chatResponse();

return Flux.concat(

// First event: send conversationId

Flux.just(ServerSentEvent.<String>builder()

.event("conversationId")

.data(finalConversantId)

.build()),

// Subsequent events: send message content

chatResponseFlux.map(response -> ServerSentEvent.<String>builder()

.id(UUID.randomUUID().toString())

.event("message")

.data(response.getResult().getOutput().getContent())

.build())

);

}

}

```

**配置文件**

```

server:

port: 8000

spring:

thymeleaf:

cache: true

check-template: true

check-template-location: true

content-type: text/html

enabled: true

encoding: UTF-8

excluded-view-names: ''

mode: HTML5

prefix: classpath:/templates/

suffix: .html

ai:

dashscope:

api-key: ${AI_DASHSCOPE_API_KEY}

chat:

client:

enabled: false

```

**前端页面**

```

<!DOCTYPE html>

<html>

<head>

<title>AI Chat Bot</title>

<style>

#chatBox {

height: 400px;

border: 1px solid #ccc;

overflow-y: auto;

margin-bottom: 10px;

padding: 10px;

}

.message {

margin: 5px;

padding: 5px;

}

.user-message {

background-color: #e3f2fd;

text-align: right;

}

.bot-message {

background-color: #f5f5f5;

white-space: pre-wrap; /* 保留换行和空格 */

word-wrap: break-word; /* 长单词换行 */

}

</style>

</head>

<body>

<h1>AI Chat Bot</h1>

<div id="chatBox"></div>

<input type="text" id="userInput" placeholder="Type your message..." style="width: 80%">

<button οnclick="sendMessage()">Send</button>

<script>

let conversationId = null;

let currentMessageDiv = null;

function addMessage(message, isUser) {

const chatBox = document.getElementById('chatBox');

const messageDiv = document.createElement('div');

messageDiv.className = `message ${isUser ? 'user-message' : 'bot-message'}`;

messageDiv.textContent = message;

chatBox.appendChild(messageDiv);

chatBox.scrollTop = chatBox.scrollHeight;

return messageDiv;

}

async function sendMessage() {

const input = document.getElementById('userInput');

const message = input.value.trim();

if (message) {

addMessage(message, true);

input.value = '';

// Create bot message container

currentMessageDiv = addMessage('', false);

const eventSource = new EventSource(`/ai/memory?conversantId={conversationId \|\| ''}\&input={encodeURIComponent(message)}`);

eventSource.onmessage = function(event) {

const content = event.data;

if (currentMessageDiv) {

currentMessageDiv.textContent += content;

}

};

eventSource.addEventListener('conversationId', function(event) {

if (!conversationId) {

conversationId = event.data;

}

});

eventSource.onerror = function(error) {

console.error('SSE Error:', error);

eventSource.close();

if (currentMessageDiv && currentMessageDiv.textContent === '') {

currentMessageDiv.textContent = 'Sorry, something went wrong!';

}

};

// Close the connection when the response is complete

eventSource.addEventListener('complete', function(event) {

eventSource.close();

currentMessageDiv = null;

});

}

}

// Allow sending message with Enter key

document.getElementById('userInput').addEventListener('keypress', function(e) {

if (e.key === 'Enter') {

sendMessage();

}

});

</script>

</body>

</html>

相关推荐
南山十一少4 分钟前
Spring Security+JWT+Redis实现项目级前后端分离认证授权
java·spring·bootstrap
427724002 小时前
IDEA使用git不提示账号密码登录,而是输入token问题解决
java·git·intellij-idea
chengooooooo2 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
李长渊哦2 小时前
常用的 JVM 参数:配置与优化指南
java·jvm
计算机小白一个2 小时前
蓝桥杯 Java B 组之设计 LRU 缓存
java·算法·蓝桥杯
Tirzano2 小时前
springsecurity自定义认证
spring boot·spring
Archie_IT4 小时前
DeepSeek R1/V3满血版——在线体验与API调用
人工智能·深度学习·ai·自然语言处理
南宫生5 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
计算机毕设定制辅导-无忧学长5 小时前
Maven 基础环境搭建与配置(一)
java·maven
bing_1586 小时前
简单工厂模式 (Simple Factory Pattern) 在Spring Boot 中的应用
spring boot·后端·简单工厂模式