Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<module>spring-ai-chat-memory</module>
<module>spring-ai-tool-calling</module>
<module>spring-ai-agent</module>
<module>spring-ai-observability</module>
</modules>

<properties>
Expand Down
35 changes: 35 additions & 0 deletions spring-ai-observability/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.glmapper</groupId>
<artifactId>spring-ai-summary</artifactId>
<version>0.0.1</version>
</parent>

<artifactId>spring-ai-observability</artifactId>
<packaging>pom</packaging>
<name>spring-ai-observability</name>
<modules>
<module>spring-ai-observability-metric</module>
<module>spring-ai-observability-tracing</module>
</modules>

<dependencies>

<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

<!-- redis vector -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>

</dependencies>

</project>
125 changes: 125 additions & 0 deletions spring-ai-observability/spring-ai-observability-metric/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Spring AI Observability Metric

该项目是基于 [Spring AI](https://docs.spring.io/spring-ai/) 构建的可观察性追踪示例,集成了 **OpenAI 客户端**(兼容阿里云 DashScope)、**工具调用(Function/Method)** 和 **向量存储(Redis VectorStore)** 功能。


通过本项目可以快速了解如何在 Spring Boot 应用中实现对 AI 模型调用的监控、追踪和可观测性管理。


## 模块结构

```
spring-ai-observability-metric
├── src/
│ ├── main/
│ │ ├── java/com/glmapper/ai/observability/tracing/
│ │ │ ├── controller/ # 各类 REST 接口(Chat, Image, Embedding, Tools)
│ │ │ ├── tools/ # 工具函数(天气查询、时间获取)
│ │ │ ├── storage/ # Redis VectorStore 存储封装
│ │ │ └── configs/ # 配置类
│ │ └── resources/
│ │ └── application.yml # 配置文件
├── pom.xml # Maven 项目配置
└── README.md # 当前文档
```

## 🧩 核心功能

| 模块 | 功能描述 |
|------|----------|
| ChatController | 调用 OpenAI 兼容接口进行聊天对话 |
| ImageController | 调用图像生成 API(如 Stable Diffusion) |
| EmbeddingController | 使用文本嵌入模型生成向量表示 |
| ToolCallingController | 支持 Function 和 Method 类型工具调用(天气、当前时间) |
| VectorStoreController | 使用 Redis 作为向量数据库进行文档存储与搜索 |
| Zipkin 集成 | 所有请求链路信息上报至 Zipkin,支持全链路追踪 |

---

## 🛠️ 技术栈

- Spring Boot 3.x
- Spring AI 1.0.x
- Redis VectorStore
- OpenAI Client(兼容 DashScope)
- Micrometer Tracing + Brave
- Zipkin 分布式追踪

---

## 🚀 快速启动
### 1. 修改配置

在 [application.yml](src/main/resources) 中更新以下参数:

```yaml
spring:
ai:
openai:
api-key: your-api-key-here
```

### 2. 启动应用

```bash
mvn spring-boot:run
```

或构建后运行:

```bash
mvn clean package
java -jar target/spring-ai-observability-tracing-*.jar
```

---

## 🌐 接口列表

| 接口路径 | 描述 |
|---------------------------------------|------------------------|
| /observability/chat?message=xxx | 发送聊天消息给 AI 模型 |
| /observability/image | 生成一张图片 |
| /observability/embedding | 获取 "hello world" 的文本嵌入 |
| /observability/embedding/generic | 使用指定模型获取嵌入 |
| /observability/tools/function | 调用 Function 工具(天气) |
| /observability/tools/method | 调用 Method 工具(当前时间) |
| /observability/vector/store?text=xxx | 存储文档到 Redis 向量库 |
| /observability/vector/search?text=xxx | 在向量库中搜索相似内容 |
| /observability/vector/delete?id=xxx | 删除向量库中指定id内容 |

---

## 📊观测指标查看

项目使用 Micrometer 和 Spring Boot Actuator 来暴露和管理指标数据。可以通过以下步骤查看观测指标:

1. **启动应用**:确保应用已经成功启动。

2. **访问指标端点**:通过浏览器或 HTTP 客户端访问 `/actuator/metrics` 端点。

```bash
curl http://localhost:8087/actuator/metrics
```

3. **查看具体指标**:可以通过指定指标名称来查看具体的指标详情。

```bash
curl http://localhost:8087/actuator/metrics/[metricName]
```

其中 `[metricName]` 是你想查看的具体指标名称。

4. **Prometheus 格式**:如果需要以 Prometheus 格式查看指标,可以访问 `/actuator/prometheus` 端点。

```bash
curl http://localhost:8087/actuator/prometheus
```

这些指标包括但不限于 JVM 指标、系统指标、HTTP 请求统计等。通过这些指标,可以更好地监控和诊断应用程序的运行状态。

## 📝 注意事项

- 确保已安装并运行 Redis。
- 如需部署到生产环境,请关闭 debug 日志并调整采样率(`management.tracing.sampling.probability`)。
- 若更换为其他 OpenAI 兼容平台,请修改对应的 `base-url` 和 `api-key`。
16 changes: 16 additions & 0 deletions spring-ai-observability/spring-ai-observability-metric/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.glmapper</groupId>
<artifactId>spring-ai-observability</artifactId>
<version>0.0.1</version>
</parent>

<groupId>com.glmapper.ai.observability.metric</groupId>
<artifactId>spring-ai-observability-metric</artifactId>


</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
### chat
GET http://localhost:8087/observability/chat

### embedding
GET http://localhost:8087/observability/embedding

### embedding generic
GET http://localhost:8087/observability/embedding/generic

### image
GET http://localhost:8087/observability/image

### tools function
GET http://localhost:8087/observability/tools/function

### tools method
GET http://localhost:8087/observability/tools/method

### vector store
GET http://localhost:8087/observability/vector/store?text=

### vector search
GET http://localhost:8087/observability/vector/search?text=

### vector delete
GET http://localhost:8087/observability/vector/delete?id=
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.glmapper.ai.observability.metric;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ObservabilityMetricApplication {

public static void main(String[] args) {
SpringApplication.run(ObservabilityMetricApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.glmapper.ai.observability.metric.configs;

import com.glmapper.ai.observability.metric.tools.function.WeatherRequest;
import com.glmapper.ai.observability.metric.tools.function.WeatherResponse;
import com.glmapper.ai.observability.metric.tools.function.WeatherService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;

import java.util.function.Function;


@Configuration(proxyBeanMethods = false)
public class WeatherToolsConfigs {
public static final String CURRENT_WEATHER_TOOL = "currentWeather";

WeatherService weatherService = new WeatherService();

@Bean(CURRENT_WEATHER_TOOL)
@Description("Get the weather in location")
Function<WeatherRequest, WeatherResponse> currentWeather() {
return weatherService;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.glmapper.ai.observability.metric.controller;

import org.springframework.ai.chat.client.ChatClient;
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;

@RestController
@RequestMapping("/observability/chat")
public class ChatController {

private final ChatClient chatClient;

public ChatController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}

@GetMapping
public String chat(@RequestParam String message) {
// 自动记录 spring.ai.chat.client 观测数据
return chatClient.prompt()
.user(message)
.call()
.content();
}




}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.glmapper.ai.observability.metric.controller;


import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingRequest;
import org.springframework.ai.openai.OpenAiEmbeddingOptions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/observability/embedding")
public class EmbeddingController {

private final EmbeddingModel embeddingModel;

public EmbeddingController(EmbeddingModel embeddingModel) {
this.embeddingModel = embeddingModel;
}

@GetMapping
public String embedding() {
var embeddings = embeddingModel.embed("hello world.");
return "embedding vector size:" + embeddings.length;
}

@GetMapping("/generic")
public String embeddingGenericOpts() {

var embeddings = embeddingModel.call(new EmbeddingRequest(
List.of("hello world."),
OpenAiEmbeddingOptions.builder().model("text-embedding-v4").build())
).getResult().getOutput();
return "embedding vector size:" + embeddings.length;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.glmapper.ai.observability.metric.controller;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;

@RestController
@RequestMapping("/observability/image")
public class ImageController {

private final ImageModel imageModel;

private static final String DEFAULT_PROMPT = "为人工智能生成一张富有科技感的图片!";

public ImageController(ImageModel imageModel) {
this.imageModel = imageModel;
}

@GetMapping
public String image() {

ImageResponse imageResponse = imageModel.call(new ImagePrompt(DEFAULT_PROMPT));

return imageResponse.getResult().getOutput().getUrl();
}
}
Loading