From dd0fc35fd2d843f1f1b11283e796bcb1596129a5 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Sat, 30 May 2026 08:59:53 +0800
Subject: [PATCH 01/20] Support tracing RAG retrieval in Spring AI 1.x plugin
---
.../apm/agent/core/context/tag/Tags.java | 15 ++
.../spring-ai-1.x-plugin/pom.xml | 7 +
.../ai/v1/ChatModelCallInterceptor.java | 5 +-
.../ai/v1/ChatModelStreamInterceptor.java | 12 +-
.../spring/ai/v1/contant/Constants.java | 2 +
.../src/main/resources/skywalking-plugin.def | 1 +
.../scenarios/spring-ai-1.x-scenario/pom.xml | 5 +
.../httpclient/config/ChatClientConfig.java | 27 ++++
.../httpclient/controller/CaseController.java | 23 +++
.../controller/LLMMockController.java | 137 ++++++++++++++++++
.../src/main/resources/application.yaml | 3 +
11 files changed, 234 insertions(+), 3 deletions(-)
diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/tag/Tags.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/tag/Tags.java
index 3d0b9f37cb..8acb2b9879 100644
--- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/tag/Tags.java
+++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/context/tag/Tags.java
@@ -250,6 +250,21 @@ public static final class HTTP {
*/
public static final StringTag GEN_AI_OUTPUT_MESSAGES = new StringTag(42, "gen_ai.output.messages");
+ /**
+ * GEN_AI_DATA_SOURCE_ID represents the data source identifier.
+ */
+ public static final StringTag GEN_AI_DATA_SOURCE_ID = new StringTag(43, "gen_ai.data_source.id");
+
+ /**
+ * GEN_AI_RETRIEVAL_DOCUMENTS represents the documents retrieved.
+ */
+ public static final StringTag GEN_AI_RETRIEVAL_DOCUMENTS = new StringTag(44, "gen_ai.retrieval.documents");
+
+ /**
+ * GEN_AI_RETRIEVAL_QUERY_TEXT represents the query text used for retrieval.
+ */
+ public static final StringTag GEN_AI_RETRIEVAL_QUERY_TEXT = new StringTag(45, "gen_ai.retrieval.query.text");
+
/**
* Creates a {@code StringTag} with the given key and cache it, if it's created before, simply return it without
* creating a new one.
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/pom.xml b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/pom.xml
index 295cf9d8ed..583ce9c90c 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/pom.xml
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/pom.xml
@@ -46,6 +46,13 @@
provided
+
+ org.springframework.ai
+ spring-ai-vector-store
+ 1.1.0
+ provided
+
+
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java
index 302db3c407..73f62629c1 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java
@@ -26,6 +26,7 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ErrorTypeResolver;
import org.apache.skywalking.apm.plugin.spring.ai.v1.config.SpringAiPluginConfig;
import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants;
import org.apache.skywalking.apm.plugin.spring.ai.v1.messages.InputMessages;
@@ -129,7 +130,9 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes, Throwable t) {
if (ContextManager.isActive()) {
- ContextManager.activeSpan().log(t);
+ AbstractSpan span = ContextManager.activeSpan();
+ span.log(t);
+ ErrorTypeResolver.setErrorType(span, t);
}
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java
index 1674e82011..7d1b380573 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java
@@ -27,6 +27,7 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ErrorTypeResolver;
import org.apache.skywalking.apm.plugin.spring.ai.v1.config.SpringAiPluginConfig;
import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants;
import org.apache.skywalking.apm.plugin.spring.ai.v1.messages.InputMessages;
@@ -94,11 +95,16 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA
return flux
.doOnNext(response -> onStreamNext(span, response, state))
- .doOnError(span::log)
+ .doOnError(t -> recordError(span, t))
.doFinally(signalType -> onStreamFinally(span, allArguments, state))
.contextWrite(c -> c.put(Constants.SKYWALKING_CONTEXT_SNAPSHOT, snapshot));
}
+ private void recordError(AbstractSpan span, Throwable t) {
+ span.log(t);
+ ErrorTypeResolver.setErrorType(span, t);
+ }
+
private void onStreamNext(AbstractSpan span, ChatResponse response, StreamState state) {
state.lastResponseRef.set(response);
@@ -248,7 +254,9 @@ private Long readAndClearStartTime() {
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes, Throwable t) {
if (ContextManager.isActive()) {
- ContextManager.activeSpan().log(t);
+ AbstractSpan span = ContextManager.activeSpan();
+ span.log(t);
+ ErrorTypeResolver.setErrorType(span, t);
}
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java
index 7348a0c11f..688f4c321a 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java
@@ -27,5 +27,7 @@ public class Constants {
public static final String EXECUTE_TOOL = "execute_tool";
+ public static final String RETRIEVAL = "retrieval";
+
public static final String DEFAULT_COMPLETIONS_PATH = "/v1/chat/completions";
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def
index 5c7eec110c..6185c8b12f 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def
@@ -17,6 +17,7 @@
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.ChatModelInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.ToolCallbackInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.DefaultToolCallingManagerInstrumentation
+spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.AbstractObservationVectorStoreInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.AnthropicApiInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.DeepSeekApiInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.HuggingfaceChatModelInstrumentation
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml b/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml
index b9d39f538a..0d508db8b0 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml
@@ -55,6 +55,11 @@
spring-ai-starter-model-openai
+
+ org.springframework.ai
+ spring-ai-vector-store
+
+
org.projectlombok
lombok
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java
index 79fde137ab..02d0f3466b 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java
@@ -17,10 +17,18 @@
package test.apache.skywalking.apm.testcase.jdk.httpclient.config;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
+import org.springframework.ai.vectorstore.SimpleVectorStore;
+import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+import java.util.ArrayList;
+import java.util.List;
@Configuration
public class ChatClientConfig {
@@ -29,4 +37,23 @@ public class ChatClientConfig {
public ChatClient openAIChatClient(OpenAiChatModel model) {
return ChatClient.create(model);
}
+
+ @Bean
+ @Lazy
+ public VectorStore vectorStore(EmbeddingModel embeddingModel) {
+ SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build();
+
+ List documentList = new ArrayList<>();
+ documentList.add(new Document("The 2025 AI Summit is scheduled for October 10-12 in San Francisco. "
+ + "The event will focus on Generative AI and Autonomous Agents."));
+ documentList.add(new Document("Apache SkyWalking is an open-source Application Performance Management system "
+ + "designed for microservices, cloud native, and container-based architectures."));
+ documentList.add(new Document("Spring AI provides a unified interface for interacting with different "
+ + "AI Models, allowing developers to switch between providers with minimal code changes."));
+ documentList.add(new Document("A new distributed tracing protocol, TraceContext v2, was proposed "
+ + "on August 25, 2025, to improve cross-cloud observability."));
+
+ vectorStore.add(documentList);
+ return vectorStore;
+ }
}
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java
index d6512b726e..73dc1b15bc 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java
@@ -18,12 +18,18 @@
package test.apache.skywalking.apm.testcase.jdk.httpclient.controller;
import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import test.apache.skywalking.apm.testcase.jdk.httpclient.tool.WeatherTool;
+import java.util.stream.Collectors;
+
@RestController
@RequestMapping("/case")
@RequiredArgsConstructor
@@ -31,6 +37,7 @@ public class CaseController {
private final WeatherTool weatherTool;
private final ChatClient chatClient;
+ private final ObjectProvider vectorStoreProvider;
@GetMapping("/healthCheck")
public String healthCheck() {
@@ -63,6 +70,22 @@ public String testCase() throws Exception {
.doOnNext(System.out::println)
.blockLast();
+ String question = "What is Apache SkyWalking?";
+ VectorStore vectorStore = vectorStoreProvider.getObject();
+ String context = vectorStore.similaritySearch(SearchRequest.builder()
+ .query(question)
+ .topK(2)
+ .build())
+ .stream()
+ .map(Document::getText)
+ .collect(Collectors.joining("\n"));
+
+ chatClient
+ .prompt(question)
+ .system("Answer using only the following context:\n" + context)
+ .call()
+ .content();
+
return "success";
}
}
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java
index 5221245280..5e35cfada5 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java
@@ -21,6 +21,7 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -177,6 +178,60 @@ public Object completions(@RequestBody JSONObject request, HttpServletResponse r
}
""";
+ String ragLlmResponse = """
+ {
+ "choices": [
+ {
+ "finish_reason": "stop",
+ "index": 0,
+ "logprobs": null,
+ "message": {
+ "annotations": [],
+ "content": "Apache SkyWalking is an open-source Application Performance Management system designed for microservices, cloud native, and container-based architectures.",
+ "refusal": null,
+ "role": "assistant"
+ }
+ }
+ ],
+ "created": 1780045046,
+ "id": "chatcmpl-DknJunZ3tgcSkKivGOyQ6gEkMCrCX",
+ "model": "gpt-4.1-mini-2025-04-14",
+ "object": "chat.completion",
+ "service_tier": "default",
+ "system_fingerprint": "fp_a7294185dc",
+ "usage": {
+ "completion_tokens": 25,
+ "completion_tokens_details": {
+ "accepted_prediction_tokens": 0,
+ "audio_tokens": 0,
+ "reasoning_tokens": 0,
+ "rejected_prediction_tokens": 0
+ },
+ "latency_checkpoint": {
+ "engine_tbt_ms": 23,
+ "engine_ttft_ms": 672,
+ "engine_ttlt_ms": 1259,
+ "pre_inference_ms": 157,
+ "service_tbt_ms": 610,
+ "service_ttft_ms": 12302,
+ "service_ttlt_ms": 27518,
+ "total_duration_ms": 27381,
+ "user_visible_ttft_ms": 12145
+ },
+ "prompt_tokens": 72,
+ "prompt_tokens_details": {
+ "audio_tokens": 0,
+ "cached_tokens": 0
+ },
+ "total_tokens": 97
+ }
+ }
+ """;
+
+ if (isRagLlmRequest(messages)) {
+ return JSON.parseObject(ragLlmResponse);
+ }
+
if ("tool".equals(lastRole)) {
return JSON.parseObject(finalResponse);
}
@@ -227,4 +282,86 @@ private String escapeJson(String input) {
if (input == null) return "";
return input.replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
}
+
+ private boolean isRagLlmRequest(JSONArray messages) {
+ if (messages == null || messages.size() < 2) {
+ return false;
+ }
+
+ JSONObject lastMessage = messages.getJSONObject(messages.size() - 1);
+ if (!"user".equals(lastMessage.getString("role"))
+ || !"What is Apache SkyWalking?".equals(lastMessage.getString("content"))) {
+ return false;
+ }
+
+ for (int i = 0; i < messages.size() - 1; i++) {
+ JSONObject message = messages.getJSONObject(i);
+ if (!"system".equals(message.getString("role"))) {
+ continue;
+ }
+ String content = message.getString("content");
+ if (content != null
+ && content.startsWith("Answer using only the following context:")
+ && content.contains("Apache SkyWalking is an open-source Application Performance Management system")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @PostMapping("/v1/embeddings")
+ public Object embeddings(@RequestBody JSONObject request) {
+ Object input = request.get("input");
+ JSONArray inputs = input instanceof JSONArray ? (JSONArray) input : new JSONArray();
+ if (!(input instanceof JSONArray)) {
+ inputs.add(String.valueOf(input));
+ }
+
+ JSONArray data = new JSONArray();
+ for (int i = 0; i < inputs.size(); i++) {
+ JSONObject item = new JSONObject();
+ item.put("object", "embedding");
+ item.put("index", i);
+ item.put("embedding", embeddingFor(inputs.getString(i)));
+ data.add(item);
+ }
+
+ JSONObject usage = new JSONObject();
+ usage.put("prompt_tokens", inputs.size());
+ usage.put("total_tokens", inputs.size());
+
+ JSONObject response = new JSONObject();
+ response.put("object", "list");
+ response.put("model", "text-embedding-3-small");
+ response.put("data", data);
+ response.put("usage", usage);
+ return response;
+ }
+
+ private JSONArray embeddingFor(String input) {
+ String text = input == null ? "" : input.toLowerCase();
+ double[] values = new double[]{
+ score(text, "summit", "san francisco", "generative", "autonomous"),
+ score(text, "skywalking", "apm", "microservices", "cloud native"),
+ score(text, "spring ai", "models", "providers"),
+ score(text, "tracecontext", "tracing", "observability"),
+ 0.1
+ };
+
+ JSONArray embedding = new JSONArray();
+ for (double value : values) {
+ embedding.add(value);
+ }
+ return embedding;
+ }
+
+ private double score(String text, String... keywords) {
+ double value = 0.0;
+ for (String keyword : keywords) {
+ if (text.contains(keyword)) {
+ value += 1.0;
+ }
+ }
+ return value;
+ }
}
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml
index c4f5c58851..fceabe447e 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml
@@ -30,6 +30,9 @@ spring:
temperature: 0.7
max-tokens: 1000
top-p: 0.9
+ embedding:
+ options:
+ model: text-embedding-3-small
From 1347195d361ae99b935f6ade36f382068ba0e456 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Sat, 30 May 2026 09:42:59 +0800
Subject: [PATCH 02/20] Add Spring AI error type resolver
---
.../ai/v1/common/ErrorTypeResolver.java | 85 +++++++++++++++++++
1 file changed, 85 insertions(+)
create mode 100644 apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
new file mode 100644
index 0000000000..9e76d5a2a4
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1.common;
+
+import org.apache.skywalking.apm.agent.core.context.tag.AbstractTag;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+
+import javax.net.ssl.SSLHandshakeException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertificateException;
+import java.util.concurrent.TimeoutException;
+
+public final class ErrorTypeResolver {
+
+ private static final AbstractTag ERROR_TYPE = Tags.ofKey("error.type");
+ private static final String TIMEOUT = "timeout";
+ private static final String SERVER_CERTIFICATE_INVALID = "server_certificate_invalid";
+ private static final String INTERNAL_SERVER_ERROR = "500";
+
+ private ErrorTypeResolver() {
+ }
+
+ public static void setErrorType(AbstractSpan span, Throwable throwable) {
+ span.tag(ERROR_TYPE, resolve(throwable));
+ }
+
+ private static String resolve(Throwable throwable) {
+ if (matches(throwable, ErrorTypeResolver::isTimeout)) {
+ return TIMEOUT;
+ }
+ if (matches(throwable, UnknownHostException.class::isInstance)) {
+ return UnknownHostException.class.getName();
+ }
+ if (matches(throwable, ErrorTypeResolver::isCertificateInvalid)) {
+ return SERVER_CERTIFICATE_INVALID;
+ }
+ return INTERNAL_SERVER_ERROR;
+ }
+
+ private static boolean isTimeout(Throwable throwable) {
+ return throwable instanceof SocketTimeoutException
+ || throwable instanceof TimeoutException
+ || throwable.getClass().getName().contains("TimeoutException");
+ }
+
+ private static boolean isCertificateInvalid(Throwable throwable) {
+ return throwable instanceof SSLHandshakeException
+ || throwable instanceof CertificateException
+ || throwable instanceof CertPathValidatorException;
+ }
+
+ private static boolean matches(Throwable throwable, Matcher matcher) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (matcher.matches(current)) {
+ return true;
+ }
+ current = current.getCause();
+ }
+ return false;
+ }
+
+ private interface Matcher {
+ boolean matches(Throwable throwable);
+ }
+}
From 81c3b2fbdab3f8e98bbe7953c0dcb5b11866033c Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Sat, 30 May 2026 10:38:34 +0800
Subject: [PATCH 03/20] Add Spring AI vector store instrumentation
---
...ractObservationVectorStoreInterceptor.java | 190 ++++++++++++++++++
...ObservationVectorStoreInstrumentation.java | 72 +++++++
2 files changed, 262 insertions(+)
create mode 100644 apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
create mode 100644 apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
new file mode 100644
index 0000000000..014d30bc19
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1;
+
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.apache.skywalking.apm.agent.core.util.GsonUtil;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingOptions;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore;
+import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AbstractObservationVectorStoreInterceptor implements InstanceMethodsAroundInterceptor {
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) throws Throwable {
+ SearchRequest request = (SearchRequest) allArguments[0];
+ VectorStoreObservationContext context = createObservationContext(objInst);
+ String dataSourceId = resolveDataSourceId(context, objInst);
+
+ AbstractSpan span = ContextManager.createExitSpan(Constants.RETRIEVAL + " " + dataSourceId, dataSourceId);
+ SpanLayer.asGenAI(span);
+ span.setComponent(ComponentsDefine.SPRING_AI);
+ Tags.GEN_AI_OPERATION_NAME.set(span, Constants.RETRIEVAL);
+ Tags.GEN_AI_PROVIDER_NAME.set(span, context.getDatabaseSystem());
+ Tags.GEN_AI_DATA_SOURCE_ID.set(span, dataSourceId);
+ String model = resolveEmbeddingModel(objInst);
+ if (StringUtils.hasText(model)) {
+ Tags.GEN_AI_REQUEST_MODEL.set(span, model);
+ }
+
+ if (request != null) {
+ Tags.GEN_AI_TOP_K.set(span, String.valueOf(request.getTopK()));
+ if (StringUtils.hasText(request.getQuery())) {
+ Tags.GEN_AI_RETRIEVAL_QUERY_TEXT.set(span, request.getQuery());
+ }
+ }
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) throws Throwable {
+ if (!ContextManager.isActive()) {
+ return ret;
+ }
+ try {
+ if (ret instanceof List>) {
+ Tags.GEN_AI_RETRIEVAL_DOCUMENTS.set(ContextManager.activeSpan(), toDocumentsJson((List>) ret));
+ }
+ } finally {
+ ContextManager.stopSpan();
+ }
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+ if (ContextManager.isActive()) {
+ ContextManager.activeSpan().log(t);
+ }
+ }
+
+ private VectorStoreObservationContext createObservationContext(EnhancedInstance objInst) {
+ return ((AbstractObservationVectorStore) objInst)
+ .createObservationContextBuilder(VectorStoreObservationContext.Operation.QUERY.value())
+ .build();
+ }
+
+ private String resolveEmbeddingModel(EnhancedInstance objInst) {
+ try {
+ Field field = AbstractObservationVectorStore.class.getDeclaredField("embeddingModel");
+ field.setAccessible(true);
+ Object embeddingModel = field.get(objInst);
+ return resolveModelFromEmbeddingModel(embeddingModel);
+ } catch (Throwable ignored) {
+ return null;
+ }
+ }
+
+ private String resolveModelFromEmbeddingModel(Object embeddingModel) {
+ if (embeddingModel == null) {
+ return null;
+ }
+ String model = resolveModelFromOptionsMethod(embeddingModel);
+ if (StringUtils.hasText(model)) {
+ return model;
+ }
+ model = resolveModelFromOptionsField(embeddingModel, "options");
+ if (StringUtils.hasText(model)) {
+ return model;
+ }
+ return resolveModelFromOptionsField(embeddingModel, "defaultOptions");
+ }
+
+ private String resolveModelFromOptionsMethod(Object embeddingModel) {
+ try {
+ Method method = embeddingModel.getClass().getMethod("getOptions");
+ return resolveModelFromOptions(method.invoke(embeddingModel));
+ } catch (Throwable ignored) {
+ return null;
+ }
+ }
+
+ private String resolveModelFromOptionsField(Object embeddingModel, String fieldName) {
+ Class> type = embeddingModel.getClass();
+ while (type != null) {
+ try {
+ Field field = type.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return resolveModelFromOptions(field.get(embeddingModel));
+ } catch (NoSuchFieldException e) {
+ type = type.getSuperclass();
+ } catch (Throwable ignored) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ private String resolveModelFromOptions(Object options) {
+ if (options instanceof EmbeddingOptions) {
+ return ((EmbeddingOptions) options).getModel();
+ }
+ return null;
+ }
+
+ private String resolveDataSourceId(VectorStoreObservationContext context, EnhancedInstance objInst) {
+ if (StringUtils.hasText(context.getCollectionName())) {
+ return context.getCollectionName();
+ }
+ if (StringUtils.hasText(context.getNamespace())) {
+ return context.getNamespace();
+ }
+ if (StringUtils.hasText(context.getDatabaseSystem())) {
+ return context.getDatabaseSystem();
+ }
+ return objInst.getClass().getSimpleName();
+ }
+
+ private String toDocumentsJson(List> documents) {
+ List
+
+ org.springframework.ai
+ spring-ai-vector-store
+ 1.1.0
+ provided
+
+
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
new file mode 100644
index 0000000000..9db95ee33e
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1;
+
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
+import org.springframework.ai.embedding.EmbeddingOptions;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class AbstractObservationVectorStoreConstructorInterceptor implements InstanceConstructorInterceptor {
+
+ @Override
+ public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
+ if (allArguments != null && allArguments.length > 0) {
+ String embeddingModelName = resolveModelFromEmbeddingModel(allArguments[0]);
+ objInst.setSkyWalkingDynamicField(new VectorStoreEnhanceContext(embeddingModelName));
+ }
+ }
+
+ private String resolveModelFromEmbeddingModel(Object embeddingModel) {
+ if (embeddingModel == null) {
+ return null;
+ }
+ String model = resolveModelFromOptionsMethod(embeddingModel);
+ if (StringUtils.hasText(model)) {
+ return model;
+ }
+ model = resolveModelFromOptionsField(embeddingModel, "options");
+ if (StringUtils.hasText(model)) {
+ return model;
+ }
+ return resolveModelFromOptionsField(embeddingModel, "defaultOptions");
+ }
+
+ private String resolveModelFromOptionsMethod(Object embeddingModel) {
+ try {
+ Method method = embeddingModel.getClass().getMethod("getOptions");
+ return resolveModelFromOptions(method.invoke(embeddingModel));
+ } catch (Throwable ignored) {
+ return null;
+ }
+ }
+
+ private String resolveModelFromOptionsField(Object embeddingModel, String fieldName) {
+ Class> type = embeddingModel.getClass();
+ while (type != null) {
+ try {
+ Field field = type.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return resolveModelFromOptions(field.get(embeddingModel));
+ } catch (NoSuchFieldException e) {
+ type = type.getSuperclass();
+ } catch (Throwable ignored) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ private String resolveModelFromOptions(Object options) {
+ if (options instanceof EmbeddingOptions) {
+ return ((EmbeddingOptions) options).getModel();
+ }
+ return null;
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
new file mode 100644
index 0000000000..8a34e695ef
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1;
+
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.apache.skywalking.apm.agent.core.util.GsonUtil;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore;
+import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AbstractObservationVectorStoreInterceptor implements InstanceMethodsAroundInterceptor {
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) throws Throwable {
+ SearchRequest request = (SearchRequest) allArguments[0];
+ VectorStoreObservationContext context = createObservationContext(objInst, request);
+ String dataSourceId = resolveDataSourceId(context, objInst);
+
+ AbstractSpan span = ContextManager.createExitSpan(Constants.RETRIEVAL + "/" + dataSourceId, dataSourceId);
+ SpanLayer.asGenAI(span);
+ span.setComponent(ComponentsDefine.SPRING_AI);
+ Tags.GEN_AI_OPERATION_NAME.set(span, Constants.RETRIEVAL);
+ Tags.GEN_AI_DATA_SOURCE_ID.set(span, dataSourceId);
+ String model = resolveEmbeddingModelName(objInst);
+ if (StringUtils.hasText(model)) {
+ Tags.GEN_AI_REQUEST_MODEL.set(span, model);
+ }
+
+ if (request != null) {
+ Tags.GEN_AI_TOP_K.set(span, String.valueOf(request.getTopK()));
+ if (StringUtils.hasText(request.getQuery())) {
+ Tags.GEN_AI_RETRIEVAL_QUERY_TEXT.set(span, request.getQuery());
+ }
+ }
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) throws Throwable {
+ if (!ContextManager.isActive()) {
+ return ret;
+ }
+ try {
+ if (ret instanceof List>) {
+ Tags.GEN_AI_RETRIEVAL_DOCUMENTS.set(ContextManager.activeSpan(), toDocumentsJson((List>) ret));
+ }
+ } finally {
+ ContextManager.stopSpan();
+ }
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+ if (ContextManager.isActive()) {
+ ContextManager.activeSpan().log(t);
+ }
+ }
+
+ private VectorStoreObservationContext createObservationContext(EnhancedInstance objInst, SearchRequest request) {
+ VectorStoreObservationContext.Builder builder = ((AbstractObservationVectorStore) objInst)
+ .createObservationContextBuilder(VectorStoreObservationContext.Operation.QUERY.value());
+ if (request != null) {
+ builder.queryRequest(request);
+ }
+ return builder.build();
+ }
+
+ private String resolveEmbeddingModelName(EnhancedInstance objInst) {
+ Object context = objInst.getSkyWalkingDynamicField();
+ if (context instanceof VectorStoreEnhanceContext) {
+ return ((VectorStoreEnhanceContext) context).getEmbeddingModelName();
+ }
+ return null;
+ }
+
+ private String resolveDataSourceId(VectorStoreObservationContext context, EnhancedInstance objInst) {
+ StringBuilder dataSourceId = new StringBuilder();
+ appendDataSourcePart(dataSourceId, context.getDatabaseSystem());
+ appendDataSourcePart(dataSourceId, context.getNamespace());
+ appendDataSourcePart(dataSourceId, context.getCollectionName());
+ if (dataSourceId.length() > 0) {
+ return dataSourceId.toString();
+ }
+ return objInst.getClass().getSimpleName();
+ }
+
+ private void appendDataSourcePart(StringBuilder dataSourceId, String value) {
+ if (!StringUtils.hasText(value)) {
+ return;
+ }
+ if (dataSourceId.length() > 0) {
+ dataSourceId.append('/');
+ }
+ dataSourceId.append(value);
+ }
+
+ private String toDocumentsJson(List> documents) {
+ List> retrievalDocuments = new ArrayList<>(documents.size());
+ for (Object item : documents) {
+ if (!(item instanceof Document)) {
+ continue;
+ }
+ Document document = (Document) item;
+ Map documentMap = new LinkedHashMap<>();
+ documentMap.put("id", document.getId());
+ if (document.getScore() != null) {
+ documentMap.put("score", document.getScore());
+ }
+ retrievalDocuments.add(documentMap);
+ }
+ return GsonUtil.toJson(retrievalDocuments);
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java
index 302db3c407..73f62629c1 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelCallInterceptor.java
@@ -26,6 +26,7 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ErrorTypeResolver;
import org.apache.skywalking.apm.plugin.spring.ai.v1.config.SpringAiPluginConfig;
import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants;
import org.apache.skywalking.apm.plugin.spring.ai.v1.messages.InputMessages;
@@ -129,7 +130,9 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes, Throwable t) {
if (ContextManager.isActive()) {
- ContextManager.activeSpan().log(t);
+ AbstractSpan span = ContextManager.activeSpan();
+ span.log(t);
+ ErrorTypeResolver.setErrorType(span, t);
}
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java
index 1674e82011..7d1b380573 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/ChatModelStreamInterceptor.java
@@ -27,6 +27,7 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ChatModelMetadataResolver;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ErrorTypeResolver;
import org.apache.skywalking.apm.plugin.spring.ai.v1.config.SpringAiPluginConfig;
import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants;
import org.apache.skywalking.apm.plugin.spring.ai.v1.messages.InputMessages;
@@ -94,11 +95,16 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA
return flux
.doOnNext(response -> onStreamNext(span, response, state))
- .doOnError(span::log)
+ .doOnError(t -> recordError(span, t))
.doFinally(signalType -> onStreamFinally(span, allArguments, state))
.contextWrite(c -> c.put(Constants.SKYWALKING_CONTEXT_SNAPSHOT, snapshot));
}
+ private void recordError(AbstractSpan span, Throwable t) {
+ span.log(t);
+ ErrorTypeResolver.setErrorType(span, t);
+ }
+
private void onStreamNext(AbstractSpan span, ChatResponse response, StreamState state) {
state.lastResponseRef.set(response);
@@ -248,7 +254,9 @@ private Long readAndClearStartTime() {
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes, Throwable t) {
if (ContextManager.isActive()) {
- ContextManager.activeSpan().log(t);
+ AbstractSpan span = ContextManager.activeSpan();
+ span.log(t);
+ ErrorTypeResolver.setErrorType(span, t);
}
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java
new file mode 100644
index 0000000000..a478d07bd7
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1;
+
+public class VectorStoreEnhanceContext {
+
+ private final String embeddingModelName;
+
+ public VectorStoreEnhanceContext(String embeddingModelName) {
+ this.embeddingModelName = embeddingModelName;
+ }
+
+ public String getEmbeddingModelName() {
+ return embeddingModelName;
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
new file mode 100644
index 0000000000..f5b8e51e7a
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1.common;
+
+import org.apache.skywalking.apm.agent.core.context.tag.AbstractTag;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+
+import javax.net.ssl.SSLHandshakeException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertificateException;
+import java.util.concurrent.TimeoutException;
+
+public final class ErrorTypeResolver {
+
+ private static final AbstractTag ERROR_TYPE = Tags.ofKey("error.type");
+ private static final String TIMEOUT = "timeout";
+ private static final String SERVER_CERTIFICATE_INVALID = "server_certificate_invalid";
+ private static final String OTHER = "_OTHER";
+
+ private ErrorTypeResolver() {
+ }
+
+ public static void setErrorType(AbstractSpan span, Throwable throwable) {
+ span.tag(ERROR_TYPE, resolve(throwable));
+ }
+
+ private static String resolve(Throwable throwable) {
+ if (matches(throwable, ErrorTypeResolver::isTimeout)) {
+ return TIMEOUT;
+ }
+ if (matches(throwable, UnknownHostException.class::isInstance)) {
+ return UnknownHostException.class.getName();
+ }
+ if (matches(throwable, ErrorTypeResolver::isCertificateInvalid)) {
+ return SERVER_CERTIFICATE_INVALID;
+ }
+ return OTHER;
+ }
+
+ private static boolean isTimeout(Throwable throwable) {
+ return throwable instanceof SocketTimeoutException
+ || throwable instanceof TimeoutException
+ || throwable.getClass().getName().contains("TimeoutException");
+ }
+
+ private static boolean isCertificateInvalid(Throwable throwable) {
+ return throwable instanceof SSLHandshakeException
+ || throwable instanceof CertificateException
+ || throwable instanceof CertPathValidatorException;
+ }
+
+ private static boolean matches(Throwable throwable, Matcher matcher) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (matcher.matches(current)) {
+ return true;
+ }
+ current = current.getCause();
+ }
+ return false;
+ }
+
+ private interface Matcher {
+ boolean matches(Throwable throwable);
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java
index 7348a0c11f..688f4c321a 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/contant/Constants.java
@@ -27,5 +27,7 @@ public class Constants {
public static final String EXECUTE_TOOL = "execute_tool";
+ public static final String RETRIEVAL = "retrieval";
+
public static final String DEFAULT_COMPLETIONS_PATH = "/v1/chat/completions";
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java
new file mode 100644
index 0000000000..895205832c
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1.define;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
+
+public class AbstractObservationVectorStoreInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
+
+ private static final String ENHANCE_CLASS = "org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore";
+
+ private static final String EMBEDDING_MODEL_CLASS = "org.springframework.ai.embedding.EmbeddingModel";
+
+ private static final String INTERCEPT_METHOD = "doSimilaritySearch";
+
+ private static final String INTERCEPTOR_CLASS =
+ "org.apache.skywalking.apm.plugin.spring.ai.v1.AbstractObservationVectorStoreInterceptor";
+
+ private static final String CONSTRUCTOR_INTERCEPTOR_CLASS =
+ "org.apache.skywalking.apm.plugin.spring.ai.v1.AbstractObservationVectorStoreConstructorInterceptor";
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return HierarchyMatch.byHierarchyMatch(ENHANCE_CLASS);
+ }
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[] {
+ new ConstructorInterceptPoint() {
+ @Override
+ public ElementMatcher getConstructorMatcher() {
+ return takesArguments(4).and(takesArgumentWithType(0, EMBEDDING_MODEL_CLASS));
+ }
+
+ @Override
+ public String getConstructorInterceptor() {
+ return CONSTRUCTOR_INTERCEPTOR_CLASS;
+ }
+ }
+ };
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[] {
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named(INTERCEPT_METHOD)
+ .and(takesArguments(1))
+ .and(takesArgumentWithType(0, "org.springframework.ai.vectorstore.SearchRequest"));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return INTERCEPTOR_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ }
+ };
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def
index 5c7eec110c..6185c8b12f 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def
@@ -17,6 +17,7 @@
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.ChatModelInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.ToolCallbackInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.DefaultToolCallingManagerInstrumentation
+spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.AbstractObservationVectorStoreInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.AnthropicApiInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.DeepSeekApiInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.provider.HuggingfaceChatModelInstrumentation
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml
index 5f40e79a48..ba3713d433 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml
@@ -142,6 +142,49 @@ segmentItems:
- { key: http.method, value: POST }
- { key: http.status_code, value: '200' }
+ - operationName: retrieval/simple/in-memory-map
+ parentSpanId: 0
+ spanId: 6
+ spanLayer: GenAI
+ startTime: not null
+ endTime: not null
+ componentId: 178
+ spanType: Exit
+ peer: simple/in-memory-map
+ tags:
+ - { key: gen_ai.operation.name, value: retrieval }
+ - { key: gen_ai.data_source.id, value: in-memory-map }
+ - { key: gen_ai.request.model, value: text-embedding-3-small }
+ - { key: gen_ai.request.top_k, value: '2' }
+ - { key: gen_ai.retrieval.query.text, value: 'What is Apache SkyWalking?' }
+ - { key: gen_ai.retrieval.documents, value: not null }
+
+ - operationName: Spring-ai/openai/call
+ parentSpanId: 0
+ spanId: 7
+ spanLayer: GenAI
+ startTime: not null
+ endTime: not null
+ componentId: 173
+ isError: false
+ spanType: Exit
+ peer: http://localhost:8080/spring-ai-1.x-scenario/llm/v1/chat/completions
+ skipAnalysis: false
+ tags:
+ - { key: gen_ai.operation.name, value: chat }
+ - { key: gen_ai.provider.name, value: openai }
+ - { key: gen_ai.request.model, value: gpt-4.1-2025-04-14 }
+ - { key: gen_ai.request.temperature, value: '0.7' }
+ - { key: gen_ai.request.top_p, value: '0.9' }
+ - { key: gen_ai.response.id, value: chatcmpl-DknJunZ3tgcSkKiv }
+ - { key: gen_ai.response.model, value: gpt-4.1-2025-04-14 }
+ - { key: gen_ai.usage.input_tokens, value: '72' }
+ - { key: gen_ai.usage.output_tokens, value: '25' }
+ - { key: gen_ai.client.token.usage, value: '97' }
+ - { key: gen_ai.response.finish_reasons, value: STOP }
+ - { key: gen_ai.input.messages, value: not null }
+ - { key: gen_ai.output.messages, value: not null }
+
- operationName: GET:/spring-ai-1.x-scenario/case/spring-ai-1.x-scenario-case
parentSpanId: -1
spanId: 0
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml b/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml
index b9d39f538a..0d508db8b0 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/pom.xml
@@ -55,6 +55,11 @@
spring-ai-starter-model-openai
+
+ org.springframework.ai
+ spring-ai-vector-store
+
+
org.projectlombok
lombok
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java
index 79fde137ab..02d0f3466b 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/config/ChatClientConfig.java
@@ -17,10 +17,18 @@
package test.apache.skywalking.apm.testcase.jdk.httpclient.config;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
+import org.springframework.ai.vectorstore.SimpleVectorStore;
+import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+import java.util.ArrayList;
+import java.util.List;
@Configuration
public class ChatClientConfig {
@@ -29,4 +37,23 @@ public class ChatClientConfig {
public ChatClient openAIChatClient(OpenAiChatModel model) {
return ChatClient.create(model);
}
+
+ @Bean
+ @Lazy
+ public VectorStore vectorStore(EmbeddingModel embeddingModel) {
+ SimpleVectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build();
+
+ List documentList = new ArrayList<>();
+ documentList.add(new Document("The 2025 AI Summit is scheduled for October 10-12 in San Francisco. "
+ + "The event will focus on Generative AI and Autonomous Agents."));
+ documentList.add(new Document("Apache SkyWalking is an open-source Application Performance Management system "
+ + "designed for microservices, cloud native, and container-based architectures."));
+ documentList.add(new Document("Spring AI provides a unified interface for interacting with different "
+ + "AI Models, allowing developers to switch between providers with minimal code changes."));
+ documentList.add(new Document("A new distributed tracing protocol, TraceContext v2, was proposed "
+ + "on August 25, 2025, to improve cross-cloud observability."));
+
+ vectorStore.add(documentList);
+ return vectorStore;
+ }
}
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java
index d6512b726e..73dc1b15bc 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/CaseController.java
@@ -18,12 +18,18 @@
package test.apache.skywalking.apm.testcase.jdk.httpclient.controller;
import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.document.Document;
+import org.springframework.ai.vectorstore.SearchRequest;
+import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import test.apache.skywalking.apm.testcase.jdk.httpclient.tool.WeatherTool;
+import java.util.stream.Collectors;
+
@RestController
@RequestMapping("/case")
@RequiredArgsConstructor
@@ -31,6 +37,7 @@ public class CaseController {
private final WeatherTool weatherTool;
private final ChatClient chatClient;
+ private final ObjectProvider vectorStoreProvider;
@GetMapping("/healthCheck")
public String healthCheck() {
@@ -63,6 +70,22 @@ public String testCase() throws Exception {
.doOnNext(System.out::println)
.blockLast();
+ String question = "What is Apache SkyWalking?";
+ VectorStore vectorStore = vectorStoreProvider.getObject();
+ String context = vectorStore.similaritySearch(SearchRequest.builder()
+ .query(question)
+ .topK(2)
+ .build())
+ .stream()
+ .map(Document::getText)
+ .collect(Collectors.joining("\n"));
+
+ chatClient
+ .prompt(question)
+ .system("Answer using only the following context:\n" + context)
+ .call()
+ .content();
+
return "success";
}
}
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java
index 5221245280..d63482c5fd 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/java/test/apache/skywalking/apm/testcase/jdk/httpclient/controller/LLMMockController.java
@@ -21,6 +21,7 @@
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -177,6 +178,60 @@ public Object completions(@RequestBody JSONObject request, HttpServletResponse r
}
""";
+ String ragLlmResponse = """
+ {
+ "choices": [
+ {
+ "finish_reason": "stop",
+ "index": 0,
+ "logprobs": null,
+ "message": {
+ "annotations": [],
+ "content": "Apache SkyWalking is an open-source Application Performance Management system designed for microservices, cloud native, and container-based architectures.",
+ "refusal": null,
+ "role": "assistant"
+ }
+ }
+ ],
+ "created": 1780045046,
+ "id": "chatcmpl-DknJunZ3tgcSkKiv",
+ "model": "gpt-4.1-2025-04-14",
+ "object": "chat.completion",
+ "service_tier": "default",
+ "system_fingerprint": "fp_a7294185dc",
+ "usage": {
+ "completion_tokens": 25,
+ "completion_tokens_details": {
+ "accepted_prediction_tokens": 0,
+ "audio_tokens": 0,
+ "reasoning_tokens": 0,
+ "rejected_prediction_tokens": 0
+ },
+ "latency_checkpoint": {
+ "engine_tbt_ms": 23,
+ "engine_ttft_ms": 672,
+ "engine_ttlt_ms": 1259,
+ "pre_inference_ms": 157,
+ "service_tbt_ms": 610,
+ "service_ttft_ms": 12302,
+ "service_ttlt_ms": 27518,
+ "total_duration_ms": 27381,
+ "user_visible_ttft_ms": 12145
+ },
+ "prompt_tokens": 72,
+ "prompt_tokens_details": {
+ "audio_tokens": 0,
+ "cached_tokens": 0
+ },
+ "total_tokens": 97
+ }
+ }
+ """;
+
+ if (isRagLlmRequest(messages)) {
+ return JSON.parseObject(ragLlmResponse);
+ }
+
if ("tool".equals(lastRole)) {
return JSON.parseObject(finalResponse);
}
@@ -227,4 +282,86 @@ private String escapeJson(String input) {
if (input == null) return "";
return input.replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
}
+
+ private boolean isRagLlmRequest(JSONArray messages) {
+ if (messages == null || messages.size() < 2) {
+ return false;
+ }
+
+ JSONObject lastMessage = messages.getJSONObject(messages.size() - 1);
+ if (!"user".equals(lastMessage.getString("role"))
+ || !"What is Apache SkyWalking?".equals(lastMessage.getString("content"))) {
+ return false;
+ }
+
+ for (int i = 0; i < messages.size() - 1; i++) {
+ JSONObject message = messages.getJSONObject(i);
+ if (!"system".equals(message.getString("role"))) {
+ continue;
+ }
+ String content = message.getString("content");
+ if (content != null
+ && content.startsWith("Answer using only the following context:")
+ && content.contains("Apache SkyWalking is an open-source Application Performance Management system")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @PostMapping("/v1/embeddings")
+ public Object embeddings(@RequestBody JSONObject request) {
+ Object input = request.get("input");
+ JSONArray inputs = input instanceof JSONArray ? (JSONArray) input : new JSONArray();
+ if (!(input instanceof JSONArray)) {
+ inputs.add(String.valueOf(input));
+ }
+
+ JSONArray data = new JSONArray();
+ for (int i = 0; i < inputs.size(); i++) {
+ JSONObject item = new JSONObject();
+ item.put("object", "embedding");
+ item.put("index", i);
+ item.put("embedding", embeddingFor(inputs.getString(i)));
+ data.add(item);
+ }
+
+ JSONObject usage = new JSONObject();
+ usage.put("prompt_tokens", inputs.size());
+ usage.put("total_tokens", inputs.size());
+
+ JSONObject response = new JSONObject();
+ response.put("object", "list");
+ response.put("model", "text-embedding-3-small");
+ response.put("data", data);
+ response.put("usage", usage);
+ return response;
+ }
+
+ private JSONArray embeddingFor(String input) {
+ String text = input == null ? "" : input.toLowerCase();
+ double[] values = new double[]{
+ score(text, "summit", "san francisco", "generative", "autonomous"),
+ score(text, "skywalking", "apm", "microservices", "cloud native"),
+ score(text, "spring ai", "models", "providers"),
+ score(text, "tracecontext", "tracing", "observability"),
+ 0.1
+ };
+
+ JSONArray embedding = new JSONArray();
+ for (double value : values) {
+ embedding.add(value);
+ }
+ return embedding;
+ }
+
+ private double score(String text, String... keywords) {
+ double value = 0.0;
+ for (String keyword : keywords) {
+ if (text.contains(keyword)) {
+ value += 1.0;
+ }
+ }
+ return value;
+ }
}
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml
index c4f5c58851..fceabe447e 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/src/main/resources/application.yaml
@@ -30,6 +30,9 @@ spring:
temperature: 0.7
max-tokens: 1000
top-p: 0.9
+ embedding:
+ options:
+ model: text-embedding-3-small
From d7721411c88371037936ff81db3382091038b922 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Sun, 31 May 2026 10:50:18 +0800
Subject: [PATCH 12/20] Fix
---
...ObservationVectorStoreConstructorInterceptor.java | 10 +++++++++-
...bstractObservationVectorStoreInstrumentation.java | 12 ++++++------
2 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
index 9db95ee33e..be5ea2c4ee 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
@@ -21,6 +21,7 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
import org.springframework.ai.embedding.EmbeddingOptions;
+import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
@@ -31,11 +32,18 @@ public class AbstractObservationVectorStoreConstructorInterceptor implements Ins
@Override
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
if (allArguments != null && allArguments.length > 0) {
- String embeddingModelName = resolveModelFromEmbeddingModel(allArguments[0]);
+ String embeddingModelName = resolveModelFromArgument(allArguments[0]);
objInst.setSkyWalkingDynamicField(new VectorStoreEnhanceContext(embeddingModelName));
}
}
+ private String resolveModelFromArgument(Object argument) {
+ if (argument instanceof EmbeddingModel) {
+ return resolveModelFromEmbeddingModel(argument);
+ }
+ return null;
+ }
+
private String resolveModelFromEmbeddingModel(Object embeddingModel) {
if (embeddingModel == null) {
return null;
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java
index 895205832c..f379c0464c 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/AbstractObservationVectorStoreInstrumentation.java
@@ -25,6 +25,8 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch;
+import org.apache.skywalking.apm.agent.core.plugin.match.MultiClassNameMatch;
+import org.apache.skywalking.apm.agent.core.plugin.match.logical.LogicalMatchOperation;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
@@ -34,8 +36,6 @@ public class AbstractObservationVectorStoreInstrumentation extends ClassInstance
private static final String ENHANCE_CLASS = "org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore";
- private static final String EMBEDDING_MODEL_CLASS = "org.springframework.ai.embedding.EmbeddingModel";
-
private static final String INTERCEPT_METHOD = "doSimilaritySearch";
private static final String INTERCEPTOR_CLASS =
@@ -46,16 +46,16 @@ public class AbstractObservationVectorStoreInstrumentation extends ClassInstance
@Override
protected ClassMatch enhanceClass() {
- return HierarchyMatch.byHierarchyMatch(ENHANCE_CLASS);
+ return LogicalMatchOperation.or(HierarchyMatch.byHierarchyMatch(ENHANCE_CLASS), MultiClassNameMatch.byMultiClassMatch(ENHANCE_CLASS));
}
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
- return new ConstructorInterceptPoint[] {
+ return new ConstructorInterceptPoint[]{
new ConstructorInterceptPoint() {
@Override
public ElementMatcher getConstructorMatcher() {
- return takesArguments(4).and(takesArgumentWithType(0, EMBEDDING_MODEL_CLASS));
+ return takesArgumentWithType(0, "org.springframework.ai.embedding.EmbeddingModel");
}
@Override
@@ -68,7 +68,7 @@ public String getConstructorInterceptor() {
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
- return new InstanceMethodsInterceptPoint[] {
+ return new InstanceMethodsInterceptPoint[]{
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher getMethodsMatcher() {
From 3b1acdceec8a70e561815006164745103517c204 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Sun, 31 May 2026 13:37:55 +0800
Subject: [PATCH 13/20] Fix e2e test
---
.../scenarios/spring-ai-1.x-scenario/config/expectedData.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml
index ba3713d433..d714ae2542 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/config/expectedData.yaml
@@ -153,7 +153,7 @@ segmentItems:
peer: simple/in-memory-map
tags:
- { key: gen_ai.operation.name, value: retrieval }
- - { key: gen_ai.data_source.id, value: in-memory-map }
+ - { key: gen_ai.data_source.id, value: simple/in-memory-map }
- { key: gen_ai.request.model, value: text-embedding-3-small }
- { key: gen_ai.request.top_k, value: '2' }
- { key: gen_ai.retrieval.query.text, value: 'What is Apache SkyWalking?' }
From e7923be8fcdf9260a614cfcbe7af40006c2938cf Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Mon, 1 Jun 2026 21:59:59 +0800
Subject: [PATCH 14/20] Fix suggestions
---
...tractObservationVectorStoreInterceptor.java | 18 ++++++++++++++++--
.../spring/ai/v1/common/ErrorTypeResolver.java | 6 +-----
2 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
index 8a34e695ef..5f18d5c2eb 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
@@ -46,10 +46,24 @@ public class AbstractObservationVectorStoreInterceptor implements InstanceMethod
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
SearchRequest request = (SearchRequest) allArguments[0];
- VectorStoreObservationContext context = createObservationContext(objInst, request);
- String dataSourceId = resolveDataSourceId(context, objInst);
+ String dataSourceId = objInst.getClass().getSimpleName();
+
+ try {
+ VectorStoreObservationContext context =
+ createObservationContext(objInst, request);
+
+ String resolved =
+ resolveDataSourceId(context, objInst);
+
+ if (StringUtils.hasText(resolved)) {
+ dataSourceId = resolved;
+ }
+ } catch (Throwable ignored) {
+
+ }
AbstractSpan span = ContextManager.createExitSpan(Constants.RETRIEVAL + "/" + dataSourceId, dataSourceId);
+
SpanLayer.asGenAI(span);
span.setComponent(ComponentsDefine.SPRING_AI);
Tags.GEN_AI_OPERATION_NAME.set(span, Constants.RETRIEVAL);
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
index f5b8e51e7a..e0b9c54c18 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
@@ -24,7 +24,6 @@
import javax.net.ssl.SSLHandshakeException;
import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.util.concurrent.TimeoutException;
@@ -47,13 +46,10 @@ private static String resolve(Throwable throwable) {
if (matches(throwable, ErrorTypeResolver::isTimeout)) {
return TIMEOUT;
}
- if (matches(throwable, UnknownHostException.class::isInstance)) {
- return UnknownHostException.class.getName();
- }
if (matches(throwable, ErrorTypeResolver::isCertificateInvalid)) {
return SERVER_CERTIFICATE_INVALID;
}
- return OTHER;
+ return throwable.getClass().getName();
}
private static boolean isTimeout(Throwable throwable) {
From c852d8ee26d06624d92e2578c69f4b9a713de741 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Mon, 1 Jun 2026 23:57:51 +0800
Subject: [PATCH 15/20] Fix suggestions
---
CHANGES.md | 1 +
...tionVectorStoreConstructorInterceptor.java | 65 +++-------------
.../ai/v1/EmbeddingModelEnhanceContext.java | 36 +++++++++
.../ai/v1/EmbeddingModelInterceptor.java | 65 ++++++++++++++++
.../ai/v1/VectorStoreEnhanceContext.java | 11 ++-
.../define/EmbeddingModelInstrumentation.java | 77 +++++++++++++++++++
6 files changed, 197 insertions(+), 58 deletions(-)
create mode 100644 apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelEnhanceContext.java
create mode 100644 apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java
create mode 100644 apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java
diff --git a/CHANGES.md b/CHANGES.md
index 3a9514fdc6..0bf6869120 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -25,6 +25,7 @@ Release Notes.
* Only publish `apm-application-toolkit` modules to Maven Central. Agent and plugins are distributed via download package and Docker images.
* Add unified release script (`tools/releasing/release.sh`) with two-step flow: `prepare-vote` and `vote-passed`.
* Fix an issue where `JDBCPluginConfig.Plugin.JDBC.SQL_BODY_MAX_LENGTH` was not honored by clickhouse-0.3.1 and clickhouse-0.3.2.x plugins.
+- Add tracing support for vector-store retrieval operations.
All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/249?closed=1)
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
index be5ea2c4ee..d1895750bb 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
@@ -20,74 +20,31 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
-import org.springframework.ai.embedding.EmbeddingOptions;
import org.springframework.ai.embedding.EmbeddingModel;
-import org.springframework.util.StringUtils;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
public class AbstractObservationVectorStoreConstructorInterceptor implements InstanceConstructorInterceptor {
@Override
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
if (allArguments != null && allArguments.length > 0) {
- String embeddingModelName = resolveModelFromArgument(allArguments[0]);
- objInst.setSkyWalkingDynamicField(new VectorStoreEnhanceContext(embeddingModelName));
- }
- }
-
- private String resolveModelFromArgument(Object argument) {
- if (argument instanceof EmbeddingModel) {
- return resolveModelFromEmbeddingModel(argument);
- }
- return null;
- }
-
- private String resolveModelFromEmbeddingModel(Object embeddingModel) {
- if (embeddingModel == null) {
- return null;
- }
- String model = resolveModelFromOptionsMethod(embeddingModel);
- if (StringUtils.hasText(model)) {
- return model;
+ objInst.setSkyWalkingDynamicField(new VectorStoreEnhanceContext(resolveContextFromArgument(allArguments[0])));
}
- model = resolveModelFromOptionsField(embeddingModel, "options");
- if (StringUtils.hasText(model)) {
- return model;
- }
- return resolveModelFromOptionsField(embeddingModel, "defaultOptions");
}
- private String resolveModelFromOptionsMethod(Object embeddingModel) {
- try {
- Method method = embeddingModel.getClass().getMethod("getOptions");
- return resolveModelFromOptions(method.invoke(embeddingModel));
- } catch (Throwable ignored) {
- return null;
- }
- }
-
- private String resolveModelFromOptionsField(Object embeddingModel, String fieldName) {
- Class> type = embeddingModel.getClass();
- while (type != null) {
- try {
- Field field = type.getDeclaredField(fieldName);
- field.setAccessible(true);
- return resolveModelFromOptions(field.get(embeddingModel));
- } catch (NoSuchFieldException e) {
- type = type.getSuperclass();
- } catch (Throwable ignored) {
- return null;
- }
+ private EmbeddingModelEnhanceContext resolveContextFromArgument(Object argument) {
+ if (argument instanceof EmbeddingModel && argument instanceof EnhancedInstance) {
+ return getOrCreateContext((EnhancedInstance) argument);
}
return null;
}
- private String resolveModelFromOptions(Object options) {
- if (options instanceof EmbeddingOptions) {
- return ((EmbeddingOptions) options).getModel();
+ private EmbeddingModelEnhanceContext getOrCreateContext(EnhancedInstance embeddingModel) {
+ Object context = embeddingModel.getSkyWalkingDynamicField();
+ if (context instanceof EmbeddingModelEnhanceContext) {
+ return (EmbeddingModelEnhanceContext) context;
}
- return null;
+ EmbeddingModelEnhanceContext embeddingModelEnhanceContext = new EmbeddingModelEnhanceContext();
+ embeddingModel.setSkyWalkingDynamicField(embeddingModelEnhanceContext);
+ return embeddingModelEnhanceContext;
}
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelEnhanceContext.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelEnhanceContext.java
new file mode 100644
index 0000000000..a077fea851
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelEnhanceContext.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1;
+
+import org.springframework.util.StringUtils;
+
+public class EmbeddingModelEnhanceContext {
+
+ private volatile String embeddingModelName;
+
+ public String getEmbeddingModelName() {
+ return embeddingModelName;
+ }
+
+ public void setEmbeddingModelNameIfAbsent(String embeddingModelName) {
+ if (!StringUtils.hasText(this.embeddingModelName) && StringUtils.hasText(embeddingModelName)) {
+ this.embeddingModelName = embeddingModelName;
+ }
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java
new file mode 100644
index 0000000000..cfc6d5da2a
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1;
+
+import java.lang.reflect.Method;
+
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.springframework.ai.embedding.EmbeddingOptions;
+import org.springframework.util.StringUtils;
+
+public class EmbeddingModelInterceptor implements InstanceMethodsAroundInterceptor {
+
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) {
+ if (allArguments == null || allArguments.length < 2 || !(allArguments[1] instanceof EmbeddingOptions)) {
+ return;
+ }
+ String model = ((EmbeddingOptions) allArguments[1]).getModel();
+ if (!StringUtils.hasText(model)) {
+ return;
+ }
+ EmbeddingModelEnhanceContext context = getOrCreateContext(objInst);
+ context.setEmbeddingModelNameIfAbsent(model);
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) {
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+ }
+
+ private EmbeddingModelEnhanceContext getOrCreateContext(EnhancedInstance objInst) {
+ Object context = objInst.getSkyWalkingDynamicField();
+ if (context instanceof EmbeddingModelEnhanceContext) {
+ return (EmbeddingModelEnhanceContext) context;
+ }
+ EmbeddingModelEnhanceContext embeddingModelEnhanceContext = new EmbeddingModelEnhanceContext();
+ objInst.setSkyWalkingDynamicField(embeddingModelEnhanceContext);
+ return embeddingModelEnhanceContext;
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java
index a478d07bd7..b0a7ff6a7a 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java
@@ -20,13 +20,16 @@
public class VectorStoreEnhanceContext {
- private final String embeddingModelName;
+ private final EmbeddingModelEnhanceContext embeddingModelEnhanceContext;
- public VectorStoreEnhanceContext(String embeddingModelName) {
- this.embeddingModelName = embeddingModelName;
+ public VectorStoreEnhanceContext(EmbeddingModelEnhanceContext embeddingModelEnhanceContext) {
+ this.embeddingModelEnhanceContext = embeddingModelEnhanceContext;
}
public String getEmbeddingModelName() {
- return embeddingModelName;
+ if (embeddingModelEnhanceContext == null) {
+ return null;
+ }
+ return embeddingModelEnhanceContext.getEmbeddingModelName();
}
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java
new file mode 100644
index 0000000000..8ad07907d6
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.apm.plugin.spring.ai.v1.define;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+import org.apache.skywalking.apm.agent.core.plugin.match.HierarchyMatch;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+import static org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ArgumentTypeNameMatch.takesArgumentWithType;
+
+public class EmbeddingModelInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
+
+ private static final String ENHANCE_CLASS = "org.springframework.ai.embedding.EmbeddingModel";
+
+ private static final String INTERCEPT_METHOD = "embed";
+
+ private static final String INTERCEPTOR_CLASS =
+ "org.apache.skywalking.apm.plugin.spring.ai.v1.EmbeddingModelInterceptor";
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return HierarchyMatch.byHierarchyMatch(ENHANCE_CLASS);
+ }
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[0];
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[]{
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named(INTERCEPT_METHOD)
+ .and(takesArguments(3))
+ .and(takesArgumentWithType(0, "java.util.List"))
+ .and(takesArgumentWithType(1, "org.springframework.ai.embedding.EmbeddingOptions"))
+ .and(takesArgumentWithType(2, "org.springframework.ai.embedding.BatchingStrategy"));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return INTERCEPTOR_CLASS;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ }
+ };
+ }
+}
From a453e4ed9df120265f8c5a55f9af1958a70d006c Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Mon, 1 Jun 2026 23:58:12 +0800
Subject: [PATCH 16/20] add plugin definition
---
.../src/main/resources/skywalking-plugin.def | 1 +
1 file changed, 1 insertion(+)
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def
index 6185c8b12f..62e1f6af3e 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/resources/skywalking-plugin.def
@@ -15,6 +15,7 @@
# limitations under the License.
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.ChatModelInstrumentation
+spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.EmbeddingModelInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.ToolCallbackInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.DefaultToolCallingManagerInstrumentation
spring-ai-1.x=org.apache.skywalking.apm.plugin.spring.ai.v1.define.AbstractObservationVectorStoreInstrumentation
From aeaf35a4b2452978a3c68de275bdd71c7d420fb6 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Tue, 2 Jun 2026 12:40:46 +0800
Subject: [PATCH 17/20] Fix EmbeddingModelInterceptor
---
.../ai/v1/EmbeddingModelInterceptor.java | 32 +++++++++----------
.../define/EmbeddingModelInstrumentation.java | 8 ++---
2 files changed, 19 insertions(+), 21 deletions(-)
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java
index cfc6d5da2a..d1767cc1bc 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java
@@ -18,39 +18,39 @@
package org.apache.skywalking.apm.plugin.spring.ai.v1;
-import java.lang.reflect.Method;
-
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
-import org.springframework.ai.embedding.EmbeddingOptions;
+import org.springframework.ai.embedding.EmbeddingResponse;
+import org.springframework.ai.embedding.EmbeddingResponseMetadata;
import org.springframework.util.StringUtils;
+import java.lang.reflect.Method;
+
public class EmbeddingModelInterceptor implements InstanceMethodsAroundInterceptor {
@Override
- public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
- MethodInterceptResult result) {
- if (allArguments == null || allArguments.length < 2 || !(allArguments[1] instanceof EmbeddingOptions)) {
- return;
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes, MethodInterceptResult result) {
+
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes, Object ret) {
+ EmbeddingResponseMetadata metadata = ((EmbeddingResponse) ret).getMetadata();
+ if (metadata == null) {
+ return ret;
}
- String model = ((EmbeddingOptions) allArguments[1]).getModel();
+ String model = metadata.getModel();
if (!StringUtils.hasText(model)) {
- return;
+ return ret;
}
EmbeddingModelEnhanceContext context = getOrCreateContext(objInst);
context.setEmbeddingModelNameIfAbsent(model);
- }
-
- @Override
- public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
- Object ret) {
return ret;
}
@Override
- public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
- Class>[] argumentsTypes, Throwable t) {
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes, Throwable t) {
}
private EmbeddingModelEnhanceContext getOrCreateContext(EnhancedInstance objInst) {
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java
index 8ad07907d6..12a327ae29 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/define/EmbeddingModelInstrumentation.java
@@ -34,7 +34,7 @@ public class EmbeddingModelInstrumentation extends ClassInstanceMethodsEnhancePl
private static final String ENHANCE_CLASS = "org.springframework.ai.embedding.EmbeddingModel";
- private static final String INTERCEPT_METHOD = "embed";
+ private static final String INTERCEPT_METHOD = "call";
private static final String INTERCEPTOR_CLASS =
"org.apache.skywalking.apm.plugin.spring.ai.v1.EmbeddingModelInterceptor";
@@ -56,10 +56,8 @@ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
@Override
public ElementMatcher getMethodsMatcher() {
return named(INTERCEPT_METHOD)
- .and(takesArguments(3))
- .and(takesArgumentWithType(0, "java.util.List"))
- .and(takesArgumentWithType(1, "org.springframework.ai.embedding.EmbeddingOptions"))
- .and(takesArgumentWithType(2, "org.springframework.ai.embedding.BatchingStrategy"));
+ .and(takesArguments(1))
+ .and(takesArgumentWithType(0, "org.springframework.ai.embedding.EmbeddingRequest"));
}
@Override
From e92817018576bba213af27e0e8c4b5087a3ebcc2 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Tue, 2 Jun 2026 12:42:54 +0800
Subject: [PATCH 18/20] remove redundant codes
---
...stractObservationVectorStoreConstructorInterceptor.java | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
index d1895750bb..55a0fee1ab 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
@@ -20,19 +20,16 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
-import org.springframework.ai.embedding.EmbeddingModel;
public class AbstractObservationVectorStoreConstructorInterceptor implements InstanceConstructorInterceptor {
@Override
public void onConstruct(EnhancedInstance objInst, Object[] allArguments) {
- if (allArguments != null && allArguments.length > 0) {
- objInst.setSkyWalkingDynamicField(new VectorStoreEnhanceContext(resolveContextFromArgument(allArguments[0])));
- }
+ objInst.setSkyWalkingDynamicField(new VectorStoreEnhanceContext(resolveContextFromArgument(allArguments[0])));
}
private EmbeddingModelEnhanceContext resolveContextFromArgument(Object argument) {
- if (argument instanceof EmbeddingModel && argument instanceof EnhancedInstance) {
+ if (argument instanceof EnhancedInstance) {
return getOrCreateContext((EnhancedInstance) argument);
}
return null;
From 2219fe7e8dbb0c3282112ed093463be53176baa3 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Tue, 2 Jun 2026 23:15:38 +0800
Subject: [PATCH 19/20] Fix suggestions
---
...vationVectorStoreConstructorInterceptor.java | 1 +
...stractObservationVectorStoreInterceptor.java | 17 +++++++++++++----
.../spring/ai/v1/EmbeddingModelInterceptor.java | 5 +++++
.../spring/ai/v1/VectorStoreEnhanceContext.java | 2 ++
.../EmbeddingModelEnhanceContext.java | 2 +-
.../spring/ai/v1/common/ErrorTypeResolver.java | 1 -
.../ai/v1/config/SpringAiPluginConfig.java | 17 +++++++++++++++++
apm-sniffer/config/agent.config | 10 +++++++++-
.../spring-ai-1.x-scenario/bin/startup.sh | 3 ++-
9 files changed, 50 insertions(+), 8 deletions(-)
rename apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/{ => common}/EmbeddingModelEnhanceContext.java (95%)
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
index 55a0fee1ab..faefa999bc 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreConstructorInterceptor.java
@@ -20,6 +20,7 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.common.EmbeddingModelEnhanceContext;
public class AbstractObservationVectorStoreConstructorInterceptor implements InstanceConstructorInterceptor {
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
index 5f18d5c2eb..4b278b6c87 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/AbstractObservationVectorStoreInterceptor.java
@@ -27,6 +27,8 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
import org.apache.skywalking.apm.agent.core.util.GsonUtil;
import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.common.ErrorTypeResolver;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.config.SpringAiPluginConfig;
import org.apache.skywalking.apm.plugin.spring.ai.v1.contant.Constants;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
@@ -75,8 +77,13 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr
if (request != null) {
Tags.GEN_AI_TOP_K.set(span, String.valueOf(request.getTopK()));
- if (StringUtils.hasText(request.getQuery())) {
- Tags.GEN_AI_RETRIEVAL_QUERY_TEXT.set(span, request.getQuery());
+ String query = request.getQuery();
+ if (StringUtils.hasText(query) && SpringAiPluginConfig.Plugin.SpringAi.COLLECT_RETRIEVAL_QUERY) {
+ int limit = SpringAiPluginConfig.Plugin.SpringAi.RETRIEVAL_QUERY_LENGTH_LIMIT;
+ if (limit > 0 && query.length() > limit) {
+ query = query.substring(0, limit);
+ }
+ Tags.GEN_AI_RETRIEVAL_QUERY_TEXT.set(span, query);
}
}
}
@@ -88,7 +95,7 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA
return ret;
}
try {
- if (ret instanceof List>) {
+ if (ret instanceof List> && SpringAiPluginConfig.Plugin.SpringAi.COLLECT_RETRIEVAL_DOCUMENTS) {
Tags.GEN_AI_RETRIEVAL_DOCUMENTS.set(ContextManager.activeSpan(), toDocumentsJson((List>) ret));
}
} finally {
@@ -101,7 +108,9 @@ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allA
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class>[] argumentsTypes, Throwable t) {
if (ContextManager.isActive()) {
- ContextManager.activeSpan().log(t);
+ AbstractSpan span = ContextManager.activeSpan();
+ span.log(t);
+ ErrorTypeResolver.setErrorType(span, t);
}
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java
index d1767cc1bc..8e2b57fae9 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelInterceptor.java
@@ -21,6 +21,7 @@
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.common.EmbeddingModelEnhanceContext;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.ai.embedding.EmbeddingResponseMetadata;
import org.springframework.util.StringUtils;
@@ -36,6 +37,10 @@ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allAr
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes, Object ret) {
+ if (!(ret instanceof EmbeddingResponse)) {
+ return ret;
+ }
+
EmbeddingResponseMetadata metadata = ((EmbeddingResponse) ret).getMetadata();
if (metadata == null) {
return ret;
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java
index b0a7ff6a7a..76916a59ea 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/VectorStoreEnhanceContext.java
@@ -18,6 +18,8 @@
package org.apache.skywalking.apm.plugin.spring.ai.v1;
+import org.apache.skywalking.apm.plugin.spring.ai.v1.common.EmbeddingModelEnhanceContext;
+
public class VectorStoreEnhanceContext {
private final EmbeddingModelEnhanceContext embeddingModelEnhanceContext;
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelEnhanceContext.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/EmbeddingModelEnhanceContext.java
similarity index 95%
rename from apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelEnhanceContext.java
rename to apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/EmbeddingModelEnhanceContext.java
index a077fea851..199bd97cd4 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/EmbeddingModelEnhanceContext.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/EmbeddingModelEnhanceContext.java
@@ -16,7 +16,7 @@
*
*/
-package org.apache.skywalking.apm.plugin.spring.ai.v1;
+package org.apache.skywalking.apm.plugin.spring.ai.v1.common;
import org.springframework.util.StringUtils;
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
index e0b9c54c18..1d3172cf3d 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/common/ErrorTypeResolver.java
@@ -33,7 +33,6 @@ public final class ErrorTypeResolver {
private static final AbstractTag ERROR_TYPE = Tags.ofKey("error.type");
private static final String TIMEOUT = "timeout";
private static final String SERVER_CERTIFICATE_INVALID = "server_certificate_invalid";
- private static final String OTHER = "_OTHER";
private ErrorTypeResolver() {
}
diff --git a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/config/SpringAiPluginConfig.java b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/config/SpringAiPluginConfig.java
index d2d5eef6f6..6a348c622b 100644
--- a/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/config/SpringAiPluginConfig.java
+++ b/apm-sniffer/apm-sdk-plugin/spring-plugins/spring-ai-1.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/spring/ai/v1/config/SpringAiPluginConfig.java
@@ -69,6 +69,23 @@ public static class SpringAi {
* Whether to collect the execution result (output) of the tool/function call.
*/
public static boolean COLLECT_TOOL_OUTPUT = false;
+
+ /**
+ * Whether to collect the query of the rag call.
+ */
+ public static boolean COLLECT_RETRIEVAL_QUERY = false;
+
+ /**
+ * The maximum characters of the collected rag query content.
+ * If the content exceeds this limit, it will be truncated.
+ * Use a negative value to represent no limit, but be aware this could cause OOM.
+ */
+ public static int RETRIEVAL_QUERY_LENGTH_LIMIT = 1024;
+
+ /**
+ * Whether to collect the documents of the rag call.
+ */
+ public static boolean COLLECT_RETRIEVAL_DOCUMENTS = false;
}
}
}
diff --git a/apm-sniffer/config/agent.config b/apm-sniffer/config/agent.config
index 38c9bbf8d5..146d0d94fc 100755
--- a/apm-sniffer/config/agent.config
+++ b/apm-sniffer/config/agent.config
@@ -362,4 +362,12 @@ plugin.springai.content_collect_threshold_tokens=${SW_PLUGIN_SPRINGAI_CONTENT_CO
# Whether to collect the arguments (input parameters) of the tool/function call.
plugin.springai.collect_tool_input=${SW_PLUGIN_SPRINGAI_COLLECT_TOOL_INPUT:false}
# Whether to collect the execution result (output) of the tool/function call.
-plugin.springai.collect_tool_output=${SW_PLUGIN_SPRINGAI_COLLECT_TOOL_OUTPUT:false}
\ No newline at end of file
+plugin.springai.collect_tool_output=${SW_PLUGIN_SPRINGAI_COLLECT_TOOL_OUTPUT:false}
+# Whether to collect the query of the rag call.
+plugin.springai.collect_retrieval_query=${SW_PLUGIN_SPRINGAI_COLLECT_RETRIEVAL_QUERY:false}
+# The maximum characters of the collected rag query.
+# If the content exceeds this limit, it will be truncated.
+# Use a negative value to represent no limit, but be aware this could cause OOM.
+plugin.springai.retrieval_query=${SW_PLUGIN_SPRINGAI_RETRIEVAL_QUERY_LENGTH_LIMIT:1024}
+# Whether to collect the documents of the rag call.
+plugin.springai.collect_retrieval_documents=${SW_PLUGIN_SPRINGAI_COLLECT_RETRIEVAL_DOCUMENTS:false}
\ No newline at end of file
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh b/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh
index 8cb423ece2..43e2734fd7 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh
@@ -18,4 +18,5 @@
home="$(cd "$(dirname $0)"; pwd)"
-java -Dskywalking.plugin.springai.collect_input_messages=true -Dskywalking.plugin.springai.collect_output_messages=true -Dskywalking.plugin.springai.collect_tool_input=true -Dskywalking.plugin.springai.collect_tool_output=true -jar ${agent_opts} ${home}/../libs/spring-ai-1.x-scenario.jar &
\ No newline at end of file
+java -Dskywalking.plugin.springai.collect_input_messages=true -Dskywalking.plugin.springai.collect_output_messages=true -Dskywalking.plugin.springai.collect_tool_input=true -Dskywalking.plugin.springai.collect_tool_output=true
+-Dskywalking.plugin.springai.collect_retrieval_query=true -Dskywalking.plugin.springai.collect_retrieval_documents=true -jar ${agent_opts} ${home}/../libs/spring-ai-1.x-scenario.jar &
\ No newline at end of file
From 9874bd447b3123d1758c3054269a7756da1fd613 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Wed, 3 Jun 2026 00:08:05 +0800
Subject: [PATCH 20/20] remove space
---
test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh b/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh
index 43e2734fd7..9950f6fed6 100644
--- a/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh
+++ b/test/plugin/scenarios/spring-ai-1.x-scenario/bin/startup.sh
@@ -18,5 +18,4 @@
home="$(cd "$(dirname $0)"; pwd)"
-java -Dskywalking.plugin.springai.collect_input_messages=true -Dskywalking.plugin.springai.collect_output_messages=true -Dskywalking.plugin.springai.collect_tool_input=true -Dskywalking.plugin.springai.collect_tool_output=true
--Dskywalking.plugin.springai.collect_retrieval_query=true -Dskywalking.plugin.springai.collect_retrieval_documents=true -jar ${agent_opts} ${home}/../libs/spring-ai-1.x-scenario.jar &
\ No newline at end of file
+java -Dskywalking.plugin.springai.collect_input_messages=true -Dskywalking.plugin.springai.collect_output_messages=true -Dskywalking.plugin.springai.collect_tool_input=true -Dskywalking.plugin.springai.collect_tool_output=true -Dskywalking.plugin.springai.collect_retrieval_query=true -Dskywalking.plugin.springai.collect_retrieval_documents=true -jar ${agent_opts} ${home}/../libs/spring-ai-1.x-scenario.jar &
\ No newline at end of file