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>

相关推荐
桦说编程1 天前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t1 天前
ZIP工具类
java·zip
lang201509281 天前
Spring Boot优雅关闭全解析
java·spring boot·后端
pengzhuofan1 天前
第10章 Maven
java·maven
百锦再1 天前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说1 天前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多1 天前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring
百锦再1 天前
对前后端分离与前后端不分离(通常指服务端渲染)的架构进行全方位的对比分析
java·开发语言·python·架构·eclipse·php·maven
DokiDoki之父1 天前
Spring—注解开发
java·后端·spring
CodeCraft Studio1 天前
【能源与流程工业案例】KBC借助TeeChart 打造工业级数据可视化平台
java·信息可视化·.net·能源·teechart·工业可视化·工业图表