import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fyy.config.PersistentChatMemoryStore;
import com.fyy.entity.ApiResponse;
import com.fyy.entity.ChatResponse;
import com.fyy.request.TChatRequest;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@RestController
public class ChatController {
@Value("${langchain4j.open-ai.chat-model.api-key}")
private String apiKey;
@Value("${langchain4j.open-ai.chat-model.model-name}")
private String modelName;
// 定义 AI 助手接口
public interface Assistant {
String chat(@dev.langchain4j.service.MemoryId int memoryId, @dev.langchain4j.service.UserMessage String userMessage);
}
// 构建 ChatMemoryProvider
private final ChatMemoryProvider chatMemoryProvider = memoryId -> MessageWindowChatMemory.builder()
.id(memoryId)
.maxMessages(10)
.chatMemoryStore( new PersistentChatMemoryStore())
.build();
// 构建 ChatModel
private ChatModel createChatModel() {
return OpenAiChatModel.builder()
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.apiKey(apiKey)
.modelName(modelName)
.temperature(0.5)
.topP(0.9)
.maxTokens(512)
.timeout(Duration.ofSeconds(30))
.logRequests(true)
.logResponses(true)
.build();
}
@PostMapping("/chat")
public ApiResponse chat(@RequestBody TChatRequest request) throws JsonProcessingException {
// Step 1: 创建 ChatModel
ChatModel model = createChatModel();
// Step 2: 获取与 memoryId 对应的 ChatMemory
ChatMemory chatMemory = chatMemoryProvider.get(request.getMemoryId());
// Step 3: 构建角色设定模板
String templateString = "你的名字是{{name}},性别是{{gender}},年龄是{{age}}岁。\n" +
"加上你的开场白介绍,只需要首次聊天介绍一次,每次发消息要有思考的时间,这个时间是随机的2-10s,话多的话自动延迟,你的性格是{{personality}},喜欢{{hobbies}},以第一人称和任何人聊天。只需要用户问你的问题,可以适当拒绝,不用一味讨好用户," +
"什么动词描述,心里想法,不需要表达,\n" +
"请用自然、亲切的语气与用户交谈,保持角色设定不变。不需要问想聊些什么的类似问题\n\n" +
"用户说:{{input}}";
// 暴躁反派角色模板
// String templateString = "你的名字是{{name}},性别是{{gender}},年龄是{{age}}岁。\n" +
// "开场白就是介绍你的身份信息,你的性格是{{personality}},喜欢{{hobbies}}。不用描述肢体语言,以第一人称和任何人聊天,什么动词描述,心里想法,不需要表达\n" +
// "你是地下黑市的统治者,拥有绝对权威。\n" +
// "说话时保持威胁性语气,用短促有力的句子,必要时使用暴力威胁。\n" +
// "对弱者毫不留情,对挑战者充满敌意。\n\n" +
// "用户说:{{input}}";
PromptTemplate promptTemplate = PromptTemplate.from(templateString);
// Step 4: 设置角色参数
Map<String, Object> variables = new HashMap<>();
variables.put("name", "小夏");
variables.put("gender", "女");
variables.put("age", 25);
variables.put("personality", "开朗、善解人意、有点害羞、没谈过男朋友、好相处、乐于助人");
variables.put("hobbies", "旅行、摄影、听音乐、打篮球");
variables.put("input", request.getUserMessage());
// Map<String, Object> variables = new HashMap<>();
// variables.put("name", "黑曜");
// variables.put("gender", "男");
// variables.put("age", 32);
// variables.put("personality", "暴躁易怒、心狠手辣、极度自负、睚眦必报、蔑视弱者");
// variables.put("hobbies", "操控人心、收集武器、摧毁对手、权力斗争");
// variables.put("input", request.getUserMessage());
// Step 5: 生成 Prompt 并加入 ChatMemory
Prompt prompt = promptTemplate.apply(variables);
chatMemory.add(prompt.toUserMessage());
// Step 6: 调用模型并获取回复
AiMessage aiMessage = model.chat(chatMemory.messages()).aiMessage();
chatMemory.add(aiMessage); // 将 AI 回复加入记忆
// Step 8: 返回响应
return ApiResponse.success(aiMessage.text());
}
}
package com.fyy.entity;
public class ApiResponse<T> {
private Integer code; // 状态码
private String message; // 响应消息
private T data; // 响应数据,使用泛型
private Long timestamp; // 时间戳
// 构造函数
public ApiResponse() {
this.timestamp = System.currentTimeMillis();
}
public ApiResponse(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
// 成功响应的静态方法
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data);
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(200, message, data);
}
// 错误响应的静态方法
public static <T> ApiResponse<T> error(Integer code, String message) {
return new ApiResponse<>(code, message, null);
}
// getter 和 setter 方法
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
}
@Data
public class TChatRequest {
private String userMessage;
private int memoryId;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>AI Chat</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f2f5;
}
.chat-container {
max-width: 800px;
margin: 0 auto;
height: 100vh;
display: flex;
flex-direction: column;
}
.chat-header {
background-color: #f6f6f6;
padding: 10px 20px;
border-bottom: 1px solid #ddd;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.message {
margin-bottom: 20px;
display: flex;
flex-direction: column;
}
.message.user {
align-items: flex-end;
}
.message.ai {
align-items: flex-start;
}
.message-content {
max-width: 70%;
padding: 10px 15px;
border-radius: 15px;
margin: 5px 0;
word-wrap: break-word;
}
.user .message-content {
background-color: #95ec69;
}
.ai .message-content {
background-color: white;
}
.chat-input {
background-color: #f6f6f6;
padding: 20px;
border-top: 1px solid #ddd;
}
.input-container {
display: flex;
gap: 10px;
}
textarea {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
resize: none;
height: 60px;
}
button {
padding: 10px 20px;
background-color: #07c160;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
height: 82px;
}
button:hover {
background-color: #06ae56;
}
button:disabled {
background-color: #ccc;
}
#memoryId {
width: 60px;
padding: 5px;
margin-right: 10px;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<h2 style="margin: 0;">AI 聊天室</h2>
<div style="margin-top: 10px;">
<label for="memoryId">Memory ID:</label>
<input type="number" id="memoryId" value="1" min="1" />
</div>
</div>
<div class="chat-messages" id="chatMessages"></div>
<div class="chat-input">
<div class="input-container">
<textarea
id="userMessage"
placeholder="输入你的问题..."
onkeydown="if(event.keyCode === 13 && !event.shiftKey) { event.preventDefault(); sendMessage(); }"
></textarea>
<button onclick="sendMessage()" id="sendButton">发送</button>
</div>
</div>
</div>
<script>
function appendMessage(content, isUser) {
const messagesDiv = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'user' : 'ai'}`;
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
contentDiv.textContent = content;
messageDiv.appendChild(contentDiv);
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
function sendMessage() {
const button = document.getElementById('sendButton');
const userMessage = document.getElementById('userMessage').value.trim();
const memoryId = parseInt(document.getElementById('memoryId').value);
if (!userMessage) {
alert("请输入消息内容");
return;
}
// 添加用户消息到聊天界面
appendMessage(userMessage, true);
// 禁用按钮并显示发送中状态
button.disabled = true;
button.textContent = '发送中...';
const requestPayload = {
memoryId: memoryId,
userMessage: userMessage
};
fetch('http://localhost:8080/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(requestPayload)
})
.then(response => response.json())
.then(data => {
console.log('Response:', data);
if (data.code !== 200) {
throw new Error(data.message || '请求失败');
}
// 添加AI回复到聊天界面
appendMessage(data.data, false);
// 清空输入框
document.getElementById('userMessage').value = '';
})
.catch(error => {
console.error('Error:', error);
appendMessage(`发生错误: ${error.message}`, false);
})
.finally(() => {
// 恢复按钮状态
button.disabled = false;
button.textContent = '发送';
});
}
</script>
</body>
</html>
spring.application.name=langchain4j_demo_springboot
#deepseek-v3 qwen-plus qwen-max ??chatglm3-6b qwen-plus-latest
langchain4j.open-ai.chat-model.api-key=百炼key
langchain4j.open-ai.chat-model.model-name=qwen-plus-latest
langchain4j.open-ai.chat-model.log-requests=true
langchain4j.open-ai.chat-model.log-responses=true
<?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.3.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.fyy</groupId>
<artifactId>langchain4j_demo_springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>langchain4j_demo_springboot</name>
<description>langchain4j_demo_springboot</description>
<properties>
<java.version>17</java.version>
<langchain4j.version>1.0.1-beta6</langchain4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapdb</groupId>
<artifactId>mapdb</artifactId>
<version>3.0.9</version>
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>org.jetbrains.kotlin</groupId>-->
<!-- <artifactId>kotlin-stdlib</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
</dependency>
<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>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- <dependencyManagement>-->
<!-- <dependencies>-->
<!-- <dependency>-->
<!-- <groupId>dev.langchain4j</groupId>-->
<!-- <artifactId>langchain4j-community-bom</artifactId>-->
<!-- <version>${langchain4j.version}</version>-->
<!-- <type>pom</type>-->
<!-- <scope>import</scope>-->
<!-- </dependency>-->
<!-- </dependencies>-->
<!-- </dependencyManagement>-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>