公司动态

Spring AI信托程序:构建可控AI Agent的监督层架构与实践

📅 2026/8/23 6:54:05
Spring AI信托程序:构建可控AI Agent的监督层架构与实践
当你的代码库开始“思考”当AI代理开始自主执行任务当整个系统不再完全由你编写的每一行指令控制时你如何确保它依然忠诚于你的业务目标这不再是科幻电影里的情节而是今天每一位引入AI Agent、大模型或自动化流程的开发者正在面临的现实困境。我们习惯了传统的软件开发范式输入确定逻辑确定输出确定。调试时我们可以逐行跟踪设置断点。但AI时代尤其是基于大语言模型的Agent系统引入了一种根本性的不确定性。模型会产生“幻觉”Agent会根据不完全的信息做出“自主”决策多Agent协作可能涌现出意料之外的行为。这时传统的“信任但验证”模式失效了。你不能信任一个黑盒但又必须依赖它。这就是“AI时代的信托程序”要解决的核心问题。它不是一个具体的软件包而是一套工程哲学和最佳实践框架。其核心思想是将AI系统尤其是自主Agent视为一个需要被监督和约束的“受托人”开发者作为“委托人”必须建立一套可观测、可干预、可归责的机制确保AI的行为始终在预设的轨道上运行。简单说就是给你的AI加上“刹车”和“方向盘”并且让你能随时看到仪表盘。如果你正在或计划将Spring AI、AutoGPT、LangChain Agents或是类似“AI小镇”这样的多智能体模拟系统集成到你的产品中那么理解并实施这套“信托程序”将是避免项目失控、确保生产环境稳定性的关键。本文将从一个工程师的视角拆解这套理念并提供可落地的架构模式与代码实践。1. 为什么你的AI项目需要一个“信托程序”在深入技术细节之前我们必须先达成一个共识AI的不可预测性不是bug而是特性。大语言模型基于概率生成Agent基于提示词和工具调用进行推理这决定了其输出具有内在的随机性和上下文依赖性。直接将其部署到生产环境尤其是涉及关键业务、资金或用户数据的场景无异于蒙眼驾驶。传统监控的盲区传统的应用监控如APM关注的是延迟、错误率、资源利用率等指标。它们能告诉你“服务挂了”或“响应慢了”但无法回答更关键的问题你的AI Agent刚刚做出的决策依据是什么可解释性它是否正在执行超出其权限范围的操作权限边界多个Agent的协作是否偏离了预设的目标目标对齐模型的输出是否包含了事实性错误或有害内容内容安全没有这些问题的答案你就在进行一场“盲目的信任”。当问题发生时你面临的将是一个难以调试、难以归因的混沌系统。“信托程序”要建立的三个核心能力可观测性不仅仅是日志而是对AI内部决策过程的“透视”。包括输入的提示词、调用的工具、中间推理步骤、引用的知识来源、最终决策的理由链。可干预性在AI行动的关键节点设置“检查点”和“否决权”。允许人工审核、提供额外约束、或直接终止危险操作。这类似于为自动驾驶汽车设置的安全员。可归责性任何由AI系统产生的结果都必须能追溯到具体的输入、模型版本、工具调用序列和决策上下文。这是事后审计、模型迭代和权责划分的基础。接下来我们将把这套理念转化为一个Spring Boot Spring AI项目中的具体架构和代码。2. 核心架构构建AI系统的“监督层”我们可以将“信托程序”实现为一个独立的“AI监督层”它介于你的业务逻辑和具体的AI模型/Agent执行引擎之间。这个层不替代AI的功能而是对其进行装饰、增强和控制。[业务逻辑层] | v [AI 监督层 (Fiduciary Layer)] --- 核心日志、审核、拦截、评估 | / | \ v v v [审计日志] [人工审核台] [规则引擎] | v [AI 执行层 (Spring AI / LangChain Agent)] | v [大模型 API / 本地模型]这个监督层的主要组件包括审计日志记录器结构化记录所有AI交互。决策拦截器/过滤器链在请求前、响应后执行检查。规则/策略引擎定义AI行为的边界规则。人工审核队列对于高风险操作路由至人工处理。评估与反馈循环收集人工反馈用于优化模型和提示词。3. 环境准备与项目初始化我们将基于Spring Boot 3.x和Spring AI来构建一个演示项目。Spring AI提供了对多种大模型和AI模式的统一抽象是构建此类系统的良好基础。前置条件JDK 17 或更高版本Maven 3.6 或 Gradle一个可用的OpenAI API Key或Azure OpenAI、Ollama等Spring AI支持的其他模型创建Spring Boot项目使用 Spring Initializr 或IDE创建新项目。Project: MavenLanguage: JavaSpring Boot: 3.2.xDependencies:Spring Web,Spring AI OpenAI(或Spring AI Azure OpenAI),Lombok(可选简化代码)关键依赖 (pom.xml)dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version0.8.1/version !-- 请使用最新稳定版 -- /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency配置API密钥 (application.yml)spring: ai: openai: api-key: ${OPENAI_API_KEY:your-api-key-here} # 建议使用环境变量 chat: options: model: gpt-3.5-turbo # 或 gpt-4 temperature: 0.74. 实现核心审计日志与可观测性可观测性是信托程序的基础。我们需要记录每一次AI交互的完整上下文。第一步定义审计事件实体创建一个结构化的审计日志对象用于持久化到数据库或发送到日志聚合系统如ELK。// 文件路径src/main/java/com/example/fiduciary/domain/AiAuditLog.java package com.example.fiduciary.domain; import lombok.Data; import java.time.LocalDateTime; import java.util.Map; import java.util.List; Data public class AiAuditLog { private String id; // UUID private String sessionId; // 会话标识用于串联多轮交互 private String userId; // 触发AI操作的用户 private String agentName; // 执行的Agent或功能名称 private String prompt; // 原始用户输入/提示词 private String fullPrompt; // 组装后的完整提示词包含系统指令等 private ListToolCallRecord toolCalls; // 工具调用记录 private String aiResponse; // AI的原始响应 private String parsedResult; // 业务层解析后的结果 private MapString, Object metadata; // 扩展元数据模型、温度、token用量等 private LocalDateTime requestTime; private LocalDateTime responseTime; private Long latencyMs; // 耗时 private String status; // SUCCESS, FAILED, INTERCEPTED, PENDING_REVIEW private String interceptReason; // 若被拦截记录原因 private String reviewComment; // 人工审核意见 } Data class ToolCallRecord { private String toolName; private String arguments; // JSON格式的参数 private String result; private LocalDateTime callTime; }第二步创建审计切面AOP使用Spring AOP我们可以无侵入地在所有调用AI服务的地方自动记录审计日志。// 文件路径src/main/java/com/example/fiduciary/aop/AiAuditAspect.java package com.example.fiduciary.aop; import com.example.fiduciary.domain.AiAuditLog; import com.example.fiduciary.service.AuditLogService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Component; import java.time.LocalDateTime; Aspect Component Slf4j RequiredArgsConstructor public class AiAuditAspect { private final AuditLogService auditLogService; // 拦截所有调用 ChatClient 的方法 Around(execution(* org.springframework.ai.chat.client.ChatClient.*(..))) public Object auditAiCall(ProceedingJoinPoint joinPoint) throws Throwable { LocalDateTime start LocalDateTime.now(); AiAuditLog auditLog new AiAuditLog(); auditLog.setRequestTime(start); auditLog.setAgentName(joinPoint.getSignature().getName()); // 尝试获取请求参数通常是Prompt Object[] args joinPoint.getArgs(); if (args.length 0 args[0] instanceof String) { auditLog.setPrompt((String) args[0]); } Object result; try { result joinPoint.proceed(); // 执行实际的AI调用 LocalDateTime end LocalDateTime.now(); auditLog.setResponseTime(end); auditLog.setLatencyMs(java.time.Duration.between(start, end).toMillis()); if (result instanceof ChatClient.ChatClientResponse) { ChatClient.ChatClientResponse response (ChatClient.ChatClientResponse) result; auditLog.setAiResponse(response.result().getOutput().getContent()); // 可以进一步解析工具调用记录 // response.result().getMetadata()... } auditLog.setStatus(SUCCESS); } catch (Exception e) { auditLog.setStatus(FAILED); auditLog.setAiResponse(Error: e.getMessage()); auditLogService.saveLog(auditLog); throw e; // 重新抛出异常 } auditLogService.saveLog(auditLog); return result; } }第三步实现审计服务AuditLogService负责将日志保存到数据库或发送到消息队列。这里以简单的内存存储为例。// 文件路径src/main/java/com/example/fiduciary/service/AuditLogService.java package com.example.fiduciary.service; import com.example.fiduciary.domain.AiAuditLog; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.concurrent.ConcurrentLinkedQueue; Service Slf4j public class AuditLogService { // 实际项目中应使用数据库如MongoDB适合JSON文档或时序数据库 private final ConcurrentLinkedQueueAiAuditLog logQueue new ConcurrentLinkedQueue(); public void saveLog(AiAuditLog log) { logQueue.offer(log); // 异步持久化到数据库或发送到Kafka/Elasticsearch // 此处仅打印示例 System.out.println([AI AUDIT] Saved log for agent: log.getAgentName() , status: log.getStatus()); // 实际存储逻辑... } public ConcurrentLinkedQueueAiAuditLog getLogs() { return new ConcurrentLinkedQueue(logQueue); } }5. 实现决策拦截与安全边界仅有观测不够我们还需要在危险行为发生前进行干预。这可以通过“过滤器链”或“拦截器”模式实现。示例内容安全与权限过滤器假设我们有一个AI客服Agent它可以调用“查询用户订单”和“执行退款”两个工具。我们必须确保“执行退款”需要更高级别的授权或人工审核。// 文件路径src/main/java/com/example/fiduciary/filter/AiRequestFilter.java package com.example.fiduciary.filter; import com.example.fiduciary.domain.AiAuditLog; import com.example.fiduciary.service.AuditLogService; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.AbstractChatClientAdvisor; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import java.util.List; Component public class SecurityAndContentFilter extends AbstractChatClientAdvisor implements Ordered { private final AuditLogService auditLogService; private final ListString highRiskTools List.of(execute_refund, delete_user_data, grant_permission); public SecurityAndContentFilter(AuditLogService auditLogService) { this.auditLogService auditLogService; } Override public ChatResponse beforeChat(Prompt prompt, ChatClient.ChatClientRequestOptions options) { // 1. 内容安全过滤检查用户输入是否包含敏感词 String userMessage prompt.getContents().get(0).getText(); // 简化处理 if (containsSensitiveWords(userMessage)) { throw new SecurityException(用户输入包含敏感内容请求被拦截。); } // 2. 检查提示词中是否意图调用高风险工具 if (intendsToCallHighRiskTool(userMessage)) { // 这里可以修改prompt增加额外警告或直接抛出异常要求人工审核 // 我们选择记录日志并添加系统指令 AiAuditLog log new AiAuditLog(); log.setPrompt(userMessage); log.setStatus(INTERCEPTED); log.setInterceptReason(检测到意图调用高风险工具); auditLogService.saveLog(log); // 在实际场景中可能将请求转入人工审核队列并返回等待消息 // 此处示例在系统指令中增加警告 // 注意更复杂的拦截需要修改prompt构造逻辑这里仅示意 } return null; // 返回null继续执行 } Override public ChatResponse afterChat(Prompt prompt, ChatClient.ChatClientRequestOptions options, ChatResponse chatResponse) { // 后置过滤检查AI的回复内容是否安全合规 String aiResponse chatResponse.getResult().getOutput().getContent(); if (containsHarmfulContent(aiResponse)) { // 可以替换为安全回复或标记日志 AiAuditLog log new AiAuditLog(); log.setAiResponse(aiResponse); log.setStatus(FLAGGED); log.setInterceptReason(AI回复包含有害内容); auditLogService.saveLog(log); // 注意直接修改ChatResponse较复杂通常做法是在上层处理或重新生成 } return chatResponse; } private boolean containsSensitiveWords(String text) { // 实现敏感词检测逻辑可接入DFA算法或外部服务 return text ! null (text.contains(违禁词A) || text.contains(违禁词B)); } private boolean intendsToCallHighRiskTool(String text) { // 简单关键词匹配实际应使用更复杂的意图识别 return highRiskTools.stream().anyMatch(text::contains); } private boolean containsHarmfulContent(String text) { // 实现有害内容检测逻辑 return text ! null text.contains(暴力内容); } Override public int getOrder() { return HIGHEST_PRECEDENCE; // 确保此过滤器最先执行 } }将过滤器注册到ChatClientSpring AI的ChatClient支持添加Advisor。在配置类或Service中构建ChatClient时注入我们的过滤器。// 文件路径src/main/java/com/example/fiduciary/service/ChatService.java package com.example.fiduciary.service; import lombok.RequiredArgsConstructor; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.Advisor; import org.springframework.stereotype.Service; Service RequiredArgsConstructor public class ChatService { private final ChatClient.Builder chatClientBuilder; private final SecurityAndContentFilter securityFilter; // 注入我们的过滤器 public String chatWithGuardrails(String userMessage) { ChatClient client chatClientBuilder .defaultAdvisors(securityFilter) // 注册安全过滤器 .build(); return client.prompt() .user(userMessage) .call() .content(); } }6. 构建人工审核与干预工作流对于最高风险的操作或者当AI置信度较低时系统应能暂停自动化流程将决策权交给人。实现一个简单的人工审核队列// 文件路径src/main/java/com/example/fiduciary/domain/ReviewTask.java package com.example.fiduciary.domain; import lombok.Data; import java.time.LocalDateTime; Data public class ReviewTask { private String taskId; private AiAuditLog relatedAuditLog; private String reviewStatus; // PENDING, APPROVED, REJECTED private String reviewerId; private LocalDateTime reviewTime; private String comment; }在拦截器中触发人工审核修改前面的SecurityAndContentFilter当检测到高风险意图时不直接抛出异常而是创建审核任务并返回一个等待消息。// 在SecurityAndContentFilter中增加 private final ReviewService reviewService; Override public ChatResponse beforeChat(Prompt prompt, ChatClient.ChatClientRequestOptions options) { String userMessage prompt.getContents().get(0).getText(); if (requiresHumanReview(userMessage)) { // 1. 创建审核任务 ReviewTask task reviewService.createReviewTask(prompt, HIGH_RISK_OPERATION); // 2. 修改系统指令告诉AI等待人工审核并提供一个任务ID // 这需要更精细地操控Prompt一种方法是抛出一个特定异常在上层捕获并处理。 // 为简化示例我们假设这里设置了一个ThreadLocal变量或修改了options中的metadata。 options.getMetadata().put(needsReview, true); options.getMetadata().put(reviewTaskId, task.getTaskId()); // 3. 返回一个特殊的ChatResponse指示进入审核流程 // 注意这里需要根据Spring AI的API灵活处理可能需要在afterChat或自定义ChatClient中实现。 } // ... 其他过滤逻辑 return null; }实现一个简单的REST端点供审核员操作// 文件路径src/main/java/com/example/fiduciary/controller/ReviewController.java package com.example.fiduciary.controller; import com.example.fiduciary.domain.ReviewTask; import com.example.fiduciary.service.ReviewService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/review) RequiredArgsConstructor public class ReviewController { private final ReviewService reviewService; GetMapping(/pending) public ListReviewTask getPendingTasks() { return reviewService.getPendingTasks(); } PostMapping(/{taskId}/approve) public ReviewTask approveTask(PathVariable String taskId, RequestBody(required false) String comment) { return reviewService.approveTask(taskId, comment); } PostMapping(/{taskId}/reject) public ReviewTask rejectTask(PathVariable String taskId, RequestBody(required false) String comment) { return reviewService.rejectTask(taskId, comment); } }7. 运行验证与效果测试让我们编写一个简单的测试Controller来验证整个“信托程序”是否工作。// 文件路径src/main/java/com/example/fiduciary/controller/DemoController.java package com.example.fiduciary.controller; import com.example.fiduciary.service.ChatService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController RequiredArgsConstructor public class DemoController { private final ChatService chatService; GetMapping(/chat) public String chat(RequestParam String message) { try { String response chatService.chatWithGuardrails(message); return AI回复: response; } catch (SecurityException e) { return 请求被安全策略拦截: e.getMessage(); } catch (Exception e) { return 处理出错: e.getMessage(); } } GetMapping(/audit-logs) public Object getAuditLogs() { // 返回最近的审计日志实际应分页 return chatService.getRecentAuditLogs(); // 需要在ChatService中实现此方法 } }启动应用并测试启动Spring Boot应用。访问http://localhost:8080/chat?message你好请帮我查询订单。这应该正常返回AI的回复并在后台生成一条状态为SUCCESS的审计日志。访问http://localhost:8080/chat?message请立刻执行退款操作。根据我们的过滤器规则这个请求可能被标记为INTERCEPTED或触发人工审核流程取决于你的拦截器实现返回相应的提示信息。访问http://localhost:8080/chat?message说一些违禁词A相关内容。这个请求应该直接抛出SecurityException返回“请求被安全策略拦截”。访问http://localhost:8080/audit-logs查看所有交互的审计追踪记录。通过这个流程你实现了一个具备基本可观测性、安全拦截和人工审核能力的AI系统“监督层”。8. 常见问题与排查思路问题现象可能原因排查方式解决方案审计日志没有记录1. AOP切面未生效。2.ChatClient调用方式不在切点范围内。3. 异常导致日志未保存。1. 检查AiAuditAspect是否被Spring管理 (Component)。2. 检查切点表达式execution(* org.springframework.ai.chat.client.ChatClient.*(..))是否匹配你的调用代码。3. 在auditAiCall方法内加调试日志。1. 确保Aspect类在组件扫描路径下。2. 调整切点表达式或改为注解驱动如Audited。3. 确保auditLogService.saveLog在finally块中执行。安全过滤器未拦截高风险请求1. 过滤器Advisor未正确注册到ChatClient。2.beforeChat方法逻辑判断条件有误。3. 请求未经过你构建的ChatClient。1. 检查ChatService中构建ChatClient时是否添加了defaultAdvisors(securityFilter)。2. 在containsSensitiveWords等方法内打印调试信息。3. 确认业务代码使用的是注入的ChatService而非直接new的ChatClient。1. 确保SecurityAndContentFilter也是一个Advisor并注入。2. 优化意图识别逻辑可使用更专业的NLP模型或规则引擎。3. 统一AI调用入口。AI响应内容过滤不生效afterChat方法中无法直接修改ChatResponse对象。检查afterChat方法是否被调用以及其中的检测逻辑。方案一在afterChat中标记日志并触发后续清理流程如异步重写、通知。方案二使用响应转换器ChatResponseTransformer或在下游业务逻辑中处理。人工审核流程中断了用户体验同步等待审核结果会导致请求长时间挂起。观察请求超时情况。改为异步流程立即返回“请求已提交审核请稍后查询结果”并通过WebSocket、轮询或消息通知用户审核结果。审计日志数据量过大所有交互都全量记录存储和查询压力大。监控数据库增长和查询性能。1. 区分日志级别全量日志存于廉价对象存储热点数据存于数据库。2. 设置保留策略定期归档或清理旧日志。3. 对日志进行采样如仅记录1%的普通请求100%记录高风险请求。9. 最佳实践与工程建议将“信托程序”理念落地到生产环境远不止上述示例代码那么简单。以下是一些关键的最佳实践1. 分层分级的安全策略L0 基础过滤关键词、正则表达式快速拦截明显违规内容。部署在网关或最外层过滤器。L1 模型层过滤使用经过安全对齐的模型或通过系统提示词System Prompt强化行为约束。L2 业务规则过滤如本文示例根据具体业务逻辑如用户权限、订单状态判断是否允许AI执行某项操作。L3 人工审核作为最后的安全网处理模糊或高风险案例。2. 可观测性体系化结构化日志审计日志应采用统一的Schema如OpenTelemetry语义约定方便接入ELK、Datadog等可观测性平台。链路追踪为每个AI请求分配唯一的Trace ID串联起从用户请求、Agent思考、工具调用到最终响应的完整链路。关键指标监控定义并监控SLA指标如AI调用成功率、平均响应延迟、幻觉率需人工标注、工具调用错误率、人工审核介入比例。3. 设计可逆与回滚机制操作前备份如果AI Agent执行的是数据库写入、文件修改等操作应在执行前自动创建快照或备份。事务边界将AI驱动的操作包裹在数据库事务中一旦后续验证失败或人工否决可以整体回滚。补偿操作为AI可能执行的关键操作设计对应的“撤销”API以便在出错时快速修复。4. 持续评估与反馈循环建立评估数据集针对你的业务场景构建一个包含典型、边界和对抗性案例的测试集。自动化评估利用规则或更高级的AI模型对生产环境中的AI输出进行自动化评分如相关性、安全性、事实准确性。人工反馈收集在产品界面提供“结果是否有用”的反馈按钮将负反馈直接关联到对应的审计日志用于模型微调或提示词优化。5. 团队与流程保障明确责任方确定AI系统行为的最终责任主体通常是产品团队或开发团队避免出现责任真空。变更管理对AI模型版本、提示词模板、工具列表、安全规则的任何变更都应像代码变更一样经过评审、测试和灰度发布流程。应急预案制定当AI系统出现严重幻觉、安全漏洞或性能退化时的应急预案包括快速降级切换至规则引擎或人工客服、熔断和回滚。10. 总结从被动响应到主动治理在AI时代尤其是Agent技术逐渐普及的当下“知其然”远远不够我们必须追求“知其所以然”。“信托程序”的本质是将对AI的“黑盒信任”转变为“白盒监督”将事后补救转变为事中干预和事前预防。本文提供的Spring AI集成示例是一个具体的工程化起点。它展示了如何通过审计日志实现可观测性通过过滤拦截器实现安全边界通过人工审核队列实现最终控制。但这套框架的深度和广度可以不断延伸你可以集成更专业的内容安全审核API如Moderation API。你可以引入向量数据库来记录和检索AI的“记忆”实现更长期的行为分析。你可以利用规则引擎如Drools来管理复杂且多变的安全与业务策略。你可以将审计日志与MLOps平台对接持续评估模型性能驱动迭代。最终的目标是构建一个健壮、可信、可控的AI增强系统。作为开发者我们的任务不仅是让AI“能做事”更是要确保它“做对的事”并且我们知道它“如何做事”。这才是“了解你的机器人”在AI时代的真正含义。