news 2026/7/6 4:06:36

Spring AI Advisor 与安全 Tool Calling:调用链追踪、权限和硬边界

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI Advisor 与安全 Tool Calling:调用链追踪、权限和硬边界

Spring AI Advisor 与安全 Tool Calling:给 AI 调用链加追踪、权限和硬边界

前面的学习已经完成了 ChatClient、Memory、RAG 和 Tool Calling。

但把这些能力串起来以后,新的问题也出现了:

  • 一次 AI 调用怎么生成统一的traceId
  • Prompt 版本怎么跟着请求进入调用链;
  • 模型调用耗时在哪里统计;
  • 多个会话怎么真正隔离;
  • 模型决定调用高风险工具时,后端如何阻止越权;
  • 为什么只在 System Prompt 里写“禁止审批”还不够。

这一章主要学习 Spring AI 的两个工程化能力:

Advisor:统一拦截和增强 ChatClient 调用链 ToolContext:把后端可信上下文传入工具方法

一、Advisor 可以理解成 AI 调用链拦截器

Spring AI 的ChatClient最终要完成一次模型调用。

Advisor 可以在这次调用的前后执行公共逻辑,例如:

  • 注入上下文;
  • 记录请求和响应;
  • 管理 Chat Memory;
  • 执行 RAG 检索;
  • 统计耗时;
  • 生成链路追踪信息。

本项目实现了一个自定义TraceAdvisor

publicclassTraceAdvisorimplementsCallAdvisor{privatefinalAiTracePropertiesaiTraceProperties;publicTraceAdvisor(AiTracePropertiesaiTraceProperties){this.aiTraceProperties=aiTraceProperties;}@OverridepublicChatClientResponseadviseCall(ChatClientRequestrequest,CallAdvisorChainchain){// 调用链增强逻辑}}

它实现的是CallAdvisor,对应同步调用:

.call()

二、ChatClientRequest 和 ChatClientResponse 是什么

ChatClientRequest表示当前即将进入模型调用链的请求。

它不只是用户输入,还包含:

  • Prompt;
  • ChatOptions;
  • Advisor context;
  • Tool 等调用配置。

ChatClientResponse表示经过调用链之后的响应。

它主要包含:

  • 模型返回的ChatResponse
  • Advisor context;
  • 调用链补充的元数据。

这里的context不是模型聊天记忆,而是 Advisor 在一次调用链中传递的键值数据。

例如:

traceId promptVersion latencyMs

这些字段不一定发送给大模型,它们主要用于 Java 侧的调用链协作。

三、在请求进入模型前注入追踪信息

StringtraceId=UUID.randomUUID().toString();longstart=System.currentTimeMillis();StringpromptVersion=request.context().getOrDefault("promptVersion",aiTraceProperties.getDefaultPromptVersion()).toString();

然后通过mutate()基于原请求创建一个新请求:

ChatClientRequesttracedRequest=request.mutate().context("traceId",traceId).context("promptVersion",promptVersion).build();

这里没有直接修改原对象,而是构建一个带追踪信息的新请求。

再把它传给下一个 Advisor 或最终模型调用:

ChatClientResponseresponse=chain.nextCall(tracedRequest);

chain.nextCall()是关键。如果不调用它,请求会停在当前 Advisor,模型也不会真正执行。

四、在响应返回后补充耗时

longlatencyMs=System.currentTimeMillis()-start;returnresponse.mutate().context("traceId",traceId).context("promptVersion",promptVersion).context("latencyMs",latencyMs).build();

这样 Controller 可以从ChatClientResponse.context()读取调用链元数据:

ChatClientResponseresponse=chatClient.prompt().system("你是一个企业 AI 助手").advisors(a->a.param("promptVersion","advisor-demo-v1")).user(message).call().chatClientResponse();Map<String,Object>result=newLinkedHashMap<>();result.put("answer",response.chatResponse().getResult().getOutput().getText());result.put("traceId",response.context().get("traceId"));result.put("promptVersion",response.context().get("promptVersion"));result.put("latencyMs",response.context().get("latencyMs"));

返回结果类似:

{"answer":"......","traceId":"1ee03d3e-...","promptVersion":"advisor-demo-v1","latencyMs":862}

五、Advisor 的开关和默认配置

@Component@ConfigurationProperties(prefix="app.ai.trace")@DatapublicclassAiTraceProperties{privatebooleanenabled=true;privateStringdefaultPromptVersion="spring-ai-learning-v1";}

Advisor 内部先判断开关:

if(!aiTraceProperties.isEnabled()){returnchain.nextCall(request);}

关闭后仍然继续调用模型,只是不再执行追踪增强。

这个设计比把开关写死在代码里更适合后续扩展。

六、Advisor 顺序为什么重要

@OverridepublicintgetOrder(){returnOrdered.HIGHEST_PRECEDENCE+100;}

一个ChatClient可以挂多个 Advisor:

builder.defaultAdvisors(newTraceAdvisor(aiTraceProperties),newSimpleLoggerAdvisor()).build();

顺序决定谁先处理请求、谁最后处理响应。

如果以后同时使用:

  • TraceAdvisor;
  • MessageChatMemoryAdvisor;
  • QuestionAnswerAdvisor;
  • 安全审计 Advisor;

就必须明确各自的职责和顺序,否则日志、记忆或检索上下文可能不符合预期。

七、会话隔离不要只使用一个 chatId

项目还尝试了更明确的会话键:

StringmemoryKey=userId+":"+conversationId;

调用时:

returnchatClient.prompt().user(message).advisors(a->a.param(ChatMemory.CONVERSATION_ID,memoryKey)).call().content();

这样可以区分:

user-01:conversation-01 user-01:conversation-02 user-02:conversation-01

同一个用户可以有多个窗口,不同用户也不会共享记忆。

清理会话时直接使用同一个 key:

chatMemory.clear(memoryKey);

当前仍然是内存级MessageWindowChatMemory,应用重启会丢失。这里学习的是会话标识设计,不是持久化方案。

八、Prompt 权限为什么不是安全边界

早期代码在 System Prompt 中写:

普通巡检员无权审批预算,必须拒绝。

这可以影响模型行为,但不能保证安全。

原因是:

  • Prompt 可能被绕过;
  • 模型可能误判用户角色;
  • 工具可能被其他入口直接调用;
  • Prompt Injection 可能改变模型决策;
  • 最终执行动作的是 Java 方法,不是 Prompt。

所以真正的权限校验必须进入工具方法。

九、用 ToolContext 传递可信后端上下文

Controller 根据当前学习示例确定用户角色:

StringuserRole="boss-01".equals(chatId)?"董事长":"普通巡检员";

注册工具时传入toolContext

returnchatClient.prompt().system(""" 你是企业 AI 调度助手。 当前用户角色是:%s。 如果工具返回 allowed=false,必须告诉用户拒绝原因。 高风险工具即使 allowed=true,也只能说生成草案。 """.formatted(userRole)).user(message).tools(newSecureAgentTools()).toolContext(Map.of("chatId",chatId,"userRole",userRole)).call().content();

模型负责决定是否调用工具并生成业务参数。

ToolContext中的角色由 Java 后端注入,不需要模型自己生成。

十、工具方法内部做硬校验

高风险预算工具:

@Tool(name="approveMaintenanceBudgetSecure",description="高风险工具:申请紧急维修预算草案")publicStringapproveMaintenanceBudget(@ToolParam(description="设备编号,例如 A-01")StringdeviceId,@ToolParam(description="预算金额")Integeramount,ToolContexttoolContext){StringuserRole=String.valueOf(toolContext.getContext().getOrDefault("userRole","UNKNOWN"));if(!isValidDeviceId(deviceId)){returndenied("approveMaintenanceBudgetSecure","HIGH","设备编号格式不合法");}if(amount==null||amount<=0||amount>50000){returndenied("approveMaintenanceBudgetSecure","HIGH","预算金额必须在 1 到 50000 之间");}if(!BUDGET_APPROVERS.contains(userRole)){returndenied("approveMaintenanceBudgetSecure","HIGH","当前角色无权申请紧急维修预算");}returnallowed("approveMaintenanceBudgetSecure","HIGH","已生成维修预算草案,等待人工最终确认");}

这里同时校验了三件事:

  1. 设备编号格式;
  2. 金额范围;
  3. 当前角色是否有权限。

即使模型坚持调用工具,Java 代码仍然可以拒绝。

十一、工具返回值也要结构化

允许时返回:

{"toolName":"approveMaintenanceBudgetSecure","allowed":true,"riskLevel":"HIGH","reason":"校验通过","data":"已生成维修预算草案,等待人工最终确认"}

拒绝时返回:

{"toolName":"approveMaintenanceBudgetSecure","allowed":false,"riskLevel":"HIGH","reason":"当前角色普通巡检员无权申请紧急维修预算","data":""}

模型只负责把工具结果组织成人能理解的答案,不能自行把allowed=false改成成功。

高风险操作即使允许,也只生成草案,不直接完成最终审批。这就是 Human-in-the-loop 的最小雏形。

十二、低风险工具和高风险工具要分级

只读查询工具:

@Tool(name="getDeviceStatusSecure",description="低风险只读工具:根据设备编号查询设备状态")

预算工具:

@Tool(name="approveMaintenanceBudgetSecure",description="高风险工具:申请紧急维修预算草案")

真实系统中可以进一步把工具分成:

风险等级示例建议
LOW查询设备、查询文档校验参数后直接执行
MEDIUM创建工单、生成邮件草稿记录审计,必要时确认
HIGH审批、删除、转账、发正式通知强权限、人工确认、可回滚

十三、测试 URL

Advisor:

http://localhost:8080/api/v1/advisor/chat?message=请说明A-01设备风险

普通用户查询设备:

http://localhost:8080/api/v1/secure-tools/chat?chatId=user-01&message=查询A-01设备当前状态

普通用户申请预算:

http://localhost:8080/api/v1/secure-tools/chat?chatId=user-01&message=给A-01申请50000元紧急维修预算

董事长申请预算草案:

http://localhost:8080/api/v1/secure-tools/chat?chatId=boss-01&message=给A-01申请50000元紧急维修预算草案

测试时除了看最终文本,还应该在工具方法设置断点,确认:

  • 模型是否真的调用了工具;
  • ToolContext是否拿到了正确角色;
  • 普通用户是否在 Java 方法内被拒绝;
  • 高风险动作是否只返回草案。

十四、本章总结

这一章把两个容易混淆的“上下文”区分开了:

Chat Memory:保存用户和模型的对话上下文 Advisor context:在一次 AI 调用链中传递系统元数据 ToolContext:把后端可信信息传给工具方法

同时也明确了 AI 工具调用的职责边界:

模型选择工具和生成参数 Advisor 负责调用链增强和观测 Java 工具负责参数、权限和风险校验 高风险动作交给人工最终确认

Tool Calling 的安全不能靠一句 Prompt。真正可靠的边界必须写在后端代码里。

下一章继续升级 RAG:不再让 Advisor 隐藏检索过程,而是显式返回来源、相似度分数、过滤条件和拒答原因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/6 4:05:53

图生视频模型训练数据集

1. 模型总览与训练数据对比 1.1 核心对比表 模型开发者发布时间参数规模训练数据规模数据来源数据筛选方法字幕生成方法开源程度SVDStability AI2023.11~1.5BLVD: ~577M clips; LVD-F: ~144M clips网络视频多级场景分割 四维评分筛选(CLIP/美学/OCR/光流)CoCa V-BLIP LLM融…

作者头像 李华
网站建设 2026/7/6 4:04:46

时间序列分解实战:STL与经典法选型及参数调优指南

1. 项目概述&#xff1a;时间序列分解不是“拆积木”&#xff0c;而是给数据做一次精准的病理切片你手头有一组连续记录的销售数据、服务器CPU使用率、某地每日气温&#xff0c;或者用户App打开次数——它们都属于时间序列。表面看是一条上下起伏的曲线&#xff0c;但真正驱动它…

作者头像 李华
网站建设 2026/7/6 4:04:22

【Bug已解决】Claude Team Plan 购买席位报错 Payment failed 解决方案

【Bug已解决】Claude Team Plan 购买席位报错 Payment failed 解决方案 1. 问题描述 企业管理员在 Claude 控制台为团队计划&#xff08;Team Plan&#xff09;增购席位时&#xff0c;反复遇到支付失败的报错&#xff1a; Payment failed It looks like your payment method…

作者头像 李华
网站建设 2026/7/6 4:02:01

鸿蒙新特性:Grid 网格布局组件详解——构建动态照片画廊

网格布局是移动端最经典的排版方式之一。从照片墙到应用抽屉&#xff0c;从商品列表到图标菜单&#xff0c;等分网格承载了大量信息展示任务。HarmonyOS NEXT ArkUI 提供了 Grid 组件&#xff0c;通过 columnsTemplate 属性灵活定义列数与列宽&#xff0c;配合 GridItem 渲染子…

作者头像 李华
网站建设 2026/7/6 4:00:50

:NLP任务的首次大一统

把分类、摘要、问答、翻译等一切 NLP 任务都塞进一个框架里&#xff1a;输入是文本&#xff0c;输出也是文本。 从地位和后续影响来说&#xff0c;T5 可以说是现代自然语言指令对话的起点&#xff0c;是对 NLP 任务形式的首次大一统&#xff0c;因此&#xff0c;本篇同样先展开…

作者头像 李华
网站建设 2026/7/6 3:58:52

告别鼠标手:3步掌握AutoClicker自动化点击工具

告别鼠标手&#xff1a;3步掌握AutoClicker自动化点击工具 【免费下载链接】AutoClicker AutoClicker is a useful simple tool for automating mouse clicks. 项目地址: https://gitcode.com/gh_mirrors/au/AutoClicker 你是否曾在深夜加班时&#xff0c;手指因为重复点…

作者头像 李华