commit
c585f9899b
@ -0,0 +1,21 @@
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
/.idea
|
||||
*.iml
|
||||
/.settings
|
||||
/bin
|
||||
/gen
|
||||
/build
|
||||
/gradle
|
||||
/classes
|
||||
.classpath
|
||||
.project
|
||||
*.gradle
|
||||
gradlew
|
||||
local.properties
|
||||
node_modules/
|
||||
data/
|
@ -0,0 +1,23 @@
|
||||
package xyz.wbsite;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 应用启动入口
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
/**
|
||||
* 程序入口
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
SpringApplication application = new SpringApplication(Application.class);
|
||||
application.run(args);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package xyz.wbsite.achat.core.base;
|
||||
|
||||
/**
|
||||
* 附件
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public class Attachment {
|
||||
private String filename;
|
||||
|
||||
private String fid;
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public void setFilename(String filename) {
|
||||
this.filename = filename;
|
||||
}
|
||||
|
||||
public String getFid() {
|
||||
return fid;
|
||||
}
|
||||
|
||||
public void setFid(String fid) {
|
||||
this.fid = fid;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package xyz.wbsite.achat.core.base;
|
||||
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 服务器推送事件
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public class Event {
|
||||
|
||||
private String sid;
|
||||
|
||||
private Date time;
|
||||
|
||||
private String text;
|
||||
|
||||
private String type;
|
||||
|
||||
public Event(String sid) {
|
||||
this.sid = sid;
|
||||
this.time = new Date();
|
||||
}
|
||||
|
||||
public String getSid() {
|
||||
return sid;
|
||||
}
|
||||
|
||||
public void setSid(String sid) {
|
||||
this.sid = sid;
|
||||
}
|
||||
|
||||
public Date getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(Date time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package xyz.wbsite.achat.core.base;
|
||||
|
||||
import xyz.wbsite.achat.enums.Role;
|
||||
|
||||
/**
|
||||
* 消息
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public class Message {
|
||||
|
||||
/**
|
||||
* 角色
|
||||
*/
|
||||
private Role role;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
private String sid;
|
||||
|
||||
/**
|
||||
* 消息
|
||||
*/
|
||||
private String content;
|
||||
|
||||
public Role getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(Role role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getSid() {
|
||||
return sid;
|
||||
}
|
||||
|
||||
public void setSid(String sid) {
|
||||
this.sid = sid;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package xyz.wbsite.achat.core.base;
|
||||
|
||||
public class Prompt {
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
private String prompt;
|
||||
|
||||
public String getPrompt() {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
public void setPrompt(String prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package xyz.wbsite.achat.core.base;
|
||||
|
||||
|
||||
/**
|
||||
* 会话
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public class Session {
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String uid;
|
||||
private String title;
|
||||
private String model;
|
||||
private String prompt;
|
||||
private String temperature;
|
||||
private String topP;
|
||||
private String frequencyPenalty;
|
||||
private String presencePenalty;
|
||||
private String maxTokens;
|
||||
private String lastTime;
|
||||
private String lastMessage;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String getPrompt() {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
public void setPrompt(String prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public String getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(String temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public String getTopP() {
|
||||
return topP;
|
||||
}
|
||||
|
||||
public void setTopP(String topP) {
|
||||
this.topP = topP;
|
||||
}
|
||||
|
||||
public String getFrequencyPenalty() {
|
||||
return frequencyPenalty;
|
||||
}
|
||||
|
||||
public void setFrequencyPenalty(String frequencyPenalty) {
|
||||
this.frequencyPenalty = frequencyPenalty;
|
||||
}
|
||||
|
||||
public String getPresencePenalty() {
|
||||
return presencePenalty;
|
||||
}
|
||||
|
||||
public void setPresencePenalty(String presencePenalty) {
|
||||
this.presencePenalty = presencePenalty;
|
||||
}
|
||||
|
||||
public String getMaxTokens() {
|
||||
return maxTokens;
|
||||
}
|
||||
|
||||
public void setMaxTokens(String maxTokens) {
|
||||
this.maxTokens = maxTokens;
|
||||
}
|
||||
|
||||
public String getLastTime() {
|
||||
return lastTime;
|
||||
}
|
||||
|
||||
public void setLastTime(String lastTime) {
|
||||
this.lastTime = lastTime;
|
||||
}
|
||||
|
||||
public String getLastMessage() {
|
||||
return lastMessage;
|
||||
}
|
||||
|
||||
public void setLastMessage(String lastMessage) {
|
||||
this.lastMessage = lastMessage;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package xyz.wbsite.achat.core.event;
|
||||
|
||||
import xyz.wbsite.achat.core.base.Event;
|
||||
|
||||
/**
|
||||
* 完成事件
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public class CompleteEvent extends Event {
|
||||
|
||||
public CompleteEvent(String sid) {
|
||||
super(sid);
|
||||
setType("complete");
|
||||
}
|
||||
|
||||
public static CompleteEvent of(String sid) {
|
||||
return new CompleteEvent(sid);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package xyz.wbsite.achat.core.event;
|
||||
|
||||
import xyz.wbsite.achat.core.base.Event;
|
||||
|
||||
/**
|
||||
* 部分消息事件
|
||||
* <p>
|
||||
* 用于流式会话下表示AI回复的部分消息
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public class PartialEvent extends Event {
|
||||
public PartialEvent(String sid) {
|
||||
super(sid);
|
||||
setType("partial");
|
||||
}
|
||||
|
||||
public PartialEvent(String sid, String partial) {
|
||||
super(sid);
|
||||
setType("partial");
|
||||
setText(partial);
|
||||
}
|
||||
|
||||
public static PartialEvent of(String sid, String partial) {
|
||||
return new PartialEvent(sid, partial);
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package xyz.wbsite.achat.core.message;
|
||||
|
||||
|
||||
import xyz.wbsite.achat.core.base.Message;
|
||||
|
||||
/**
|
||||
* 用户消息
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public class AiMessage extends Message {
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package xyz.wbsite.achat.core.message;
|
||||
|
||||
import xyz.wbsite.achat.core.base.Attachment;
|
||||
import xyz.wbsite.achat.core.base.Message;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户消息
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public class UserMessage extends Message {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private String uid;
|
||||
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private List<Attachment> attachments;
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public List<Attachment> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public void setAttachments(List<Attachment> attachments) {
|
||||
this.attachments = attachments;
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package xyz.wbsite.achat.core.prompt;
|
||||
|
||||
import xyz.wbsite.achat.core.base.Message;
|
||||
import xyz.wbsite.achat.core.base.Prompt;
|
||||
import xyz.wbsite.achat.core.message.UserMessage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MessagePrompt extends Prompt {
|
||||
|
||||
/**
|
||||
* 模型
|
||||
*/
|
||||
private String model;
|
||||
|
||||
/**
|
||||
* 消息列表
|
||||
*/
|
||||
private List<Message> messages;
|
||||
|
||||
/**
|
||||
* 是否流式返回
|
||||
*/
|
||||
private Boolean stream;
|
||||
|
||||
/**
|
||||
* 最大token数
|
||||
*/
|
||||
private Integer maxTokens;
|
||||
|
||||
/**
|
||||
* 温度参
|
||||
*/
|
||||
private Double temperature;
|
||||
|
||||
/**
|
||||
* 额外参数
|
||||
*/
|
||||
private Map<String, Object> extraParams;
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public List<Message> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void setMessages(List<Message> messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public Boolean getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void setStream(Boolean stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public Integer getMaxTokens() {
|
||||
return maxTokens;
|
||||
}
|
||||
|
||||
public void setMaxTokens(Integer maxTokens) {
|
||||
this.maxTokens = maxTokens;
|
||||
}
|
||||
|
||||
public Double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(Double temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public Map<String, Object> getExtraParams() {
|
||||
return extraParams;
|
||||
}
|
||||
|
||||
public void setExtraParams(Map<String, Object> extraParams) {
|
||||
this.extraParams = extraParams;
|
||||
}
|
||||
|
||||
|
||||
public UserMessage getLastUserMessage() {
|
||||
List<Message> messageList = messages.stream().filter(message -> message instanceof UserMessage).collect(Collectors.toList());
|
||||
UserMessage userMessage = (UserMessage)messageList.get(messageList.size() - 1);
|
||||
return userMessage;
|
||||
}
|
||||
|
||||
public String getUid() {
|
||||
return getLastUserMessage().getUid();
|
||||
}
|
||||
|
||||
public String getSid(){
|
||||
return getLastUserMessage().getSid();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package xyz.wbsite.achat.core.service;
|
||||
|
||||
import xyz.wbsite.achat.core.message.MessageSseEmitter;
|
||||
import xyz.wbsite.achat.core.base.Message;
|
||||
|
||||
/**
|
||||
* 消息生成器
|
||||
* <p>
|
||||
* 抽象出来用于生成消息的实现层
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public interface MessageGenerator {
|
||||
|
||||
void on(MessageSseEmitter emitter, Message message);
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package xyz.wbsite.achat.core.service.impl;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import xyz.wbsite.achat.core.message.MessageSseEmitter;
|
||||
import xyz.wbsite.achat.core.base.Message;
|
||||
import xyz.wbsite.achat.core.base.Result;
|
||||
import xyz.wbsite.achat.core.base.Session;
|
||||
import xyz.wbsite.achat.core.message.UserMessage;
|
||||
import xyz.wbsite.achat.core.prompt.MessagePrompt;
|
||||
import xyz.wbsite.achat.core.service.SessionService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 会话管理服务实现类
|
||||
* 实现会话的创建、删除、查询、消息收发等功能
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
@Service
|
||||
public class SessionServiceMemoryImpl implements SessionService {
|
||||
|
||||
private final Map<String, Session> sessionStore = new HashMap<>();
|
||||
|
||||
private final List<Message> messageStore = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 创建新会话
|
||||
*/
|
||||
@Override
|
||||
public Result<Session> createSession(String uid) {
|
||||
Session session = new Session();
|
||||
session.setId(String.valueOf(UUID.randomUUID().toString()));
|
||||
session.setUid(uid);
|
||||
session.setTitle("新对话");
|
||||
sessionStore.put(session.getId(), session);
|
||||
return Result.success(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
*/
|
||||
@Override
|
||||
public Result<Void> deleteSession(String sid) {
|
||||
sessionStore.remove(sid);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询会话列表
|
||||
*/
|
||||
@Override
|
||||
public Result<List<Session>> listSessions(String uid) {
|
||||
List<Session> collect = sessionStore.values()
|
||||
.stream()
|
||||
.filter(item -> item.getUid().equals(uid))
|
||||
.collect(Collectors.toList());
|
||||
return Result.success(collect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话详情
|
||||
*/
|
||||
@Override
|
||||
public Result<Session> getSession(String sid) {
|
||||
Session session = sessionStore.get(sid);
|
||||
if (session == null) {
|
||||
return Result.error("会话不存在");
|
||||
}
|
||||
return Result.success(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息并获取流式响应
|
||||
*/
|
||||
@Override
|
||||
public SseEmitter sendMessage(MessagePrompt message) {
|
||||
// 创建VChatSseEmitter来处理流式响应
|
||||
return new MessageSseEmitter(message, (emitter, message1) -> {
|
||||
// 这边模拟LLM复述一遍用户问题
|
||||
String text = message1.getContent();
|
||||
for (char c : text.toCharArray()) {
|
||||
if (emitter.isComplete()) {
|
||||
return;
|
||||
}
|
||||
emitter.onPartialResponse(String.valueOf(c));
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
emitter.onCompleteResponse(null);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> stopSession(String sid) {
|
||||
// todo
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<List<Message>> listMessage(String sid) {
|
||||
List<Message> messages = messageStore.stream().filter(item -> item.getSid().equals(sid)).collect(Collectors.toList());
|
||||
return Result.success(messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> deleteMessage(String sid) {
|
||||
messageStore.forEach(item -> messageStore.remove(item));
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package xyz.wbsite.achat.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
/**
|
||||
* 消息角色
|
||||
*
|
||||
* @author wangbing
|
||||
* @version 0.0.1
|
||||
* @since 1.8
|
||||
*/
|
||||
public enum Role {
|
||||
USER("user"),
|
||||
ASSISTANT("assistant"),
|
||||
SYSTEM("system"),
|
||||
FUNCTION("function"),
|
||||
TOOL("tool"),
|
||||
UNKNOWN("unknown");
|
||||
|
||||
private final String value;
|
||||
|
||||
Role(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
// 序列化时返回小写字符串
|
||||
@JsonValue
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 反序列化时根据字符串匹配枚举
|
||||
public static Role fromValue(String value) {
|
||||
for (Role role : Role.values()) {
|
||||
if (role.value.equalsIgnoreCase(value)) {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid Role value: " + value);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package xyz.wbsite.achat.enums;
|
||||
|
||||
/**
|
||||
* 消息状态枚举
|
||||
*/
|
||||
public enum Status {
|
||||
/**
|
||||
* 待处理
|
||||
*/
|
||||
PENDING,
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS,
|
||||
/**
|
||||
* 错误
|
||||
*/
|
||||
ERROR,
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
CANCELLED
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
server.port=8080
|
||||
spring.application.name=achat
|
@ -0,0 +1,19 @@
|
||||
package xyz.wbsite.achat;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Configuration
|
||||
public class TestConfig {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@PostConstruct
|
||||
public void initLocalData() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
debug级别及以上
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>DEBUG</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%highlight(%d{yyyy-MM-dd HH:mm:ss.SSS} [%-4level] [%thread] [%logger{36}-%method] %ex %msg%n)</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="java.sql" level="INFO"></logger>
|
||||
|
||||
<!-- 日志总入口 -->
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="Console"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
Loading…
Reference in new issue