44import org .springframework .ai .chat .memory .MessageWindowChatMemory ;
55import org .springframework .ai .chat .memory .repository .jdbc .JdbcChatMemoryRepository ;
66import org .springframework .ai .chat .memory .repository .jdbc .PostgresChatMemoryRepositoryDialect ;
7- import org .springframework .ai .chat .messages .UserMessage ;
8- import org .springframework .ai .chat .messages .AssistantMessage ;
9- import org .springframework .ai .chat .messages .SystemMessage ;
10- import org .springframework .ai .chat .messages .Message ;
7+ import org .springframework .ai .chat .messages .*;
118import org .springframework .ai .chat .prompt .Prompt ;
129import reactor .core .publisher .Flux ;
13-
1410import org .springframework .stereotype .Service ;
15-
1611import org .slf4j .Logger ;
1712import org .slf4j .LoggerFactory ;
1813import javax .sql .DataSource ;
@@ -25,15 +20,14 @@ public class ChatMemoryService {
2520 private static final int MAX_CONTEXT_SUMMARIES = 10 ;
2621 private static final int MAX_PREFERENCES = 1 ;
2722
23+ // Three-tier memory: Session (recent), Context (summaries), Preferences (profile)
2824 private final MessageWindowChatMemory sessionMemory ;
2925 private final MessageWindowChatMemory contextMemory ;
3026 private final MessageWindowChatMemory preferencesMemory ;
31-
32- // Thread-local to store current userId per request
3327 private final ThreadLocal <String > currentUserId = ThreadLocal .withInitial (() -> "user1" );
3428
3529 public ChatMemoryService (DataSource dataSource ) {
36-
30+ // Single JDBC repository shared by all three memory tiers
3731 var jdbcRepository = JdbcChatMemoryRepository .builder ()
3832 .dataSource (dataSource )
3933 .dialect (new PostgresChatMemoryRepositoryDialect ())
@@ -55,79 +49,50 @@ public ChatMemoryService(DataSource dataSource) {
5549 .build ();
5650 }
5751
58- public MessageWindowChatMemory getSessionMemory () {
59- return sessionMemory ;
60- }
61-
62- public MessageWindowChatMemory getContextMemory () {
63- return contextMemory ;
64- }
65-
66- public MessageWindowChatMemory getPreferencesMemory () {
67- return preferencesMemory ;
68- }
69-
70- public void setCurrentUserId (String userId ) {
71- this .currentUserId .set (userId );
72- }
73-
74- public String getCurrentConversationId () {
75- return currentUserId .get ();
76- }
77-
7852 public Flux <String > callWithMemory (ChatClient chatClient , String prompt ) {
7953 String conversationId = getCurrentConversationId ();
8054
81- // Check if first message - load previous context from JDBC
55+ // 1. Auto- load previous context on first message
8256 if (sessionMemory .get (conversationId ).isEmpty ()) {
8357 loadPreviousContext (conversationId , chatClient );
8458 }
8559
86- // Add user message to session memory
87- UserMessage userMessage = new UserMessage (prompt );
88- sessionMemory .add (conversationId , userMessage );
60+ // 2. Add user message to session memory
61+ sessionMemory .add (conversationId , new UserMessage (prompt ));
8962
90- // Stream response with conversation history
63+ // 3. Stream AI response with full conversation history
9164 StringBuilder fullResponse = new StringBuilder ();
92-
9365 return chatClient
9466 .prompt (new Prompt (sessionMemory .get (conversationId )))
9567 .stream ()
9668 .content ()
97- .doOnNext (chunk -> {
98- fullResponse .append (chunk );
99- logger .debug ("Streaming chunk: {} chars" , chunk .length ());
100- })
69+ .doOnNext (fullResponse ::append )
10170 .doOnComplete (() -> {
102- // Add complete assistant response to session memory after streaming completes
71+ // 4. Save complete response to session memory
10372 String responseText = fullResponse .toString ();
104- if (responseText != null && !responseText .isEmpty ()) {
105- AssistantMessage assistantMessage = new AssistantMessage (responseText );
106- sessionMemory .add (conversationId , assistantMessage );
107- logger .info ("Added assistant response to memory: {} chars" , responseText .length ());
73+ if (!responseText .isEmpty ()) {
74+ sessionMemory .add (conversationId , new AssistantMessage (responseText ));
75+ logger .info ("Saved response to memory: {} chars" , responseText .length ());
10876 }
109- logger .info ("Completed streaming response." );
11077 });
11178 }
11279
11380 private void loadPreviousContext (String conversationId , ChatClient chatClient ) {
114- logger .info ("Loading previous context for conversation : {}" , conversationId );
81+ logger .info ("Loading previous context for: {}" , conversationId );
11582
116- // Load user preferences (userId_preferences)
83+ // 1. Load preferences (userId_preferences) and context (userId_context )
11784 List <Message > preferences = preferencesMemory .get (conversationId + "_preferences" );
11885 String preferencesText = preferences .isEmpty () ? "" : preferences .get (0 ).getText ();
119-
120- // Load context summaries (userId_context)
12186 List <Message > summaries = contextMemory .get (conversationId + "_context" );
12287
12388 if (summaries .isEmpty () && preferencesText .isEmpty ()) {
12489 logger .info ("No previous context found" );
12590 return ;
12691 }
12792
128- logger .info ("Found {} context summaries and {} preferences" , summaries .size (), preferences .isEmpty () ? 0 : 1 );
93+ logger .info ("Found {} summaries, {} preferences" , summaries .size (), preferences .isEmpty () ? 0 : 1 );
12994
130- // Combine preferences and summaries
95+ // 2. Combine preferences and summaries
13196 StringBuilder contextBuilder = new StringBuilder ();
13297 if (!preferencesText .isEmpty ()) {
13398 contextBuilder .append ("User Preferences:\n " ).append (preferencesText ).append ("\n \n " );
@@ -137,13 +102,15 @@ private void loadPreviousContext(String conversationId, ChatClient chatClient) {
137102 summaries .forEach (msg -> contextBuilder .append (msg .getText ()).append ("\n \n " ));
138103 }
139104
140- String combinedContext = contextBuilder . toString ();
105+ // 3. Summarize combined context with AI
141106 var chatResponse = chatClient .prompt ()
142- .user ("Summarize this user information concisely:\n \n " + combinedContext )
107+ .user ("Summarize this user information concisely:\n \n " + contextBuilder )
143108 .call ()
144109 .chatResponse ();
145110
146- String contextSummary = (chatResponse != null && chatResponse .getResult () != null && chatResponse .getResult ().getOutput () != null )
111+ String contextSummary = (chatResponse != null &&
112+ chatResponse .getResult () != null &&
113+ chatResponse .getResult ().getOutput () != null )
147114 ? chatResponse .getResult ().getOutput ().getText ()
148115 : null ;
149116
@@ -152,13 +119,34 @@ private void loadPreviousContext(String conversationId, ChatClient chatClient) {
152119 return ;
153120 }
154121
155- logger .info ("Loaded context summary: {}" , contextSummary );
156-
122+ // 4. Add context as system message to session memory
157123 String contextMessage = String .format (
158124 "You are continuing a conversation with this user. Here is what you know:\n \n %s\n \n " +
159125 "Use this information to provide personalized responses." ,
160126 contextSummary
161127 );
162128 sessionMemory .add (conversationId , new SystemMessage (contextMessage ));
129+ logger .info ("Loaded context summary" );
130+ }
131+
132+ // Getters and setters
133+ public MessageWindowChatMemory getSessionMemory () {
134+ return sessionMemory ;
135+ }
136+
137+ public MessageWindowChatMemory getContextMemory () {
138+ return contextMemory ;
139+ }
140+
141+ public MessageWindowChatMemory getPreferencesMemory () {
142+ return preferencesMemory ;
143+ }
144+
145+ public void setCurrentUserId (String userId ) {
146+ this .currentUserId .set (userId );
147+ }
148+
149+ public String getCurrentConversationId () {
150+ return currentUserId .get ();
163151 }
164152}
0 commit comments