原文链接:Spring Ai Alibaba Graph 集成 Langfuse 观测—快速上手
[!TIP]
Graph 观测,可对每个节点的输入、输出、执行状态、异常等进行实时追踪和流式采集,可帮助开发者快速理解复杂链路数据流转,还可为性能分析、异常定位提供帮助本期先带来快速上手教程(代码零入侵,即可观测 Graph 相关信息),后面会出文章讲解 Graph 观测的架构设计等
实战代码可见:https://github.com/GTyingzi/spring-ai-tutorial 的 graph 目录下的 observe-langfuse 模块
pom.xml
<properties>
<!-- Spring AI -->
<spring-ai.version>1.0.1</spring-ai.version>
<!-- Spring Boot -->
<spring-boot.version>3.4.5</spring-boot.version>
<!-- Spring AI Alibaba -->
<spring-ai-alibaba.version>1.0.0.3</spring-ai-alibaba.version>
</properties>
<dependencyManagement>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>${spring-ai-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom</artifactId>
<version>2.17.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-graph-core</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-graph-observation</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot Actuator for observability support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micrometer Observation -> OpenTelemetry bridge -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<!-- OpenTelemetry OTLP exporter for traces -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
</dependencies>
application.yml
server:
port: 8080
spring:
application:
name: observe-langfuse
profiles:
# LangFuse可观测配置
include: observability
ai:
openai:
api-key: ${AIDASHSCOPEAPIKEY}
base-url: https://dashscope.aliyuncs.com/compatible-mode
chat:
options:
model: qwen-max
alibaba:
graph:
observation:
enabled: true
- 为了让配置文件更清晰,这里单独把观测配置抽离出来
spring.profiles.include = observability - 开启 graph 观测
spring.ai.alibaba.graph.observation = true
application-observability.yml 文件如下,在前文中已经介绍过 langfuse 如何配的了集成 Langfuse
# OpenTelemetry Observation configuration for Langfuse integration
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
# health status check with detailed messages
# show-details: always
access:
tracing:
sampling:
# trace information with every request
probability: 1.0
observations:
annotations:
enabled: true
# OpenTelemetry configuration
otel:
service:
name: spring-ai-alibaba-deepresearch-langfuse
resource:
attributes:
deployment.environment: development
# configure exporter
traces:
exporter: otlp
sampler: alwayson
metrics:
exporter: otlp
# logs exportation inhibited for langfuse currently cannot support
logs:
exporter: none
exporter:
otlp:
# OpenTelemetry exporter endpoint configuration. For details, refer to the official Langfuse documentation: https://langfuse.com/docs/opentelemetry/get-started
endpoint: "https://us.cloud.langfuse.com/api/public/otel" # 🇺🇸 US data region
headers:
Authorization: "Basic ${YOURBASE64ENCODEDCREDENTIALS}" # echo -n "pk-xxx:sk-xxx" | base64
protocol: http/protobuf
config
package com.spring.ai.tutorial.graph.observe.config;
import com.alibaba.cloud.ai.graph.GraphRepresentation;
import com.alibaba.cloud.ai.graph.KeyStrategy;
import com.alibaba.cloud.ai.graph.KeyStrategyFactory;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;
import com.spring.ai.tutorial.graph.observe.node.ExpanderNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.nodeasync;
/**
* @author yingzi
* @since 2025/6/13
*/
@Configuration
public class GraphConfiguration {
private static final Logger logger = LoggerFactory.getLogger(GraphConfiguration.class);
@Bean
public StateGraph simpleGraph(ChatClient.Builder chatClientBuilder) throws GraphStateException {
KeyStrategyFactory keyStrategyFactory = () -> {
HashMap<String, KeyStrategy> keyStrategyHashMap = new HashMap<>();
// 用户输入
keyStrategyHashMap.put("query", new ReplaceStrategy());
keyStrategyHashMap.put("expandernumber", new ReplaceStrategy());
keyStrategyHashMap.put("expandercontent", new ReplaceStrategy());
return keyStrategyHashMap;
};
StateGraph stateGraph = new StateGraph(keyStrategyFactory)
.addNode("expander", nodeasync(new ExpanderNode(chatClientBuilder)))
.addEdge(StateGraph.START, "expander")
.addEdge("expander", StateGraph.END);
// 添加 PlantUML 打印
GraphRepresentation representation = stateGraph.getGraph(GraphRepresentation.Type.PLANTUML,
"expander flow");
logger.info("\n=== expander UML Flow ===");
logger.info(representation.content());
logger.info("==================================\n");
return stateGraph;
}
}
node
ExpanderNode
- PromptTemplate DEFAULTPROMPTTEMPLATE:扩展文本的提示词
- ChatClient chatClient:调用 AI 模型的 client 端
- Integer NUMBER:默认扩展为 3 条相似问题
最后将 AI 模型的响应内容返回给给到字段 expandercontent 中
package com.spring.ai.tutorial.graph.node;
import com.alibaba.cloud.ai.graph.NodeOutput;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import com.alibaba.cloud.ai.graph.streaming.StreamingChatGenerator;
import org.bsc.async.AsyncGenerator;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.PromptTemplate;
import reactor.core.publisher.Flux;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ExpanderNode implements NodeAction {
private static final PromptTemplate DEFAULTPROMPTTEMPLATE = new PromptTemplate("You are an expert at information retrieval and search optimization.\nYour task is to generate {number} different versions of the given query.\n\nEach variant must cover different perspectives or aspects of the topic,\nwhile maintaining the core intent of the original query. The goal is to\nexpand the search space and improve the chances of finding relevant information.\n\nDo not explain your choices or add any other text.\nProvide the query variants separated by newlines.\n\nOriginal query: {query}\n\nQuery variants:\n");
private final ChatClient chatClient;
private final Integer NUMBER = 3;
public ExpanderNode(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@Override
public Map<String, Object> apply(OverAllState state) throws Exception {
String query = state.value("query", "");
Integer expanderNumber = state.value("expandernumber", this.NUMBER);
Flux<String> streamResult = this.chatClient.prompt().user((user) -> user.text(DEFAULTPROMPTTEMPLATE.getTemplate()).param("number", expanderNumber).param("query", query)).stream().content();
String result = streamResult.reduce("", (acc, item) -> acc + item).block();
List<String> queryVariants = Arrays.asList(result.split("\n"));
HashMap<String, Object> resultMap = new HashMap<>();
resultMap.put("expandercontent", queryVariants);
return resultMap;
}
}
controller
package com.spring.ai.tutorial.graph.observe.controller;
import com.alibaba.cloud.ai.graph.CompileConfig;
import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* @author yingzi
* @since 2025/6/13
*/
@RestController
@RequestMapping("/graph")
public class SimpleGraphController {
private static final Logger logger = LoggerFactory.getLogger(SimpleGraphController.class);
private final CompiledGraph compiledGraph;
public SimpleGraphController(@Qualifier("simpleGraph") StateGraph stateGraph, CompileConfig observationCompileConfig) throws GraphStateException {
this.compiledGraph = stateGraph.compile(observationCompileConfig);
}
@GetMapping(value = "/expand")
public Map<String, Object> expand(@RequestParam(value = "query", defaultValue = "你好,我的外号是影子,请记住呀!", required = false) String query,
@RequestParam(value = "expandernumber", defaultValue = "3", required = false) Integer expanderNumber,
@RequestParam(value = "threadid", defaultValue = "yingzi", required = false) String threadId) throws GraphRunnerException {
RunnableConfig runnableConfig = RunnableConfig.builder().threadId(threadId).build();
Map<String, Object> objectMap = new HashMap<>();
objectMap.put("query", query);
objectMap.put("expandernumber", expanderNumber);
Optional<OverAllState> invoke = this.compiledGraph.invoke(objectMap, runnableConfig);
return invoke.map(OverAllState::data).orElse(new HashMap<>());
}}
这里 CompileConfig observationCompileConfig 带了观测信息,可以定位到 GraphObservationAutoConfiguration 自动注入类

效果

往期资料

Spring AI + Spring Ai Aliabba系统化学习资料
本教程将采用2025年5月20日正式的GA版,给出如下内容
- 核心功能模块的快速上手教程
- 核心功能模块的源码级解读
- Spring ai alibaba增强的快速上手教程 + 源码级解读
版本:
- JDK21
- SpringBoot3.4.5
- SpringAI 1.0.1
- SpringAI Alibaba 1.0.3+
免费渠道:
- 为Spring Ai Alibaba开源社区解决解决有效的issue or 提供有价值的PR,可免费获取上述教程
- 往届微信推文
收费服务:收费69.9元
- 飞书在线云文档
- Spring AI会员群教程代码答疑
- 若Spring AI、Spring AI Alibaba教程内容无法满足业务诉求,可定制提供解决方案,带价私聊
学习交流圈
你好,我是影子,曾先后在🐻、新能源、老铁就职,兼任Spring AI Alibaba开源社区的Committer。目前新建了一个交流群,一个人走得快,一群人走得远,另外,本人长期维护一套飞书云文档笔记,涵盖后端、大数据系统化的面试资料,可私信免费获取


957

被折叠的 条评论
为什么被折叠?



