This is an automated email from the ASF dual-hosted git repository.

yuluo-yx pushed a commit to branch 0812-yuluo/feat
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git

commit 95fe36445bd94884836815666fb2f51cc078db0e
Author: yuluo-yx <[email protected]>
AuthorDate: Wed Aug 12 21:13:39 2026 +0800

    fix(ai): create conversation when conversation ID is missing
    
    Signed-off-by: yuluo-yx <[email protected]>
---
 .../ai/service/impl/ConversationServiceImpl.java   | 40 +++++++++++++--------
 .../service/impl/ConversationServiceImplTest.java  | 42 ++++++++++++++++++++++
 2 files changed, 68 insertions(+), 14 deletions(-)

diff --git 
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImpl.java
 
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImpl.java
index c674617dea..bc48bbaa8b 100644
--- 
a/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImpl.java
+++ 
b/hertzbeat-ai/src/main/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImpl.java
@@ -78,24 +78,32 @@ public class ConversationServiceImpl implements 
ConversationService {
                 .build());
         }
 
-        log.info("Starting streaming conversation: {}", conversationId);
-        ChatConversation conversation = 
conversationDao.findById(conversationId)
-            .orElseThrow(() -> new IllegalArgumentException("Conversation not 
found: " + conversationId));
+        ChatConversation conversation;
+        if (conversationId == null) {
+            // 接口约定 conversationId 可选,首次聊天时按需创建会话。
+            conversation = new ChatConversation();
+            conversation.setTitle(buildConversationTitle(message));
+            conversation = conversationDao.save(conversation);
+        } else {
+            conversation = conversationDao.findById(conversationId)
+                .orElseThrow(() -> new IllegalArgumentException("Conversation 
not found: " + conversationId));
+        }
+        Long currentConversationId = conversation.getId();
+        log.info("Starting streaming conversation: {}", currentConversationId);
 
         // Manually load messages for conversation history
-        List<ChatMessage> messages = 
messageDao.findByConversationIdOrderByGmtCreateAsc(conversationId);
+        List<ChatMessage> messages = 
messageDao.findByConversationIdOrderByGmtCreateAsc(currentConversationId);
         conversation.setMessages(messages);
 
         if (conversation.getTitle().startsWith("conversation")) {
             // Auto-generate title from first user message
-            String title = message.length() > 30 ? message.substring(0, 27) + 
"..." : message;
-            conversation.setTitle(title);
+            conversation.setTitle(buildConversationTitle(message));
             conversationDao.save(conversation);
         }
 
         // Add user message to conversation
         ChatMessage chatMessage = ChatMessage.builder()
-            .conversationId(conversationId)
+            .conversationId(currentConversationId)
             .content(message)
             .role("user")
             .build();
@@ -103,7 +111,7 @@ public class ConversationServiceImpl implements 
ConversationService {
 
         ChatRequestContext context = ChatRequestContext.builder()
             .message(message)
-            .conversationId(conversationId)
+            .conversationId(currentConversationId)
             .conversationHistory(messages)
             .build();
 
@@ -116,7 +124,7 @@ public class ConversationServiceImpl implements 
ConversationService {
             .map(chunk -> {
                 fullResponse.append(chunk);
                 ChatResponseChunk responseChunk = ChatResponseChunk.builder()
-                    .conversationId(conversationId)
+                    .conversationId(currentConversationId)
                     .userMessageId(finalChatMessage.getId())
                     .response(chunk)
                     .build();
@@ -128,13 +136,13 @@ public class ConversationServiceImpl implements 
ConversationService {
             .concatWith(Flux.defer(() -> {
                 // Add the complete AI response to conversation
                 ChatMessage assistantMessage = ChatMessage.builder()
-                    .conversationId(conversationId)
+                    .conversationId(currentConversationId)
                     .content(fullResponse.toString())
                     .role("assistant")
                     .build();
                 assistantMessage = messageDao.save(assistantMessage);
                 ChatResponseChunk finalResponse = ChatResponseChunk.builder()
-                    .conversationId(conversationId)
+                    .conversationId(currentConversationId)
                     .response("")
                     .assistantMessageId(assistantMessage.getId())
                     .build();
@@ -143,12 +151,12 @@ public class ConversationServiceImpl implements 
ConversationService {
                     .event("complete")
                     .build());
             }))
-            .doOnComplete(() -> log.info("Streaming completed for 
conversation: {}", conversationId))
-            .doOnError(error -> log.error("Error in streaming chat for 
conversation {}: {}", conversationId,
+            .doOnComplete(() -> log.info("Streaming completed for 
conversation: {}", currentConversationId))
+            .doOnError(error -> log.error("Error in streaming chat for 
conversation {}: {}", currentConversationId,
                 error.getMessage(), error))
             .onErrorResume(error -> {
                 ChatResponseChunk errorResponse = ChatResponseChunk.builder()
-                    .conversationId(conversationId)
+                    .conversationId(currentConversationId)
                     .response("An error occurred: " + error.getMessage())
                     .userMessageId(finalChatMessage.getId())
                     .build();
@@ -165,6 +173,10 @@ public class ConversationServiceImpl implements 
ConversationService {
         return conversationDao.save(conversation);
     }
 
+    private String buildConversationTitle(String message) {
+        return message.length() > 30 ? message.substring(0, 27) + "..." : 
message;
+    }
+
     @Override
     public ChatConversation getConversation(Long conversationId) {
         if (conversationId == null) {
diff --git 
a/hertzbeat-ai/src/test/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImplTest.java
 
b/hertzbeat-ai/src/test/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImplTest.java
index b192073e45..cfe7d8c217 100644
--- 
a/hertzbeat-ai/src/test/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImplTest.java
+++ 
b/hertzbeat-ai/src/test/java/org/apache/hertzbeat/ai/service/impl/ConversationServiceImplTest.java
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import com.usthe.sureness.subject.SubjectSum;
@@ -123,6 +124,47 @@ class ConversationServiceImplTest {
         assertEquals(subject, contextCaptor.getValue().getSubject());
     }
 
+    /**
+     * 客户端省略可选会话 ID 时,服务端应创建会话并在整个流式响应中返回新 ID。
+     */
+    @Test
+    void streamChatShouldCreateConversationWhenConversationIdIsMissing() {
+        AtomicLong messageId = new AtomicLong(20L);
+        when(chatClientProviderService.isConfigured()).thenReturn(true);
+        
when(conversationDao.save(any(ChatConversation.class))).thenAnswer(invocation 
-> {
+            ChatConversation savedConversation = invocation.getArgument(0);
+            savedConversation.setId(CONVERSATION_ID);
+            return savedConversation;
+        });
+        
when(messageDao.findByConversationIdOrderByGmtCreateAsc(CONVERSATION_ID)).thenReturn(List.of());
+        when(messageDao.save(any(ChatMessage.class))).thenAnswer(invocation -> 
{
+            ChatMessage savedMessage = invocation.getArgument(0);
+            savedMessage.setId(messageId.getAndIncrement());
+            return savedMessage;
+        });
+        
when(chatClientProviderService.streamChat(any(ChatRequestContext.class)))
+            .thenReturn(Flux.just("本轮回答"));
+
+        List<ServerSentEvent<ChatResponseChunk>> events = conversationService
+            .streamChat("本轮问题", null)
+            .collectList()
+            .block();
+
+        assertNotNull(events);
+        assertEquals(2, events.size());
+        assertEquals(CONVERSATION_ID, 
events.get(0).data().getConversationId());
+        assertEquals(CONVERSATION_ID, 
events.get(1).data().getConversationId());
+
+        ArgumentCaptor<ChatRequestContext> contextCaptor = 
ArgumentCaptor.forClass(ChatRequestContext.class);
+        verify(chatClientProviderService).streamChat(contextCaptor.capture());
+        assertEquals(CONVERSATION_ID, 
contextCaptor.getValue().getConversationId());
+        assertEquals(List.of(), 
contextCaptor.getValue().getConversationHistory());
+        ArgumentCaptor<ChatConversation> conversationCaptor = 
ArgumentCaptor.forClass(ChatConversation.class);
+        verify(conversationDao).save(conversationCaptor.capture());
+        assertEquals("本轮问题", conversationCaptor.getValue().getTitle());
+        verifyNoMoreInteractions(conversationDao);
+    }
+
     /**
      * Deleting a conversation must remove its schedules before they can push 
more messages.
      */


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to