用Spring Boot搭建AI工具执行网关:白名单、审批、幂等与审计完整实战
文章摘要直接把订单、退款、邮件、文件删除等业务方法暴露给大模型会把模型的不确定性带入真实业务系统。更稳妥的做法是建立独立工具执行网关模型只能提出工具名称和参数网关负责身份校验、工具白名单、JSON Schema验证、风险分级、人工确认、幂等执行、结果脱敏和审计。本文使用Spring Boot实现一个可运行的轻量级工具网关并给出ToolDefinition、PolicyEngine、Approval、Idempotency和Audit的核心代码。一、为什么需要工具执行网关最简单的Agent工具调用大模型 → 直接调用业务方法 → 返回结果Demo阶段很方便进入生产环境后会暴露多个问题模型可能选错工具参数可能缺失或格式错误用户没有工具权限同一动作可能重复执行高风险操作缺少确认工具返回敏感数据无法追踪谁在什么时候做了什么工具升级后Schema不兼容服务异常时模型反复重试。工具执行网关把链路改为模型生成Tool Call → 工具网关接收 → 身份与白名单 → Schema校验 → 风险策略 → 审批或确认 → 幂等执行 → 结果脱敏 → 审计 → 返回模型二、项目结构ai-tool-gateway ├── pom.xml └── src/main/java/com/zyentor/toolgateway ├── api │ ├── ToolExecutionController.java │ ├── ToolExecutionRequest.java │ └── ToolExecutionResponse.java ├── definition │ ├── ToolDefinition.java │ ├── ToolRiskLevel.java │ └── ToolRegistry.java ├── execution │ ├── ToolExecutor.java │ ├── ToolExecutionService.java │ └── ToolExecutionContext.java ├── policy │ ├── ToolPolicyEngine.java │ ├── PolicyDecision.java │ └── PermissionService.java ├── approval │ ├── ApprovalService.java │ └── ApprovalStatus.java ├── idempotency │ └── IdempotencyService.java └── audit ├── ToolAuditEvent.java └── ToolAuditService.java三、核心依赖dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-validation/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependencydependencygroupIdcom.networknt/groupIdartifactIdjson-schema-validator/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependencygroupIdorg.postgresql/groupIdartifactIdpostgresql/artifactIdscoperuntime/scope/dependency/dependencies生产项目可以替换Schema验证库但必须使用确定性验证不能只让模型自己判断参数是否合法。四、定义工具风险等级packagecom.zyentor.toolgateway.definition;publicenumToolRiskLevel{LOW,MEDIUM,HIGH,CRITICAL}推荐含义等级示例策略LOW查询天气、公开资料自动执行MEDIUM查询内部库存权限校验后执行HIGH取消订单、发送邮件用户确认CRITICAL退款、删除数据、修改权限二次认证与人工审批风险等级必须由工具所有者配置不能让模型动态决定。五、定义ToolDefinitionpackagecom.zyentor.toolgateway.definition;importcom.fasterxml.jackson.databind.JsonNode;importjava.time.Duration;importjava.util.Set;publicrecordToolDefinition(Stringname,Stringversion,Stringdescription,JsonNodeinputSchema,ToolRiskLevelriskLevel,SetStringrequiredPermissions,booleanidempotent,booleanrequiresConfirmation,Durationtimeout,intmaxResultBytes){}每个工具除了名称和描述还必须包含版本 参数Schema 风险等级 所需权限 是否幂等 是否需要确认 超时 最大返回值六、工具注册表packagecom.zyentor.toolgateway.definition;importorg.springframework.stereotype.Component;importjava.util.Collection;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;ComponentpublicclassToolRegistry{privatefinalMapString,ToolDefinitiondefinitionsnewConcurrentHashMap();publicvoidregister(ToolDefinitiondefinition){Stringkeykey(definition.name(),definition.version());ToolDefinitionexistingdefinitions.putIfAbsent(key,definition);if(existing!null){thrownewIllegalStateException(工具已经注册key);}}publicToolDefinitionget(Stringname,Stringversion){ToolDefinitiondefinitiondefinitions.get(key(name,version));if(definitionnull){thrownewToolNotFoundException(name,version);}returndefinition;}publicCollectionToolDefinitionlist(){returnList.copyOf(definitions.values());}privateStringkey(Stringname,Stringversion){returnname:version;}}生产环境还应防止同名不同语义工具并支持Active Deprecated Disabled Removed生命周期。七、定义执行请求packagecom.zyentor.toolgateway.api;importcom.fasterxml.jackson.databind.JsonNode;importjakarta.validation.constraints.NotBlank;importjakarta.validation.constraints.NotNull;publicrecordToolExecutionRequest(NotBlankStringrequestId,NotBlankStringconversationId,NotBlankStringtoolName,NotBlankStringtoolVersion,NotBlankStringidempotencyKey,NotNullJsonNodearguments,StringapprovalId){}请求中不应让客户端直接传tenantId userId permissions这些字段必须从认证上下文读取。八、定义执行上下文packagecom.zyentor.toolgateway.execution;importjava.util.Set;publicrecordToolExecutionContext(StringrequestId,StringconversationId,StringtenantId,StringuserId,SetStringpermissions,StringclientId,StringsourceIp){}上下文应由网关从JWTOAuth TokenAPI Gateway Header服务身份中解析并进行签名校验。九、参数Schema验证ComponentpublicclassToolArgumentValidator{privatefinalJsonSchemaFactoryschemaFactoryJsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);publicvoidvalidate(ToolDefinitiondefinition,JsonNodearguments){JsonSchemaschemaschemaFactory.getSchema(definition.inputSchema());SetValidationMessageerrorsschema.validate(arguments);if(!errors.isEmpty()){thrownewInvalidToolArgumentsException(errors.stream().limit(10).map(ValidationMessage::getMessage).toList());}}}必须限制Schema大小 Schema深度 参数大小 数组长度 字符串长度 验证时间 错误数量避免恶意Schema和超大参数消耗资源。十、权限与白名单策略ComponentpublicclassPermissionService{publicbooleanhasAllPermissions(ToolExecutionContextcontext,ToolDefinitiondefinition){returncontext.permissions().containsAll(definition.requiredPermissions());}}策略决策publicenumPolicyDecision{ALLOW,REQUIRE_CONFIRMATION,REQUIRE_APPROVAL,DENY}ComponentpublicclassToolPolicyEngine{privatefinalPermissionServicepermissionService;publicToolPolicyEngine(PermissionServicepermissionService){this.permissionServicepermissionService;}publicPolicyDecisiondecide(ToolExecutionContextcontext,ToolDefinitiondefinition){if(!permissionService.hasAllPermissions(context,definition)){returnPolicyDecision.DENY;}returnswitch(definition.riskLevel()){caseLOW,MEDIUM-definition.requiresConfirmation()?PolicyDecision.REQUIRE_CONFIRMATION:PolicyDecision.ALLOW;caseHIGH-PolicyDecision.REQUIRE_CONFIRMATION;caseCRITICAL-PolicyDecision.REQUIRE_APPROVAL;};}}Prompt中的“请谨慎使用”不能替代策略引擎。十一、确认和审批需要分开用户确认用户本人确认当前动作取消订单A1001是否确认人工审批由拥有审批权限的其他人批准退款金额超过5000元需要财务审批状态publicenumApprovalStatus{PENDING,APPROVED,REJECTED,EXPIRED,CANCELLED}审批记录必须绑定工具名称 参数Hash 申请人 审批人 有效期 业务对象参数变化后旧审批不得继续使用。十二、幂等设计模型可能因为网络超时流式断开重试Tool Calling循环用户重复点击重复发起同一工具。数据库表CREATETABLEtool_idempotency(tenant_idVARCHAR(64)NOTNULL,idempotency_keyVARCHAR(200)NOTNULL,tool_nameVARCHAR(100)NOTNULL,arguments_hashVARCHAR(64)NOTNULL,statusVARCHAR(30)NOTNULL,result_jsonTEXT,created_atTIMESTAMPNOTNULL,updated_atTIMESTAMPNOTNULL,PRIMARYKEY(tenant_id,idempotency_key));规则同一幂等键同一参数 → 返回原结果 同一幂等键不同参数 → 拒绝不能只使用Redis短缓存处理付款、退款等关键业务。十三、定义ToolExecutorpackagecom.zyentor.toolgateway.execution;importcom.fasterxml.jackson.databind.JsonNode;publicinterfaceToolExecutor{StringtoolName();StringtoolVersion();JsonNodeexecute(ToolExecutionContextcontext,JsonNodearguments);}示例订单查询ComponentpublicclassQueryOrderExecutorimplementsToolExecutor{privatefinalOrderServiceorderService;privatefinalObjectMapperobjectMapper;OverridepublicStringtoolName(){returnquery_order;}OverridepublicStringtoolVersion(){return1.0;}OverridepublicJsonNodeexecute(ToolExecutionContextcontext,JsonNodearguments){StringorderIdarguments.required(orderId).asText();OrderSummaryresultorderService.query(context.tenantId(),orderId);returnobjectMapper.valueToTree(result);}}十四、执行器注册表ComponentpublicclassToolExecutorRegistry{privatefinalMapString,ToolExecutorexecutors;publicToolExecutorRegistry(ListToolExecutorexecutorList){this.executorsexecutorList.stream().collect(Collectors.toUnmodifiableMap(executor-key(executor.toolName(),executor.toolVersion()),Function.identity()));}publicToolExecutorget(Stringname,Stringversion){ToolExecutorexecutorexecutors.get(key(name,version));if(executornull){thrownewToolExecutorNotFoundException(name,version);}returnexecutor;}}十五、审计事件publicrecordToolAuditEvent(StringauditId,StringrequestId,StringconversationId,StringtenantId,StringuserId,StringtoolName,StringtoolVersion,StringargumentsHash,ToolRiskLevelriskLevel,PolicyDecisionpolicyDecision,StringapprovalId,StringexecutionStatus,longdurationMs,StringresultHash,InstantoccurredAt){}审计日志不建议直接保存完整敏感参数。可以保存参数Hash 脱敏摘要 业务对象ID完整敏感内容放到受控业务系统中。十六、完整ToolExecutionServiceServicepublicclassToolExecutionService{privatefinalToolRegistrytoolRegistry;privatefinalToolExecutorRegistryexecutorRegistry;privatefinalToolArgumentValidatorargumentValidator;privatefinalToolPolicyEnginepolicyEngine;privatefinalApprovalServiceapprovalService;privatefinalIdempotencyServiceidempotencyService;privatefinalToolAuditServiceauditService;publicToolExecutionResponseexecute(ToolExecutionContextcontext,ToolExecutionRequestrequest){longstartSystem.nanoTime();ToolDefinitiondefinitiontoolRegistry.get(request.toolName(),request.toolVersion());argumentValidator.validate(definition,request.arguments());PolicyDecisiondecisionpolicyEngine.decide(context,definition);if(decisionPolicyDecision.DENY){thrownewToolAccessDeniedException();}if(decisionPolicyDecision.REQUIRE_CONFIRMATION){returnToolExecutionResponse.confirmationRequired(request.requestId(),buildConfirmation(definition,request));}if(decisionPolicyDecision.REQUIRE_APPROVAL){approvalService.assertApproved(request.approvalId(),context,definition,request.arguments());}returnidempotencyService.executeOnce(context.tenantId(),request.idempotencyKey(),request.toolName(),request.arguments(),()-executeActual(context,request,definition,decision,start));}}十七、结果大小和脱敏执行成功后不能直接把所有结果返回模型。先处理字段白名单 敏感字段脱敏 最大字节数 分页 结果摘要例如客户对象只返回{customerId:C1001,name:张**,level:VIP,status:ACTIVE}不要返回身份证号完整手机号密码Hash银行卡内部备注数据库技术字段。十八、ControllerRestControllerRequestMapping(/api/tool-executions)publicclassToolExecutionController{privatefinalToolExecutionServiceservice;privatefinalCurrentUserServicecurrentUserService;PostMappingpublicToolExecutionResponseexecute(ValidRequestBodyToolExecutionRequestrequest,HttpServletRequesthttpRequest){CurrentUserusercurrentUserService.requireUser();ToolExecutionContextcontextnewToolExecutionContext(request.requestId(),request.conversationId(),user.tenantId(),user.userId(),user.permissions(),user.clientId(),httpRequest.getRemoteAddr());returnservice.execute(context,request);}}十九、返回协议publicrecordToolExecutionResponse(StringrequestId,Stringstatus,Stringcode,Stringmessage,JsonNodedata,ConfirmationPayloadconfirmation,StringbusinessResultId){}状态建议SUCCESS FAILED DENIED CONFIRMATION_REQUIRED APPROVAL_REQUIRED IN_PROGRESS二十、如何与Spring AI接入Spring AI中的工具不直接执行核心业务而是调用网关Tool(description取消指定订单。高风险动作可能需要确认。)publicToolExecutionResponsecancelOrder(CancelOrderArgumentsarguments,ToolContexttoolContext){returngatewayClient.execute(buildRequest(arguments,toolContext));}模型收到CONFIRMATION_REQUIRED后向用户展示确认内容而不是绕过网关执行。二十一、测试重点至少覆盖未知工具 禁用工具 Schema错误 无权限 确认未完成 审批过期 幂等重复 幂等参数冲突 执行超时 结果过长 敏感字段脱敏 审计写入失败 业务执行成功但响应中断高风险工具要做并发幂等测试。二十二、生产环境还需要补齐OAuth与服务身份数据库事务Outbox事件熔断超时限流多区域幂等Secret管理OpenTelemetry审批通知工具版本灰度Schema兼容检查工具停用开关。总结AI工具网关的核心不是“把函数统一放到一个接口”而是建立确定性控制面白名单 Schema校验 权限 风险策略 确认与审批 幂等 脱敏 审计模型负责提出动作网关负责判断动作是否允许、是否安全以及能否被可靠地执行一次。