
在将大模型与企业微服务架构深度整合后很多运维和架构同学在排查线上慢请求时经常会发出这种无力的咆哮“用户发起的 HTTP 请求进到智能客服网关TraceId 记录得好好的大模型流式返回给用户TraceId 也在。但是大模型在中间自主发起的那个查库存、核销优惠券的 Tool 接口在 SkyWalking 和 Zipkin 链路追踪大盘上为什么彻底断掉了在微服务集群里凭空多出了几千条没有上游 ParentId 的孤儿 Span根本搞不清楚哪次 RPC 调用是由哪个大模型会话触发的”这种“链路失联”是大模型 Agent 落地中最令人抓狂的工程黑洞。在传统的微服务调用中Spring Cloud Sleuth 或 SkyWalking 探针会自动通过 HTTP Header 或者 RPC 隐式传参RRC Attachment把traceparent一路透传下去。然而在 Spring AI 的 Function CallingTool Calling机制中大模型决定调用 Tool 的过程往往跨越了异步线程池、响应式 Flux 管道以及反射调用上下文。原本保存在ThreadLocal里的分布式追踪上下文在这一连串的异步穿梭中被全部清空下游微服务收到 Tool 的请求时以为这是一个全新发起的孤立请求全链路追踪体系当场瘫痪。今天我们就基于 Spring Boot 3 最新的Micrometer Observation API手把手实现大模型 Tool 调用的上下文自动染色彻底打通 TraceId 的全链路贯穿。一、Tool 链路断层的根本病因分析要修好链路先看它是在哪里断掉的[用户 HTTP 请求] (带 TraceId: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01) │ ▼ [Spring Boot 3 业务 Controller] (Trace 上下文存活于 ThreadLocal) │ ▼ (发起 Spring AI ChatClient 异步流式推理) [Reactor 工作线程池 (Schedulers.boundedElastic)] --- 第一次上下文丢失 (Reactive Boundary) │ ▼ (大模型返回 ToolCall 指令: execute tool queryInventory) [Tool 执行器线程池 (ExecutorService / 虚拟线程)] --- 第二次上下文丢失 (Async Thread Switch) │ ▼ [Tool 内部发起 Feign / Dubbo 远程调用] │ ▼ [下游微服务] (丢失 Parent Span! 产生孤儿 TraceId链路彻底断裂)要在这一连串异步与响应式断层中坚守 TraceId核心思路就是在每一个调度边界通过Observation Scope观测作用域进行手动的捕获与恢复Capture Restore。二、基于 Micrometer Observation 构建链路染色传递器Spring Boot 3 废弃了传统的 Sleuth全面拥抱Micrometer Tracing与Micrometer Observation。我们首先构建一个专门负责在异步 Tool 执行前恢复观测上下文的工具拦截器package com.yali.ai.tracing; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; import io.micrometer.tracing.Tracer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.concurrent.Callable; Component public class TraceableToolExecutor { private static final Logger log LoggerFactory.getLogger(TraceableToolExecutor.class); private final ObservationRegistry observationRegistry; private final Tracer tracer; public TraceableToolExecutor(ObservationRegistry observationRegistry, Tracer tracer) { this.observationRegistry observationRegistry; this.tracer tracer; } /** * 在带有分布式链路上下文的受保护环境下执行 Tool 逻辑 */ public T T executeWithObservation(String toolName, CallableT task) { // 创建一个专属于当前 Tool 调用的 Observation 子 Span Observation toolObservation Observation.createNotStarted(spring.ai.tool.execution, observationRegistry) .lowCardinalityKeyValue(ai.tool.name, toolName) .contextualName(AI-Tool: toolName); // 记录当前 TraceId 用于排查日志 String currentTraceId tracer.currentSpan() ! null ? tracer.currentSpan().context().traceId() : NONE; log.info(【Tool 链路染色】开启 Tool 执行观测 Span: tool[{}], traceId[{}], toolName, currentTraceId); return toolObservation.observe(() - { try { // 此时 Observation 会自动把当前 Span 绑定到当前线程的 ThreadLocal // 内部发起的 OpenFeign / WebClient / RestTemplate 能够原生提取并注入 W3C TraceContext return task.call(); } catch (Exception e) { toolObservation.error(e); log.error(Tool 执行链路异常: tool[{}], err[{}], toolName, e.getMessage()); throw new RuntimeException(e); } }); } }三、与 Spring AI Tool 注解的无缝切面整合为了不侵入具体的业务 Tool 代码我们使用 Spring AOP 针对所有标记了Tool注解的方法进行统一的切面编织与链路染色package com.yali.ai.tracing.aspect; import com.yali.ai.tracing.TraceableToolExecutor; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; Aspect Component public class ToolTracingAspect { private final TraceableToolExecutor toolExecutor; public ToolTracingAspect(TraceableToolExecutor toolExecutor) { this.toolExecutor toolExecutor; } /** * 拦截所有标注了 Tool 注解的方法执行 */ Around(annotation(toolAnnotation)) public Object traceToolExecution(ProceedingJoinPoint joinPoint, Tool toolAnnotation) throws Throwable { String toolName toolAnnotation.name().isEmpty() ? joinPoint.getSignature().getName() : toolAnnotation.name(); // 借由 TraceableToolExecutor 包装执行打通链路上下文 return toolExecutor.executeWithObservation(toolName, () - { try { return joinPoint.proceed(); } catch (Throwable e) { if (e instanceof Exception ex) { throw ex; } throw new RuntimeException(e); } }); } }四、打通 HTTP/Feign 下游调用的 Header 透传有了上面的 Observation 包装当下游通过 OpenFeign 发起真实 RPC 时Spring Cloud OpenFeign 会自动配置FeignObservationAnnotationBPP向 HTTP 请求头中注入标准的 W3C TraceContextPOST /api/inventory/deduct HTTP/1.1 Host: inventory-service.internal traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-5c3a4f89d34211ef-01 tracestate: Content-Type: application/json在 SkyWalking / Zipkin 监控拓扑大屏上原本断开的链路瞬间串联成一条优美的树状调用链[GET /api/ai/chat] (Web Entry, traceId: 4bf92f3577b34da6a3ce929d0e0e4736) │ ├── [Spring AI Model Inference: GLM 5.3] (耗时 850ms) │ └── [AI-Tool: deductInventory] (SpanId: 5c3a4f89d34211ef) │ └── [POST inventory-service:/api/inventory/deduct] (下游微服务, 耗时 32ms) │ └── [MySQL: UPDATE t_inventory ...] (耗时 2ms)整个会话期间大模型哪一毫秒决定调 Tool、Tool 在本地花了多长时间准备参数、下游微服务执行了哪条 SQL、耗时多久全部在同一个 Trace 界面下一目了然五、老鸭梨的运维实操心法把全链路追踪接入大模型有三个关键点必须落地为大模型流式推理打上自定义 Tag在 Observation 中除了记录默认指标务必打上业务专有标签如ai.model.nameglm-5.3、ai.prompt.tokens1250。当下游链路慢的时候可以瞬间过滤出是某款特定模型导致的慢调用还是公共网络问题虚拟线程模式下的上下文传递如果你启用了 Java 24 虚拟线程务必在配置中开启 Micrometer 的虚拟线程上下文传播器ContextRegistry.getInstance().registerThreadLocalAccessor(...)否则虚拟线程在卸载挂载时依然可能丢失 MDC 日志打点注意日志脱敏与 Token 保护TraceSpan 的 Tag 中严禁把大模型的完整用户隐私 Prompt如包含用户身份证、手机号作为 Tag 裸传给可观测系统。这些系统通常是明文存储且全公司可查的做好合规脱敏才能安稳度过大促自适应动态链路采样率Adaptive Sampling Rate在大促巅峰期全站 QPS 暴涨数十倍如果对所有 AI 交互与 Tool Calling 进行 100% 全量 Span 采集上报可观测平台如 Elasticsearch / OTel Collector的存储集群会在半小时内被直接写爆。生产标准实践是平时配置 10% 的基础采样率同时配置尾部自适应采样Tail-based Sampling。只要单次 Tool 调用耗时超过 500ms或者发生任意未捕获异常HTTP 5xx、RPC 失败该条链路强制 100% 提升为高优先级全量上报实现兼顾系统低开销与故障 100% 可回溯的完美平衡。把黑盒的 AI 对话拆解为可度量、可追踪、可诊断的标准化分布式链路是大模型应用真正走向工业级成熟的成人礼。遵循这套工程范式当线上再次出现慢调用或故障时你将永远握有第一时间的根因掌控权。