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>

相关推荐
ss2732 分钟前
2025元旦源码免费送
spring boot·微信小程序·开源
2401_871151074 分钟前
1月第三讲:Java子线程无法获取Attributes的解决方法
java·开发语言
武昌库里写JAVA4 分钟前
mysql乱码、mysql数据中文问号
java·开发语言·spring boot·学习·课程设计
山山而川粤7 分钟前
记忆旅游系统|Java|SSM|VUE| 前后端分离
java·开发语言·后端·学习·mysql
AI大模型_学习君7 分钟前
基于 InternLM 和 LangChain 搭建你的知识库
vscode·深度学习·microsoft·ai·langchain·编辑器·大模型应用
顾北辰208 分钟前
监督学习及其方法
java·机器学习
杨荧18 分钟前
【开源免费】基于Vue和SpringBoot的网上商城系统(附论文)
前端·javascript·jvm·vue.js·spring boot·spring cloud·开源
孙尚香蕉21 分钟前
Spring ----深入理解AOP(面向切面编程)
java·spring
落霞与孤鹭齐飞。。21 分钟前
家教系统|Java|SSM|VUE| 前后端分离
java·mysql·毕业设计·课程设计
一起喝芬达201026 分钟前
Spring Boot中使用Zookeeper实现分布式锁的案例
spring boot·分布式·java-zookeeper