Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,7 @@ private ToolResultBlock toToolResultBlock(ChatRequest.ToolResultInput toolResult

private Flux<ChatStreamEvent> toErrorEvent(Throwable exception) {
ErrorResponse errorResponse = exceptionResponseMapper.resolve(exception);
if (errorResponse.isServerError()) {
log.error("Agent stream failed", exception);
}
exceptionResponseMapper.logMapped(log, exception, errorResponse);
return Flux.just(
ChatStreamEvent.builder()
.type(ChatStreamEventType.ERROR)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public ToolResultBlock executeSql(
@ToolParam(name = "sql", description = "The SELECT SQL query statement to execute")
String sql) {
return toolExceptionMapper.run(
"execute SQL",
() -> {
Datasource datasource =
dataSourceService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ public ToolResultBlock generateReport(
}
String reportSessionId = sessionId;
return toolExceptionMapper.run(
"generate report",
() ->
ToolResultBlock.text(
ToolCallConstants.SUCCESS_PREFIX
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public class GetDomainsTool implements MarkAgentTool {
+ " what domains are available before querying tables.")
public ToolResultBlock getDomains() {
return toolExceptionMapper.run(
"get domains",
() ->
ToolResultBlock.text(
JsonUtils.getJsonCodec().toJson(domainService.listDomainNames())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public ToolResultBlock getTableSchema(
@ToolParam(name = "table_name", description = "The table name to query schema for")
String tableName) {
return toolExceptionMapper.run(
"get table schema",
() -> {
Datasource datasource =
dataSourceService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ public ToolResultBlock getTables(
required = false)
List<String> domains) {
return toolExceptionMapper.run(
"get tables",
() -> {
Datasource dataSource =
dataSourceService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
package io.github.malonetalk.exception;

import io.github.malonetalk.common.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.TransientDataAccessException;
Expand All @@ -29,6 +31,7 @@

/** 将已知异常统一映射为 ErrorCode 和对外提示,供 HTTP、SSE、agent tool 共用。 */
@Component
@Slf4j
public class ExceptionResponseMapper {

/** 统一异常映射入口;沿 cause 链逐层识别,被框架包装过的异常仍能保留原 ErrorCode。 */
Expand All @@ -48,9 +51,10 @@ private ErrorResponse map(Throwable current) {
return fromBusinessException(businessException);
}
if (current instanceof IllegalArgumentException) {
// 未迁移的裸参数断言统一视为请求参数错误,
// 避免这些调用点落成 500。
return of(ErrorCode.BAD_REQUEST, current.getMessage());
// 未迁移的裸参数断言统一视为请求参数错误,避免落成 500;
// 原始 message 可能含连接串等内部细节,只进日志不进响应体。
log.debug("IllegalArgumentException mapped to BAD_REQUEST: {}", current.getMessage());
return of(ErrorCode.BAD_REQUEST);
}
if (current instanceof DataIntegrityViolationException) {
return of(ErrorCode.DATA_CONFLICT);
Expand Down Expand Up @@ -91,6 +95,18 @@ private ErrorResponse fromBusinessException(BusinessException exception) {
return of(exception.getErrorCode(), exception.getMessage());
}

/** 统一日志策略:5xx 记完整堆栈,4xx 记一行摘要;HTTP、SSE、tool 三出口共用。 */
public void logMapped(Logger logger, Throwable exception, ErrorResponse errorResponse) {
if (errorResponse.isServerError()) {
logger.error("Mapped server exception", exception);
} else {
logger.warn(
"Mapped client error: {} - {}",
errorResponse.errorCode(),
errorResponse.message());
}
}

/** 对外错误文案不能是空值,避免前端拿到不可展示的 message。 */
private String defaultIfBlank(String value, String defaultValue) {
return value == null || value.isBlank() ? defaultValue : value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,18 @@ public ResponseEntity<Result<Object>> handleConstraintViolation(
@ExceptionHandler(Exception.class)
public ResponseEntity<Result<Object>> handleException(Exception exception) {
ErrorResponse errorResponse = exceptionResponseMapper.resolve(exception);
logMappedException(exception, errorResponse);
exceptionResponseMapper.logMapped(log, exception, errorResponse);
return response(errorResponse);
}

private String resolveBadRequestMessage(Exception exception) {
if (exception instanceof HttpMessageNotReadableException) {
return "Malformed request body.";
}
if (exception instanceof MethodArgumentTypeMismatchException typeMismatch) {
// 转换异常消息含内部类型名(如 java.lang.Long),对外只暴露参数名。
return "Invalid value for parameter '" + typeMismatch.getName() + "'.";
}
return exception.getMessage() == null
? "Invalid request parameters."
: exception.getMessage();
Expand Down Expand Up @@ -129,21 +133,16 @@ private FieldValidationError toFieldValidationError(ObjectError error) {
return new FieldValidationError(field, message);
}

/** ConstraintViolation 的路径可能带方法名或对象名前缀,这里只保留最后一级字段。 */
/** ConstraintViolation 的路径形如 "tableName"(参数直接约束)或 "arg0.address.city"(嵌套 DTO),
* 去掉第一段参数/方法前缀,保留嵌套字段路径供前端定位。 */
private String resolveConstraintField(String propertyPath) {
int separatorIndex = propertyPath.lastIndexOf('.');
int separatorIndex = propertyPath.indexOf('.');
if (separatorIndex < 0 || separatorIndex == propertyPath.length() - 1) {
return propertyPath;
}
return propertyPath.substring(separatorIndex + 1);
}

private void logMappedException(Exception exception, ErrorResponse errorResponse) {
if (errorResponse.isServerError()) {
log.error("Mapped server exception", exception);
}
}

/** 按错误码和指定文案组装 HTTP 响应。 */
private ResponseEntity<Result<Object>> response(ErrorCode errorCode, String message) {
return response(exceptionResponseMapper.of(errorCode, message));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,15 @@ public class ToolExceptionMapper {

private final ExceptionResponseMapper exceptionResponseMapper;

public ToolResultBlock run(String actionName, ToolAction action) {
public ToolResultBlock run(ToolAction action) {
try {
return action.run();
} catch (ToolSuspendException exception) {
// 挂起不是错误:交给 agentscope 走 ask_user/ask_caliber 恢复流程,吞掉会破坏交互语义。
throw exception;
} catch (Exception exception) {
ErrorResponse errorResponse = exceptionResponseMapper.resolve(exception);
if (errorResponse.isServerError()) {
log.error("Tool action failed: {}", actionName, exception);
}
exceptionResponseMapper.logMapped(log, exception, errorResponse);
return toToolError(errorResponse);
}
}
Expand Down
Loading