Skip to content

Commit 48ad502

Browse files
Merge pull request #89 from weekenthralling/dev
Implement summary assistant feature
2 parents 4771ee1 + 43138aa commit 48ad502

File tree

6 files changed

+103
-1
lines changed

6 files changed

+103
-1
lines changed

src/main/java/dev/chatbot/aiservice/AssistantConfiguration.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
import dev.langchain4j.memory.chat.ChatMemoryProvider;
1010
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
11+
import dev.langchain4j.model.chat.ChatModel;
1112
import dev.langchain4j.model.chat.StreamingChatModel;
1213
import dev.langchain4j.model.chat.listener.ChatModelListener;
14+
import dev.langchain4j.model.openai.OpenAiChatModel;
1315
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
1416
import dev.langchain4j.service.AiServices;
1517
import dev.langchain4j.web.search.WebSearchEngine;
@@ -61,6 +63,20 @@ StreamingChatModel model() {
6163
.build();
6264
}
6365

66+
@Bean
67+
ChatModel chatModel(StreamingChatModel streamingChatModel) {
68+
return OpenAiChatModel.builder()
69+
.baseUrl(llmProperties.getBaseUrl())
70+
.apiKey(llmProperties.getApiKey())
71+
.modelName(llmProperties.getModelName())
72+
.temperature(llmProperties.getTemperature())
73+
.topP(llmProperties.getTopP())
74+
.maxTokens(llmProperties.getMaxTokens())
75+
.returnThinking(true)
76+
.listeners(listeners)
77+
.build();
78+
}
79+
6480
@Bean
6581
@Scope(SCOPE_PROTOTYPE)
6682
ChatMemoryProvider chatMemoryProvider() {
@@ -86,4 +102,10 @@ StreamingAssistant assistant(
86102
.chatMemoryProvider(chatMemoryProvider)
87103
.build();
88104
}
105+
106+
@Bean
107+
@Scope(SCOPE_PROTOTYPE)
108+
SummaryAssistant summaryAssistant(ChatModel chatModel, ChatMemoryProvider chatMemoryProvider) {
109+
return AiServices.builder(SummaryAssistant.class).chatModel(chatModel).build();
110+
}
89111
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.chatbot.aiservice;
2+
3+
import dev.langchain4j.service.SystemMessage;
4+
import dev.langchain4j.service.UserMessage;
5+
6+
public interface SummaryAssistant {
7+
8+
@SystemMessage(fromResource = "prompts/summarize_assistant_prompt.txt")
9+
String summarize(@UserMessage String conversation);
10+
}

src/main/java/dev/chatbot/controller/AssistantController.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package dev.chatbot.controller;
22

33
import java.time.Instant;
4+
import java.util.Arrays;
5+
import java.util.Objects;
46
import java.util.UUID;
7+
import java.util.stream.Collectors;
58

69
import org.springframework.web.bind.annotation.PathVariable;
710
import org.springframework.web.bind.annotation.PostMapping;
@@ -19,11 +22,14 @@
1922
import reactor.core.publisher.Flux;
2023

2124
import dev.chatbot.aiservice.StreamingAssistant;
25+
import dev.chatbot.aiservice.SummaryAssistant;
2226
import dev.chatbot.domain.Conversation;
2327
import dev.chatbot.dto.ChatMessage;
2428
import dev.chatbot.exception.ForbiddenException;
29+
import dev.chatbot.repository.ChatHistoryRepository;
2530
import dev.chatbot.service.ConversationService;
2631

32+
import static dev.langchain4j.data.message.ChatMessageDeserializer.messagesFromJson;
2733
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE;
2834

2935
import static dev.chatbot.utils.Json.toJson;
@@ -44,8 +50,12 @@ public class AssistantController {
4450

4551
private final StreamingAssistant assistant;
4652

53+
private final SummaryAssistant summaryAssistant;
54+
4755
private final ConversationService conversationService;
4856

57+
private final ChatHistoryRepository chatHistoryRepository;
58+
4959
@PostMapping(value = "/{conversationId}/assistant", produces = TEXT_EVENT_STREAM_VALUE)
5060
@Operation(summary = "Assistant API", description = "Get assistant response")
5161
@ApiResponses(
@@ -100,6 +110,25 @@ public Flux<String> assistant(
100110
sink.next(toJson(thinkMessage));
101111
})
102112
.onCompleteResponse(response -> {
113+
// After the main response is sent, trigger summarization if enabled
114+
var requireSummarization = message.getAdditionalKwargs().get("require_summarization");
115+
if (Boolean.TRUE.equals(requireSummarization)) {
116+
String title = this.summaryAssistant.summarize(chatMessages(sessionId));
117+
118+
conversation.setTitle(title);
119+
this.conversationService.saveConversation(conversation);
120+
121+
// Send the updated conversation with the new title
122+
ChatMessage summaryMessage = ChatMessage.builder()
123+
.id(UUID.randomUUID().toString())
124+
.type("info")
125+
.from("ai")
126+
.sentAt(Instant.now())
127+
.content(title)
128+
.build();
129+
sink.next(toJson(summaryMessage));
130+
}
131+
103132
sink.next("[DONE]");
104133
sink.complete();
105134
})
@@ -117,4 +146,30 @@ public Flux<String> assistant(
117146
.start();
118147
});
119148
}
149+
150+
/**
151+
* Retrieve chat messages for a given session ID and format them as a single
152+
* string.
153+
* @param sessionId the session ID
154+
* @return the formatted chat messages
155+
*/
156+
private String chatMessages(UUID sessionId) {
157+
var chatHistory = this.chatHistoryRepository.findById(sessionId).orElse(null);
158+
if (chatHistory == null) {
159+
return "No conversation history.";
160+
}
161+
return messagesFromJson(chatHistory.getMessage()).stream()
162+
.map(ChatMessage::fromLC)
163+
.filter(Objects::nonNull)
164+
.filter(message -> Objects.nonNull(message.getContent()))
165+
.filter(message -> Arrays.asList("human", "ai").contains(message.getType()))
166+
.map(message -> {
167+
return switch (message.getType()) {
168+
case "human" -> "Human: " + message.getContent();
169+
case "ai" -> "AI: " + message.getContent();
170+
default -> "";
171+
};
172+
})
173+
.collect(Collectors.joining("\n"));
174+
}
120175
}

src/main/java/dev/chatbot/controller/ConversationController.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ public ResponseEntity<Chat> getConversation(
170170
messages = messagesFromJson(history.getMessage()).stream()
171171
.map(ChatMessage::fromLC)
172172
.filter(Objects::nonNull)
173-
.filter(message -> Objects.nonNull(message.getContent()))
173+
// Remove empty messages and messages with only new lines(functional messages)
174+
.filter(message -> Objects.nonNull(message.getContent())
175+
&& !message.getContent().equals("\n\n"))
174176
// TODO: chatbot only stream "human" and "ai" message now, so we just show these two types
175177
// here. In the future, we may need to show other types of messages.
176178
.filter(message -> Arrays.asList("human", "ai").contains(message.getType()))
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Based on the conversation provided, generate a concise and accurate title that captures the core topic or main intent of the dialogue.
2+
3+
Requirements:
4+
1. The title must be clear, professional, and highly relevant to the main subject of the conversation.
5+
2. The title length must be strictly limited to **20** characters (including spaces and letters).
6+
3. Do not introduce any information not present in the conversation.
7+
4. Maintain a neutral and objective tone—no exaggeration or emotional wording.
8+
5. Use the same language to user.
9+
10+
Output the final title only. Do not include explanations or numbering.

web/src/routes/conversation/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ const Chatbox = () => {
119119
case "ToolMessageChunk": // langchain's ToolMessageChunk.type
120120
appendOrAddMessage(currentConvId, message);
121121
break;
122+
case "info":
123+
updateConv({ id: currentConvId, title: message.content });
124+
break;
122125
case "error":
123126
showErrorNotification("发生错误", message.content);
124127
handleStreamComplete(currentConvId);

0 commit comments

Comments
 (0)