debug和superagent提交的数据保持一致
This commit is contained in:
@@ -14,7 +14,7 @@ import java.time.LocalDateTime;
|
||||
* @param originalFileName 原始 EML 安全文件名
|
||||
* @param originalEmlOssUrl 原始 EML OSS URL
|
||||
* @param originalEmlSha256 原始 EML SHA-256
|
||||
* @param payloadJson 发送给 SuperAgent 的 AgentBus-like payload
|
||||
* @param payloadJson 发送给 SuperAgent 的 AgentBus Outlook-like 主 payload
|
||||
* @param superagentSessionId SuperAgent session ID
|
||||
* @param superagentRunId SuperAgent run ID
|
||||
* @param superagentProfileId SuperAgent profile ID
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.time.LocalDateTime;
|
||||
* @param originalFileName 原始上传文件安全文件名
|
||||
* @param originalEmlOssUrl 原始 .eml OSS URL
|
||||
* @param originalEmlSha256 原始 .eml SHA-256
|
||||
* @param payloadJson AgentBus-like payload JSON
|
||||
* @param payloadJson AgentBus Outlook-like 主 payload JSON
|
||||
* @param superagentSessionId SuperAgent session ID
|
||||
* @param superagentRunId SuperAgent run ID
|
||||
* @param superagentProfileId SuperAgent profile ID
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.Map;
|
||||
* @param htmlBodySanitized 后端清洗后的 HTML,前端调试展示应优先使用
|
||||
* @param htmlSanitizeRequired 是否要求前端按安全 HTML 规则展示
|
||||
* @param htmlRenderMode HTML 渲染建议模式
|
||||
* @param agentbusLikePayload 发送给 SuperAgent 的结构化 payload
|
||||
* @param agentbusLikePayload 发送给 SuperAgent 的 AgentBus Outlook-like 主 payload
|
||||
* @param superagentSessionId SuperAgent session ID
|
||||
* @param superagentRunId SuperAgent run ID
|
||||
* @param superagentRawAnswer SuperAgent 原始最终回答
|
||||
|
||||
@@ -30,7 +30,7 @@ public class DebugEmlSuperAgentRunEntity {
|
||||
private String originalEmlOssUrl;
|
||||
/** 原始 .eml 文件 SHA-256。 */
|
||||
private String originalEmlSha256;
|
||||
/** AgentBus-like payload JSON。 */
|
||||
/** 发送给 SuperAgent 的 AgentBus Outlook-like 主 payload JSON。 */
|
||||
private String payloadJson;
|
||||
/** SuperAgent Open API session ID。 */
|
||||
private String superagentSessionId;
|
||||
|
||||
@@ -82,6 +82,7 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
|
||||
private static final Logger log = LoggerFactory.getLogger(DebugEmlSuperAgentRunServiceImpl.class);
|
||||
private static final String SOURCE_PROVIDER = "DEBUG_EML_UPLOAD";
|
||||
private static final String SOURCE_CHANNEL = "EMAIL";
|
||||
private static final String REPLY_POLICY_MODE = "manual";
|
||||
private static final String SCHEMA_VERSION = "debug-eml-upload-v1";
|
||||
private static final DateTimeFormatter DATE_FOLDER_FORMATTER = DateTimeFormatter.BASIC_ISO_DATE;
|
||||
private static final Pattern CID_REFERENCE_PATTERN = Pattern.compile(
|
||||
@@ -479,12 +480,9 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
|
||||
String htmlBodySanitized = htmlSanitizerService.sanitizeHtml(htmlWithOssUrls);
|
||||
String htmlRenderMode = htmlSanitizerService.htmlRenderMode(htmlWithOssUrls);
|
||||
Map<String, Object> payload = buildAgentBusLikePayload(
|
||||
runId,
|
||||
runLabel,
|
||||
createdAt,
|
||||
externalMessageId,
|
||||
externalConversationId,
|
||||
originalMessageId,
|
||||
originalConversationId,
|
||||
parsed,
|
||||
htmlWithOssUrls,
|
||||
uploadedMedia);
|
||||
@@ -528,11 +526,15 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
|
||||
SuperAgentMailDebugRequest superAgentRequest = new SuperAgentMailDebugRequest(
|
||||
buildSuperAgentMessage(payloadJson),
|
||||
"debug-eml-" + runId,
|
||||
Map.of(
|
||||
"source", "th-hotel-debug-eml-upload",
|
||||
"debug_run_id", runId.toString(),
|
||||
"source_message_id", captureResult.inboxId().toString(),
|
||||
"hotel_id", hotelId));
|
||||
buildSuperAgentMetadata(
|
||||
runId,
|
||||
runLabel,
|
||||
captureResult.inboxId(),
|
||||
hotelId,
|
||||
externalMessageId,
|
||||
externalConversationId,
|
||||
originalMessageId,
|
||||
originalConversationId));
|
||||
List<SuperAgentOpenApiTraceEvent> streamedTraceEvents = new ArrayList<>();
|
||||
Consumer<SuperAgentOpenApiTraceEvent> traceConsumer = traceEvent -> {
|
||||
streamedTraceEvents.add(traceEvent);
|
||||
@@ -919,28 +921,24 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装接近 AgentBus 邮件输入的 payload。
|
||||
* 组装发给 SuperAgent 的 AgentBus Outlook 邮件 payload。调试来源信息放入 Open API metadata,
|
||||
* 避免 DEBUG_EML_UPLOAD 等调试语义影响 SuperAgent 对实时 AgentBus 邮件的判断。
|
||||
*/
|
||||
private Map<String, Object> buildAgentBusLikePayload(
|
||||
Long runId,
|
||||
String runLabel,
|
||||
LocalDateTime createdAt,
|
||||
String externalMessageId,
|
||||
String externalConversationId,
|
||||
String originalMessageId,
|
||||
String originalConversationId,
|
||||
ParsedEmlMessage parsed,
|
||||
String htmlWithOssUrls,
|
||||
List<UploadedMedia> uploadedMedia) {
|
||||
Map<String, Object> source = new LinkedHashMap<>();
|
||||
source.put("channel", SOURCE_CHANNEL);
|
||||
source.put("provider", SOURCE_PROVIDER);
|
||||
source.put("external_message_id", externalMessageId);
|
||||
source.put("external_conversation_id", externalConversationId);
|
||||
source.put("original_message_id", originalMessageId);
|
||||
source.put("original_conversation_id", originalConversationId);
|
||||
source.put("sender", parsed.sender());
|
||||
source.put("subject", parsed.subject());
|
||||
source.put("sent_at", parsed.sentAt() == null ? null : parsed.sentAt().toString());
|
||||
source.put("received_at", utcInstantText(createdAt));
|
||||
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
body.put("content_type", contentType(parsed, htmlWithOssUrls));
|
||||
@@ -950,26 +948,56 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
|
||||
List<Map<String, Object>> inlineImages = mediaPayload(uploadedMedia, SourceMessageMediaType.INLINE_IMAGE.code());
|
||||
List<Map<String, Object>> attachments = mediaPayload(uploadedMedia, SourceMessageMediaType.ATTACHMENT.code());
|
||||
|
||||
Map<String, Object> replyPolicy = new LinkedHashMap<>();
|
||||
replyPolicy.put("mode", "debug_only");
|
||||
replyPolicy.put("final_only", true);
|
||||
|
||||
Map<String, Object> debugContext = new LinkedHashMap<>();
|
||||
debugContext.put("debug_run_id", runId.toString());
|
||||
debugContext.put("run_label", runLabel);
|
||||
debugContext.put("original_eml_oss_url", uploadedMedia.get(0).result().externalUrl());
|
||||
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("schema_version", SCHEMA_VERSION);
|
||||
payload.put("text", parsed.textBody());
|
||||
payload.put("received_at", utcInstantText(createdAt));
|
||||
payload.put("source", source);
|
||||
payload.put("body", body);
|
||||
payload.put("inline_images", inlineImages);
|
||||
payload.put("attachments", attachments);
|
||||
payload.put("reply_policy", replyPolicy);
|
||||
payload.put("debug_context", debugContext);
|
||||
payload.put("reply_policy", replyPolicy());
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 AgentBus Outlook 关键字段中的回复策略。Debug 链路第一版不自动回复客户,使用非 Debug 专属的人工处理语义。
|
||||
*/
|
||||
private Map<String, Object> replyPolicy() {
|
||||
Map<String, Object> replyPolicy = new LinkedHashMap<>();
|
||||
replyPolicy.put("mode", REPLY_POLICY_MODE);
|
||||
replyPolicy.put("final_only", true);
|
||||
return replyPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 Debug EML 调用 SuperAgent 的 metadata。这里保存调试来源和原始 EML 标识,不混入邮件主 payload。
|
||||
*/
|
||||
private Map<String, Object> buildSuperAgentMetadata(
|
||||
Long runId,
|
||||
String runLabel,
|
||||
Long sourceMessageId,
|
||||
String hotelId,
|
||||
String externalMessageId,
|
||||
String externalConversationId,
|
||||
String originalMessageId,
|
||||
String originalConversationId) {
|
||||
Map<String, Object> debugContext = new LinkedHashMap<>();
|
||||
putIfNotNull(debugContext, "debug_run_id", runId == null ? null : runId.toString());
|
||||
putIfNotNull(debugContext, "run_label", runLabel);
|
||||
putIfNotNull(debugContext, "original_message_id", originalMessageId);
|
||||
putIfNotNull(debugContext, "original_conversation_id", originalConversationId);
|
||||
|
||||
Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
metadata.put("source", "th-hotel-debug-eml-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);
|
||||
putIfNotNull(metadata, "external_message_id", externalMessageId);
|
||||
putIfNotNull(metadata, "external_conversation_id", externalConversationId);
|
||||
metadata.put("debug_context", debugContext);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造媒体 payload 数组。
|
||||
*/
|
||||
@@ -978,13 +1006,12 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
|
||||
.filter(item -> mediaType.equals(item.result().mediaType()))
|
||||
.map(item -> {
|
||||
Map<String, Object> media = new LinkedHashMap<>();
|
||||
media.put("id", item.result().externalMediaId());
|
||||
media.put("media_type", item.result().mediaType());
|
||||
media.put("file_name", item.result().fileName());
|
||||
media.put("content_type", item.result().contentType());
|
||||
media.put("size_bytes", item.result().sizeBytes());
|
||||
media.put("external_url", item.result().externalUrl());
|
||||
media.put("external_media_id", item.result().externalMediaId());
|
||||
media.put("object_key", item.result().objectKey());
|
||||
if (item.contentId() != null) {
|
||||
media.put("content_id", item.contentId());
|
||||
}
|
||||
@@ -1000,16 +1027,21 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
|
||||
boolean hasText = parsed.textBody() != null && !parsed.textBody().isBlank();
|
||||
boolean hasHtml = htmlWithOssUrls != null && !htmlWithOssUrls.isBlank();
|
||||
if (hasText && hasHtml) {
|
||||
return "MIXED";
|
||||
return "mixed";
|
||||
}
|
||||
return hasHtml ? "HTML" : "TEXT";
|
||||
return hasHtml ? "html" : "text";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造发送给 SuperAgent 的消息文本。
|
||||
*/
|
||||
private String buildSuperAgentMessage(String payloadJson) {
|
||||
return "请基于以下 Debug 邮件 JSON 输出结构化任务抽取结果,只返回 JSON,不要创建订单或任务:\n" + payloadJson;
|
||||
return """
|
||||
请基于以下 AgentBus Outlook 邮件 JSON 输出结构化任务抽取结果。
|
||||
只返回 SuperAgent 当前约定的 JSON 或 S10/S99 特殊结果,不要创建订单、任务、OPERA 操作或客户回复。
|
||||
|
||||
AgentBus Outlook 邮件 JSON:
|
||||
""" + payloadJson;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1282,6 +1314,25 @@ public class DebugEmlSuperAgentRunServiceImpl implements DebugEmlSuperAgentRunSe
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向 metadata 写入非空字段,避免 Map.of 不能容纳 null,也避免输出无意义空值。
|
||||
*/
|
||||
private void putIfNotNull(Map<String, Object> target, String key, Object value) {
|
||||
if (value != null) {
|
||||
target.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据库 UTC LocalDateTime 转为带 Z 的 ISO-8601 时间点。
|
||||
*/
|
||||
private String utcInstantText(LocalDateTime value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return value.atOffset(ZoneOffset.UTC).toInstant().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 去掉 URL 末尾斜杠,便于用公开基础 URL 还原对象路径。
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
@@ -16,6 +17,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import cn.nianxx.thhotel.ThHotelApplication;
|
||||
import cn.nianxx.thhotel.integrations.ai.superagent.common.request.SuperAgentMailDebugRequest;
|
||||
import cn.nianxx.thhotel.integrations.ai.superagent.common.result.SuperAgentOpenApiResult;
|
||||
import cn.nianxx.thhotel.integrations.ai.superagent.common.result.SuperAgentOpenApiTraceEvent;
|
||||
import cn.nianxx.thhotel.integrations.ai.superagent.service.SuperAgentOpenApiClient;
|
||||
@@ -33,6 +35,7 @@ import java.util.function.Consumer;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
@@ -139,11 +142,18 @@ class DebugEmlSuperAgentControllerTest {
|
||||
.andExpect(jsonPath("$.html_body_sanitized", containsString("https://oss.example.test/")))
|
||||
.andExpect(jsonPath("$.html_sanitize_required").value(true))
|
||||
.andExpect(jsonPath("$.html_render_mode").value("SANITIZED_HTML"))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.schema_version").value("debug-eml-upload-v1"))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.source.provider").value("DEBUG_EML_UPLOAD"))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.source.original_message_id")
|
||||
.value("debug-controller-message-001@example.test"))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.reply_policy.mode").value("debug_only"))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.text",
|
||||
containsString("Please create booking from debug email.")))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.body.html", containsString("https://oss.example.test/")))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.source.channel").value("EMAIL"))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.source.external_message_id",
|
||||
containsString("debug-eml-run-")))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.schema_version").doesNotExist())
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.source.provider").doesNotExist())
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.source.original_message_id").doesNotExist())
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.reply_policy.mode").value("manual"))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.reply_policy.final_only").value(true))
|
||||
.andExpect(jsonPath("$.agentbus_like_payload.debug_context").doesNotExist())
|
||||
.andExpect(jsonPath("$.superagent_session_id").value("session-debug-001"))
|
||||
.andExpect(jsonPath("$.superagent_run_id").value("run-debug-001"))
|
||||
.andExpect(jsonPath("$.superagent_raw_answer", containsString("ai_task_results")))
|
||||
@@ -153,13 +163,15 @@ class DebugEmlSuperAgentControllerTest {
|
||||
|
||||
Long sourceCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM platform_source_message_inbox inbox
|
||||
FROM platform_debug_eml_superagent_run run
|
||||
JOIN platform_source_message_inbox inbox ON inbox.id = run.source_message_id
|
||||
JOIN platform_source_message_payload payload ON payload.inbox_id = inbox.id
|
||||
WHERE inbox.hotel_id = 'HOTEL-TEST'
|
||||
AND inbox.provider = 'DEBUG_EML_UPLOAD'
|
||||
AND inbox.channel = 'EMAIL'
|
||||
AND payload.schema_version = 'debug-eml-upload-v1'
|
||||
AND payload.payload_json LIKE '%controller-test%'
|
||||
AND inbox.external_message_id LIKE 'debug-eml-run-%'
|
||||
AND run.run_label = 'controller-test'
|
||||
""", Long.class);
|
||||
org.assertj.core.api.Assertions.assertThat(sourceCount).isEqualTo(1L);
|
||||
|
||||
@@ -167,13 +179,35 @@ class DebugEmlSuperAgentControllerTest {
|
||||
SELECT COUNT(*)
|
||||
FROM platform_source_message_media media
|
||||
JOIN platform_source_message_inbox inbox ON inbox.id = media.inbox_id
|
||||
JOIN platform_debug_eml_superagent_run run ON run.source_message_id = inbox.id
|
||||
JOIN platform_source_message_payload payload ON payload.inbox_id = inbox.id
|
||||
WHERE inbox.provider = 'DEBUG_EML_UPLOAD'
|
||||
AND media.media_type = 'ORIGINAL_EMAIL'
|
||||
AND payload.payload_json LIKE '%controller-test%'
|
||||
AND inbox.external_message_id LIKE 'debug-eml-run-%'
|
||||
AND run.run_label = 'controller-test'
|
||||
""", Long.class);
|
||||
org.assertj.core.api.Assertions.assertThat(originalEmailMediaCount).isEqualTo(1L);
|
||||
|
||||
ArgumentCaptor<SuperAgentMailDebugRequest> superAgentRequestCaptor =
|
||||
ArgumentCaptor.forClass(SuperAgentMailDebugRequest.class);
|
||||
verify(superAgentOpenApiClient).invokeMailDebug(superAgentRequestCaptor.capture());
|
||||
SuperAgentMailDebugRequest superAgentRequest = superAgentRequestCaptor.getValue();
|
||||
org.assertj.core.api.Assertions.assertThat(superAgentRequest.message())
|
||||
.contains("AgentBus Outlook 邮件 JSON")
|
||||
.contains("\"external_message_id\":\"debug-eml-run-")
|
||||
.contains("\"body\"")
|
||||
.doesNotContain("\"provider\":\"DEBUG_EML_UPLOAD\"")
|
||||
.doesNotContain("\"schema_version\"")
|
||||
.doesNotContain("\"debug_context\"")
|
||||
.doesNotContain("\"original_message_id\"")
|
||||
.doesNotContain("\"mode\":\"debug_only\"")
|
||||
.contains("\"reply_policy\"")
|
||||
.contains("\"mode\":\"manual\"")
|
||||
.contains("\"final_only\":true");
|
||||
org.assertj.core.api.Assertions.assertThat(superAgentRequest.metadata())
|
||||
.containsEntry("source", "th-hotel-debug-eml-upload")
|
||||
.containsKey("debug_context");
|
||||
|
||||
Long debugRunCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM platform_debug_eml_superagent_run
|
||||
@@ -509,13 +543,13 @@ class DebugEmlSuperAgentControllerTest {
|
||||
|
||||
Long sourceCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM platform_source_message_inbox inbox
|
||||
JOIN platform_source_message_payload payload ON payload.inbox_id = inbox.id
|
||||
FROM platform_debug_eml_superagent_run run
|
||||
JOIN platform_source_message_inbox inbox ON inbox.id = run.source_message_id
|
||||
WHERE inbox.hotel_id = 'HOTEL-TEST'
|
||||
AND inbox.provider = 'DEBUG_EML_UPLOAD'
|
||||
AND inbox.channel = 'EMAIL'
|
||||
AND inbox.external_message_id LIKE 'debug-eml-run-%'
|
||||
AND payload.payload_json LIKE '%repeat-debug-upload%'
|
||||
AND run.run_label = 'repeat-debug-upload'
|
||||
""", Long.class);
|
||||
org.assertj.core.api.Assertions.assertThat(sourceCount).isEqualTo(2L);
|
||||
}
|
||||
@@ -534,11 +568,11 @@ class DebugEmlSuperAgentControllerTest {
|
||||
|
||||
Long sourceCount = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM platform_source_message_inbox inbox
|
||||
JOIN platform_source_message_payload payload ON payload.inbox_id = inbox.id
|
||||
FROM platform_debug_eml_superagent_run run
|
||||
JOIN platform_source_message_inbox inbox ON inbox.id = run.source_message_id
|
||||
WHERE inbox.hotel_id = 'HOTEL-TEST'
|
||||
AND inbox.provider = 'DEBUG_EML_UPLOAD'
|
||||
AND payload.payload_json LIKE '%system-hotel-debug-upload%'
|
||||
AND run.run_label = 'system-hotel-debug-upload'
|
||||
""", Long.class);
|
||||
org.assertj.core.api.Assertions.assertThat(sourceCount).isEqualTo(1L);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user