feat: initialize project backend structure and document system specifications across all modules
This commit is contained in:
@@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@SpringBootApplication
|
||||
public class LoyaltyAgentApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
static void main(String[] args) {
|
||||
SpringApplication.run(LoyaltyAgentApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
package dev.sonpx.loyalty.agent.config;
|
||||
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.NonNull;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
public class FlywayConfig implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
|
||||
if (bean instanceof DataSource) {
|
||||
System.out.println("--- Running Flyway Migration before DataSource is used ---");
|
||||
Flyway.configure()
|
||||
.dataSource((DataSource) bean)
|
||||
.locations("classpath:db/migration")
|
||||
.load()
|
||||
.migrate();
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
//package dev.sonpx.loyalty.agent.config;
|
||||
//
|
||||
//import org.flywaydb.core.Flyway;
|
||||
//import org.springframework.beans.BeansException;
|
||||
//import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.lang.NonNull;
|
||||
//import javax.sql.DataSource;
|
||||
//
|
||||
//@Configuration
|
||||
//public class FlywayConfig {
|
||||
//
|
||||
// @Bean
|
||||
// public FlywayMigrationStrategy flywayMigrationStrategy() {
|
||||
// return flyway -> {
|
||||
// // Ví dụ: Xóa hết schema cũ rồi mới chạy migration (chỉ áp dụng thử nghiệm!)
|
||||
// // flyway.clean();
|
||||
// flyway.migrate();
|
||||
// };
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package dev.sonpx.loyalty.agent.config;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class McpConfig {
|
||||
|
||||
@Bean
|
||||
public static BeanFactoryPostProcessor lazyMcpClientPostProcessor() {
|
||||
return beanFactory -> {
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
if (beanName.toLowerCase().contains("mcp") || beanName.toLowerCase().contains("toolcallbackprovider")) {
|
||||
beanFactory.getBeanDefinition(beanName).setLazyInit(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
package dev.sonpx.loyalty.agent.config;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
||||
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker
|
||||
@@ -22,10 +27,10 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/ws")
|
||||
.setAllowedOriginPatterns("*")
|
||||
.setHandshakeHandler(new org.springframework.web.socket.server.support.DefaultHandshakeHandler() {
|
||||
.setHandshakeHandler(new DefaultHandshakeHandler() {
|
||||
@Override
|
||||
protected java.security.Principal determineUser(org.springframework.http.server.ServerHttpRequest request, org.springframework.web.socket.WebSocketHandler wsHandler, java.util.Map<String, Object> attributes) {
|
||||
return new java.security.Principal() {
|
||||
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
|
||||
return new Principal() {
|
||||
private final String id = UUID.randomUUID().toString();
|
||||
@Override
|
||||
public String getName() {
|
||||
|
||||
@@ -58,8 +58,9 @@ public class SimpleAgentExecutor {
|
||||
* The LLM decides which tools to call via ReAct pattern.
|
||||
*/
|
||||
public void executeWithTools(ChatRequest request, String connectionSessionId, String conversationKey, Intent intent) {
|
||||
List<ToolCallback> wrappedTools = toolInterceptor.getWrappedTools(
|
||||
connectionSessionId, request.messageId(), AgentToolScope.READ_ONLY);
|
||||
try {
|
||||
List<ToolCallback> wrappedTools = toolInterceptor.getWrappedTools(
|
||||
connectionSessionId, request.messageId(), AgentToolScope.READ_ONLY);
|
||||
|
||||
String systemPrompt = promptBuilder.buildSystemPrompt(intent);
|
||||
String userPrompt = promptBuilder.buildUserPrompt(request);
|
||||
@@ -94,10 +95,34 @@ public class SimpleAgentExecutor {
|
||||
},
|
||||
error -> {
|
||||
log.error("Error during SimpleAgent execution for connection {}: {}", connectionSessionId, error.getMessage(), error);
|
||||
eventPublisher.error(connectionSessionId, request.messageId(),
|
||||
"Xin lỗi, đã xảy ra lỗi khi xử lý yêu cầu. Vui lòng thử lại.");
|
||||
String errorMessage = "Xin lỗi, đã xảy ra lỗi khi xử lý yêu cầu. Vui lòng thử lại.";
|
||||
if (isConnectionError(error)) {
|
||||
errorMessage = "Hiện tại dịch vụ AI (Ollama) đang tạm thời gián đoạn. Vui lòng thử lại sau.";
|
||||
}
|
||||
eventPublisher.error(connectionSessionId, request.messageId(), errorMessage);
|
||||
}
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.error("Error setting up SimpleAgent for connection {}: {}", connectionSessionId, e.getMessage(), e);
|
||||
String errorMessage = "Xin lỗi, không thể khởi tạo tool. Vui lòng thử lại.";
|
||||
if (isConnectionError(e)) {
|
||||
errorMessage = "Hiện tại dịch vụ tool (MCP) đang tạm thời gián đoạn. Vui lòng thử lại sau.";
|
||||
}
|
||||
eventPublisher.error(connectionSessionId, request.messageId(), errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isConnectionError(Throwable t) {
|
||||
if (t == null) return false;
|
||||
Throwable current = t;
|
||||
while (current != null) {
|
||||
String name = current.getClass().getName();
|
||||
if (name.contains("ConnectException") || name.contains("SocketException") || name.contains("ResourceAccessException")) {
|
||||
return true;
|
||||
}
|
||||
current = current.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,8 +161,11 @@ public class SimpleAgentExecutor {
|
||||
},
|
||||
error -> {
|
||||
log.error("Error during DirectChat execution for connection {}: {}", connectionSessionId, error.getMessage(), error);
|
||||
eventPublisher.error(connectionSessionId, request.messageId(),
|
||||
"Xin lỗi, đã xảy ra lỗi. Vui lòng thử lại.");
|
||||
String errorMessage = "Xin lỗi, đã xảy ra lỗi. Vui lòng thử lại.";
|
||||
if (isConnectionError(error)) {
|
||||
errorMessage = "Hiện tại dịch vụ AI (Ollama) đang tạm thời gián đoạn. Vui lòng thử lại sau.";
|
||||
}
|
||||
eventPublisher.error(connectionSessionId, request.messageId(), errorMessage);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class ToolInterceptor {
|
||||
|
||||
private final List<ToolCallbackProvider> toolProviders;
|
||||
private final ObjectProvider<ToolCallbackProvider> toolProviders;
|
||||
private final AgentEventPublisher eventPublisher;
|
||||
private final ToolResultPresentationProcessor toolResultProcessor;
|
||||
private final AgentProperties agentProperties;
|
||||
@@ -46,7 +46,7 @@ public class ToolInterceptor {
|
||||
public List<ToolCallback> getWrappedTools(String sessionId, String messageId, AgentToolScope scope) {
|
||||
List<ToolCallback> wrappedTools = new ArrayList<>();
|
||||
|
||||
for (ToolCallbackProvider provider : toolProviders) {
|
||||
for (ToolCallbackProvider provider : toolProviders.stream().toList()) {
|
||||
for (ToolCallback tool : provider.getToolCallbacks()) {
|
||||
String toolName = tool.getToolDefinition().name();
|
||||
if (scope.allows(toolName)) {
|
||||
@@ -107,9 +107,10 @@ public class ToolInterceptor {
|
||||
try {
|
||||
return tool.call(toolInput);
|
||||
} catch (Exception e) {
|
||||
if (isConnectionOrSessionError(e)) {
|
||||
Throwable rootCause = getRootCause(e);
|
||||
if (isConnectionOrSessionError(rootCause)) {
|
||||
log.warn("Detected MCP connection/session drop on tool {}: {}. Attempting auto-reconnect and retry...",
|
||||
toolName, e.getMessage());
|
||||
toolName, rootCause.getMessage());
|
||||
attemptReconnect();
|
||||
try {
|
||||
return tool.call(toolInput);
|
||||
@@ -122,7 +123,7 @@ public class ToolInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
private void attemptReconnect() {
|
||||
private synchronized void attemptReconnect() {
|
||||
if (mcpSyncClientsProvider != null) {
|
||||
List<McpSyncClient> clients = mcpSyncClientsProvider.getIfAvailable();
|
||||
if (clients != null && !clients.isEmpty()) {
|
||||
@@ -138,6 +139,14 @@ public class ToolInterceptor {
|
||||
}
|
||||
}
|
||||
|
||||
private Throwable getRootCause(Throwable t) {
|
||||
if (t == null) return null;
|
||||
Throwable current = t;
|
||||
while (current.getCause() != null && current.getCause() != current) {
|
||||
current = current.getCause();
|
||||
}
|
||||
return current;
|
||||
}
|
||||
public boolean isConnectionOrSessionError(Throwable t) {
|
||||
if (t == null) {
|
||||
return false;
|
||||
@@ -147,7 +156,9 @@ public class ToolInterceptor {
|
||||
if (current instanceof EOFException
|
||||
|| current instanceof ConnectException
|
||||
|| current instanceof SocketException
|
||||
|| current instanceof ClosedChannelException) {
|
||||
|| current instanceof ClosedChannelException
|
||||
|| current.getClass().getName().contains("ConnectException")
|
||||
|| current.getClass().getName().contains("SocketException")) {
|
||||
return true;
|
||||
}
|
||||
String msg = current.getMessage();
|
||||
|
||||
@@ -40,7 +40,7 @@ spring:
|
||||
num_ctx: 32768
|
||||
mcp:
|
||||
client:
|
||||
request-timeout: 60000
|
||||
request-timeout: 3000
|
||||
enabled: true
|
||||
type: sync
|
||||
sse:
|
||||
|
||||
Reference in New Issue
Block a user