实现酒店上下文单酒店收口

This commit is contained in:
andy
2026-07-10 15:31:51 +08:00
parent 7492c21350
commit d69f3975ef
52 changed files with 1183 additions and 188 deletions

View File

@@ -4,7 +4,7 @@ import cn.nianxx.thhotel.integrations.ai.superagent.common.request.SuperAgentTas
import cn.nianxx.thhotel.integrations.ai.superagent.service.SuperAgentTaskResultSecurityService;
import cn.nianxx.thhotel.integrations.ai.superagent.service.impl.SuperAgentTaskResultException;
import cn.nianxx.thhotel.integrations.ai.superagent.service.impl.SuperAgentTaskResultProperties;
import cn.nianxx.thhotel.integrations.messaging.agentbus.adapter.AgentBusProperties;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextService;
import cn.nianxx.thhotel.workflows.reservation.common.result.SuperAgentTaskResultResponse;
import cn.nianxx.thhotel.workflows.reservation.service.ReservationAiTaskIntakeService;
import java.nio.charset.StandardCharsets;
@@ -29,7 +29,7 @@ public class SuperAgentTaskResultController {
private final SuperAgentTaskResultSecurityService securityService;
private final SuperAgentTaskResultProperties properties;
private final AgentBusProperties agentBusProperties;
private final HotelContextService hotelContextService;
private final ReservationAiTaskIntakeService intakeService;
/**
@@ -38,11 +38,11 @@ public class SuperAgentTaskResultController {
public SuperAgentTaskResultController(
SuperAgentTaskResultSecurityService securityService,
SuperAgentTaskResultProperties properties,
AgentBusProperties agentBusProperties,
HotelContextService hotelContextService,
ReservationAiTaskIntakeService intakeService) {
this.securityService = securityService;
this.properties = properties;
this.agentBusProperties = agentBusProperties;
this.hotelContextService = hotelContextService;
this.intakeService = intakeService;
}
@@ -74,7 +74,7 @@ public class SuperAgentTaskResultController {
requestBody,
clientId,
requestId,
agentBusProperties.getCapture().getDefaultHotelId());
hotelContextService.resolveSystemHotelId());
HttpStatus status = response.idempotentReplay() ? HttpStatus.OK : HttpStatus.CREATED;
return ResponseEntity.status(status).body(response);
}

View File

@@ -2,6 +2,7 @@ package cn.nianxx.thhotel.integrations.ai.superagent.control;
import cn.nianxx.thhotel.integrations.ai.superagent.common.result.SuperAgentTaskResultErrorResponse;
import cn.nianxx.thhotel.integrations.ai.superagent.service.impl.SuperAgentTaskResultException;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextException;
import cn.nianxx.thhotel.workflows.reservation.service.impl.ReservationAiTaskIntakeException;
import java.util.List;
import org.springframework.http.ResponseEntity;
@@ -34,6 +35,16 @@ public class SuperAgentTaskResultControllerAdvice {
.body(error(exception.getErrorCode(), exception.getMessage()));
}
/**
* 处理系统酒店缺失、多 ACTIVE 酒店或酒店访问受限等上下文异常。
*/
@ExceptionHandler(HotelContextException.class)
public ResponseEntity<SuperAgentTaskResultErrorResponse> handleHotelContextException(
HotelContextException exception) {
return ResponseEntity.status(exception.getStatus())
.body(error(exception.getErrorCode(), exception.getMessage()));
}
/**
* 构建统一错误响应,第一版不回显 request_id避免异常路径暴露未经校验的外部输入。
*/

View File

@@ -8,6 +8,8 @@ import cn.nianxx.thhotel.integrations.mcp.superagent.common.result.SuperAgentMcp
import cn.nianxx.thhotel.integrations.mcp.superagent.common.result.SuperAgentMcpToolDefinition;
import cn.nianxx.thhotel.integrations.mcp.superagent.common.result.SuperAgentMcpToolsListResult;
import cn.nianxx.thhotel.integrations.mcp.superagent.service.SuperAgentMcpService;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextService;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextException;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationAiCaseContextQueryRequest;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationAiObjectDetailQueryRequest;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationMessageConversationQueryRequest;
@@ -48,6 +50,7 @@ public class SuperAgentMcpServiceImpl implements SuperAgentMcpService {
private final ReservationAiTaskIntakeService intakeService;
private final SuperAgentMcpProperties properties;
private final ObjectMapper objectMapper;
private final HotelContextService hotelContextService;
/**
* 注入已有业务服务和 JSON 工具MCP 层不直接访问 Mapper 或数据库。
@@ -56,11 +59,13 @@ public class SuperAgentMcpServiceImpl implements SuperAgentMcpService {
ReservationAiQueryService aiQueryService,
ReservationAiTaskIntakeService intakeService,
SuperAgentMcpProperties properties,
ObjectMapper objectMapper) {
ObjectMapper objectMapper,
HotelContextService hotelContextService) {
this.aiQueryService = aiQueryService;
this.intakeService = intakeService;
this.properties = properties;
this.objectMapper = objectMapper;
this.hotelContextService = hotelContextService;
}
/**
@@ -154,6 +159,13 @@ public class SuperAgentMcpServiceImpl implements SuperAgentMcpService {
exception.getErrorCode(),
exception.getMessage(),
Map.of("http_status", exception.getStatus().value())));
} catch (HotelContextException exception) {
return SuperAgentMcpToolCallResult.error(
"TH Hotel 酒店上下文解析失败:" + exception.getMessage(),
errorStructuredContent(
exception.getErrorCode(),
exception.getMessage(),
Map.of("http_status", exception.getStatus().value())));
} catch (IllegalArgumentException exception) {
return SuperAgentMcpToolCallResult.error(
"MCP 工具参数不合法。",
@@ -238,7 +250,11 @@ public class SuperAgentMcpServiceImpl implements SuperAgentMcpService {
Map.of("tool", TOOL_SUBMIT_TASK_RESULTS)));
}
String rawBody = objectMapper.writeValueAsString(arguments);
SuperAgentTaskResultResponse response = intakeService.accept(rawBody, MCP_CLIENT_ID, null);
SuperAgentTaskResultResponse response = intakeService.accept(
rawBody,
MCP_CLIENT_ID,
null,
hotelContextService.resolveSystemHotelId());
return SuperAgentMcpToolCallResult.success(TOOL_SUBMIT_TASK_RESULTS + " 调用成功。", response);
}
@@ -311,37 +327,37 @@ public class SuperAgentMcpServiceImpl implements SuperAgentMcpService {
private Map<String, Object> caseContextSchema() {
Map<String, Object> propertiesMap = new LinkedHashMap<>();
propertiesMap.put("hotel_id", stringField("酒店上下文 ID"));
propertiesMap.put("hotel_id", stringField("可选酒店上下文 ID;缺省由 TH Hotel 后端解析系统酒店"));
propertiesMap.put("group_code", nullableStringField("Group / Allotment 查询 key"));
propertiesMap.put("confirmation_number", nullableStringField("FIT confirmation number 查询 key"));
propertiesMap.put("reservation_no", nullableStringField("OPERA reservation no"));
propertiesMap.put("object_type_hint", nullableStringField("调用方推测的对象类型"));
propertiesMap.put("target_key_source", nullableStringField("key 来源,例如 body_current"));
propertiesMap.put("body_thread_used_only_as_evidence", Map.of("type", "boolean", "description", "历史线程 key 是否仅作为证据"));
return objectSchema(propertiesMap, List.of("hotel_id"));
return objectSchema(propertiesMap, List.of());
}
private Map<String, Object> objectDetailSchema() {
Map<String, Object> propertiesMap = new LinkedHashMap<>();
propertiesMap.put("hotel_id", stringField("酒店上下文 ID"));
propertiesMap.put("hotel_id", stringField("可选酒店上下文 ID;缺省由 TH Hotel 后端解析系统酒店"));
propertiesMap.put("object_id", stringField("查询对象 ID第一版支持 ORDER:{order_id}"));
propertiesMap.put("object_type", nullableStringField("调用方对象类型提示"));
return objectSchema(propertiesMap, List.of("hotel_id", "object_id"));
return objectSchema(propertiesMap, List.of("object_id"));
}
private Map<String, Object> conversationQuerySchema() {
Map<String, Object> propertiesMap = new LinkedHashMap<>();
propertiesMap.put("hotel_id", stringField("酒店上下文 ID"));
propertiesMap.put("hotel_id", stringField("可选酒店上下文 ID;缺省由 TH Hotel 后端解析系统酒店"));
propertiesMap.put("source_provider", nullableStringField("来源提供方,默认 AGENTBUS"));
propertiesMap.put("source_channel", nullableStringField("来源渠道,默认 EMAIL"));
propertiesMap.put("external_conversation_id", nullableStringField("外部邮件会话 ID"));
propertiesMap.put("source_message_id", nullableStringField("外部来源消息 ID可作为锚点反查会话"));
return objectSchema(propertiesMap, List.of("hotel_id"));
return objectSchema(propertiesMap, List.of());
}
private Map<String, Object> submitTaskResultsSchema() {
Map<String, Object> propertiesMap = new LinkedHashMap<>();
propertiesMap.put("hotel_id", stringField("酒店上下文 ID"));
propertiesMap.put("hotel_id", stringField("可选酒店上下文 ID;缺省由 TH Hotel 后端解析系统酒店"));
propertiesMap.put("source_provider", nullableStringField("来源提供方,默认 AGENTBUS"));
propertiesMap.put("source_channel", nullableStringField("来源渠道,默认 EMAIL"));
propertiesMap.put("source_message_id", stringField("外部来源消息 ID对应 AgentBus source.external_message_id"));
@@ -353,7 +369,7 @@ public class SuperAgentMcpServiceImpl implements SuperAgentMcpService {
"type", "array",
"description", "AI 抽取警告",
"items", Map.of("type", "object")));
return objectSchema(propertiesMap, List.of("hotel_id", "source_message_id", "ai_task_results"));
return objectSchema(propertiesMap, List.of("source_message_id", "ai_task_results"));
}
private Map<String, Object> objectSchema(Map<String, Object> propertiesMap, List<String> required) {

View File

@@ -3,6 +3,7 @@ package cn.nianxx.thhotel.integrations.messaging.agentbus.adapter;
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageCommand;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageCaptureResult;
import cn.nianxx.thhotel.platform.message.service.SourceMessageCaptureService;
import cn.nianxx.thhotel.platform.hotel.service.HotelContextService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -26,6 +27,7 @@ public class AgentBusFrameProcessor {
private final SourceMessageCaptureService captureService;
private final AgentBusConnectionStatus status;
private final AgentBusProperties properties;
private final HotelContextService hotelContextService;
/**
* 注入 AgentBus frame 处理依赖。外部协议转换与平台捕获服务通过稳定命令隔离。
@@ -35,12 +37,14 @@ public class AgentBusFrameProcessor {
AgentBusSourceMessageAdapter sourceMessageAdapter,
SourceMessageCaptureService captureService,
AgentBusConnectionStatus status,
AgentBusProperties properties) {
AgentBusProperties properties,
HotelContextService hotelContextService) {
this.objectMapper = objectMapper;
this.sourceMessageAdapter = sourceMessageAdapter;
this.captureService = captureService;
this.status = status;
this.properties = properties;
this.hotelContextService = hotelContextService;
}
/**
@@ -69,8 +73,9 @@ public class AgentBusFrameProcessor {
return AgentBusFrameProcessResult.ignored();
}
try {
String hotelId = hotelContextService.resolveSystemHotelId();
CaptureSourceMessageCommand command = sourceMessageAdapter.toCaptureCommand(
properties.getCapture().getDefaultHotelId(),
hotelId,
frame);
SourceMessageCaptureResult result = captureService.capture(command);
status.markFrameCaptured();

View File

@@ -123,8 +123,6 @@ public class AgentBusProperties {
/** 是否把业务 frame 写入 SourceMessage Inbox。 */
private boolean enabled = true;
/** AgentBus 未提供酒店上下文时使用的默认酒店 ID。 */
private String defaultHotelId = "HOTEL-TEST";
public boolean isEnabled() {
return enabled;
@@ -133,13 +131,5 @@ public class AgentBusProperties {
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getDefaultHotelId() {
return defaultHotelId;
}
public void setDefaultHotelId(String defaultHotelId) {
this.defaultHotelId = defaultHotelId;
}
}
}