Skip to content

Commit f79451d

Browse files
authored
Support virtual gen ai analysis for otlp and zipkin traces (#13767)
1 parent 2da8c5b commit f79451d

23 files changed

Lines changed: 693 additions & 88 deletions

File tree

.github/workflows/skywalking.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,8 @@ jobs:
635635
config: test/e2e-v2/cases/zipkin/banyandb/e2e.yaml
636636
- name: Virtual GenAI
637637
config: test/e2e-v2/cases/virtual-genai/e2e.yaml
638+
- name: OTLP Virtual GenAI
639+
config: test/e2e-v2/cases/otlp-virtual-genai/e2e.yaml
638640

639641
- name: Nginx
640642
config: test/e2e-v2/cases/nginx/e2e.yaml

docs/en/changes/changes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
precedence over the `service.name` fallback.
177177
* OTel log handler: prefer `service.instance.id` (OTel spec) over `service.instance` with fallback.
178178
* Add `SampleFamily.debugDump()` for MAL debugging.
179+
* Support virtual GenAI analysis for otlp and zipkin traces.
179180

180181
#### UI
181182
* Fix the missing icon in new native trace view.

docs/en/setup/service-agent/virtual-genai.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ metrics of the GenAI operations are from the GenAI client-side perspective.
66
For example, a Spring AI plugin in the Java agent could detect the latency of a chat completion request.
77
As a result, SkyWalking would show traffic, latency, success rate, token usage (input/output), and estimated cost in the GenAI dashboard.
88

9+
# Data Sources
10+
Virtual GenAI metrics are derived from distributed tracing data. SkyWalking OAP can ingest and analyze trace data adhering to GenAI semantic conventions from the following sources:
11+
12+
1. Native SkyWalking Traces via SkyWalking Java Agent
13+
2. OpenTelemetry format trace
14+
3. Zipkin format Traces
15+
916
## Span Contract
1017

1118
The GenAI operation span should have the following properties:
@@ -60,4 +67,19 @@ The following metrics are available at the **model** (service instance) level:
6067
- `gen_ai_model_total_estimated_cost / avg_estimated_cost` - Estimated cost
6168

6269
## Requirement
63-
`SkyWalking Java Agent` version >= 9.7
70+
71+
### Version
72+
`SkyWalking Java Agent` version >= 9.7
73+
74+
### Semantic Conventions and Compatibility
75+
The tag keys used in Virtual GenAI follow the **OpenTelemetry GenAI Semantic Conventions**. SkyWalking OAP identifies GenAI-related spans based on the following criteria depending on the data source:
76+
77+
* **SkyWalking Native Agent**: Requires an **Exit** span with `SpanLayer == GENAI` and relevant `gen_ai.*` tags.
78+
* **OTLP / Zipkin Traces**: Any span containing the `gen_ai.response.model` tag will be identified as a GenAI operation.
79+
80+
**Note on OTLP / Zipkin Provider Identification**:
81+
To ensure broad compatibility with different OpenTelemetry instrumentation versions, SkyWalking OAP identifies the GenAI provider using the following prioritized logic:
82+
83+
1. **`gen_ai.provider.name`**: SkyWalking first looks for this tag (the latest OTel semantic convention).
84+
2. **`gen_ai.system`**: If the above is missing, it falls back to this legacy tag for backward compatibility with older instrumentation (e.g., current OTel Python auto-instrumentation).
85+
3. **Prefix Matching**: If neither tag is present, SkyWalking attempts to identify the provider by matching the model name against the `prefix-match` rules defined in the `gen-ai-config.yml`.

oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/VirtualServiceAnalysisListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public AnalysisListener create(ModuleManager moduleManager, AnalyzerModuleConfig
9595
new VirtualCacheProcessor(namingControl, config),
9696
new VirtualDatabaseProcessor(namingControl, config),
9797
new VirtualMQProcessor(namingControl),
98-
new VirtualGenAIProcessor(namingControl, genAIMeterAnalyzerService)
98+
new VirtualGenAIProcessor(genAIMeterAnalyzerService)
9999
)
100100
);
101101
}

oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/vservice/VirtualGenAIProcessor.java

Lines changed: 1 addition & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,7 @@
2222
import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer;
2323
import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
2424
import org.apache.skywalking.oap.analyzer.genai.service.IGenAIMeterAnalyzerService;
25-
import org.apache.skywalking.oap.server.core.analysis.Layer;
26-
import org.apache.skywalking.oap.server.core.config.NamingControl;
2725
import org.apache.skywalking.oap.server.core.source.GenAIMetrics;
28-
import org.apache.skywalking.oap.server.core.source.GenAIModelAccess;
29-
import org.apache.skywalking.oap.server.core.source.GenAIProviderAccess;
30-
import org.apache.skywalking.oap.server.core.source.ServiceInstance;
31-
import org.apache.skywalking.oap.server.core.source.ServiceMeta;
3226
import org.apache.skywalking.oap.server.core.source.Source;
3327

3428
import java.util.ArrayList;
@@ -38,8 +32,6 @@
3832
@RequiredArgsConstructor
3933
public class VirtualGenAIProcessor implements VirtualServiceProcessor {
4034

41-
private final NamingControl namingControl;
42-
4335
private final IGenAIMeterAnalyzerService meterAnalyzerService;
4436

4537
private final List<Source> recordList = new ArrayList<>();
@@ -55,53 +47,7 @@ public void prepareVSIfNecessary(SpanObject span, SegmentObject segmentObject) {
5547
return;
5648
}
5749

58-
recordList.add(toServiceMeta(metrics));
59-
recordList.add(toInstance(metrics));
60-
recordList.add(toProviderAccess(metrics));
61-
recordList.add(toModelAccess(metrics));
62-
}
63-
64-
private ServiceMeta toServiceMeta(GenAIMetrics metrics) {
65-
ServiceMeta service = new ServiceMeta();
66-
service.setName(namingControl.formatServiceName(metrics.getProviderName()));
67-
service.setLayer(Layer.VIRTUAL_GENAI);
68-
service.setTimeBucket(metrics.getTimeBucket());
69-
return service;
70-
}
71-
72-
private Source toInstance(GenAIMetrics metrics) {
73-
ServiceInstance instance = new ServiceInstance();
74-
instance.setTimeBucket(metrics.getTimeBucket());
75-
instance.setName(namingControl.formatInstanceName(metrics.getModelName()));
76-
instance.setServiceLayer(Layer.VIRTUAL_GENAI);
77-
instance.setServiceName(metrics.getProviderName());
78-
return instance;
79-
}
80-
81-
private GenAIProviderAccess toProviderAccess(GenAIMetrics metrics) {
82-
GenAIProviderAccess source = new GenAIProviderAccess();
83-
source.setName(namingControl.formatServiceName(metrics.getProviderName()));
84-
source.setInputTokens(metrics.getInputTokens());
85-
source.setOutputTokens(metrics.getOutputTokens());
86-
source.setTotalEstimatedCost(metrics.getTotalEstimatedCost());
87-
source.setLatency(metrics.getLatency());
88-
source.setStatus(metrics.isStatus());
89-
source.setTimeBucket(metrics.getTimeBucket());
90-
return source;
91-
}
92-
93-
private GenAIModelAccess toModelAccess(GenAIMetrics metrics) {
94-
GenAIModelAccess source = new GenAIModelAccess();
95-
source.setServiceName(namingControl.formatServiceName(metrics.getProviderName()));
96-
source.setModelName(namingControl.formatInstanceName(metrics.getModelName()));
97-
source.setInputTokens(metrics.getInputTokens());
98-
source.setOutputTokens(metrics.getOutputTokens());
99-
source.setTotalEstimatedCost(metrics.getTotalEstimatedCost());
100-
source.setTimeToFirstToken(metrics.getTimeToFirstToken());
101-
source.setLatency(metrics.getLatency());
102-
source.setStatus(metrics.isStatus());
103-
source.setTimeBucket(metrics.getTimeBucket());
104-
return source;
50+
recordList.addAll(meterAnalyzerService.transferToSources(metrics));
10551
}
10652

10753
@Override

oap-server/analyzer/gen-ai-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/genai/GenAIAnalyzerModuleProvider.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.skywalking.oap.analyzer.genai.service.GenAIMeterAnalyzer;
2727
import org.apache.skywalking.oap.analyzer.genai.service.IGenAIMeterAnalyzerService;
2828
import org.apache.skywalking.oap.server.core.CoreModule;
29+
import org.apache.skywalking.oap.server.core.config.NamingControl;
2930
import org.apache.skywalking.oap.server.core.oal.rt.OALEngineLoaderService;
3031
import org.apache.skywalking.oap.server.library.module.ModuleConfig;
3132
import org.apache.skywalking.oap.server.library.module.ModuleDefine;
@@ -37,6 +38,8 @@ public class GenAIAnalyzerModuleProvider extends ModuleProvider {
3738

3839
private GenAIConfig config;
3940

41+
private GenAIMeterAnalyzer analyzer;
42+
4043
@Override
4144
public String name() {
4245
return "default";
@@ -67,10 +70,11 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
6770
GenAIConfigLoader loader = new GenAIConfigLoader(config);
6871
config = loader.loadConfig();
6972
GenAIProviderPrefixMatcher matcher = GenAIProviderPrefixMatcher.build();
73+
74+
this.analyzer = new GenAIMeterAnalyzer(matcher);
7075
this.registerServiceImplementation(
7176
IGenAIMeterAnalyzerService.class,
72-
new GenAIMeterAnalyzer(matcher)
73-
);
77+
analyzer);
7478
}
7579

7680
@Override
@@ -79,6 +83,12 @@ public void start() throws ServiceNotProvidedException, ModuleStartException {
7983
.provider()
8084
.getService(OALEngineLoaderService.class)
8185
.load(GenAIOALDefine.INSTANCE);
86+
87+
NamingControl namingControl = getManager().find(CoreModule.NAME)
88+
.provider()
89+
.getService(NamingControl.class);
90+
91+
this.analyzer.setNamingControl(namingControl);
8292
}
8393

8494
@Override
@@ -88,7 +98,7 @@ public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleSta
8898

8999
@Override
90100
public String[] requiredModules() {
91-
return new String[] {
101+
return new String[]{
92102
CoreModule.NAME
93103
};
94104
}

oap-server/analyzer/gen-ai-analyzer/src/main/java/org/apache/skywalking/oap/analyzer/genai/config/GenAITagKeys.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ public class GenAITagKeys {
2525
public static final String INPUT_TOKENS = "gen_ai.usage.input_tokens";
2626
public static final String OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
2727
public static final String SERVER_TIME_TO_FIRST_TOKEN = "gen_ai.server.time_to_first_token";
28+
29+
public static final String ESTIMATED_COST = "gen_ai.estimated.cost";
30+
31+
public static final String SYSTEM_NAME = "gen_ai.system";
2832
}

0 commit comments

Comments
 (0)