news 2026/4/16 14:15:25

LangChain4J + OpenTelemetry:AI 调用全链路可观测方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangChain4J + OpenTelemetry:AI 调用全链路可观测方案

系列文章目录

第一篇 AI 数据治理:LangChain4J 文本分类器在字段对标中的高级玩法


文章目录

  • 系列文章目录
  • 前言
  • 一、简介
    • 1.1 全链路拓扑
    • 1.2 组件职责
  • 二、代码实践
    • 2.1.应用侧:配置与模型接入
      • 2.1.1 配置属性:Provider & Tools 收口
      • 2.1.2 LangChain4J:OpenAiChatModel(OpenAI 兼容协议)
      • 2.1.3 工具调用客户端:RestClient Builder 统一配置
      • 2.1.4 入口 Controller
    • 2.2 观测核心:Pipeline 分段 Trace + 指标 + Token
      • 2.2.1 下游工具服务(演示依赖 + Trace 透传)
      • 2.2.2 AiChatService:把一次 AI 请求拆成 3 段 Span
    • 2.3 配置:Tracing / Metrics / Logging
    • 2.4 观测栈:Docker Compose
      • 2.4.1 docker-compose.yml
      • 2.4.2 Grafana 数据源(datasources.yml)
    • 2.5 验证
    • 2.6 Grafana 查询语句示例(PromQL + LogQL)
      • 2.6.1 Prometheus(PromQL)示例
        • 1)AI 请求 QPS(每秒请求数)
        • 2)AI 请求在 1 分钟窗口的请求量(更直观)
        • 3)LLM 平均耗时(近 5 分钟)
        • 4)LLM 调用次数(用 Timer 的 count)
        • 5)Prompt Token 平均值(近 5 分钟)
        • 6)Completion Token 平均值(近 5 分钟)
        • 7)Prompt Token 峰值(近 15 分钟)
        • 8)接口层:/api/chat 的 HTTP QPS(Spring 默认指标)
        • 9)接口层:/api/chat 的 P95 延迟(Spring 默认指标)
      • 2.6.2 Loki(LogQL)示例
  • 总结

前言

AI 服务的“慢 / 贵 / 不稳定”,往往不是单点问题,而是链路问题

  • Prompt 组装是否过重?
  • 工具调用是否抖动?
  • LLM 推理是否超时 / 限流?
  • Token 是否在某些场景突然暴涨?

没有可观测,你只能靠猜;有可观测,你能靠数据定位。
本文用一个可运行的 Demo,把一次/api/chat请求拆成清晰的入口 → 工具 → LLM → 返回,并把 Trace / Metrics / Logs 同时打通。


一、简介

1.1 全链路拓扑

1.2 组件职责

组件它负责什么你能得到什么
LangChain4J调用 OpenAI 兼容模型(DashScope compatible-mode)LLM 返回内容 +TokenUsage
Micrometer Observation业务语义埋点、span 分段、上下文传播ai.pipeline / ai.tool.* / ai.llm
OpenTelemetry + OTLP标准化导出协议traces / metrics 统一上报
JaegerTrace 收集与 UIspan 耗时、错误、上下游关系
Prometheus v3OTLP Metrics 接收QPS、延迟直方图、Token 分布
Grafana统一看板Metrics + Logs(可选再接 Trace 数据源)
Loki + Promtail日志采集与检索结构化日志 + trace 关联排障

⚠️ 低基数原则(非常重要)
指标/Span 的标签只用:model / scene / provider / tool 这类有限枚举。
不要把 sessionId / message 当标签,否则会造成指标爆炸、存储膨胀、查询变慢。

  • 应用侧(Spring Boot + LangChain4J)

    1. LangChain4J(OpenAiChatModel):负责调用兼容 OpenAI 协议的大模型(你这里是 DashScope 的 compatible-mode)
    2. Micrometer Observation / @Observed:在业务代码里做“埋点语义化”,把 AI Pipeline 拆成多个 span
    3. MeterRegistry(Counter/Timer/Summary):把 LLM 延迟、Token 使用量沉淀为可查询指标
  • Tracing:OpenTelemetry + Jaeger

    1. spring-boot-starter-opentelemetry:让 Spring Boot 4 一键具备 OTel Tracing 能力(Micrometer Tracing bridge + exporter)
    2. OTLP Exporter:把 trace 数据通过 OTLP 发给 Jaeger
    3. Jaeger all-in-one:接收 OTLP 并提供 UI(16686)查看调用链
  • Metrics:Prometheus(OTLP Receiver)+ Grafana

    1. Prometheus v3 OTLP Receiver:直接接收应用通过 OTLP 推送的 metrics
    2. Grafana:配置 Prometheus 数据源,做仪表盘与告警
  • Logging:结构化日志 + Loki + Promtail

    1. ECS 结构化日志:日志字段更适合检索、聚合
    2. Loki:存日志
    3. Promtail:采集 Docker 容器日志推送到 Loki

二、代码实践

2.1.应用侧:配置与模型接入

2.1.1 配置属性:Provider & Tools 收口

packageorg.example.config;importjakarta.validation.constraints.NotBlank;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.validation.annotation.Validated;importjava.time.Duration;@Validated@ConfigurationProperties(prefix="ai.provider")publicrecordAiProviderProperties(@NotBlankStringbaseUrl,@NotBlankStringmodel,Durationtimeout){}
packageorg.example.config;importjakarta.validation.constraints.NotBlank;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.validation.annotation.Validated;@Validated@ConfigurationProperties(prefix="ai.tools")publicrecordAiToolsProperties(@NotBlankStringbaseUrl){}

✅ 把模型地址/模型名/超时、工具地址抽成“可运营配置”,后续做多模型路由、灰度策略都不动业务代码。

2.1.2 LangChain4J:OpenAiChatModel(OpenAI 兼容协议)

packageorg.example.config;importdev.langchain4j.model.chat.ChatModel;importdev.langchain4j.model.openai.OpenAiChatModel;importorg.springframework.boot.context.properties.EnableConfigurationProperties;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@Configuration@EnableConfigurationProperties(AiProviderProperties.class)publicclassLangChain4jConfig{@BeanpublicChatModelchatModel(AiProviderPropertiesprops){returnOpenAiChatModel.builder().baseUrl(props.baseUrl()).apiKey(System.getenv("LANGCHAIN4J_KEY")).modelName(props.model()).timeout(props.timeout()).build();}}
  • 作用:对外统一成 ChatModel,业务层只依赖接口
  • 可观测收益:后面我们在 ai.llm span 里打上 model/provider 维度,Jaeger 上能按模型过滤,Metrics 也能按 model 分组

2.1.3 工具调用客户端:RestClient Builder 统一配置

packageorg.example.config;importorg.springframework.boot.restclient.autoconfigure.RestClientBuilderConfigurer;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.client.RestClient;@ConfigurationpublicclassRestClientConfig{@BeanpublicRestClient.BuilderrestClientBuilder(RestClientBuilderConfigurerconfigurer){returnconfigurer.configure(RestClient.builder());}}
  • 作用:让 RestClient 走 Spring Boot 的统一配置(拦截器、超时、观测等)
  • 链路价值:工具调用也会在 trace 里形成独立 span(你在业务里进一步手动包了 Observation)

2.1.4 入口 Controller

packageorg.example.controller;importjakarta.annotation.Resource;importorg.example.dto.ChatRequest;importorg.example.dto.ChatResponse;importorg.example.service.AiChatService;importorg.springframework.web.bind.annotation.*;@RestControllerpublicclassChatController{@ResourceprivateAiChatServiceaiChatService;@PostMapping("/api/chat")publicChatResponsechat(@RequestBodyChatRequestchatRequest){returnaiChatService.chat(chatRequest);}}

DTO

packageorg.example.dto;publicrecordChatRequest(StringsessionId,Stringscene,Stringmessage){}publicrecordChatResponse(StringsessionId,Stringanswer){}

2.2 观测核心:Pipeline 分段 Trace + 指标 + Token

2.2.1 下游工具服务(演示依赖 + Trace 透传)

packageorg.example.controller;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;importjava.time.Instant;importjava.util.Map;@RestControllerpublicclassToolController{@GetMapping("/api/tools/time")publicMap<String,Object>time(){returnMap.of("now",Instant.now().toString());}}
  • 作用:模拟一个内部工具/依赖服务(真实项目里可以是:查询配置、查用户画像、查数据库、查规则引擎等)
  • 链路价值:你能在 Jaeger 看到 ai.tool.time 的耗时,并判断“慢在工具还是慢在 LLM”

2.2.2 AiChatService:把一次 AI 请求拆成 3 段 Span

✅ 建议命名:

  • ai.pipeline:总链路(入口到返回)
  • ai.tool.time:工具调用
  • ai.llm:模型调用

✅ 指标:

  • ai_chat_requests_total:请求量
  • ai_llm_latency:LLM 延迟(直方图 + P95/P99)
packageorg.example.service;importdev.langchain4j.data.message.UserMessage;importdev.langchain4j.model.chat.ChatModel;importdev.langchain4j.model.output.TokenUsage;importio.micrometer.core.instrument.*;importio.micrometer.observation.Observation;importio.micrometer.observation.ObservationRegistry;importio.micrometer.observation.annotation.Observed;importjakarta.annotation.Resource;importorg.example.config.AiProviderProperties;importorg.example.config.AiToolsProperties;importorg.example.dto.ChatRequest;importorg.example.dto.ChatResponse;importorg.springframework.stereotype.Service;importorg.springframework.web.client.RestClient;importjava.util.Map;@ServicepublicclassAiChatService{@ResourceprivateChatModelchatModel;@ResourceprivateObservationRegistryobservationRegistry;privatefinalCounterrequests;privatefinalTimerllmLatency;privatefinalDistributionSummarypromptTokens;privatefinalDistributionSummarycompletionTokens;privatefinalRestClienttoolClient;privatefinalAiProviderPropertiesprops;publicAiChatService(AiProviderPropertiesprops,AiToolsPropertiestoolsProperties,RestClient.BuilderrestClientBuilder,MeterRegistrymeterRegistry){this.props=props;// 内部工具调用(演示 trace 分段)this.toolClient=restClientBuilder.baseUrl(System.getProperty("TOOLS_BASE_URL",toolsProperties.baseUrl())).build();// Metrics:请求量this.requests=Counter.builder("ai_chat_requests_total").description("Total AI chat requests").tag("model",props.model()).register(meterRegistry);// Metrics:LLM 延迟this.llmLatency=Timer.builder("ai_llm_latency").description("LLM call latency").tag("model",props.model()).publishPercentileHistogram().register(meterRegistry);// Metrics:Tokenthis.promptTokens=DistributionSummary.builder("ai_llm_tokens_prompt").description("Prompt tokens").tag("model",props.model()).register(meterRegistry);this.completionTokens=DistributionSummary.builder("ai_llm_tokens_completion").description("Completion tokens").tag("model",props.model()).register(meterRegistry);}@Observed(name="ai.chat",contextualName="ai-chat")publicChatResponsechat(ChatRequestreq){returnObservation.createNotStarted("ai.pipeline",observationRegistry).lowCardinalityKeyValue("scene",safe(req.scene())).lowCardinalityKeyValue("model",props.model()).observe(()->{Stringprompt="你是企业级 Java AI 助手,请用工程化语言回答。\n"+"当前时间: "+fetchNowFromTool()+"\n"+"场景: "+safe(req.scene())+"\n"+"用户问题: "+safe(req.message())+"\n";requests.increment();Stringanswer=llmLatency.record(()->invokeModel(prompt));returnnewChatResponse(req.sessionId(),answer);});}privateStringfetchNowFromTool(){returnObservation.createNotStarted("ai.tool.time",observationRegistry).lowCardinalityKeyValue("tool","time").observe(()->{Map<?,?>body=toolClient.get().uri("/api/tools/time").retrieve().body(Map.class);Objectnow=(body!=null)?body.get("now"):null;returnnow!=null?now.toString():"unknown";});}privateStringinvokeModel(Stringprompt){returnObservation.createNotStarted("ai.llm",observationRegistry).lowCardinalityKeyValue("provider","openai-compatible").lowCardinalityKeyValue("model",props.model()).observe(()->{dev.langchain4j.model.chat.response.ChatResponseresp=chatModel.chat(UserMessage.from(prompt));TokenUsageusage=resp.tokenUsage();if(usage!=null){if(usage.inputTokenCount()!=null)promptTokens.record(usage.inputTokenCount());if(usage.outputTokenCount()!=null)completionTokens.record(usage.outputTokenCount());}returnresp.aiMessage().text();});}privatestaticStringsafe(Strings){return(s==null||s.isBlank())?"unknown":s;}}

2.3 配置:Tracing / Metrics / Logging

# App spring.application.name=ai-observability server.port=8080 # AI Provider ai.provider.base-url=https://dashscope.aliyuncs.com/compatible-mode/v1 ai.provider.model=qwen-long ai.provider.timeout=10s ai.tools.base-url=http://localhost:8080 # Actuator management.endpoints.web.exposure.include=health,info,metrics,loggers,threaddump,httpexchanges # Tracing(Demo:100% 采样) management.tracing.sampling.probability=1.0 management.opentelemetry.tracing.export.otlp.endpoint=http://localhost:4318/v1/traces management.opentelemetry.tracing.export.otlp.transport=http management.opentelemetry.tracing.export.otlp.timeout=5s # Metrics(OTLP -> Prometheus OTLP Receiver) management.otlp.metrics.export.url=http://localhost:9090/api/v1/otlp/v1/metrics management.otlp.metrics.export.step=10s # Logging logging.structured.format.console=ecs logging.level.org.example=INFO logging.level.io.opentelemetry.exporter=INFO logging.level.io.micrometer.tracing=INFO
  • Tracing 关键点:OTLP traces 发给 Jaeger 的 4318(HTTP)
  • Metrics 关键点:OTLP metrics 直接推给 Prometheus v3 的 OTLP receiver
  • Logging 关键点:ECS 结构化日志便于 Loki 检索(后续建议加入 traceId 字段注入)

2.4 观测栈:Docker Compose

2.4.1 docker-compose.yml

services:prometheus:image:prom/prometheus:v3.8.0command:-"--config.file=/etc/prometheus/prometheus.yml"-"--web.enable-otlp-receiver"volumes:-./observability/prometheus.yml:/etc/prometheus/prometheus.yml:roports:-"9090:9090"jaeger:image:jaegertracing/all-in-one:1.76.0environment:COLLECTOR_OTLP_ENABLED:"true"ports:-"16686:16686"-"4317:4317"-"4318:4318"loki:image:grafana/loki:latestcommand:["-config.file=/etc/loki/config.yml"]volumes:-./observability/loki-config.yml:/etc/loki/config.yml:roports:-"3100:3100"grafana:image:grafana/grafana:latestenvironment:GF_SECURITY_ADMIN_USER:adminGF_SECURITY_ADMIN_PASSWORD:adminvolumes:-./observability/grafana/provisioning:/etc/grafana/provisioning:roports:-"3001:3000"depends_on:-prometheus-loki-jaeger

访问入口:

  • Grafana:http://localhost:3001(admin/admin)
  • Jaeger:http://localhost:16686
  • Prometheus:http://localhost:9090
  • Loki:http://localhost:3100

2.4.2 Grafana 数据源(datasources.yml)

apiVersion:1datasources:-name:Prometheustype:prometheusaccess:proxyurl:http://prometheus:9090isDefault:true-name:Lokitype:lokiaccess:proxyurl:http://loki:3100

作用:Grafana 开箱即用:指标看 Prometheus、日志看 Loki

2.5 验证

  1. 启动观测栈
    docker compose up -d
  2. 启动应用
  3. 发起请求
  4. 检查结果
    • Jaeger:http://localhost:16686。service 选 ai-observability,能看到 ai.pipeline / ai.tool.time / ai.llm 等 span(并且 HTTP 调用链路会串起来)

    • Prometheus:http://localhost:9090。查询:ai_chat_requests_total、ai_llm_latency_seconds_count 等(OTLP 推进来的指标)

    • Grafana:http://localhost:3000(admin/admin)Explore → Prometheus / Loki 可直接查

2.6 Grafana 查询语句示例(PromQL + LogQL)

说明(Prometheus 指标命名规律)

  • Micrometer 的Timer通常会导出为*_seconds_bucket / *_seconds_sum / *_seconds_count(开启直方图时)
  • Counter一般保持原名(如ai_chat_requests_total
  • DistributionSummary通常为*_sum / *_count / *_max

如果你的指标名不完全一致:在 GrafanaExplore → Metrics里先搜索ai_,再按实际指标名替换。


2.6.1 Prometheus(PromQL)示例

1)AI 请求 QPS(每秒请求数)

sum by (model) (rate(ai_chat_requests_total[5m]))

2)AI 请求在 1 分钟窗口的请求量(更直观)

sum by (model) (increase(ai_chat_requests_total[1m]))

3)LLM 平均耗时(近 5 分钟)

sum by (model) (rate(ai_llm_latency_seconds_sum[5m]))
/
sum by (model) (rate(ai_llm_latency_seconds_count[5m]))

4)LLM 调用次数(用 Timer 的 count)

sum by (model) (rate(ai_llm_latency_seconds_count[5m]))

5)Prompt Token 平均值(近 5 分钟)

sum by (model) (rate(ai_llm_tokens_prompt_sum[5m]))
/
sum by (model) (rate(ai_llm_tokens_prompt_count[5m]))

6)Completion Token 平均值(近 5 分钟)

sum by (model) (rate(ai_llm_tokens_completion_sum[5m]))
/
sum by (model) (rate(ai_llm_tokens_completion_count[5m]))

7)Prompt Token 峰值(近 15 分钟)

max_over_time(ai_llm_tokens_prompt_max[15m])

8)接口层:/api/chat 的 HTTP QPS(Spring 默认指标)

sum(rate(http_server_requests_seconds_count{uri=“/api/chat”}[5m]))

9)接口层:/api/chat 的 P95 延迟(Spring 默认指标)

histogram_quantile(
0.95,
sum by (le) (rate(http_server_requests_seconds_bucket{uri=“/api/chat”}[5m]))

2.6.2 Loki(LogQL)示例

启用了 logging.structured.format.console=ecs,日志通常为 JSON。
常见玩法:先用 | json 解析字段,再进行过滤/聚合。
ECS 中 trace 字段常见为 trace.id(有的链路会变成 trace_id),以你的实际字段为准。

  1. 查看应用日志(按容器名筛选):{container=~“.ai-observability.”}
  2. 只看 ERROR:{container=~“.ai-observability.”} | json | level=“ERROR”
  3. 按关键字检索(例如 ai.llm):{container=~“.ai-observability.”} |= “ai.llm”
  4. 按 traceId 串起来看(ECS 常见 trace.id):{container=~“.ai-observability.”} | json | trace.id=“YOUR_TRACE_ID”
    如果你的字段是 trace_id:{container=~“.ai-observability.”} | json | trace_id=“YOUR_TRACE_ID”
  5. 5 分钟内 ERROR 数量(用于图表):sum(count_over_time({container=~“.ai-observability.”} | json | level=“ERROR”[5m]))
  6. 按异常关键词统计(例:timeout)
    sum(count_over_time({container=~“.ai-observability.”} |= “timeout”[5m]))

总结

这套方案的关键不是“把 OTel 跑起来”,而是把 AI 请求变成可解释、可量化、可追踪的流水线:

  • Tracing:把一次 AI 请求拆成 pipeline → tool → llm,定位瓶颈不用猜
  • Metrics:用 Timer + Counter + Token Summary 把性能与成本沉淀为数据资产
  • Logging:结构化日志进入 Loki,为排障提供“证据链”

下一步升级

  • RAG:新增 ai.rag.retrieve / ai.rerank span,定位检索与重排耗时

  • Memory:新增 ai.memory.read / ai.memory.write span,观察命中率与冷启动成本

  • 错误分层:对超时、限流、5xx、解析失败打不同 error event,做告警与归因

  • 成本面板:按 model/scene 聚合 token,做 TOP 场景与阈值告警(P95 + token 双维)

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

Min浏览器:重新定义移动端隐私浏览体验的轻量级解决方案

在数字隐私日益受到重视的今天&#xff0c;Min浏览器以其极简设计和强大的隐私保护功能&#xff0c;为用户提供了全新的浏览体验。这款专注于隐私保护的轻量级浏览器&#xff0c;正在为移动端用户打造更安全的上网环境。 【免费下载链接】min A fast, minimal browser that pro…

作者头像 李华
网站建设 2026/4/15 22:48:32

java计算机毕业设计社区老人健康服务跟踪系统 基于SpringBoot的社区长者智慧健康照护平台 JavaWeb社区老年健康动态跟踪与干预系统

计算机毕业设计社区老人健康服务跟踪系统t86i39&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。居家老人最怕的是“身体有状况&#xff0c;子女不在场&#xff1b;体检报告看不懂…

作者头像 李华
网站建设 2026/4/15 12:46:42

java计算机毕业设计社区人员信息管理系统设计与实现 基于SpringBoot的社区居民档案智慧管理平台 JavaWeb社区人口信息综合服务平台

计算机毕业设计社区人员信息管理系统设计与实现0146g9&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。社区网格员最头疼的就是“人户分离”&#xff1a;Excel 里 3000 条记录&…

作者头像 李华
网站建设 2026/4/15 17:00:54

腾讯混元3D-Part:AI技术如何重塑游戏美术生产的未来格局

在数字内容创作领域&#xff0c;3D资产制作一直是制约游戏开发效率的关键瓶颈。传统制作流程中&#xff0c;美术师需要在十余个专业软件间反复切换&#xff0c;一个中等复杂度的角色模型从概念设计到最终绑定动画往往需要数周时间。腾讯混元3D-Part的出现&#xff0c;通过七大A…

作者头像 李华
网站建设 2026/4/8 21:33:42

Linux内核动态调试终极指南:从入门到实战精通

Linux内核动态调试终极指南&#xff1a;从入门到实战精通 【免费下载链接】linux Linux kernel source tree 项目地址: https://gitcode.com/GitHub_Trending/li/linux 还在为Linux内核崩溃后无从下手而苦恼&#xff1f;面对系统卡顿、死锁、内存泄漏等棘手问题&#xf…

作者头像 李华
网站建设 2026/4/16 10:40:15

12、Puppet模块使用与开发全解析

Puppet模块使用与开发全解析 1. Puppet Forge模块使用原则 在使用Puppet管理应用时,通常应用需要数据库来存储状态,以及用户凭证来访问它。以创建 cat_pictures 数据库并设置 greebo 用户账户访问为例,Puppet可以轻松完成这些操作,而 mysql 模块能让配置变得非常简…

作者头像 李华