Skip to content

Commit 126c4ce

Browse files
authored
feat(spring-ai-observability): 添加可观察性模块 (#28)
* fix(spring-ai-chat-qwen): 修复 API 密钥配置问题 - 将 spring.ai.openai.chat.api-key 更名为 spring.ai.openai.api-key - 统一 API 密钥配置,解决潜在的配置错误问题 * feat(spring-ai-observability): 添加可观察性模块 - 新增 spring-ai-observability 模块,包含 metric 和 tracing 两个子模块 - 实现了对 AI 模型调用的监控、追踪和可观测性管理 - 集成了 OpenAI 客户端、Redis VectorStore 和 Zipkin 分布式追踪 - 提供了聊天、图像生成、文本嵌入等功能的 REST 接口 - 添加了 Prometheus 指标和健康检查支持 * refactor(spring-ai-observability-metric): 优化 image 控制器的实现 - 将 image 方法的返回类型从 void改为 String - 移除了 HttpServletResponse 参数 - 直接返回图像 URL 字符串,而不是通过输出流传输图像数据 * docs(spring-ai-observability): 更新快速启动指南中的配置文件路径 - 在 metric 和 tracing模块的 README 文件中,将 application.yml 的路径从绝对路径修改为相对路径 - 优化文档的可读性和通用性,避免特定于某个用户的路径
1 parent ea1282a commit 126c4ce

37 files changed

+1219
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<module>spring-ai-chat-memory</module>
2323
<module>spring-ai-tool-calling</module>
2424
<module>spring-ai-agent</module>
25+
<module>spring-ai-observability</module>
2526
</modules>
2627

2728
<properties>

spring-ai-observability/pom.xml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>com.glmapper</groupId>
8+
<artifactId>spring-ai-summary</artifactId>
9+
<version>0.0.1</version>
10+
</parent>
11+
12+
<artifactId>spring-ai-observability</artifactId>
13+
<packaging>pom</packaging>
14+
<name>spring-ai-observability</name>
15+
<modules>
16+
<module>spring-ai-observability-metric</module>
17+
<module>spring-ai-observability-tracing</module>
18+
</modules>
19+
20+
<dependencies>
21+
22+
<dependency>
23+
<groupId>org.springframework.ai</groupId>
24+
<artifactId>spring-ai-starter-model-openai</artifactId>
25+
</dependency>
26+
27+
<!-- redis vector -->
28+
<dependency>
29+
<groupId>org.springframework.ai</groupId>
30+
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
31+
</dependency>
32+
33+
</dependencies>
34+
35+
</project>
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Spring AI Observability Metric
2+
3+
该项目是基于 [Spring AI](https://docs.spring.io/spring-ai/) 构建的可观察性追踪示例,集成了 **OpenAI 客户端**(兼容阿里云 DashScope)、**工具调用(Function/Method)****向量存储(Redis VectorStore)** 功能。
4+
5+
6+
通过本项目可以快速了解如何在 Spring Boot 应用中实现对 AI 模型调用的监控、追踪和可观测性管理。
7+
8+
9+
## 模块结构
10+
11+
```
12+
spring-ai-observability-metric
13+
├── src/
14+
│ ├── main/
15+
│ │ ├── java/com/glmapper/ai/observability/tracing/
16+
│ │ │ ├── controller/ # 各类 REST 接口(Chat, Image, Embedding, Tools)
17+
│ │ │ ├── tools/ # 工具函数(天气查询、时间获取)
18+
│ │ │ ├── storage/ # Redis VectorStore 存储封装
19+
│ │ │ └── configs/ # 配置类
20+
│ │ └── resources/
21+
│ │ └── application.yml # 配置文件
22+
├── pom.xml # Maven 项目配置
23+
└── README.md # 当前文档
24+
```
25+
26+
## 🧩 核心功能
27+
28+
| 模块 | 功能描述 |
29+
|------|----------|
30+
| ChatController | 调用 OpenAI 兼容接口进行聊天对话 |
31+
| ImageController | 调用图像生成 API(如 Stable Diffusion) |
32+
| EmbeddingController | 使用文本嵌入模型生成向量表示 |
33+
| ToolCallingController | 支持 Function 和 Method 类型工具调用(天气、当前时间) |
34+
| VectorStoreController | 使用 Redis 作为向量数据库进行文档存储与搜索 |
35+
| Zipkin 集成 | 所有请求链路信息上报至 Zipkin,支持全链路追踪 |
36+
37+
---
38+
39+
## 🛠️ 技术栈
40+
41+
- Spring Boot 3.x
42+
- Spring AI 1.0.x
43+
- Redis VectorStore
44+
- OpenAI Client(兼容 DashScope)
45+
- Micrometer Tracing + Brave
46+
- Zipkin 分布式追踪
47+
48+
---
49+
50+
## 🚀 快速启动
51+
### 1. 修改配置
52+
53+
[application.yml](src/main/resources) 中更新以下参数:
54+
55+
```yaml
56+
spring:
57+
ai:
58+
openai:
59+
api-key: your-api-key-here
60+
```
61+
62+
### 2. 启动应用
63+
64+
```bash
65+
mvn spring-boot:run
66+
```
67+
68+
或构建后运行:
69+
70+
```bash
71+
mvn clean package
72+
java -jar target/spring-ai-observability-tracing-*.jar
73+
```
74+
75+
---
76+
77+
## 🌐 接口列表
78+
79+
| 接口路径 | 描述 |
80+
|---------------------------------------|------------------------|
81+
| /observability/chat?message=xxx | 发送聊天消息给 AI 模型 |
82+
| /observability/image | 生成一张图片 |
83+
| /observability/embedding | 获取 "hello world" 的文本嵌入 |
84+
| /observability/embedding/generic | 使用指定模型获取嵌入 |
85+
| /observability/tools/function | 调用 Function 工具(天气) |
86+
| /observability/tools/method | 调用 Method 工具(当前时间) |
87+
| /observability/vector/store?text=xxx | 存储文档到 Redis 向量库 |
88+
| /observability/vector/search?text=xxx | 在向量库中搜索相似内容 |
89+
| /observability/vector/delete?id=xxx | 删除向量库中指定id内容 |
90+
91+
---
92+
93+
## 📊观测指标查看
94+
95+
项目使用 Micrometer 和 Spring Boot Actuator 来暴露和管理指标数据。可以通过以下步骤查看观测指标:
96+
97+
1. **启动应用**:确保应用已经成功启动。
98+
99+
2. **访问指标端点**:通过浏览器或 HTTP 客户端访问 `/actuator/metrics` 端点。
100+
101+
```bash
102+
curl http://localhost:8087/actuator/metrics
103+
```
104+
105+
3. **查看具体指标**:可以通过指定指标名称来查看具体的指标详情。
106+
107+
```bash
108+
curl http://localhost:8087/actuator/metrics/[metricName]
109+
```
110+
111+
其中 `[metricName]` 是你想查看的具体指标名称。
112+
113+
4. **Prometheus 格式**:如果需要以 Prometheus 格式查看指标,可以访问 `/actuator/prometheus` 端点。
114+
115+
```bash
116+
curl http://localhost:8087/actuator/prometheus
117+
```
118+
119+
这些指标包括但不限于 JVM 指标、系统指标、HTTP 请求统计等。通过这些指标,可以更好地监控和诊断应用程序的运行状态。
120+
121+
## 📝 注意事项
122+
123+
- 确保已安装并运行 Redis。
124+
- 如需部署到生产环境,请关闭 debug 日志并调整采样率(`management.tracing.sampling.probability`)。
125+
- 若更换为其他 OpenAI 兼容平台,请修改对应的 `base-url``api-key`
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>com.glmapper</groupId>
8+
<artifactId>spring-ai-observability</artifactId>
9+
<version>0.0.1</version>
10+
</parent>
11+
12+
<groupId>com.glmapper.ai.observability.metric</groupId>
13+
<artifactId>spring-ai-observability-metric</artifactId>
14+
15+
16+
</project>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
### chat
2+
GET http://localhost:8087/observability/chat
3+
4+
### embedding
5+
GET http://localhost:8087/observability/embedding
6+
7+
### embedding generic
8+
GET http://localhost:8087/observability/embedding/generic
9+
10+
### image
11+
GET http://localhost:8087/observability/image
12+
13+
### tools function
14+
GET http://localhost:8087/observability/tools/function
15+
16+
### tools method
17+
GET http://localhost:8087/observability/tools/method
18+
19+
### vector store
20+
GET http://localhost:8087/observability/vector/store?text=
21+
22+
### vector search
23+
GET http://localhost:8087/observability/vector/search?text=
24+
25+
### vector delete
26+
GET http://localhost:8087/observability/vector/delete?id=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.glmapper.ai.observability.metric;
2+
3+
4+
import org.springframework.boot.SpringApplication;
5+
import org.springframework.boot.autoconfigure.SpringBootApplication;
6+
7+
@SpringBootApplication
8+
public class ObservabilityMetricApplication {
9+
10+
public static void main(String[] args) {
11+
SpringApplication.run(ObservabilityMetricApplication.class, args);
12+
}
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.glmapper.ai.observability.metric.configs;
2+
3+
import com.glmapper.ai.observability.metric.tools.function.WeatherRequest;
4+
import com.glmapper.ai.observability.metric.tools.function.WeatherResponse;
5+
import com.glmapper.ai.observability.metric.tools.function.WeatherService;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.context.annotation.Description;
9+
10+
import java.util.function.Function;
11+
12+
13+
@Configuration(proxyBeanMethods = false)
14+
public class WeatherToolsConfigs {
15+
public static final String CURRENT_WEATHER_TOOL = "currentWeather";
16+
17+
WeatherService weatherService = new WeatherService();
18+
19+
@Bean(CURRENT_WEATHER_TOOL)
20+
@Description("Get the weather in location")
21+
Function<WeatherRequest, WeatherResponse> currentWeather() {
22+
return weatherService;
23+
}
24+
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.glmapper.ai.observability.metric.controller;
2+
3+
import org.springframework.ai.chat.client.ChatClient;
4+
import org.springframework.web.bind.annotation.GetMapping;
5+
import org.springframework.web.bind.annotation.RequestMapping;
6+
import org.springframework.web.bind.annotation.RequestParam;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
@RestController
10+
@RequestMapping("/observability/chat")
11+
public class ChatController {
12+
13+
private final ChatClient chatClient;
14+
15+
public ChatController(ChatClient.Builder chatClientBuilder) {
16+
this.chatClient = chatClientBuilder.build();
17+
}
18+
19+
@GetMapping
20+
public String chat(@RequestParam String message) {
21+
// 自动记录 spring.ai.chat.client 观测数据
22+
return chatClient.prompt()
23+
.user(message)
24+
.call()
25+
.content();
26+
}
27+
28+
29+
30+
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.glmapper.ai.observability.metric.controller;
2+
3+
4+
import org.springframework.ai.embedding.EmbeddingModel;
5+
import org.springframework.ai.embedding.EmbeddingRequest;
6+
import org.springframework.ai.openai.OpenAiEmbeddingOptions;
7+
import org.springframework.web.bind.annotation.GetMapping;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
import java.util.List;
12+
13+
@RestController
14+
@RequestMapping("/observability/embedding")
15+
public class EmbeddingController {
16+
17+
private final EmbeddingModel embeddingModel;
18+
19+
public EmbeddingController(EmbeddingModel embeddingModel) {
20+
this.embeddingModel = embeddingModel;
21+
}
22+
23+
@GetMapping
24+
public String embedding() {
25+
var embeddings = embeddingModel.embed("hello world.");
26+
return "embedding vector size:" + embeddings.length;
27+
}
28+
29+
@GetMapping("/generic")
30+
public String embeddingGenericOpts() {
31+
32+
var embeddings = embeddingModel.call(new EmbeddingRequest(
33+
List.of("hello world."),
34+
OpenAiEmbeddingOptions.builder().model("text-embedding-v4").build())
35+
).getResult().getOutput();
36+
return "embedding vector size:" + embeddings.length;
37+
}
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.glmapper.ai.observability.metric.controller;
2+
3+
import jakarta.servlet.http.HttpServletResponse;
4+
import org.springframework.ai.image.ImageModel;
5+
import org.springframework.ai.image.ImagePrompt;
6+
import org.springframework.ai.image.ImageResponse;
7+
import org.springframework.http.MediaType;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
import java.io.IOException;
13+
import java.io.InputStream;
14+
import java.net.URI;
15+
import java.net.URL;
16+
17+
@RestController
18+
@RequestMapping("/observability/image")
19+
public class ImageController {
20+
21+
private final ImageModel imageModel;
22+
23+
private static final String DEFAULT_PROMPT = "为人工智能生成一张富有科技感的图片!";
24+
25+
public ImageController(ImageModel imageModel) {
26+
this.imageModel = imageModel;
27+
}
28+
29+
@GetMapping
30+
public String image() {
31+
32+
ImageResponse imageResponse = imageModel.call(new ImagePrompt(DEFAULT_PROMPT));
33+
34+
return imageResponse.getResult().getOutput().getUrl();
35+
}
36+
}

0 commit comments

Comments
 (0)