feat: 全新智能体功能

- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
This commit is contained in:
2026-05-25 11:42:48 +08:00
parent 6c3d98eaac
commit 72df00f25b
168 changed files with 22045 additions and 400 deletions

View File

@@ -16,12 +16,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.*;
@Repository
public class MySqlChatSessionRepository {
@@ -110,6 +105,10 @@ public class MySqlChatSessionRepository {
}
public List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, ChatPageQuery query) {
return listSessions(userId, assistantId, null, query);
}
public List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, String assistantCode, ChatPageQuery query) {
String table = tableRouter.resolveSessionTable();
List<Object> params = new ArrayList<>();
StringBuilder sql = new StringBuilder("SELECT * FROM `").append(table)
@@ -119,6 +118,10 @@ public class MySqlChatSessionRepository {
sql.append(" AND assistant_id=?");
params.add(assistantId);
}
if (assistantCode != null && !assistantCode.isBlank()) {
sql.append(" AND assistant_code=?");
params.add(assistantCode);
}
sql.append(" ORDER BY last_message_at DESC, id DESC LIMIT ? OFFSET ?");
params.add(query.getPageSize());
params.add(query.getOffset());
@@ -126,6 +129,10 @@ public class MySqlChatSessionRepository {
}
public long countSessions(BigInteger userId, BigInteger assistantId) {
return countSessions(userId, assistantId, null);
}
public long countSessions(BigInteger userId, BigInteger assistantId, String assistantCode) {
String table = tableRouter.resolveSessionTable();
List<Object> params = new ArrayList<>();
StringBuilder sql = new StringBuilder("SELECT COUNT(1) FROM `").append(table)
@@ -135,6 +142,10 @@ public class MySqlChatSessionRepository {
sql.append(" AND assistant_id=?");
params.add(assistantId);
}
if (assistantCode != null && !assistantCode.isBlank()) {
sql.append(" AND assistant_code=?");
params.add(assistantCode);
}
Long count = jdbcTemplate.queryForObject(sql.toString(), Long.class, params.toArray());
return count == null ? 0L : count;
}

View File

@@ -1,8 +1,8 @@
package tech.easyflow.chatlog.service;
import org.slf4j.MDC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;
import tech.easyflow.chatlog.cache.ChatHotStateService;
import tech.easyflow.chatlog.domain.command.ChatAppendMessageCommand;
@@ -107,7 +107,7 @@ public class ChatPersistDispatcher {
payload.setTitle(title);
payload.setOperatorId(operatorId);
payload.setOperateAt(operateAt);
eventProducer.send(buildEvent(
ChatPersistEvent event = buildEvent(
UUID.randomUUID().toString(),
ChatPersistEventType.SESSION_RENAMED,
sessionId,
@@ -115,7 +115,9 @@ public class ChatPersistDispatcher {
BigInteger.ZERO,
operateAt,
chatJsonSupport.toJson(payload)
));
);
persistImmediately(event);
eventProducer.send(event);
}
public void deleteSession(BigInteger sessionId, BigInteger userId, BigInteger operatorId) {

View File

@@ -2,13 +2,8 @@ package tech.easyflow.chatlog.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.chatlog.domain.command.ChatAppendMessageCommand;
import tech.easyflow.chatlog.domain.command.ChatRoundSelectCommand;
import tech.easyflow.chatlog.domain.command.ChatRoundUpsertCommand;
import tech.easyflow.chatlog.domain.command.ChatSessionSummaryCommand;
import tech.easyflow.chatlog.domain.command.ChatSessionUpsertCommand;
import tech.easyflow.chatlog.domain.command.*;
import tech.easyflow.chatlog.domain.event.ChatPersistEvent;
import tech.easyflow.chatlog.domain.event.ChatPersistEventType;
import tech.easyflow.chatlog.domain.event.payload.ChatSessionDeletePayload;
import tech.easyflow.chatlog.domain.event.payload.ChatSessionRenamePayload;
import tech.easyflow.chatlog.repository.mysql.MySqlChatLogRepository;
@@ -21,13 +16,7 @@ import tech.easyflow.chatlog.support.ChatJsonSupport;
import java.math.BigInteger;
import java.time.YearMonth;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
@Service
public class ChatPersistMySqlApplyService {
@@ -231,9 +220,6 @@ public class ChatPersistMySqlApplyService {
if (isBlank(upsert.getUserAccount())) {
upsert.setUserAccount("");
}
if (isBlank(upsert.getTitle())) {
upsert.setTitle("会话-" + upsert.getSessionId());
}
if (upsert.getOperateAt() == null) {
upsert.setOperateAt(new Date());
}

View File

@@ -1,7 +1,7 @@
package tech.easyflow.chatlog.service;
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
import tech.easyflow.chatlog.domain.dto.ChatSessionPage;
import tech.easyflow.chatlog.domain.dto.ChatSessionSummary;
import tech.easyflow.chatlog.domain.query.ChatPageQuery;
@@ -13,10 +13,57 @@ public interface ChatSessionQueryService {
List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, ChatPageQuery query);
/**
* 按助手编码查询用户会话列表。
*
* @param userId 用户 ID
* @param assistantId 助手 ID可为空
* @param assistantCode 助手编码,可为空
* @param query 分页参数
* @return 会话列表
*/
default List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, String assistantCode, ChatPageQuery query) {
if (assistantCode != null && !assistantCode.isBlank()) {
throw new UnsupportedOperationException("当前会话查询实现不支持 assistantCode 过滤");
}
return listSessions(userId, assistantId, query);
}
long countSessions(BigInteger userId, BigInteger assistantId);
/**
* 按助手编码统计用户会话数。
*
* @param userId 用户 ID
* @param assistantId 助手 ID可为空
* @param assistantCode 助手编码,可为空
* @return 会话数
*/
default long countSessions(BigInteger userId, BigInteger assistantId, String assistantCode) {
if (assistantCode != null && !assistantCode.isBlank()) {
throw new UnsupportedOperationException("当前会话查询实现不支持 assistantCode 过滤");
}
return countSessions(userId, assistantId);
}
ChatSessionPage pageSessions(BigInteger userId, BigInteger assistantId, ChatPageQuery query);
/**
* 按助手编码分页查询用户会话。
*
* @param userId 用户 ID
* @param assistantId 助手 ID可为空
* @param assistantCode 助手编码,可为空
* @param query 分页参数
* @return 会话分页
*/
default ChatSessionPage pageSessions(BigInteger userId, BigInteger assistantId, String assistantCode, ChatPageQuery query) {
if (assistantCode != null && !assistantCode.isBlank()) {
throw new UnsupportedOperationException("当前会话查询实现不支持 assistantCode 过滤");
}
return pageSessions(userId, assistantId, query);
}
ChatSessionSummary getSessionSummary(BigInteger sessionId);
/**

View File

@@ -13,12 +13,7 @@ import tech.easyflow.chatlog.repository.mysql.MySqlChatSessionRepository;
import tech.easyflow.chatlog.service.ChatSessionQueryService;
import java.math.BigInteger;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.*;
@Service
public class ChatSessionQueryServiceImpl implements ChatSessionQueryService {
@@ -40,22 +35,37 @@ public class ChatSessionQueryServiceImpl implements ChatSessionQueryService {
@Override
public List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, ChatPageQuery query) {
return sessionRepository.listSessions(userId, assistantId, query);
return listSessions(userId, assistantId, null, query);
}
@Override
public List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, String assistantCode, ChatPageQuery query) {
return sessionRepository.listSessions(userId, assistantId, assistantCode, query);
}
@Override
public long countSessions(BigInteger userId, BigInteger assistantId) {
return sessionRepository.countSessions(userId, assistantId);
return countSessions(userId, assistantId, null);
}
@Override
public long countSessions(BigInteger userId, BigInteger assistantId, String assistantCode) {
return sessionRepository.countSessions(userId, assistantId, assistantCode);
}
@Override
public ChatSessionPage pageSessions(BigInteger userId, BigInteger assistantId, ChatPageQuery query) {
return pageSessions(userId, assistantId, null, query);
}
@Override
public ChatSessionPage pageSessions(BigInteger userId, BigInteger assistantId, String assistantCode, ChatPageQuery query) {
ChatSessionPage page = new ChatSessionPage();
page.setPageNumber(query.getPageNumber());
page.setPageSize(query.getPageSize());
page.setTotal(sessionRepository.countSessions(userId, assistantId));
page.setRecords(listSessions(userId, assistantId, query));
page.setTotal(sessionRepository.countSessions(userId, assistantId, assistantCode));
page.setRecords(listSessions(userId, assistantId, assistantCode, query));
return page;
}

View File

@@ -62,7 +62,7 @@ public class ChatPersistMySqlApplyServiceTest {
}
@Test
public void shouldFallbackTitleWhenMessageMetadataMissing() {
public void shouldKeepTitleBlankWhenMessageMetadataMissing() {
ChatAppendMessageCommand command = new ChatAppendMessageCommand();
command.setSessionId(BigInteger.valueOf(202));
command.setTenantId(BigInteger.ONE);
@@ -77,7 +77,7 @@ public class ChatPersistMySqlApplyServiceTest {
Assert.assertEquals(1, upserts.size());
ChatSessionUpsertCommand upsert = upserts.get(0);
Assert.assertEquals("", upsert.getUserAccount());
Assert.assertEquals("会话-202", upsert.getTitle());
Assert.assertNull(upsert.getTitle());
Assert.assertEquals(BigInteger.valueOf(7), upsert.getOperatorId());
}

View File

@@ -48,6 +48,27 @@ public class ChatSessionQueryServiceImplTest {
Assert.assertEquals(1, sessionRepository.listSessionsCalls);
}
/**
* Agent 会话查询必须把 assistantCode 过滤条件下推到 MySQL 会话表,避免混入 Bot 会话。
*/
@Test
public void pageSessionsShouldPassAssistantCodeFilterToMysqlRepository() {
FakeSessionRepository sessionRepository = new FakeSessionRepository();
sessionRepository.sessions = List.of(session(BigInteger.valueOf(1002), 2));
sessionRepository.count = 1;
ChatSessionQueryServiceImpl service = new ChatSessionQueryServiceImpl(
sessionRepository,
new FakeLogRepository(),
new FakeTableManager(List.of()),
new FakeHotStateService()
);
service.pageSessions(BigInteger.valueOf(7), BigInteger.valueOf(88), "AGENT", new ChatPageQuery());
Assert.assertEquals("AGENT", sessionRepository.capturedListAssistantCode);
Assert.assertEquals("AGENT", sessionRepository.capturedCountAssistantCode);
}
/**
* 工作台消息分页必须走 MySQL 热表主线查询,并保持分页参数语义。
*/
@@ -124,6 +145,8 @@ public class ChatSessionQueryServiceImplTest {
private long count;
private int countSessionsCalls;
private int listSessionsCalls;
private String capturedListAssistantCode;
private String capturedCountAssistantCode;
private ChatSessionSummary summary;
private List<ChatSessionSummary> sessions = new ArrayList<>();
@@ -133,13 +156,25 @@ public class ChatSessionQueryServiceImplTest {
@Override
public List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, ChatPageQuery query) {
return listSessions(userId, assistantId, null, query);
}
@Override
public List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, String assistantCode, ChatPageQuery query) {
listSessionsCalls += 1;
capturedListAssistantCode = assistantCode;
return sessions;
}
@Override
public long countSessions(BigInteger userId, BigInteger assistantId) {
return countSessions(userId, assistantId, null);
}
@Override
public long countSessions(BigInteger userId, BigInteger assistantId, String assistantCode) {
countSessionsCalls += 1;
capturedCountAssistantCode = assistantCode;
return count;
}