|
|
@@ -1,9 +1,6 @@
|
|
|
package com.emoon.openplatform.service.impl;
|
|
|
|
|
|
import com.emoon.ai.agent.application.FastGptQueryMetadataService;
|
|
|
-import com.emoon.ai.card.api.dto.CardInstanceVo;
|
|
|
-import com.emoon.ai.card.api.dto.CreateCardInstanceRequest;
|
|
|
-import com.emoon.ai.card.api.facade.CardInstanceFacade;
|
|
|
import com.emoon.ai.device.api.DeviceRegistryFacade;
|
|
|
import com.emoon.mcp.domain.vo.AiConversationVo;
|
|
|
import com.emoon.mcp.engine.AgentEngine;
|
|
|
@@ -34,7 +31,6 @@ import java.util.Set;
|
|
|
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
|
import static org.mockito.ArgumentMatchers.eq;
|
|
|
-import static org.mockito.ArgumentMatchers.isNull;
|
|
|
import static org.mockito.Mockito.never;
|
|
|
import static org.mockito.Mockito.doAnswer;
|
|
|
import static org.mockito.Mockito.verify;
|
|
|
@@ -55,8 +51,6 @@ class AgentChatServiceImplTest {
|
|
|
@Mock
|
|
|
private IAiConversationService aiConversationService;
|
|
|
@Mock
|
|
|
- private CardInstanceFacade cardInstanceFacade;
|
|
|
- @Mock
|
|
|
private FastGptQueryMetadataService fastGptQueryMetadataService;
|
|
|
@Mock
|
|
|
private DeviceRegistryFacade deviceRegistryFacade;
|
|
|
@@ -298,7 +292,7 @@ class AgentChatServiceImplTest {
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void chatDoesNotCreateCardWhenFastGptMetadataRequiresClarification() {
|
|
|
+ void chatDoesNotSetCardFieldsWhenClarificationRequired() {
|
|
|
AgentChatRequest request = new AgentChatRequest();
|
|
|
request.setAgentId("agent_fastgpt");
|
|
|
request.setQuery("帮我查李医生的号");
|
|
|
@@ -335,8 +329,6 @@ class AgentChatServiceImplTest {
|
|
|
.reply("先给我一个日期。")
|
|
|
.conversationId("chat_001")
|
|
|
.messageId("msg_001")
|
|
|
- .cardKey("time-slot-selection")
|
|
|
- .cardData("{\"slots\":[]}")
|
|
|
.finished(true)
|
|
|
.metadata(Map.of("toolCalls", List.of(Map.of("toolName", "his.querySchedules"))))
|
|
|
.build());
|
|
|
@@ -355,14 +347,11 @@ class AgentChatServiceImplTest {
|
|
|
AgentChatResponse response = service.chat(request, 7L, "tenant-h001");
|
|
|
|
|
|
assertThat(response.getReply()).isEqualTo("日期我还没确认清楚,请按 YYYY-MM-DD 的格式告诉我。");
|
|
|
- assertThat(response.getCardKey()).isNull();
|
|
|
- assertThat(response.getCardData()).isNull();
|
|
|
- assertThat(response.getCardInstanceId()).isNull();
|
|
|
- verify(cardInstanceFacade, never()).create(any(CreateCardInstanceRequest.class));
|
|
|
+ assertThat(response.getResultRefs()).isNull();
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void chatDoesNotCreateLegacyCardWhenFastGptMetadataCreatesSnapshot() {
|
|
|
+ void chatReturnsOnlyResultRefsWhenSnapshotCreated() {
|
|
|
AgentChatRequest request = new AgentChatRequest();
|
|
|
request.setAgentId("agent_fastgpt");
|
|
|
request.setQuery("帮我查一下明天李医生的号源");
|
|
|
@@ -399,8 +388,6 @@ class AgentChatServiceImplTest {
|
|
|
.reply("明天李医生上午还有 1 个号源。")
|
|
|
.conversationId("chat_001")
|
|
|
.messageId("msg_001")
|
|
|
- .cardKey("time-slot-selection")
|
|
|
- .cardData("{\"slots\":[]}")
|
|
|
.finished(true)
|
|
|
.metadata(Map.of("toolCalls", List.of(Map.of("toolName", "his.querySchedules"))))
|
|
|
.build());
|
|
|
@@ -421,140 +408,15 @@ class AgentChatServiceImplTest {
|
|
|
|
|
|
AgentChatResponse response = service.chat(request, 7L, "tenant-h001");
|
|
|
|
|
|
- assertThat(response.getCardKey()).isNull();
|
|
|
- assertThat(response.getCardData()).isNull();
|
|
|
- assertThat(response.getCardInstanceId()).isNull();
|
|
|
assertThat(response.getResultRefs()).hasSize(1);
|
|
|
- verify(cardInstanceFacade, never()).create(any(CreateCardInstanceRequest.class));
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void chatBindsGeneratedConversationIdToCardInstanceForNewConversation() {
|
|
|
- AgentChatRequest request = new AgentChatRequest();
|
|
|
- request.setAgentId("agent_fastgpt");
|
|
|
- request.setQuery("帮我查一下明天李医生的号源");
|
|
|
- request.setTraceId("trace_001");
|
|
|
- request.setUserId(99L);
|
|
|
- request.setDeviceId("KIOSK-001");
|
|
|
-
|
|
|
- AiAgentApp app = new AiAgentApp();
|
|
|
- app.setId(1L);
|
|
|
- app.setStatus("0");
|
|
|
- app.setEngineConfigId(2L);
|
|
|
- when(aiOpenPlatformFacade.findAgentAppByAgentId("agent_fastgpt", 7L, "tenant-h001"))
|
|
|
- .thenReturn(app);
|
|
|
-
|
|
|
- AiAgentEngineConfig config = new AiAgentEngineConfig();
|
|
|
- config.setEngineType("fastgpt");
|
|
|
- config.setConfigJson("{\"baseUrl\":\"http://fastgpt:3000\",\"apiKey\":\"k1\"}");
|
|
|
- when(aiOpenPlatformFacade.findActiveEngineConfigById(2L, 7L, "tenant-h001"))
|
|
|
- .thenReturn(config);
|
|
|
-
|
|
|
- when(agentEngineFactory.getEngine("fastgpt")).thenReturn(fastGptEngine);
|
|
|
- when(fastGptEngine.chat(any())).thenReturn(AgentResponse.builder()
|
|
|
- .reply("明天李医生上午还有 1 个号源。")
|
|
|
- .conversationId("chat_001")
|
|
|
- .messageId("msg_001")
|
|
|
- .cardKey("time-slot-selection")
|
|
|
- .cardData("{\"slots\":[]}")
|
|
|
- .finished(true)
|
|
|
- .build());
|
|
|
- when(aiConversationService.create(
|
|
|
- eq(7), eq(1L), any(String.class), any(String.class), eq(99L),
|
|
|
- eq("fastgpt"), eq("chat_001"), eq("tenant-h001"), eq(1), eq(0)))
|
|
|
- .thenReturn(AiConversationVo.builder().conversationId("unused").build());
|
|
|
- when(cardInstanceFacade.create(any(CreateCardInstanceRequest.class)))
|
|
|
- .thenReturn(CardInstanceVo.builder().instanceId("card_inst_001").build());
|
|
|
-
|
|
|
- AgentChatResponse response = service.chat(request, 7L, "tenant-h001");
|
|
|
-
|
|
|
- ArgumentCaptor<String> conversationIdCaptor = ArgumentCaptor.forClass(String.class);
|
|
|
- verify(aiConversationService).create(
|
|
|
- eq(7), eq(1L), conversationIdCaptor.capture(), any(String.class), eq(99L),
|
|
|
- eq("fastgpt"), eq("chat_001"), eq("tenant-h001"), eq(1), eq(0));
|
|
|
- ArgumentCaptor<CreateCardInstanceRequest> cardCaptor =
|
|
|
- ArgumentCaptor.forClass(CreateCardInstanceRequest.class);
|
|
|
- verify(cardInstanceFacade).create(cardCaptor.capture());
|
|
|
-
|
|
|
- assertThat(response.getConversationId()).isEqualTo(conversationIdCaptor.getValue());
|
|
|
- assertThat(cardCaptor.getValue().getConversationId()).isEqualTo(conversationIdCaptor.getValue());
|
|
|
+ assertThat(response.getResultRefs().get(0).getResultRef()).isEqualTo("result_001");
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void chatStreamKeepsGeneratedConversationIdConsistentForNewConversation() {
|
|
|
+ void chatStreamEmitsSemanticResultWhenFastGptMetadataCreatesSnapshot() {
|
|
|
AgentChatRequest request = new AgentChatRequest();
|
|
|
request.setAgentId("agent_fastgpt");
|
|
|
request.setQuery("帮我查一下明天李医生的号源");
|
|
|
- request.setTraceId("trace_001");
|
|
|
- request.setUserId(99L);
|
|
|
- request.setDeviceId("KIOSK-001");
|
|
|
- request.setInputs(Map.of("taskId", "task_001"));
|
|
|
-
|
|
|
- AiAgentApp app = new AiAgentApp();
|
|
|
- app.setId(1L);
|
|
|
- app.setStatus("0");
|
|
|
- app.setEngineConfigId(2L);
|
|
|
- when(aiOpenPlatformFacade.findAgentAppByAgentId("agent_fastgpt", 7L, "tenant-h001"))
|
|
|
- .thenReturn(app);
|
|
|
-
|
|
|
- AiAgentEngineConfig config = new AiAgentEngineConfig();
|
|
|
- config.setEngineType("fastgpt");
|
|
|
- config.setConfigJson("{\"baseUrl\":\"http://fastgpt:3000\",\"apiKey\":\"k1\"}");
|
|
|
- when(aiOpenPlatformFacade.findActiveEngineConfigById(2L, 7L, "tenant-h001"))
|
|
|
- .thenReturn(config);
|
|
|
-
|
|
|
- when(agentEngineFactory.getEngine("fastgpt")).thenReturn(fastGptEngine);
|
|
|
- doAnswer(invocation -> {
|
|
|
- var callback = invocation.getArgument(1, java.util.function.Consumer.class);
|
|
|
- callback.accept(AgentResponse.builder()
|
|
|
- .reply("明天李医生上午还有 1 个号源。")
|
|
|
- .conversationId("chat_001")
|
|
|
- .messageId("msg_001")
|
|
|
- .cardKey("time-slot-selection")
|
|
|
- .cardData("{\"slots\":[]}")
|
|
|
- .finished(true)
|
|
|
- .metadata(Map.of("toolCalls", List.of()))
|
|
|
- .build());
|
|
|
- return null;
|
|
|
- }).when(fastGptEngine).chatStream(any(), any());
|
|
|
- when(deviceRegistryFacade.hospitalId("7", "KIOSK-001")).thenReturn("hospital-a");
|
|
|
- when(aiConversationService.create(
|
|
|
- eq(7), eq(1L), any(String.class), any(String.class), eq(99L),
|
|
|
- eq("fastgpt"), eq("chat_001"), eq("tenant-h001"), eq(1), eq(0)))
|
|
|
- .thenReturn(AiConversationVo.builder().conversationId("unused").build());
|
|
|
- when(fastGptQueryMetadataService.apply(
|
|
|
- any(AgentResponse.class), any(String.class), eq("task_001"),
|
|
|
- eq("7"), eq("tenant-h001"), eq("hospital-a"), eq("trace_001")))
|
|
|
- .thenReturn(FastGptQueryMetadataService.FastGptQueryMetadataOutcome.noop());
|
|
|
- when(cardInstanceFacade.create(any(CreateCardInstanceRequest.class)))
|
|
|
- .thenReturn(CardInstanceVo.builder().instanceId("card_inst_001").build());
|
|
|
-
|
|
|
- service.chatStream(request, 7L, "tenant-h001", new SseEmitter(5_000L));
|
|
|
-
|
|
|
- ArgumentCaptor<String> conversationIdCaptor = ArgumentCaptor.forClass(String.class);
|
|
|
- verify(aiConversationService).create(
|
|
|
- eq(7), eq(1L), conversationIdCaptor.capture(), any(String.class), eq(99L),
|
|
|
- eq("fastgpt"), eq("chat_001"), eq("tenant-h001"), eq(1), eq(0));
|
|
|
- verify(fastGptQueryMetadataService).apply(
|
|
|
- any(AgentResponse.class),
|
|
|
- eq(conversationIdCaptor.getValue()),
|
|
|
- eq("task_001"),
|
|
|
- eq("7"),
|
|
|
- eq("tenant-h001"),
|
|
|
- eq("hospital-a"),
|
|
|
- eq("trace_001")
|
|
|
- );
|
|
|
- ArgumentCaptor<CreateCardInstanceRequest> cardCaptor =
|
|
|
- ArgumentCaptor.forClass(CreateCardInstanceRequest.class);
|
|
|
- verify(cardInstanceFacade).create(cardCaptor.capture());
|
|
|
- assertThat(cardCaptor.getValue().getConversationId()).isEqualTo(conversationIdCaptor.getValue());
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- void chatStreamDoesNotCreateCardWhenFastGptMetadataRequiresClarification() {
|
|
|
- AgentChatRequest request = new AgentChatRequest();
|
|
|
- request.setAgentId("agent_fastgpt");
|
|
|
- request.setQuery("帮我查李医生的号");
|
|
|
request.setConversationId("conv_001");
|
|
|
request.setTraceId("trace_001");
|
|
|
request.setInputs(Map.of("taskId", "task_001"));
|
|
|
@@ -586,11 +448,9 @@ class AgentChatServiceImplTest {
|
|
|
doAnswer(invocation -> {
|
|
|
var callback = invocation.getArgument(1, java.util.function.Consumer.class);
|
|
|
callback.accept(AgentResponse.builder()
|
|
|
- .reply("先给我一个日期。")
|
|
|
+ .reply("明天李医生上午还有 1 个号源。")
|
|
|
.conversationId("chat_001")
|
|
|
.messageId("msg_001")
|
|
|
- .cardKey("time-slot-selection")
|
|
|
- .cardData("{\"slots\":[]}")
|
|
|
.finished(true)
|
|
|
.metadata(Map.of("toolCalls", List.of(Map.of("toolName", "his.querySchedules"))))
|
|
|
.build());
|
|
|
@@ -604,17 +464,26 @@ class AgentChatServiceImplTest {
|
|
|
eq("tenant-h001"),
|
|
|
eq("hospital-a"),
|
|
|
eq("trace_001")
|
|
|
- )).thenReturn(FastGptQueryMetadataService.FastGptQueryMetadataOutcome.clarify(
|
|
|
- "日期我还没确认清楚,请按 YYYY-MM-DD 的格式告诉我。"
|
|
|
+ )).thenReturn(FastGptQueryMetadataService.FastGptQueryMetadataOutcome.applied(
|
|
|
+ "result_001",
|
|
|
+ 1,
|
|
|
+ "APPOINTMENT_SLOT_CANDIDATES",
|
|
|
+ LocalDateTime.of(2026, 7, 6, 14, 30)
|
|
|
));
|
|
|
|
|
|
- service.chatStream(request, 7L, "tenant-h001", new SseEmitter(5_000L));
|
|
|
+ CapturingSseEmitter emitter = new CapturingSseEmitter();
|
|
|
+ service.chatStream(request, 7L, "tenant-h001", emitter);
|
|
|
|
|
|
- verify(cardInstanceFacade, never()).create(any(CreateCardInstanceRequest.class));
|
|
|
+ String semanticPayload = emitter.payloadFor("semantic_result");
|
|
|
+ assertThat(semanticPayload).contains("\"turnId\":\"msg_001\"");
|
|
|
+ assertThat(semanticPayload).contains("\"answer\":\"明天李医生上午还有 1 个号源。\"");
|
|
|
+ assertThat(semanticPayload).contains("\"resultRef\":\"result_001\"");
|
|
|
+ assertThat(semanticPayload).contains("\"version\":1");
|
|
|
+ assertThat(semanticPayload).contains("\"resultType\":\"APPOINTMENT_SLOT_CANDIDATES\"");
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void chatStreamEmitsSemanticResultWhenFastGptMetadataCreatesSnapshot() {
|
|
|
+ void chatStreamDoesNotEmitCardCreatedEvent() {
|
|
|
AgentChatRequest request = new AgentChatRequest();
|
|
|
request.setAgentId("agent_fastgpt");
|
|
|
request.setQuery("帮我查一下明天李医生的号源");
|
|
|
@@ -675,19 +544,15 @@ class AgentChatServiceImplTest {
|
|
|
CapturingSseEmitter emitter = new CapturingSseEmitter();
|
|
|
service.chatStream(request, 7L, "tenant-h001", emitter);
|
|
|
|
|
|
- String semanticPayload = emitter.payloadFor("semantic_result");
|
|
|
- assertThat(semanticPayload).contains("\"turnId\":\"msg_001\"");
|
|
|
- assertThat(semanticPayload).contains("\"answer\":\"明天李医生上午还有 1 个号源。\"");
|
|
|
- assertThat(semanticPayload).contains("\"resultRef\":\"result_001\"");
|
|
|
- assertThat(semanticPayload).contains("\"version\":1");
|
|
|
- assertThat(semanticPayload).contains("\"resultType\":\"APPOINTMENT_SLOT_CANDIDATES\"");
|
|
|
+ assertThat(emitter.hasEvent("card_created")).isFalse();
|
|
|
+ assertThat(emitter.hasEvent("semantic_result")).isTrue();
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- void chatStreamDoesNotEmitLegacyCardEventWhenFastGptMetadataCreatesSnapshot() {
|
|
|
+ void chatStreamDoesNotEmitCardEventWhenClarificationRequired() {
|
|
|
AgentChatRequest request = new AgentChatRequest();
|
|
|
request.setAgentId("agent_fastgpt");
|
|
|
- request.setQuery("帮我查一下明天李医生的号源");
|
|
|
+ request.setQuery("帮我查李医生的号");
|
|
|
request.setConversationId("conv_001");
|
|
|
request.setTraceId("trace_001");
|
|
|
request.setInputs(Map.of("taskId", "task_001"));
|
|
|
@@ -719,11 +584,9 @@ class AgentChatServiceImplTest {
|
|
|
doAnswer(invocation -> {
|
|
|
var callback = invocation.getArgument(1, java.util.function.Consumer.class);
|
|
|
callback.accept(AgentResponse.builder()
|
|
|
- .reply("明天李医生上午还有 1 个号源。")
|
|
|
+ .reply("先给我一个日期。")
|
|
|
.conversationId("chat_001")
|
|
|
.messageId("msg_001")
|
|
|
- .cardKey("time-slot-selection")
|
|
|
- .cardData("{\"slots\":[]}")
|
|
|
.finished(true)
|
|
|
.metadata(Map.of("toolCalls", List.of(Map.of("toolName", "his.querySchedules"))))
|
|
|
.build());
|
|
|
@@ -737,18 +600,14 @@ class AgentChatServiceImplTest {
|
|
|
eq("tenant-h001"),
|
|
|
eq("hospital-a"),
|
|
|
eq("trace_001")
|
|
|
- )).thenReturn(FastGptQueryMetadataService.FastGptQueryMetadataOutcome.applied(
|
|
|
- "result_001",
|
|
|
- 1,
|
|
|
- "APPOINTMENT_SLOT_CANDIDATES",
|
|
|
- LocalDateTime.of(2026, 7, 6, 14, 30)
|
|
|
+ )).thenReturn(FastGptQueryMetadataService.FastGptQueryMetadataOutcome.clarify(
|
|
|
+ "日期我还没确认清楚,请按 YYYY-MM-DD 的格式告诉我。"
|
|
|
));
|
|
|
|
|
|
CapturingSseEmitter emitter = new CapturingSseEmitter();
|
|
|
service.chatStream(request, 7L, "tenant-h001", emitter);
|
|
|
|
|
|
assertThat(emitter.hasEvent("card_created")).isFalse();
|
|
|
- verify(cardInstanceFacade, never()).create(any(CreateCardInstanceRequest.class));
|
|
|
}
|
|
|
|
|
|
@Test
|