对齐Debug EML V4入站模型

This commit is contained in:
andy
2026-07-20 19:53:44 +07:00
parent 626e87abdb
commit 038fdc3c8d
17 changed files with 175 additions and 37 deletions

View File

@@ -17,8 +17,8 @@ public class SuperAgentOpenApiProperties {
private String baseUrl = "";
/** SuperAgent Open API Key。 */
private String apiKey = "";
/** Debug EML 创建 session 时使用的 external_subject_id。 */
private String externalSubjectId = "th-hotel-debug-eml-upload";
/** Debug EML 创建 session 时使用的 external_subject_id;默认对齐实时 AgentBus V4 profile。 */
private String externalSubjectId = "th-hotel-agentbus-source-message";
/** AgentBus 自动分发创建 session 时使用的 external_subject_id。 */
private String agentbusExternalSubjectId = "th-hotel-agentbus-source-message";
/** 建立连接超时。 */

View File

@@ -157,7 +157,7 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
}
/**
* 处理单封 Debug EML 上传;第一版只展示 SuperAgent 结果,不创建订单或任务
* 处理单封 Debug EML 上传;本服务只展示 SuperAgent 结果,业务模型写入仍由正式回调 / MCP 负责
*/
@Override
public DebugEmlSuperAgentRunResult uploadAndRun(
@@ -1098,7 +1098,7 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
putIfNotNull(debugContext, "original_conversation_id", originalConversationId);
Map<String, Object> metadata = new LinkedHashMap<>();
metadata.put("source", "th-hotel-debug-eml-upload");
metadata.put("source", "th-hotel-debug-eml-v4-upload");
putIfNotNull(metadata, "debug_run_id", runId == null ? null : runId.toString());
putIfNotNull(metadata, "source_message_id", sourceMessageId == null ? null : sourceMessageId.toString());
putIfNotNull(metadata, "hotel_id", hotelId);

View File

@@ -47,7 +47,9 @@ superagent:
enabled: ${SUPERAGENT_DEV_OPEN_API_ENABLED:${SUPERAGENT_OPEN_API_ENABLED:false}}
base-url: ${DEERFLOW_DEV_BASE_URL:${DEERFLOW_BASE_URL:}}
api-key: ${DEERFLOW_DEV_OPEN_API_KEY:${DEERFLOW_OPEN_API_KEY:}}
external-subject-id: ${SUPERAGENT_DEV_DEBUG_EML_EXTERNAL_SUBJECT_ID:${SUPERAGENT_DEBUG_EML_EXTERNAL_SUBJECT_ID:th-hotel-debug-eml-upload}}
# Debug EML 默认复用实时 AgentBus V4 subject避免 V4 smoke 误打到历史 Debug V2/V3 profile。
# 如需调历史 profile必须显式配置 SUPERAGENT_DEV_DEBUG_EML_EXTERNAL_SUBJECT_ID。
external-subject-id: ${SUPERAGENT_DEV_DEBUG_EML_EXTERNAL_SUBJECT_ID:${SUPERAGENT_DEBUG_EML_EXTERNAL_SUBJECT_ID:${SUPERAGENT_DEV_AGENTBUS_EXTERNAL_SUBJECT_ID:${SUPERAGENT_AGENTBUS_EXTERNAL_SUBJECT_ID:th-hotel-agentbus-source-message}}}}
agentbus-external-subject-id: ${SUPERAGENT_DEV_AGENTBUS_EXTERNAL_SUBJECT_ID:${SUPERAGENT_AGENTBUS_EXTERNAL_SUBJECT_ID:th-hotel-agentbus-source-message}}
connect-timeout: ${SUPERAGENT_DEV_DEBUG_EML_CONNECT_TIMEOUT:${SUPERAGENT_DEBUG_EML_CONNECT_TIMEOUT:15s}}
read-timeout: ${SUPERAGENT_DEV_DEBUG_EML_READ_TIMEOUT:${SUPERAGENT_DEBUG_EML_READ_TIMEOUT:180s}}

View File

@@ -45,7 +45,8 @@ superagent:
enabled: ${SUPERAGENT_PROD_OPEN_API_ENABLED:false}
base-url: ${DEERFLOW_PROD_BASE_URL:${DEERFLOW_BASE_URL}}
api-key: ${DEERFLOW_PROD_OPEN_API_KEY:${DEERFLOW_OPEN_API_KEY}}
external-subject-id: ${SUPERAGENT_PROD_DEBUG_EML_EXTERNAL_SUBJECT_ID:${SUPERAGENT_DEBUG_EML_EXTERNAL_SUBJECT_ID:th-hotel-debug-eml-upload}}
# Debug EML 默认复用实时 AgentBus V4 subject生产如需启用 Debug EML必须额外确认权限、审计和 profile。
external-subject-id: ${SUPERAGENT_PROD_DEBUG_EML_EXTERNAL_SUBJECT_ID:${SUPERAGENT_DEBUG_EML_EXTERNAL_SUBJECT_ID:${SUPERAGENT_PROD_AGENTBUS_EXTERNAL_SUBJECT_ID:${SUPERAGENT_AGENTBUS_EXTERNAL_SUBJECT_ID:th-hotel-agentbus-source-message}}}}
agentbus-external-subject-id: ${SUPERAGENT_PROD_AGENTBUS_EXTERNAL_SUBJECT_ID:${SUPERAGENT_AGENTBUS_EXTERNAL_SUBJECT_ID:th-hotel-agentbus-source-message}}
connect-timeout: ${SUPERAGENT_PROD_DEBUG_EML_CONNECT_TIMEOUT:${SUPERAGENT_DEBUG_EML_CONNECT_TIMEOUT:15s}}
read-timeout: ${SUPERAGENT_PROD_DEBUG_EML_READ_TIMEOUT:${SUPERAGENT_DEBUG_EML_READ_TIMEOUT:180s}}

View File

@@ -49,7 +49,9 @@ superagent:
enabled: ${SUPERAGENT_TEST_OPEN_API_ENABLED:${SUPERAGENT_OPEN_API_ENABLED:false}}
base-url: ${DEERFLOW_TEST_BASE_URL:${DEERFLOW_BASE_URL:}}
api-key: ${DEERFLOW_TEST_OPEN_API_KEY:${DEERFLOW_OPEN_API_KEY:}}
external-subject-id: ${SUPERAGENT_TEST_DEBUG_EML_EXTERNAL_SUBJECT_ID:${SUPERAGENT_DEBUG_EML_EXTERNAL_SUBJECT_ID:th-hotel-debug-eml-upload}}
# Debug EML 默认复用实时 AgentBus V4 subject避免 V4 smoke 误打到历史 Debug V2/V3 profile。
# 如需调历史 profile必须显式配置 SUPERAGENT_TEST_DEBUG_EML_EXTERNAL_SUBJECT_ID。
external-subject-id: ${SUPERAGENT_TEST_DEBUG_EML_EXTERNAL_SUBJECT_ID:${SUPERAGENT_DEBUG_EML_EXTERNAL_SUBJECT_ID:${SUPERAGENT_TEST_AGENTBUS_EXTERNAL_SUBJECT_ID:${SUPERAGENT_AGENTBUS_EXTERNAL_SUBJECT_ID:th-hotel-agentbus-source-message}}}}
agentbus-external-subject-id: ${SUPERAGENT_TEST_AGENTBUS_EXTERNAL_SUBJECT_ID:${SUPERAGENT_AGENTBUS_EXTERNAL_SUBJECT_ID:th-hotel-agentbus-source-message}}
connect-timeout: ${SUPERAGENT_TEST_DEBUG_EML_CONNECT_TIMEOUT:${SUPERAGENT_DEBUG_EML_CONNECT_TIMEOUT:15s}}
read-timeout: ${SUPERAGENT_TEST_DEBUG_EML_READ_TIMEOUT:${SUPERAGENT_DEBUG_EML_READ_TIMEOUT:180s}}

View File

@@ -42,11 +42,13 @@ class SuperAgentOpenApiClientImplTest {
void shouldRequestStreamingMessagesWithIncludeTraceAndEmitPublicTraceEvents() throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
AtomicReference<String> streamQuery = new AtomicReference<>();
AtomicReference<String> sessionBody = new AtomicReference<>();
AtomicReference<String> sessionCsrfToken = new AtomicReference<>();
AtomicReference<String> sessionCookie = new AtomicReference<>();
AtomicReference<String> streamCsrfToken = new AtomicReference<>();
AtomicReference<String> streamCookie = new AtomicReference<>();
server.createContext("/api/open/agent-sessions", exchange -> {
sessionBody.set(new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8));
sessionCsrfToken.set(exchange.getRequestHeaders().getFirst("X-CSRF-Token"));
sessionCookie.set(exchange.getRequestHeaders().getFirst("Cookie"));
byte[] response = "{\"session_id\":\"session-http-001\"}".getBytes(StandardCharsets.UTF_8);
@@ -91,7 +93,6 @@ class SuperAgentOpenApiClientImplTest {
properties.setEnabled(true);
properties.setBaseUrl("http://127.0.0.1:" + server.getAddress().getPort());
properties.setApiKey("df_open_test");
properties.setExternalSubjectId("debug-subject");
properties.setConnectTimeout(Duration.ofSeconds(5));
properties.setReadTimeout(Duration.ofSeconds(5));
SuperAgentOpenApiClientImpl client = new SuperAgentOpenApiClientImpl(
@@ -106,6 +107,7 @@ class SuperAgentOpenApiClientImplTest {
Map.of("source", "unit-test")), traceEvents::add);
assertThat(streamQuery.get()).isEqualTo("include_trace=true");
assertThat(sessionBody.get()).contains("\"external_subject_id\":\"th-hotel-agentbus-source-message\"");
assertThat(sessionCsrfToken.get()).isNotBlank();
assertThat(sessionCookie.get()).contains("csrf_token=" + sessionCsrfToken.get());
assertThat(streamCsrfToken.get()).isNotBlank();

View File

@@ -22,6 +22,7 @@ import cn.nianxx.thhotel.integrations.ai.superagent.common.result.SuperAgentOpen
import cn.nianxx.thhotel.integrations.ai.superagent.common.result.SuperAgentOpenApiTraceEvent;
import cn.nianxx.thhotel.integrations.ai.superagent.service.SuperAgentOpenApiClient;
import cn.nianxx.thhotel.integrations.ai.superagent.service.impl.SuperAgentOpenApiException;
import cn.nianxx.thhotel.integrations.ai.superagent.service.impl.SuperAgentOpenApiProperties;
import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.request.ObjectStoragePutRequest;
import cn.nianxx.thhotel.integrations.storage.aliyunoss.common.result.ObjectStoragePutResult;
import cn.nianxx.thhotel.integrations.storage.aliyunoss.service.ObjectStorageService;
@@ -68,8 +69,7 @@ import org.springframework.web.client.RestClientResponseException;
"debug.eml-upload.include-booking-excel-extractions=true",
"aliyun.oss.debug-eml-prefix=debug/eml/",
"reservation.booking-excel-extraction.enabled=true",
"superagent.open-api.enabled=true",
"superagent.open-api.external-subject-id=test-debug-eml"
"superagent.open-api.enabled=true"
})
@AutoConfigureMockMvc
@ActiveProfiles("test")
@@ -87,6 +87,9 @@ class DebugEmlSuperAgentControllerTest {
@Autowired
private DebugEmlSuperAgentRunService runService;
@Autowired
private SuperAgentOpenApiProperties superAgentOpenApiProperties;
@Autowired
private BookingExcelExtractionProperties bookingExcelExtractionProperties;
@@ -96,6 +99,13 @@ class DebugEmlSuperAgentControllerTest {
@MockBean
private SuperAgentOpenApiClient superAgentOpenApiClient;
@Test
void shouldUseAgentBusV4SubjectByDefaultForDebugEmlSmoke() {
org.assertj.core.api.Assertions.assertThat(superAgentOpenApiProperties.getExternalSubjectId())
.isEqualTo("th-hotel-agentbus-source-message")
.isEqualTo(superAgentOpenApiProperties.getAgentbusExternalSubjectId());
}
@Test
void shouldRejectUploadWhenDebugKeyMissing() throws Exception {
mockMvc.perform(multipart(ENDPOINT)
@@ -218,7 +228,7 @@ class DebugEmlSuperAgentControllerTest {
.contains("\"mode\":\"manual\"")
.contains("\"final_only\":true");
org.assertj.core.api.Assertions.assertThat(superAgentRequest.metadata())
.containsEntry("source", "th-hotel-debug-eml-upload")
.containsEntry("source", "th-hotel-debug-eml-v4-upload")
.containsKey("debug_context");
Long debugRunCount = jdbcTemplate.queryForObject("""

View File

@@ -1117,6 +1117,44 @@ class SuperAgentTaskResultControllerTest {
assertThat(replayCardCount).isEqualTo(4L);
}
@Test
void shouldRouteDebugEmlV4BusinessResultToV4ModelWithoutLegacyTask() throws Exception {
SourceMessageCaptureResult source = captureDebugEmlSourceMessage("debug-eml-run-v4-business-001");
String body = v4BusinessRootBody("debug-eml-run-v4-business-001");
mockMvc.perform(signedPost(body, "nonce-debug-eml-v4-business-001"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.source_message_id").value("debug-eml-run-v4-business-001"))
.andExpect(jsonPath("$.accepted_count").value(2))
.andExpect(jsonPath("$.items[0].task_id").doesNotExist())
.andExpect(jsonPath("$.items[1].task_id").doesNotExist());
Long legacyTaskCount = jdbcTemplate.queryForObject("""
SELECT COUNT(*)
FROM workflow_reservation_task
WHERE source_message_id = ?
""", Long.class, source.inboxId());
Long v4OrderTaskCount = jdbcTemplate.queryForObject("""
SELECT COUNT(*)
FROM workflow_reservation_v4_order_task
WHERE source_message_id = ?
AND order_ref = 'order-1'
""", Long.class, source.inboxId());
List<String> v4CardTypes = jdbcTemplate.queryForList("""
SELECT card_type
FROM workflow_reservation_v4_task_card
WHERE source_message_id = ?
ORDER BY card_sort_order, source_event_index
""", String.class, source.inboxId());
assertThat(legacyTaskCount).isZero();
assertThat(v4OrderTaskCount).isEqualTo(1L);
assertThat(v4CardTypes).containsExactly(
"SOURCE_MESSAGE_DISPLAY",
"BASIC_INFORMATION",
"ROOM_INFORMATION",
"PAYMENT");
}
@Test
void shouldCreateV4OrderTasksForMultipleOrderRefs() throws Exception {
SourceMessageCaptureResult source = captureSourceMessage("mail-v4-two-order-refs-001");
@@ -3441,13 +3479,26 @@ class SuperAgentTaskResultControllerTest {
}
private SourceMessageCaptureResult captureSourceMessage(String externalMessageId, String hotelId, String channel) {
return captureSourceMessage(externalMessageId, hotelId, channel, "AGENTBUS", "agentbus-outlook-v1");
}
private SourceMessageCaptureResult captureDebugEmlSourceMessage(String externalMessageId) {
return captureSourceMessage(externalMessageId, "HOTEL-TEST", "EMAIL", "DEBUG_EML_UPLOAD", "debug-eml-upload-v1");
}
private SourceMessageCaptureResult captureSourceMessage(
String externalMessageId,
String hotelId,
String channel,
String provider,
String schemaVersion) {
String providerFrameId = "frame-" + externalMessageId;
if (providerFrameId.length() > 128) {
providerFrameId = "frame-" + Integer.toHexString(externalMessageId.hashCode());
}
return captureService.capture(new CaptureSourceMessageCommand(
hotelId,
"AGENTBUS",
provider,
channel,
externalMessageId,
"thread-" + externalMessageId,
@@ -3459,7 +3510,7 @@ class SuperAgentTaskResultControllerTest {
"Please handle booking message.",
"<html><body>Please handle booking message.</body></html>",
"{\"source\":{\"external_message_id\":\"" + externalMessageId + "\"}}",
"agentbus-outlook-v1",
schemaVersion,
List.of()
));
}