tags) {
+ try {
+ LogTags logTags = LogTags.parseFrom(dataBinary);
+ logTags.getDataList().forEach(pair -> tags.add(new KeyValue(pair.getKey(), pair.getValue())));
+ } catch (InvalidProtocolBufferException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/trace/OTLPSpanReader.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/trace/OTLPSpanReader.java
index 80731cfbe3ff..b625afb35e53 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/trace/OTLPSpanReader.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/trace/OTLPSpanReader.java
@@ -25,6 +25,16 @@
* Implementations in the OTLP receiver module wrap the real proto object.
*/
public interface OTLPSpanReader {
+ /**
+ * @return the trace id encoded as lowercase hex
+ */
+ String traceId();
+
+ /**
+ * @return the span id encoded as lowercase hex
+ */
+ String spanId();
+
/**
* @return the span name (e.g., "HTTP GET", "MXMetricPayload")
*/
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java
index 390d7ab3a348..7e95f09c5baa 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/GraphQLQueryProvider.java
@@ -34,6 +34,7 @@
import org.apache.skywalking.oap.query.graphql.resolver.EBPFProcessProfilingMutation;
import org.apache.skywalking.oap.query.graphql.resolver.EBPFProcessProfilingQuery;
import org.apache.skywalking.oap.query.graphql.resolver.EventQuery;
+import org.apache.skywalking.oap.query.graphql.resolver.GenAIEvaluationRecordQuery;
import org.apache.skywalking.oap.query.graphql.resolver.HealthQuery;
import org.apache.skywalking.oap.query.graphql.resolver.HierarchyQuery;
import org.apache.skywalking.oap.query.graphql.resolver.LogQuery;
@@ -136,6 +137,8 @@ public void prepare() throws ServiceNotProvidedException {
new LogQuery(getManager()),
new LogTestQuery(getManager(), config)
)
+ .file("query-protocol/gen-ai-evaluation-record.graphqls")
+ .resolvers(new GenAIEvaluationRecordQuery(getManager()))
.file("query-protocol/profile.graphqls")
.resolvers(new ProfileQuery(getManager()), new ProfileMutation(getManager()))
.file("query-protocol/browser-log.graphqls")
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
new file mode 100644
index 000000000000..53acca440bb1
--- /dev/null
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
@@ -0,0 +1,110 @@
+/*
+ * 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.oap.query.graphql.resolver;
+
+import graphql.kickstart.tools.GraphQLQueryResolver;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.UnexpectedException;
+import org.apache.skywalking.oap.server.core.query.GenAIEvaluationRecordQueryService;
+import org.apache.skywalking.oap.server.core.query.enumeration.Order;
+import org.apache.skywalking.oap.server.core.query.input.GenAIEvaluationRecordQueryCondition;
+import org.apache.skywalking.oap.server.core.query.type.GenAIEvaluationRecords;
+import org.apache.skywalking.oap.server.core.query.type.debugging.DebuggingSpan;
+import org.apache.skywalking.oap.server.core.query.type.debugging.DebuggingTraceContext;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
+
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+
+import static java.util.Objects.isNull;
+import static org.apache.skywalking.oap.query.graphql.AsyncQueryUtils.queryAsync;
+import static org.apache.skywalking.oap.server.core.query.type.debugging.DebuggingTraceContext.TRACE_CONTEXT;
+
+public class GenAIEvaluationRecordQuery implements GraphQLQueryResolver {
+ private final ModuleManager moduleManager;
+ private GenAIEvaluationRecordQueryService genAIEvaluationRecordQueryService;
+
+ public GenAIEvaluationRecordQuery(ModuleManager moduleManager) {
+ this.moduleManager = moduleManager;
+ }
+
+ public boolean supportGenAIEvaluationRecordQueryByKeywords() {
+ return getQueryService().supportQueryGenAIEvaluationRecordByKeywords();
+ }
+
+ private GenAIEvaluationRecordQueryService getQueryService() {
+ if (genAIEvaluationRecordQueryService == null) {
+ this.genAIEvaluationRecordQueryService = moduleManager.find(CoreModule.NAME).provider().getService(GenAIEvaluationRecordQueryService.class);
+ }
+ return genAIEvaluationRecordQueryService;
+ }
+
+ public CompletableFuture queryGenAIEvaluationRecord(
+ GenAIEvaluationRecordQueryCondition condition, boolean debug) {
+ return queryAsync(() -> {
+ DebuggingTraceContext traceContext = new DebuggingTraceContext(
+ "GenAIEvaluationRecordCondition: " + condition, debug, false);
+ DebuggingTraceContext.TRACE_CONTEXT.set(traceContext);
+ DebuggingSpan span = traceContext.createSpan("Query gen AI evaluation records");
+ try {
+ GenAIEvaluationRecords evaluationRecords = queryGenAIEvaluationRecord(condition);
+ if (debug) {
+ evaluationRecords.setDebuggingTrace(traceContext.getExecTrace());
+ }
+ return evaluationRecords;
+ } finally {
+ traceContext.stopSpan(span);
+ traceContext.stopTrace();
+ TRACE_CONTEXT.remove();
+ }
+ });
+ }
+
+ private GenAIEvaluationRecords queryGenAIEvaluationRecord(
+ GenAIEvaluationRecordQueryCondition condition) throws IOException {
+ if (isNull(condition.getQueryDuration()) && isNull(condition.getRelatedTrace())) {
+ throw new UnexpectedException("The condition must contains either queryDuration or relatedTrace.");
+ }
+
+ Order queryOrder = isNull(condition.getQueryOrder()) ? Order.DES : condition.getQueryOrder();
+ if (CollectionUtils.isNotEmpty(condition.getTags())) {
+ condition.getTags().forEach(tag -> {
+ if (tag != null) {
+ if (StringUtil.isNotEmpty(tag.getKey())) {
+ tag.setKey(tag.getKey().trim());
+ }
+ if (StringUtil.isNotEmpty(tag.getValue())) {
+ tag.setValue(tag.getValue().trim());
+ }
+ }
+ });
+ }
+ return getQueryService().queryGenAIEvaluationRecord(
+ condition.getServiceId(),
+ condition.getServiceInstanceId(),
+ condition.getRelatedTrace(),
+ condition.getPaging(),
+ queryOrder,
+ condition.getQueryDuration(),
+ condition.getTags()
+ );
+ }
+}
diff --git a/oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OTLPSpanReaderImpl.java b/oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OTLPSpanReaderImpl.java
index 91f926e24ed0..d77c41600ac5 100644
--- a/oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OTLPSpanReaderImpl.java
+++ b/oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OTLPSpanReaderImpl.java
@@ -17,21 +17,35 @@
package org.apache.skywalking.oap.server.receiver.otel.otlp;
+import com.google.protobuf.ByteString;
import io.opentelemetry.proto.common.v1.AnyValue;
import io.opentelemetry.proto.common.v1.KeyValue;
import io.opentelemetry.proto.trace.v1.Span;
import org.apache.skywalking.oap.server.core.trace.OTLPSpanReader;
+import java.math.BigInteger;
+
/**
* {@link OTLPSpanReader} implementation wrapping the real OTLP {@link Span} proto.
*/
public class OTLPSpanReaderImpl implements OTLPSpanReader {
+
private final Span span;
public OTLPSpanReaderImpl(final Span span) {
this.span = span;
}
+ @Override
+ public String traceId() {
+ return idToHexString(span.getTraceId());
+ }
+
+ @Override
+ public String spanId() {
+ return idToHexString(span.getSpanId());
+ }
+
@Override
public String spanName() {
return span.getName();
@@ -77,4 +91,11 @@ private static String convertValue(final AnyValue value) {
}
return "";
}
+
+ private String idToHexString(ByteString id) {
+ if (id == null) {
+ return "";
+ }
+ return new BigInteger(1, id.toByteArray()).toString();
+ }
}
diff --git a/oap-server/server-starter/src/main/resources/ai-evaluation.yml b/oap-server/server-starter/src/main/resources/ai-evaluation.yml
new file mode 100644
index 000000000000..69d2b85afa94
--- /dev/null
+++ b/oap-server/server-starter/src/main/resources/ai-evaluation.yml
@@ -0,0 +1,108 @@
+# 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.
+
+# AI evaluation runtime configuration. `application.yml` only enables/disables
+# the ai-evaluation module; judge model and tasks are maintained here.
+
+judge:
+ provider: openai
+ endpoint: ${AI_EVALUATION_ENDPOINT:}
+ model: ${AI_EVALUATION_MODEL:gpt-5.5}
+ api-key: ${AI_EVALUATION_API_KEY:}
+ request-timeout-seconds: 30
+ temperature: 0.2
+ max_tokens: 100000
+
+level:
+ undefined: undefined
+ score:
+ - min: 0.0
+ max: 0.3
+ level: fail
+ - min: 0.3
+ max: 0.6
+ level: warning
+ - min: 0.6
+ max: 0.85
+ level: good
+ - min: 0.85
+ max: 1.0
+ level: excellent
+ boolean:
+ true: excellent
+ false: fail
+
+system-prompt: |
+ You are an AI observability evaluator.
+
+ Your task is to evaluate GenAI spans according to the evaluation tasks provided by the user.
+
+ General Rules:
+
+ 1. Return ONLY valid JSON.
+ 2. Do NOT return markdown.
+ 3. Do NOT return code fences.
+ 4. Do NOT return explanations outside the JSON result.
+ 5. Do NOT add any fields that are not requested.
+ 6. Do NOT omit any evaluation task.
+ 7. The JSON keys MUST appear in exactly the same order as the evaluation tasks provided by the user.
+ 8. Each evaluation task MUST contain:
+ - value
+ - reason
+ 9. The reason field should be concise and explain the evaluation result in one or two sentences.
+ 10. If the available context is insufficient, provide the best evaluation possible and explain the limitation in the reason field.
+
+ Value Type Rules:
+
+ SCORE
+ - value must be a number between 0.0 and 1.0.
+ - 1.0 represents the best possible evaluation result.
+ - 0.0 represents the worst possible evaluation result.
+
+ BOOLEAN
+ - value must be either true or false.
+
+ STRING
+ - value must be a string.
+
+ JSON
+ - value must be a valid JSON object.
+
+ Evaluation Principles:
+
+ - Evaluate only using the information provided in the span context.
+ - Do not assume facts that are not present in the input.
+ - Do not invent missing information.
+ - Be objective and consistent.
+ - Use the task instruction as the primary evaluation criterion.
+
+ Return ONLY the JSON object.
+
+tasks:
+ - name: Faithfulness
+ valueType: SCORE
+ instruction: Evaluate factual grounding.
+
+ - name: Relevance
+ valueType: SCORE
+ instruction: Evaluate response relevance.
+
+ - name: TaskCompletion
+ valueType: SCORE
+ instruction: Evaluate task completion.
+
+ - name: Hallucination
+ valueType: SCORE
+ instruction: Evaluate hallucination severity.
diff --git a/oap-server/server-starter/src/main/resources/application.yml b/oap-server/server-starter/src/main/resources/application.yml
index 9fa3eeb31525..3b603a808f31 100644
--- a/oap-server/server-starter/src/main/resources/application.yml
+++ b/oap-server/server-starter/src/main/resources/application.yml
@@ -253,6 +253,11 @@ gen-ai-analyzer:
selector: ${SW_GENAI_ANALYZER:default}
default:
+ai-evaluation:
+ selector: ${SW_AI_EVALUATION:default}
+ default:
+ sampleRate: ${SW_AI_EVALUTION_SAMPLE_RATE:1000000}
+
receiver-sharing-server:
selector: ${SW_RECEIVER_SHARING_SERVER:default}
default:
diff --git a/oap-server/server-starter/src/main/resources/gen-ai-evaluation-rules/gen-ai-model.yaml b/oap-server/server-starter/src/main/resources/gen-ai-evaluation-rules/gen-ai-model.yaml
new file mode 100644
index 000000000000..0031bbcc7b83
--- /dev/null
+++ b/oap-server/server-starter/src/main/resources/gen-ai-evaluation-rules/gen-ai-model.yaml
@@ -0,0 +1,21 @@
+# 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.
+
+metricPrefix: gen_ai_model_evaluation
+expSuffix: instance(['provider_name'], ['model_name'], Layer.VIRTUAL_GENAI)
+metricsRules:
+ # Scores are scaled by 1,000,000 before entering MAL because labeled meter values are stored as long values.
+ - name: score_ppm
+ exp: gen_ai_model_evaluation_score_ppm.avg(['provider_name', 'model_name', 'task_name'])
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
index dbb2b7f3dffc..5c3e07e54f88 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
@@ -56,6 +56,7 @@
import org.apache.skywalking.oap.server.core.storage.query.IAlarmQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IBrowserLogQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IEventQueryDAO;
+import org.apache.skywalking.oap.server.core.storage.query.IGenAIEvaluationRecordQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IHierarchyQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.ILogQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IMetadataQueryDAO;
@@ -75,6 +76,7 @@
import org.apache.skywalking.oap.server.library.util.StringUtil;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.measure.BanyanDBEBPFProfilingScheduleQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.stream.BanyanDBEventQueryDAO;
+import org.apache.skywalking.oap.server.storage.plugin.banyandb.stream.BanyanDBGenAIGenAIEvaluationRecordQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.measure.BanyanDBHierarchyQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.measure.BanyanDBMetadataQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.measure.BanyanDBMetricsQueryDAO;
@@ -175,6 +177,8 @@ IBatchDAO.class, new BanyanDBBatchDAO(client, config.getGlobal().getMaxBulkSize(
this.registerServiceImplementation(IMetadataQueryDAO.class, new BanyanDBMetadataQueryDAO(client, this.config));
this.registerServiceImplementation(IAlarmQueryDAO.class, new BanyanDBAlarmQueryDAO(client));
this.registerServiceImplementation(ILogQueryDAO.class, new BanyanDBLogQueryDAO(client));
+ this.registerServiceImplementation(
+ IGenAIEvaluationRecordQueryDAO.class, new BanyanDBGenAIGenAIEvaluationRecordQueryDAO(client));
this.registerServiceImplementation(
IProfileTaskQueryDAO.class, new BanyanDBProfileTaskQueryDAO(client,
this.config.getGlobal().getProfileTaskQueryMaxSize()
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIGenAIEvaluationRecordQueryDAO.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIGenAIEvaluationRecordQueryDAO.java
new file mode 100644
index 000000000000..d0c3cadfaa0a
--- /dev/null
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIGenAIEvaluationRecordQueryDAO.java
@@ -0,0 +1,135 @@
+/*
+ * 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.oap.server.storage.plugin.banyandb.stream;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.skywalking.library.banyandb.v1.client.AbstractQuery;
+import org.apache.skywalking.library.banyandb.v1.client.RowEntity;
+import org.apache.skywalking.library.banyandb.v1.client.StreamQuery;
+import org.apache.skywalking.library.banyandb.v1.client.StreamQueryResponse;
+import org.apache.skywalking.oap.server.core.analysis.manual.genai.GenAIEvaluationRecord;
+import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
+import org.apache.skywalking.oap.server.core.query.enumeration.Order;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.TraceScopeCondition;
+import org.apache.skywalking.oap.server.core.query.type.GenAIEvaluationRecords;
+import org.apache.skywalking.oap.server.core.storage.query.IGenAIEvaluationRecordQueryDAO;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
+import org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageClient;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * {@link org.apache.skywalking.oap.server.core.analysis.manual.log.LogRecord} is a gen-ai evaluation result
+ */
+public class BanyanDBGenAIGenAIEvaluationRecordQueryDAO extends AbstractBanyanDBDAO implements IGenAIEvaluationRecordQueryDAO {
+ private static final Set TAGS = ImmutableSet.of(
+ GenAIEvaluationRecord.TRACE_ID,
+ GenAIEvaluationRecord.SERVICE_ID,
+ GenAIEvaluationRecord.SERVICE_INSTANCE_ID,
+ GenAIEvaluationRecord.SEGMENT_ID,
+ GenAIEvaluationRecord.SPAN_ID,
+ GenAIEvaluationRecord.SPAN_TYPE,
+ GenAIEvaluationRecord.TASK_NAME,
+ GenAIEvaluationRecord.VALUE_TYPE,
+ GenAIEvaluationRecord.VALUE,
+ GenAIEvaluationRecord.EVALUATION_LEVEL,
+ GenAIEvaluationRecord.REASON,
+ GenAIEvaluationRecord.JUDGE_MODEL,
+ GenAIEvaluationRecord.EVALUATION_TIME
+ );
+
+ public BanyanDBGenAIGenAIEvaluationRecordQueryDAO(BanyanDBStorageClient client) {
+ super(client);
+ }
+
+ @Override
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId, String serviceInstanceId,
+ TraceScopeCondition relatedTrace, Order queryOrder, int from, int limit,
+ Duration duration, List tags) throws IOException {
+ final boolean isColdStage = duration != null && duration.isColdStage();
+ final QueryBuilder query = new QueryBuilder<>() {
+ @Override
+ public void apply(StreamQuery query) {
+ if (StringUtil.isNotEmpty(serviceId)) {
+ query.and(eq(GenAIEvaluationRecord.SERVICE_ID, serviceId));
+ }
+ if (StringUtil.isNotEmpty(serviceInstanceId)) {
+ query.and(eq(GenAIEvaluationRecord.SERVICE_INSTANCE_ID, serviceInstanceId));
+ }
+
+ if (Objects.nonNull(relatedTrace)) {
+ if (StringUtil.isNotEmpty(relatedTrace.getTraceId())) {
+ query.and(eq(GenAIEvaluationRecord.TRACE_ID, relatedTrace.getTraceId()));
+ }
+ if (StringUtil.isNotEmpty(relatedTrace.getSegmentId())) {
+ query.and(eq(GenAIEvaluationRecord.SEGMENT_ID, relatedTrace.getSegmentId()));
+ }
+ if (Objects.nonNull(relatedTrace.getSpanId())) {
+ query.and(eq(GenAIEvaluationRecord.SPAN_ID, (long) relatedTrace.getSpanId()));
+ }
+ }
+
+ if (CollectionUtils.isNotEmpty(tags)) {
+ for (final Tag tag : tags) {
+ if (StringUtil.isNotEmpty(tag.getKey()) && StringUtil.isNotEmpty(tag.getValue())) {
+ query.and(eq(tag.getKey(), tag.getValue()));
+ }
+ }
+ }
+ if (queryOrder == Order.ASC) {
+ query.setOrderBy(
+ new AbstractQuery.OrderBy(AbstractQuery.Sort.ASC));
+ } else {
+ query.setOrderBy(
+ new AbstractQuery.OrderBy(AbstractQuery.Sort.DESC));
+ }
+ query.setLimit(limit);
+ query.setOffset(from);
+ }
+ };
+
+ StreamQueryResponse resp = queryDebuggable(isColdStage, GenAIEvaluationRecord.INDEX_NAME, TAGS, getTimestampRange(duration), query);
+
+ GenAIEvaluationRecords genAIEvaluationRecords = new GenAIEvaluationRecords();
+
+ for (final RowEntity rowEntity : resp.getElements()) {
+ GenAIEvaluationRecord evaluationRecord = new GenAIEvaluationRecord();
+ evaluationRecord.setTraceId(rowEntity.getTagValue(GenAIEvaluationRecord.TRACE_ID));
+ evaluationRecord.setServiceId(rowEntity.getTagValue(GenAIEvaluationRecord.SERVICE_ID));
+ evaluationRecord.setServiceInstanceId(rowEntity.getTagValue(GenAIEvaluationRecord.SERVICE_INSTANCE_ID));
+ evaluationRecord.setSegmentId(rowEntity.getTagValue(GenAIEvaluationRecord.SEGMENT_ID));
+ evaluationRecord.setSpanId(rowEntity.getTagValue(GenAIEvaluationRecord.SPAN_ID));
+ evaluationRecord.setSpanType(rowEntity.getTagValue(GenAIEvaluationRecord.SPAN_TYPE));
+ evaluationRecord.setTaskName(rowEntity.getTagValue(GenAIEvaluationRecord.TASK_NAME));
+ evaluationRecord.setEvaluationTime(((Number) rowEntity.getTagValue(GenAIEvaluationRecord.EVALUATION_TIME)).longValue());
+ evaluationRecord.setValueType(rowEntity.getTagValue(GenAIEvaluationRecord.VALUE_TYPE));
+ evaluationRecord.setValue(rowEntity.getTagValue(GenAIEvaluationRecord.VALUE));
+ evaluationRecord.setEvaluationLevel(rowEntity.getTagValue(GenAIEvaluationRecord.EVALUATION_LEVEL));
+ evaluationRecord.setReason(rowEntity.getTagValue(GenAIEvaluationRecord.REASON));
+ evaluationRecord.setJudgeModel(rowEntity.getTagValue(GenAIEvaluationRecord.JUDGE_MODEL));
+ genAIEvaluationRecords.getGenAIEvaluationRecordList().add(evaluationRecord);
+ }
+ return genAIEvaluationRecords;
+ }
+}
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
index 7f456b2e6f98..e6856e2d4c26 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/StorageModuleElasticsearchProvider.java
@@ -55,6 +55,7 @@
import org.apache.skywalking.oap.server.core.storage.query.IAlarmQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IBrowserLogQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IEventQueryDAO;
+import org.apache.skywalking.oap.server.core.storage.query.IGenAIEvaluationRecordQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IHierarchyQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.ILogQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IMetadataQueryDAO;
@@ -93,6 +94,7 @@
import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query.EBPFProfilingTaskEsDAO;
import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query.ESEventQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query.HierarchyQueryEsDAO;
+import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query.GenAIEvaluationRecordQueryEsDAO;
import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query.JFRDataQueryEsDAO;
import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query.PprofDataQueryEsDAO;
import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.query.LogQueryEsDAO;
@@ -241,6 +243,8 @@ public void prepare() throws ServiceNotProvidedException {
this.registerServiceImplementation(IAlarmQueryDAO.class, new AlarmQueryEsDAO(elasticSearchClient));
this.registerServiceImplementation(IRecordsQueryDAO.class, new RecordsQueryEsDAO(elasticSearchClient));
this.registerServiceImplementation(ILogQueryDAO.class, new LogQueryEsDAO(elasticSearchClient));
+ this.registerServiceImplementation(
+ IGenAIEvaluationRecordQueryDAO.class, new GenAIEvaluationRecordQueryEsDAO(elasticSearchClient));
this.registerServiceImplementation(
IProfileTaskQueryDAO.class, new ProfileTaskQueryEsDAO(elasticSearchClient, config
.getProfileTaskQueryMaxSize()));
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
new file mode 100644
index 000000000000..6ec0f3e7641c
--- /dev/null
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
@@ -0,0 +1,162 @@
+/*
+ * 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.oap.server.storage.plugin.elasticsearch.query;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import org.apache.skywalking.library.elasticsearch.requests.search.BoolQueryBuilder;
+import org.apache.skywalking.library.elasticsearch.requests.search.Query;
+import org.apache.skywalking.library.elasticsearch.requests.search.Search;
+import org.apache.skywalking.library.elasticsearch.requests.search.SearchBuilder;
+import org.apache.skywalking.library.elasticsearch.requests.search.Sort;
+import org.apache.skywalking.library.elasticsearch.response.search.SearchHit;
+import org.apache.skywalking.library.elasticsearch.response.search.SearchResponse;
+import org.apache.skywalking.oap.server.core.analysis.manual.genai.GenAIEvaluationRecord;
+import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
+import org.apache.skywalking.oap.server.core.analysis.record.Record;
+import org.apache.skywalking.oap.server.core.query.enumeration.Order;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.TraceScopeCondition;
+import org.apache.skywalking.oap.server.core.query.type.GenAIEvaluationRecords;
+import org.apache.skywalking.oap.server.core.storage.query.IGenAIEvaluationRecordQueryDAO;
+import org.apache.skywalking.oap.server.library.client.elasticsearch.ElasticSearchClient;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
+import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.base.EsDAO;
+import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.base.IndexController;
+import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.base.TimeRangeIndexNameGenerator;
+
+import static java.util.Objects.nonNull;
+import static org.apache.skywalking.oap.server.library.util.StringUtil.isNotEmpty;
+
+public class GenAIEvaluationRecordQueryEsDAO extends EsDAO implements IGenAIEvaluationRecordQueryDAO {
+ private static final Set QUERYABLE_TAG_KEYS = Set.of(
+ GenAIEvaluationRecord.TRACE_ID,
+ GenAIEvaluationRecord.SERVICE_ID,
+ GenAIEvaluationRecord.SERVICE_INSTANCE_ID,
+ GenAIEvaluationRecord.SEGMENT_ID,
+ GenAIEvaluationRecord.SPAN_ID,
+ GenAIEvaluationRecord.SPAN_TYPE,
+ GenAIEvaluationRecord.TASK_NAME,
+ GenAIEvaluationRecord.VALUE_TYPE,
+ GenAIEvaluationRecord.VALUE,
+ GenAIEvaluationRecord.EVALUATION_LEVEL,
+ GenAIEvaluationRecord.REASON,
+ GenAIEvaluationRecord.JUDGE_MODEL
+ );
+
+ public GenAIEvaluationRecordQueryEsDAO(final ElasticSearchClient client) {
+ super(client);
+ }
+
+ @Override
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
+ final String serviceInstanceId,
+ final TraceScopeCondition relatedTrace,
+ final Order queryOrder,
+ final int from,
+ final int limit,
+ final Duration duration,
+ final List tags) throws IOException {
+ long startSecondTB = 0;
+ long endSecondTB = 0;
+ if (nonNull(duration)) {
+ startSecondTB = duration.getStartTimeBucketInSec();
+ endSecondTB = duration.getEndTimeBucketInSec();
+ }
+
+ final BoolQueryBuilder query = Query.bool();
+ if (IndexController.LogicIndicesRegister.isMergedTable(GenAIEvaluationRecord.INDEX_NAME)) {
+ query.must(Query.term(
+ IndexController.LogicIndicesRegister.RECORD_TABLE_NAME,
+ GenAIEvaluationRecord.INDEX_NAME
+ ));
+ }
+ if (startSecondTB != 0 && endSecondTB != 0) {
+ query.must(Query.range(Record.TIME_BUCKET).gte(startSecondTB).lte(endSecondTB));
+ }
+ if (isNotEmpty(serviceId)) {
+ query.must(Query.term(GenAIEvaluationRecord.SERVICE_ID, serviceId));
+ }
+ if (isNotEmpty(serviceInstanceId)) {
+ query.must(Query.term(GenAIEvaluationRecord.SERVICE_INSTANCE_ID, serviceInstanceId));
+ }
+ if (nonNull(relatedTrace)) {
+ if (isNotEmpty(relatedTrace.getTraceId())) {
+ query.must(Query.term(GenAIEvaluationRecord.TRACE_ID, relatedTrace.getTraceId()));
+ }
+ if (isNotEmpty(relatedTrace.getSegmentId())) {
+ query.must(Query.term(GenAIEvaluationRecord.SEGMENT_ID, relatedTrace.getSegmentId()));
+ }
+ if (nonNull(relatedTrace.getSpanId())) {
+ query.must(Query.term(GenAIEvaluationRecord.SPAN_ID, String.valueOf(relatedTrace.getSpanId())));
+ }
+ }
+ if (CollectionUtils.isNotEmpty(tags)) {
+ for (final Tag tag : tags) {
+ if (isNotEmpty(tag.getKey()) && isNotEmpty(tag.getValue())) {
+ if (!QUERYABLE_TAG_KEYS.contains(tag.getKey())) {
+ return new GenAIEvaluationRecords();
+ }
+ query.must(Query.term(tag.getKey(), tag.getValue()));
+ }
+ }
+ }
+
+ final SearchBuilder search = Search.builder()
+ .query(query)
+ .sort(
+ GenAIEvaluationRecord.EVALUATION_TIME,
+ Order.DES.equals(queryOrder) ? Sort.Order.DESC : Sort.Order.ASC
+ )
+ .size(limit)
+ .from(from);
+
+ final SearchResponse response = searchDebuggable(new TimeRangeIndexNameGenerator(
+ IndexController.LogicIndicesRegister.getPhysicalTableName(GenAIEvaluationRecord.INDEX_NAME),
+ startSecondTB,
+ endSecondTB
+ ), search.build());
+
+ final GenAIEvaluationRecords records = new GenAIEvaluationRecords();
+ for (SearchHit searchHit : response.getHits().getHits()) {
+ records.getGenAIEvaluationRecordList().add(parseRecord(searchHit));
+ }
+ return records;
+ }
+
+ private GenAIEvaluationRecord parseRecord(final SearchHit searchHit) {
+ final var source = searchHit.getSource();
+ final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
+ record.setTraceId((String) source.get(GenAIEvaluationRecord.TRACE_ID));
+ record.setServiceId((String) source.get(GenAIEvaluationRecord.SERVICE_ID));
+ record.setServiceInstanceId((String) source.get(GenAIEvaluationRecord.SERVICE_INSTANCE_ID));
+ record.setSegmentId((String) source.get(GenAIEvaluationRecord.SEGMENT_ID));
+ record.setSpanId((String) source.get(GenAIEvaluationRecord.SPAN_ID));
+ record.setSpanType((String) source.get(GenAIEvaluationRecord.SPAN_TYPE));
+ record.setTaskName((String) source.get(GenAIEvaluationRecord.TASK_NAME));
+ record.setValueType((String) source.get(GenAIEvaluationRecord.VALUE_TYPE));
+ record.setValue((String) source.get(GenAIEvaluationRecord.VALUE));
+ record.setEvaluationLevel((String) source.get(GenAIEvaluationRecord.EVALUATION_LEVEL));
+ record.setReason((String) source.get(GenAIEvaluationRecord.REASON));
+ record.setJudgeModel((String) source.get(GenAIEvaluationRecord.JUDGE_MODEL));
+ record.setEvaluationTime(((Number) source.get(GenAIEvaluationRecord.EVALUATION_TIME)).longValue());
+ return record;
+ }
+}
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/JDBCStorageProvider.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/JDBCStorageProvider.java
index d947fd3eb24b..43a80b1d558e 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/JDBCStorageProvider.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/JDBCStorageProvider.java
@@ -48,6 +48,7 @@
import org.apache.skywalking.oap.server.core.storage.query.IAlarmQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IBrowserLogQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IEventQueryDAO;
+import org.apache.skywalking.oap.server.core.storage.query.IGenAIEvaluationRecordQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IHierarchyQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.ILogQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IMetadataQueryDAO;
@@ -79,6 +80,7 @@
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.dao.JDBCEBPFProfilingScheduleDAO;
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.dao.JDBCEBPFProfilingTaskDAO;
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.dao.JDBCEventQueryDAO;
+import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.dao.JDBCGenAIEvaluationRecordQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.dao.JDBCHierarchyQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.dao.JDBCHistoryDeleteDAO;
import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.dao.JDBCJFRDataQueryDAO;
@@ -198,6 +200,9 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
this.registerServiceImplementation(
ILogQueryDAO.class,
new JDBCLogQueryDAO(jdbcClient, getManager(), tableHelper));
+ this.registerServiceImplementation(
+ IGenAIEvaluationRecordQueryDAO.class,
+ new JDBCGenAIEvaluationRecordQueryDAO(jdbcClient, tableHelper));
this.registerServiceImplementation(
IProfileTaskQueryDAO.class,
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
new file mode 100644
index 000000000000..0b85466bb297
--- /dev/null
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
@@ -0,0 +1,257 @@
+/*
+ * 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.oap.server.storage.plugin.jdbc.common.dao;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.skywalking.oap.server.core.analysis.manual.genai.GenAIEvaluationRecord;
+import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
+import org.apache.skywalking.oap.server.core.query.enumeration.Order;
+import org.apache.skywalking.oap.server.core.query.input.Duration;
+import org.apache.skywalking.oap.server.core.query.input.TraceScopeCondition;
+import org.apache.skywalking.oap.server.core.query.type.GenAIEvaluationRecords;
+import org.apache.skywalking.oap.server.core.storage.model.ColumnName;
+import org.apache.skywalking.oap.server.core.storage.model.ModelColumn;
+import org.apache.skywalking.oap.server.core.storage.query.IGenAIEvaluationRecordQueryDAO;
+import org.apache.skywalking.oap.server.library.client.jdbc.hikaricp.JDBCClient;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
+import org.apache.skywalking.oap.server.library.util.StringUtil;
+import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.JDBCTableInstaller;
+import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.SQLAndParameters;
+import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.TableHelper;
+import org.apache.skywalking.oap.server.storage.plugin.jdbc.TableMetaInfo;
+
+import static java.util.Comparator.comparing;
+import static java.util.Objects.nonNull;
+import static java.util.stream.Collectors.toList;
+
+@RequiredArgsConstructor
+public class JDBCGenAIEvaluationRecordQueryDAO implements IGenAIEvaluationRecordQueryDAO {
+ private static final Set QUERYABLE_TAG_KEYS = Set.of(
+ GenAIEvaluationRecord.TRACE_ID,
+ GenAIEvaluationRecord.SERVICE_ID,
+ GenAIEvaluationRecord.SERVICE_INSTANCE_ID,
+ GenAIEvaluationRecord.SEGMENT_ID,
+ GenAIEvaluationRecord.SPAN_ID,
+ GenAIEvaluationRecord.SPAN_TYPE,
+ GenAIEvaluationRecord.TASK_NAME,
+ GenAIEvaluationRecord.VALUE_TYPE,
+ GenAIEvaluationRecord.VALUE,
+ GenAIEvaluationRecord.EVALUATION_LEVEL,
+ GenAIEvaluationRecord.REASON,
+ GenAIEvaluationRecord.JUDGE_MODEL
+ );
+
+ private static final List SELECTED_COLUMNS = List.of(
+ GenAIEvaluationRecord.TRACE_ID,
+ GenAIEvaluationRecord.SERVICE_ID,
+ GenAIEvaluationRecord.SERVICE_INSTANCE_ID,
+ GenAIEvaluationRecord.SEGMENT_ID,
+ GenAIEvaluationRecord.SPAN_ID,
+ GenAIEvaluationRecord.SPAN_TYPE,
+ GenAIEvaluationRecord.TASK_NAME,
+ GenAIEvaluationRecord.VALUE_TYPE,
+ GenAIEvaluationRecord.VALUE,
+ GenAIEvaluationRecord.EVALUATION_LEVEL,
+ GenAIEvaluationRecord.REASON,
+ GenAIEvaluationRecord.JUDGE_MODEL,
+ GenAIEvaluationRecord.EVALUATION_TIME
+ );
+
+ private final JDBCClient jdbcClient;
+ private final TableHelper tableHelper;
+
+ @Override
+ @SneakyThrows
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
+ final String serviceInstanceId,
+ final TraceScopeCondition relatedTrace,
+ final Order queryOrder,
+ final int from,
+ final int limit,
+ final Duration duration,
+ final List tags) {
+ if (CollectionUtils.isNotEmpty(tags)) {
+ for (final Tag tag : tags) {
+ if (StringUtil.isNotEmpty(tag.getKey())
+ && StringUtil.isNotEmpty(tag.getValue())
+ && !QUERYABLE_TAG_KEYS.contains(tag.getKey())) {
+ return new GenAIEvaluationRecords();
+ }
+ }
+ }
+
+ final List tables;
+ if (nonNull(duration)) {
+ tables = tableHelper.getTablesForRead(
+ GenAIEvaluationRecord.INDEX_NAME,
+ duration.getStartTimeBucket(),
+ duration.getEndTimeBucket()
+ );
+ } else {
+ tables = tableHelper.getTablesWithinTTL(GenAIEvaluationRecord.INDEX_NAME);
+ }
+
+ final var records = new ArrayList();
+ for (final var table : tables) {
+ final var sqlAndParameters = buildSQL(
+ serviceId, serviceInstanceId, relatedTrace, queryOrder, from, limit, duration, tags, table);
+ records.addAll(
+ jdbcClient.executeQuery(
+ sqlAndParameters.sql(),
+ this::parseResults,
+ sqlAndParameters.parameters()
+ )
+ );
+ }
+
+ final var comparator = Order.ASC.equals(queryOrder) ?
+ comparing(GenAIEvaluationRecord::getEvaluationTime) :
+ comparing(GenAIEvaluationRecord::getEvaluationTime).reversed();
+ return new GenAIEvaluationRecords(
+ records.stream().sorted(comparator).skip(from).limit(limit).collect(toList())
+ );
+ }
+
+ protected ArrayList parseResults(final ResultSet resultSet) throws SQLException {
+ final var records = new ArrayList();
+ while (resultSet.next()) {
+ final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
+ record.setTraceId(resultSet.getString(GenAIEvaluationRecord.TRACE_ID));
+ record.setServiceId(resultSet.getString(GenAIEvaluationRecord.SERVICE_ID));
+ record.setServiceInstanceId(resultSet.getString(GenAIEvaluationRecord.SERVICE_INSTANCE_ID));
+ record.setSegmentId(resultSet.getString(GenAIEvaluationRecord.SEGMENT_ID));
+ record.setSpanId(resultSet.getString(GenAIEvaluationRecord.SPAN_ID));
+ record.setSpanType(resultSet.getString(GenAIEvaluationRecord.SPAN_TYPE));
+ record.setTaskName(resultSet.getString(GenAIEvaluationRecord.TASK_NAME));
+ record.setValueType(resultSet.getString(GenAIEvaluationRecord.VALUE_TYPE));
+ record.setValue(resultSet.getString(GenAIEvaluationRecord.VALUE));
+ record.setEvaluationLevel(resultSet.getString(GenAIEvaluationRecord.EVALUATION_LEVEL));
+ record.setReason(resultSet.getString(GenAIEvaluationRecord.REASON));
+ record.setJudgeModel(resultSet.getString(GenAIEvaluationRecord.JUDGE_MODEL));
+ record.setEvaluationTime(resultSet.getLong(GenAIEvaluationRecord.EVALUATION_TIME));
+ records.add(record);
+ }
+ return records;
+ }
+
+ protected SQLAndParameters buildSQL(final String serviceId,
+ final String serviceInstanceId,
+ final TraceScopeCondition relatedTrace,
+ final Order queryOrder,
+ final int from,
+ final int limit,
+ final Duration duration,
+ final List tags,
+ final String table) {
+ long startSecondTB = 0;
+ long endSecondTB = 0;
+ if (nonNull(duration)) {
+ startSecondTB = duration.getStartTimeBucketInSec();
+ endSecondTB = duration.getEndTimeBucketInSec();
+ }
+
+ final StringBuilder sql = new StringBuilder("select ");
+ final List parameters = new ArrayList<>(10);
+ sql.append(selectColumns())
+ .append(" from ")
+ .append(table)
+ .append(" where ")
+ .append(JDBCTableInstaller.TABLE_COLUMN)
+ .append(" = ?");
+ parameters.add(GenAIEvaluationRecord.INDEX_NAME);
+
+ if (startSecondTB != 0 && endSecondTB != 0) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.TIME_BUCKET)).append(" >= ?");
+ parameters.add(startSecondTB);
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.TIME_BUCKET)).append(" <= ?");
+ parameters.add(endSecondTB);
+ }
+ if (StringUtil.isNotEmpty(serviceId)) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SERVICE_ID)).append(" = ?");
+ parameters.add(serviceId);
+ }
+ if (StringUtil.isNotEmpty(serviceInstanceId)) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SERVICE_INSTANCE_ID)).append(" = ?");
+ parameters.add(serviceInstanceId);
+ }
+ if (nonNull(relatedTrace)) {
+ if (StringUtil.isNotEmpty(relatedTrace.getTraceId())) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.TRACE_ID)).append(" = ?");
+ parameters.add(relatedTrace.getTraceId());
+ }
+ if (StringUtil.isNotEmpty(relatedTrace.getSegmentId())) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SEGMENT_ID)).append(" = ?");
+ parameters.add(relatedTrace.getSegmentId());
+ }
+ if (nonNull(relatedTrace.getSpanId())) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SPAN_ID)).append(" = ?");
+ parameters.add(String.valueOf(relatedTrace.getSpanId()));
+ }
+ }
+ if (CollectionUtils.isNotEmpty(tags)) {
+ for (final Tag tag : tags) {
+ if (StringUtil.isNotEmpty(tag.getKey())
+ && StringUtil.isNotEmpty(tag.getValue())
+ && QUERYABLE_TAG_KEYS.contains(tag.getKey())) {
+ sql.append(" and ").append(storageColumn(tag.getKey())).append(" = ?");
+ parameters.add(tag.getValue());
+ }
+ }
+ }
+
+ sql.append(" order by ")
+ .append(storageColumn(GenAIEvaluationRecord.EVALUATION_TIME))
+ .append(" ")
+ .append(Order.DES.equals(queryOrder) ? "desc" : "asc");
+ sql.append(" limit ").append(from + limit);
+
+ return new SQLAndParameters(sql.toString(), parameters);
+ }
+
+ private String selectColumns() {
+ return SELECTED_COLUMNS.stream()
+ .map(this::selectColumn)
+ .collect(java.util.stream.Collectors.joining(", "));
+ }
+
+ private String selectColumn(final String logicalColumn) {
+ final String storageColumn = storageColumn(logicalColumn);
+ if (storageColumn.equals(logicalColumn)) {
+ return storageColumn;
+ }
+ return storageColumn + " as " + logicalColumn;
+ }
+
+ private String storageColumn(final String logicalColumn) {
+ return TableMetaInfo.get(GenAIEvaluationRecord.INDEX_NAME)
+ .getColumns()
+ .stream()
+ .map(ModelColumn::getColumnName)
+ .filter(it -> logicalColumn.equals(it.getName()))
+ .findFirst()
+ .map(ColumnName::getStorageName)
+ .orElse(logicalColumn);
+ }
+}
From 84233e5f52e8008d33231a56cb1342478185d7c1 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Sun, 12 Jul 2026 17:32:09 +0800
Subject: [PATCH 2/8] enabling openai config from env
---
.../evaluation/judge/provider/OpenAICompatibleProvider.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/judge/provider/OpenAICompatibleProvider.java b/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/judge/provider/OpenAICompatibleProvider.java
index 1e4e9b503c8e..67a89dea4b64 100644
--- a/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/judge/provider/OpenAICompatibleProvider.java
+++ b/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/judge/provider/OpenAICompatibleProvider.java
@@ -37,6 +37,7 @@
import org.apache.skywalking.oap.server.ai.evaluation.judge.JudgeModelRequest;
import org.apache.skywalking.oap.server.ai.evaluation.judge.JudgeModelResponse;
import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.util.PropertyPlaceholderHelper;
import org.apache.skywalking.oap.server.library.util.StringUtil;
public class OpenAICompatibleProvider implements JudgeModelProvider {
@@ -179,7 +180,10 @@ private static String getString(final Properties properties, final String key) {
return null;
}
final Object value = properties.get(key);
- return value == null ? null : String.valueOf(value);
+ if (value == null) {
+ return null;
+ }
+ return PropertyPlaceholderHelper.INSTANCE.replacePlaceholders(String.valueOf(value), properties);
}
private static Double getDouble(final Properties properties, final String key) throws ModuleStartException {
From 95d4437b45c9009c5e6aa92643966e63f22c61ee Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Sun, 12 Jul 2026 22:30:44 +0800
Subject: [PATCH 3/8] docs: add AI evaluation release notes
---
docs/en/changes/changes.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 15839723ecef..f7c937a41103 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -333,8 +333,10 @@
* Fix Envoy ALS rendering for the LAL live-debugger and the persisted log `content`: an Istio metadata-exchange peer in `common_properties.filter_state_objects` (legacy Wasm `wasm.*_peer` = `Any{BytesValue}` wrapping a FlatBuffer, or modern `*_peer` = `Any{Struct}`) is now decoded into the readable peer metadata (pod / namespace / labels) instead of an opaque `jsonformat-failed` envelope or base64. The serialization is hardened so a single un-printable field can no longer blank the whole entry — the `LalPayloadDebugDump` printer carries a well-known-type `TypeRegistry` and sanitizes every value `JsonFormat` would reject (an unresolvable, no-slash, or corrupt-bytes `Any` degrades to an `@unresolved` placeholder; a non-finite `Value` double `NaN`/`Infinity` is rendered as a string), keeping the rest of the entry readable. Because the LAL output builder's `bindInput` runs eagerly before the debug capture, this also stops an unregistered `filter_state_objects` type from throwing and aborting the whole rule (dropping the mesh log). Decoding is wired through a new `LalInputDebugRenderer` SPI (`EnvoyAlsHttpDebugRenderer` / `EnvoyAlsTcpDebugRenderer`) so `log-analyzer` reaches the receiver-side decoders without depending on the Envoy receiver, and covers both HTTP and TCP access logs.
* Surface the effective BanyanDB configuration (`bydb.yml` / `bydb-topn.yml`) in the `/debugging/config/dump` admin API. Because the BanyanDB config moved to a separate file in 10.2.0, a BanyanDB deployment previously showed an empty `storage.banyandb` block in the dump; its post-environment-resolution values are now merged into the same response under `storage.banyandb.*` (TopN rules under `storage.banyandb.topN.*`), masked by the same secret-keyword list, via a generic `ConfigDumpExtension` SPI on `ServerStatusService` that any module loading config from a secondary file can implement.
* Fix: an MQE `top_n(metric, N, order, attrX='value')` query whose attribute is not a column of the target metric now returns a descriptive MQE error instead of a raw storage `IOException` surfaced as `Internal IO exception, query metrics error.`. Attribute columns (`attr0..attrN`) exist only on decorated metrics (`service_*` / `endpoint_*` / `kubernetes_service_*`, set to the layer name via OAL `.decorator(...)`) and the MAL meter base; metrics such as relations or database / cache / mq access carry none, so passing an attribute condition previously reached the storage engine with a tag it does not define and failed there. `MQEVisitor` now validates each attribute key against the metric's registered queryable columns before the storage call and raises `IllegalExpressionException` (naming the attribute and the metric) when it is absent.
+* Add customizable LLM-as-judge support for AI evaluation, with OpenAI-compatible endpoint / model / API key configuration, and persist the evaluation result as queryable `GenAIEvaluationRecord` rows for later inspection.
#### UI
+* Add a Virtual GenAI evaluation-record page and evaluation-score chart in Horizon UI, so operators can inspect evaluation result, level, reason, judge model, timestamp, trace linkage, and the `gen_ai_model_evaluation_score_ppm` trend for evaluated records.
* Add Airflow layer dashboards and menu i18n under Workflow Scheduler in Horizon UI (SWIP-7).
* Add mobile menu icon and i18n labels for the iOS layer.
* Fix metric label rendering in multi-expression dashboard widgets.
From 7f1c72b648feeed8f528cb1e4bc0d330f8d2e079 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Mon, 13 Jul 2026 22:29:23 +0800
Subject: [PATCH 4/8] Use unique id for GenAI evaluation records
---
.../strategy/span/SpanAIEvaluationStrategy.java | 2 ++
.../manual/genai/GenAIEvaluationRecord.java | 16 +++++++---------
.../query/GenAIEvaluationRecordQueryEsDAO.java | 1 +
.../dao/JDBCGenAIEvaluationRecordQueryDAO.java | 2 ++
4 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java b/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java
index 41fb95aceb34..b0b6bd6cae28 100644
--- a/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java
+++ b/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java
@@ -44,6 +44,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Optional;
+import java.util.UUID;
@Slf4j
public class SpanAIEvaluationStrategy implements AIEvaluationStrategy {
@@ -125,6 +126,7 @@ private void persistResults(final AIEvaluationContext context,
Layer.VIRTUAL_GENAI.isNormal()
);
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
+ record.setUniqueId(UUID.randomUUID().toString().replace("-", ""));
record.setTraceId(context.getTraceId());
record.setServiceId(serviceId);
record.setServiceInstanceId(IDManager.ServiceInstanceID.buildId(
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java
index ce5d14163780..7310f8f39538 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java
@@ -45,6 +45,7 @@
public class GenAIEvaluationRecord extends Record {
public static final String INDEX_NAME = "gen_ai_evaluation_record";
+ public static final String UNIQUE_ID = "unique_id";
public static final String TRACE_ID = "trace_id";
public static final String SERVICE_ID = "service_id";
public static final String SERVICE_INSTANCE_ID = "service_instance_id";
@@ -59,6 +60,9 @@ public class GenAIEvaluationRecord extends Record {
public static final String JUDGE_MODEL = "judge_model";
public static final String EVALUATION_TIME = "evaluation_time";
+ @Column(name = UNIQUE_ID)
+ private String uniqueId;
+
@Column(name = TRACE_ID, length = 150)
@BanyanDB.IndexRule(indexType = BanyanDB.IndexRule.IndexType.SKIPPING)
private String traceId;
@@ -109,21 +113,14 @@ public class GenAIEvaluationRecord extends Record {
@Override
public StorageID id() {
- return new StorageID()
- .append(TRACE_ID, traceId)
- .append(SERVICE_ID, serviceId)
- .append(SERVICE_INSTANCE_ID, serviceInstanceId)
- .append(SPAN_ID, spanId)
- .append(SPAN_TYPE, spanType)
- .append(TASK_NAME, taskName)
- .append(EVALUATION_LEVEL, evaluationLevel)
- .append(EVALUATION_TIME, evaluationTime);
+ return new StorageID().append(UNIQUE_ID, uniqueId);
}
public static class Builder implements StorageBuilder {
@Override
public GenAIEvaluationRecord storage2Entity(final Convert2Entity converter) {
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
+ record.setUniqueId((String) converter.get(UNIQUE_ID));
record.setTraceId((String) converter.get(TRACE_ID));
record.setServiceId((String) converter.get(SERVICE_ID));
record.setServiceInstanceId((String) converter.get(SERVICE_INSTANCE_ID));
@@ -143,6 +140,7 @@ public GenAIEvaluationRecord storage2Entity(final Convert2Entity converter) {
@Override
public void entity2Storage(final GenAIEvaluationRecord storageData, final Convert2Storage converter) {
+ converter.accept(UNIQUE_ID, storageData.getUniqueId());
converter.accept(TRACE_ID, storageData.getTraceId());
converter.accept(SERVICE_ID, storageData.getServiceId());
converter.accept(SERVICE_INSTANCE_ID, storageData.getServiceInstanceId());
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
index 6ec0f3e7641c..2fcfcb1eed09 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
@@ -144,6 +144,7 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
private GenAIEvaluationRecord parseRecord(final SearchHit searchHit) {
final var source = searchHit.getSource();
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
+ record.setUniqueId((String) source.get(GenAIEvaluationRecord.UNIQUE_ID));
record.setTraceId((String) source.get(GenAIEvaluationRecord.TRACE_ID));
record.setServiceId((String) source.get(GenAIEvaluationRecord.SERVICE_ID));
record.setServiceInstanceId((String) source.get(GenAIEvaluationRecord.SERVICE_INSTANCE_ID));
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
index 0b85466bb297..2101afe75adb 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
@@ -64,6 +64,7 @@ public class JDBCGenAIEvaluationRecordQueryDAO implements IGenAIEvaluationRecord
);
private static final List SELECTED_COLUMNS = List.of(
+ GenAIEvaluationRecord.UNIQUE_ID,
GenAIEvaluationRecord.TRACE_ID,
GenAIEvaluationRecord.SERVICE_ID,
GenAIEvaluationRecord.SERVICE_INSTANCE_ID,
@@ -138,6 +139,7 @@ protected ArrayList parseResults(final ResultSet resultSe
final var records = new ArrayList();
while (resultSet.next()) {
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
+ record.setUniqueId(resultSet.getString(GenAIEvaluationRecord.UNIQUE_ID));
record.setTraceId(resultSet.getString(GenAIEvaluationRecord.TRACE_ID));
record.setServiceId(resultSet.getString(GenAIEvaluationRecord.SERVICE_ID));
record.setServiceInstanceId(resultSet.getString(GenAIEvaluationRecord.SERVICE_INSTANCE_ID));
From 536485f6043f3f07d2f8effba406ffdb061e7617 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Tue, 4 Aug 2026 00:28:58 +0800
Subject: [PATCH 5/8] refactor the genai evaluation record structure
---
docs/en/swip/SWIP-16.md | 8 +-
.../span/SpanAIEvaluationStrategy.java | 24 +++---
.../manual/genai/GenAIEvaluationRecord.java | 73 ++++++++++++++++---
.../GenAIEvaluationRecordQueryService.java | 32 ++++----
.../GenAIEvaluationRecordQueryCondition.java | 7 +-
.../query/IGenAIEvaluationRecordQueryDAO.java | 24 +++---
.../resolver/GenAIEvaluationRecordQuery.java | 11 ++-
.../src/main/resources/ai-evaluation.yml | 38 +++++-----
.../banyandb/BanyanDBStorageProvider.java | 4 +-
.../GenAIEvaluationRecordQueryEsDAO.java | 36 ++++++---
.../JDBCGenAIEvaluationRecordQueryDAO.java | 59 ++++++++++-----
11 files changed, 204 insertions(+), 112 deletions(-)
diff --git a/docs/en/swip/SWIP-16.md b/docs/en/swip/SWIP-16.md
index 8450ffc069c4..5ab310a26c7f 100644
--- a/docs/en/swip/SWIP-16.md
+++ b/docs/en/swip/SWIP-16.md
@@ -190,7 +190,7 @@ For tasks where `valueType = SCORE`, the judge returns a numeric result in `[0.0
The initial metric is:
-- `gen_ai_evaluation_score_ppm`
+- `gen_ai_model_evaluation_score_ppm`
The metric is attached to the Virtual GenAI service instance dimension, using `service_name` as the service key and `model_name` as the instance key. The `task_name` remains as a labeled value dimension, allowing the same metric to represent scores for `Faithfulness`, `Relevance`, `TaskCompletion`, `Hallucination`, or any user-defined task.
@@ -241,7 +241,7 @@ This SWIP introduces a new OAP capability and a new record data model. The main
- A new structured record type `ai_evaluation_result`, including the normalized `evaluation_level` field
- `SCORE`-type evaluation results additionally generate a MAL labeled metric for dashboard display
-- The `gen_ai_evaluation_score_ppm` metric stores scores scaled by `1,000,000`; query and UI layers need to divide by `1,000,000` to display the original `[0.0, 1.0]` score
+- The `gen_ai_model_evaluation_score_ppm` metric stores scores scaled by `1,000,000`; query and UI layers need to divide by `1,000,000` to display the original `[0.0, 1.0]` score
- The capability depends on the existing GenAI observability pipeline being able to recognize GenAI spans from SkyWalking native traces, OTLP, and Zipkin
- The UI adds an evaluation result page and trace jump based on `traceId`
- The current implementation uses an asynchronous local in-memory queue to carry evaluation tasks, and queue data is not part of any persistent protocol
@@ -255,6 +255,6 @@ This SWIP introduces a new OAP capability and a new record data model. The main
4. OAP generates evaluation tasks for sampled GenAI spans according to the PPM sampling rate and puts them into the asynchronous local in-memory queue.
5. A background evaluation consumer takes tasks from the queue and sends requests to the configured judge model.
6. OAP writes each evaluation result into SkyWalking structured records.
-7. For `SCORE`-type tasks, OAP generates the MAL labeled metric `gen_ai_evaluation_score_ppm` from evaluation results.
-8. Users observe both existing GenAI runtime metrics and newly added quality metrics in the GenAI dashboard. The UI should divide `gen_ai_evaluation_score_ppm` values by `1,000,000` to display the original score.
+7. For `SCORE`-type tasks, OAP generates the MAL labeled metric `gen_ai_model_evaluation_score_ppm` from evaluation results.
+8. Users observe both existing GenAI runtime metrics and newly added quality metrics in the GenAI dashboard. The UI should divide `gen_ai_model_evaluation_score_ppm` values by `1,000,000` to display the original score.
9. Users can also open the evaluation result page, inspect evaluation details, and jump from a single result to the related trace.
\ No newline at end of file
diff --git a/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java b/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java
index b0b6bd6cae28..987c01402bf3 100644
--- a/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java
+++ b/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java
@@ -33,8 +33,6 @@
import org.apache.skywalking.oap.server.ai.evaluation.service.strategy.AIEvaluationStrategy;
import org.apache.skywalking.oap.server.ai.evaluation.task.EvaluationTaskRegistry;
import org.apache.skywalking.oap.server.ai.evaluation.value.ValueType;
-import org.apache.skywalking.oap.server.core.analysis.IDManager;
-import org.apache.skywalking.oap.server.core.analysis.Layer;
import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
import org.apache.skywalking.oap.server.core.analysis.manual.genai.GenAIEvaluationRecord;
import org.apache.skywalking.oap.server.core.analysis.worker.RecordStreamProcessor;
@@ -121,24 +119,20 @@ private void persistResults(final AIEvaluationContext context,
final String judgeModel) {
final long evaluationTime = System.currentTimeMillis();
for (EvaluationResult result : results) {
- final String serviceId = IDManager.ServiceID.buildId(
- namingControl.formatServiceName(context.getProviderName()),
- Layer.VIRTUAL_GENAI.isNormal()
- );
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
record.setUniqueId(UUID.randomUUID().toString().replace("-", ""));
record.setTraceId(context.getTraceId());
- record.setServiceId(serviceId);
- record.setServiceInstanceId(IDManager.ServiceInstanceID.buildId(
- serviceId,
- namingControl.formatServiceName(context.getModelName())
- ));
+ record.setServiceName(context.getServiceName());
+ record.setProviderName(namingControl.formatServiceName(context.getProviderName()));
+ record.setModelName(namingControl.formatInstanceName(context.getModelName()));
+ record.setOperationName(operationName(context));
record.setSegmentId(context.getSegmentId());
record.setSpanId(context.getSpanId());
record.setSpanType(plan.getSpanType() == null ? "" : plan.getSpanType().name());
record.setTaskName(result.getName());
record.setValueType(result.getValueType() == null ? "" : result.getValueType().name());
record.setValue(result.getValue());
+ record.setScoreValuePpm(GenAIEvaluationRecord.toScoreValuePpm(numericValue(result.getValue())));
record.setEvaluationLevel(levelResolver.resolve(result.getValueType(), result.getValue()));
record.setReason(result.getReason());
record.setJudgeModel(judgeModel);
@@ -173,4 +167,12 @@ private static String operationName(final AIEvaluationContext context) {
return context.getTags().get(GenAISemanticAttributes.OPERATION_NAME);
}
+ private static Double numericValue(final String value) {
+ try {
+ return Double.valueOf(value);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java
index 7310f8f39538..1c7f0e71205c 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java
@@ -18,6 +18,8 @@
package org.apache.skywalking.oap.server.core.analysis.manual.genai;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
import lombok.Getter;
import lombok.Setter;
import org.apache.skywalking.oap.server.core.analysis.Stream;
@@ -47,8 +49,12 @@ public class GenAIEvaluationRecord extends Record {
public static final String INDEX_NAME = "gen_ai_evaluation_record";
public static final String UNIQUE_ID = "unique_id";
public static final String TRACE_ID = "trace_id";
- public static final String SERVICE_ID = "service_id";
- public static final String SERVICE_INSTANCE_ID = "service_instance_id";
+ public static final String PROVIDER_NAME = "provider_name";
+ public static final String MODEL_NAME = "model_name";
+ public static final String SERVICE_NAME = "service_name";
+ public static final String OPERATION_NAME = "operation_name";
+ public static final String SCORE_VALUE = "score_value";
+ public static final long SCORE_SCALE = 1_000_000L;
public static final String SEGMENT_ID = "segment_id";
public static final String SPAN_ID = "span_id";
public static final String SPAN_TYPE = "span_type";
@@ -68,14 +74,52 @@ public class GenAIEvaluationRecord extends Record {
private String traceId;
@ElasticSearch.EnableDocValues
- @Column(name = SERVICE_ID, length = 150, storageOnly = true)
+ @Column(name = SERVICE_NAME, length = 150)
+ private String serviceName;
+
+ @ElasticSearch.EnableDocValues
+ @Column(name = PROVIDER_NAME, length = 150)
@BanyanDB.SeriesID(index = 0)
- private String serviceId;
+ private String providerName;
@ElasticSearch.EnableDocValues
- @Column(name = SERVICE_INSTANCE_ID, length = 150, storageOnly = true)
+ @Column(name = MODEL_NAME, length = 150)
@BanyanDB.SeriesID(index = 1)
- private String serviceInstanceId;
+ private String modelName;
+
+ @ElasticSearch.EnableDocValues
+ @Column(name = OPERATION_NAME, length = 150)
+ private String operationName;
+
+ @ElasticSearch.EnableDocValues
+ @BanyanDB.EnableSort
+ @Column(name = SCORE_VALUE)
+ private Long scoreValuePpm;
+
+ public Double getScoreValue() {
+ return scoreValuePpm == null ? null : scoreValuePpm / (double) SCORE_SCALE;
+ }
+
+ public static Long toScoreValuePpm(final Double score) {
+ return score == null ? null : BigDecimal.valueOf(score)
+ .movePointRight(6)
+ .setScale(0, RoundingMode.HALF_UP)
+ .longValueExact();
+ }
+
+ public static long minScoreValuePpm(final double score) {
+ return BigDecimal.valueOf(score)
+ .movePointRight(6)
+ .setScale(0, RoundingMode.CEILING)
+ .longValueExact();
+ }
+
+ public static long maxScoreValuePpm(final double score) {
+ return BigDecimal.valueOf(score)
+ .movePointRight(6)
+ .setScale(0, RoundingMode.FLOOR)
+ .longValueExact();
+ }
@Column(name = SEGMENT_ID, length = 150, storageOnly = true)
private String segmentId;
@@ -83,7 +127,7 @@ public class GenAIEvaluationRecord extends Record {
@Column(name = SPAN_ID, length = 150, storageOnly = true)
private String spanId;
- @Column(name = SPAN_TYPE, length = 64)
+ @Column(name = SPAN_TYPE, length = 64, storageOnly = true)
private String spanType;
@ElasticSearch.EnableDocValues
@@ -122,8 +166,12 @@ public GenAIEvaluationRecord storage2Entity(final Convert2Entity converter) {
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
record.setUniqueId((String) converter.get(UNIQUE_ID));
record.setTraceId((String) converter.get(TRACE_ID));
- record.setServiceId((String) converter.get(SERVICE_ID));
- record.setServiceInstanceId((String) converter.get(SERVICE_INSTANCE_ID));
+ record.setServiceName((String) converter.get(SERVICE_NAME));
+ record.setProviderName((String) converter.get(PROVIDER_NAME));
+ record.setModelName((String) converter.get(MODEL_NAME));
+ record.setOperationName((String) converter.get(OPERATION_NAME));
+ final Number scoreValue = (Number) converter.get(SCORE_VALUE);
+ record.setScoreValuePpm(scoreValue == null ? null : scoreValue.longValue());
record.setSegmentId((String) converter.get(SEGMENT_ID));
record.setSpanId((String) converter.get(SPAN_ID));
record.setSpanType((String) converter.get(SPAN_TYPE));
@@ -142,8 +190,11 @@ public GenAIEvaluationRecord storage2Entity(final Convert2Entity converter) {
public void entity2Storage(final GenAIEvaluationRecord storageData, final Convert2Storage converter) {
converter.accept(UNIQUE_ID, storageData.getUniqueId());
converter.accept(TRACE_ID, storageData.getTraceId());
- converter.accept(SERVICE_ID, storageData.getServiceId());
- converter.accept(SERVICE_INSTANCE_ID, storageData.getServiceInstanceId());
+ converter.accept(SERVICE_NAME, storageData.getServiceName());
+ converter.accept(PROVIDER_NAME, storageData.getProviderName());
+ converter.accept(MODEL_NAME, storageData.getModelName());
+ converter.accept(OPERATION_NAME, storageData.getOperationName());
+ converter.accept(SCORE_VALUE, storageData.getScoreValuePpm());
converter.accept(SEGMENT_ID, storageData.getSegmentId());
converter.accept(SPAN_ID, storageData.getSpanId());
converter.accept(SPAN_TYPE, storageData.getSpanType());
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java
index 4fb443881c29..9aadacd9ae37 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java
@@ -51,12 +51,11 @@ private IGenAIEvaluationRecordQueryDAO getGenAIEvaluationRecordQueryDAO() {
return genAIEvaluationRecordQueryDAO;
}
- public boolean supportQueryGenAIEvaluationRecordByKeywords() {
- return getGenAIEvaluationRecordQueryDAO().supportQueryGenAIEvaluationRecordByKeywords();
- }
-
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId,
- String serviceInstanceId,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName,
+ String modelName,
+ Double minScore,
+ Double maxScore,
+ String sortField,
TraceScopeCondition relatedTrace,
Pagination paging,
Order queryOrder,
@@ -68,8 +67,8 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId,
if (traceContext != null) {
StringBuilder msg = new StringBuilder();
span = traceContext.createSpan("Query Service: queryGenAIEvaluationRecord");
- msg.append("ServiceId: ").append(serviceId).append(", ");
- msg.append("ServiceInstanceId: ").append(serviceInstanceId).append(", ");
+ msg.append("ProviderName: ").append(providerName).append(", ");
+ msg.append("ModelName: ").append(modelName).append(", ");
msg.append("RelatedTrace: ").append(relatedTrace).append(", ");
msg.append("Pagination: ").append(paging).append(", ");
msg.append("QueryOrder: ").append(queryOrder).append(", ");
@@ -78,7 +77,7 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId,
span.setMsg(msg.toString());
}
return queryGenAIEvaluationRecordInternal(
- serviceId, serviceInstanceId, relatedTrace, paging, queryOrder, duration, tags
+ providerName, modelName, minScore, maxScore, sortField, relatedTrace, paging, queryOrder, duration, tags
);
} finally {
if (traceContext != null) {
@@ -87,8 +86,11 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId,
}
}
- private GenAIEvaluationRecords queryGenAIEvaluationRecordInternal(String serviceId,
- String serviceInstanceId,
+ private GenAIEvaluationRecords queryGenAIEvaluationRecordInternal(String providerName,
+ String modelName,
+ Double minScore,
+ Double maxScore,
+ String sortField,
TraceScopeCondition relatedTrace,
Pagination paging,
Order queryOrder,
@@ -96,12 +98,8 @@ private GenAIEvaluationRecords queryGenAIEvaluationRecordInternal(String service
final List tags) throws IOException {
PaginationUtils.Page page = PaginationUtils.INSTANCE.exchange(paging);
- return getGenAIEvaluationRecordQueryDAO().queryGenAIEvaluationRecordDebuggable(serviceId,
- serviceInstanceId,
- relatedTrace,
- queryOrder,
- page.getFrom(), page.getLimit(),
- duration, tags
+ return getGenAIEvaluationRecordQueryDAO().queryGenAIEvaluationRecordDebuggable(
+ providerName, modelName, minScore, maxScore, sortField, relatedTrace, queryOrder, page.getFrom(), page.getLimit(), duration, tags
);
}
}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java
index e61f735dab94..7ba1856deaf7 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java
@@ -30,8 +30,11 @@
@Setter
@ToString
public class GenAIEvaluationRecordQueryCondition {
- private String serviceId;
- private String serviceInstanceId;
+ private String providerName;
+ private String modelName;
+ private Double minScore;
+ private Double maxScore;
+ private String sortField;
private TraceScopeCondition relatedTrace;
private Duration queryDuration;
private Pagination paging;
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java
index e2ce96d12ac3..9f2eb6ecad78 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java
@@ -38,12 +38,11 @@
public interface IGenAIEvaluationRecordQueryDAO extends Service {
- default boolean supportQueryGenAIEvaluationRecordByKeywords() {
- return false;
- }
-
- default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String serviceId,
- String serviceInstanceId,
+ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String providerName,
+ String modelName,
+ Double minScore,
+ Double maxScore,
+ String sortField,
TraceScopeCondition relatedTrace,
Order queryOrder,
int from,
@@ -56,8 +55,8 @@ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String servi
if (traceContext != null) {
span = traceContext.createSpan("Query Dao: queryGenAIEvaluationRecord");
StringBuilder msg = new StringBuilder();
- msg.append("ServiceId: ").append(serviceId)
- .append(", ServiceInstanceId: ").append(serviceInstanceId)
+ msg.append("ProviderName: ").append(providerName)
+ .append(", ModelName: ").append(modelName)
.append(", RelatedTrace: ").append(relatedTrace)
.append(", QueryOrder: ").append(queryOrder)
.append(", From: ").append(from)
@@ -67,7 +66,7 @@ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String servi
span.setMsg(msg.toString());
}
return queryGenAIEvaluationRecord(
- serviceId, serviceInstanceId, relatedTrace, queryOrder, from, limit, duration, tags
+ providerName, modelName, minScore, maxScore, sortField, relatedTrace, queryOrder, from, limit, duration, tags
);
} finally {
if (traceContext != null && span != null) {
@@ -76,8 +75,11 @@ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String servi
}
}
- GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId,
- String serviceInstanceId,
+ GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName,
+ String modelName,
+ Double minScore,
+ Double maxScore,
+ String sortField,
TraceScopeCondition relatedTrace,
Order queryOrder,
int from,
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
index 53acca440bb1..1588f302450b 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
@@ -46,10 +46,6 @@ public GenAIEvaluationRecordQuery(ModuleManager moduleManager) {
this.moduleManager = moduleManager;
}
- public boolean supportGenAIEvaluationRecordQueryByKeywords() {
- return getQueryService().supportQueryGenAIEvaluationRecordByKeywords();
- }
-
private GenAIEvaluationRecordQueryService getQueryService() {
if (genAIEvaluationRecordQueryService == null) {
this.genAIEvaluationRecordQueryService = moduleManager.find(CoreModule.NAME).provider().getService(GenAIEvaluationRecordQueryService.class);
@@ -98,8 +94,11 @@ private GenAIEvaluationRecords queryGenAIEvaluationRecord(
});
}
return getQueryService().queryGenAIEvaluationRecord(
- condition.getServiceId(),
- condition.getServiceInstanceId(),
+ condition.getProviderName(),
+ condition.getModelName(),
+ condition.getMinScore(),
+ condition.getMaxScore(),
+ condition.getSortField(),
condition.getRelatedTrace(),
condition.getPaging(),
queryOrder,
diff --git a/oap-server/server-starter/src/main/resources/ai-evaluation.yml b/oap-server/server-starter/src/main/resources/ai-evaluation.yml
index 69d2b85afa94..7ce14e231593 100644
--- a/oap-server/server-starter/src/main/resources/ai-evaluation.yml
+++ b/oap-server/server-starter/src/main/resources/ai-evaluation.yml
@@ -25,25 +25,6 @@ judge:
temperature: 0.2
max_tokens: 100000
-level:
- undefined: undefined
- score:
- - min: 0.0
- max: 0.3
- level: fail
- - min: 0.3
- max: 0.6
- level: warning
- - min: 0.6
- max: 0.85
- level: good
- - min: 0.85
- max: 1.0
- level: excellent
- boolean:
- true: excellent
- false: fail
-
system-prompt: |
You are an AI observability evaluator.
@@ -90,6 +71,25 @@ system-prompt: |
Return ONLY the JSON object.
+level:
+ undefined: undefined
+ score:
+ - min: 0.0
+ max: 0.3
+ level: fail
+ - min: 0.3
+ max: 0.6
+ level: warning
+ - min: 0.6
+ max: 0.85
+ level: good
+ - min: 0.85
+ max: 1.0
+ level: excellent
+ boolean:
+ true: excellent
+ false: fail
+
tasks:
- name: Faithfulness
valueType: SCORE
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
index 5c3e07e54f88..1941316e14a0 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
@@ -76,7 +76,7 @@
import org.apache.skywalking.oap.server.library.util.StringUtil;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.measure.BanyanDBEBPFProfilingScheduleQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.stream.BanyanDBEventQueryDAO;
-import org.apache.skywalking.oap.server.storage.plugin.banyandb.stream.BanyanDBGenAIGenAIEvaluationRecordQueryDAO;
+import org.apache.skywalking.oap.server.storage.plugin.banyandb.stream.BanyanDBGenAIEvaluationRecordQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.measure.BanyanDBHierarchyQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.measure.BanyanDBMetadataQueryDAO;
import org.apache.skywalking.oap.server.storage.plugin.banyandb.measure.BanyanDBMetricsQueryDAO;
@@ -178,7 +178,7 @@ IBatchDAO.class, new BanyanDBBatchDAO(client, config.getGlobal().getMaxBulkSize(
this.registerServiceImplementation(IAlarmQueryDAO.class, new BanyanDBAlarmQueryDAO(client));
this.registerServiceImplementation(ILogQueryDAO.class, new BanyanDBLogQueryDAO(client));
this.registerServiceImplementation(
- IGenAIEvaluationRecordQueryDAO.class, new BanyanDBGenAIGenAIEvaluationRecordQueryDAO(client));
+ IGenAIEvaluationRecordQueryDAO.class, new BanyanDBGenAIEvaluationRecordQueryDAO(client));
this.registerServiceImplementation(
IProfileTaskQueryDAO.class, new BanyanDBProfileTaskQueryDAO(client,
this.config.getGlobal().getProfileTaskQueryMaxSize()
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
index 2fcfcb1eed09..2fcc139f4271 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
@@ -48,8 +48,11 @@
public class GenAIEvaluationRecordQueryEsDAO extends EsDAO implements IGenAIEvaluationRecordQueryDAO {
private static final Set QUERYABLE_TAG_KEYS = Set.of(
GenAIEvaluationRecord.TRACE_ID,
- GenAIEvaluationRecord.SERVICE_ID,
- GenAIEvaluationRecord.SERVICE_INSTANCE_ID,
+ GenAIEvaluationRecord.SERVICE_NAME,
+ GenAIEvaluationRecord.PROVIDER_NAME,
+ GenAIEvaluationRecord.MODEL_NAME,
+ GenAIEvaluationRecord.OPERATION_NAME,
+ GenAIEvaluationRecord.SCORE_VALUE,
GenAIEvaluationRecord.SEGMENT_ID,
GenAIEvaluationRecord.SPAN_ID,
GenAIEvaluationRecord.SPAN_TYPE,
@@ -66,8 +69,9 @@ public GenAIEvaluationRecordQueryEsDAO(final ElasticSearchClient client) {
}
@Override
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
- final String serviceInstanceId,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerName,
+ final String modelName,
+ final Double minScore, final Double maxScore, final String sortField,
final TraceScopeCondition relatedTrace,
final Order queryOrder,
final int from,
@@ -91,11 +95,17 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
if (startSecondTB != 0 && endSecondTB != 0) {
query.must(Query.range(Record.TIME_BUCKET).gte(startSecondTB).lte(endSecondTB));
}
- if (isNotEmpty(serviceId)) {
- query.must(Query.term(GenAIEvaluationRecord.SERVICE_ID, serviceId));
+ if (isNotEmpty(providerName)) {
+ query.must(Query.term(GenAIEvaluationRecord.PROVIDER_NAME, providerName));
}
- if (isNotEmpty(serviceInstanceId)) {
- query.must(Query.term(GenAIEvaluationRecord.SERVICE_INSTANCE_ID, serviceInstanceId));
+ if (isNotEmpty(modelName)) {
+ query.must(Query.term(GenAIEvaluationRecord.MODEL_NAME, modelName));
+ }
+ if (minScore != null || maxScore != null) {
+ final var scoreRange = Query.range(GenAIEvaluationRecord.SCORE_VALUE);
+ if (minScore != null) scoreRange.gte(GenAIEvaluationRecord.minScoreValuePpm(minScore));
+ if (maxScore != null) scoreRange.lte(GenAIEvaluationRecord.maxScoreValuePpm(maxScore));
+ query.must(scoreRange);
}
if (nonNull(relatedTrace)) {
if (isNotEmpty(relatedTrace.getTraceId())) {
@@ -122,7 +132,7 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
final SearchBuilder search = Search.builder()
.query(query)
.sort(
- GenAIEvaluationRecord.EVALUATION_TIME,
+ GenAIEvaluationRecord.SCORE_VALUE.equals(sortField) ? GenAIEvaluationRecord.SCORE_VALUE : GenAIEvaluationRecord.EVALUATION_TIME,
Order.DES.equals(queryOrder) ? Sort.Order.DESC : Sort.Order.ASC
)
.size(limit)
@@ -146,8 +156,12 @@ private GenAIEvaluationRecord parseRecord(final SearchHit searchHit) {
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
record.setUniqueId((String) source.get(GenAIEvaluationRecord.UNIQUE_ID));
record.setTraceId((String) source.get(GenAIEvaluationRecord.TRACE_ID));
- record.setServiceId((String) source.get(GenAIEvaluationRecord.SERVICE_ID));
- record.setServiceInstanceId((String) source.get(GenAIEvaluationRecord.SERVICE_INSTANCE_ID));
+ record.setServiceName((String) source.get(GenAIEvaluationRecord.SERVICE_NAME));
+ record.setProviderName((String) source.get(GenAIEvaluationRecord.PROVIDER_NAME));
+ record.setModelName((String) source.get(GenAIEvaluationRecord.MODEL_NAME));
+ record.setOperationName((String) source.get(GenAIEvaluationRecord.OPERATION_NAME));
+ final Number scoreValue = (Number) source.get(GenAIEvaluationRecord.SCORE_VALUE);
+ record.setScoreValuePpm(scoreValue == null ? null : scoreValue.longValue());
record.setSegmentId((String) source.get(GenAIEvaluationRecord.SEGMENT_ID));
record.setSpanId((String) source.get(GenAIEvaluationRecord.SPAN_ID));
record.setSpanType((String) source.get(GenAIEvaluationRecord.SPAN_TYPE));
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
index 2101afe75adb..7fc98f49c02a 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
@@ -50,8 +50,11 @@
public class JDBCGenAIEvaluationRecordQueryDAO implements IGenAIEvaluationRecordQueryDAO {
private static final Set QUERYABLE_TAG_KEYS = Set.of(
GenAIEvaluationRecord.TRACE_ID,
- GenAIEvaluationRecord.SERVICE_ID,
- GenAIEvaluationRecord.SERVICE_INSTANCE_ID,
+ GenAIEvaluationRecord.SERVICE_NAME,
+ GenAIEvaluationRecord.PROVIDER_NAME,
+ GenAIEvaluationRecord.MODEL_NAME,
+ GenAIEvaluationRecord.OPERATION_NAME,
+ GenAIEvaluationRecord.SCORE_VALUE,
GenAIEvaluationRecord.SEGMENT_ID,
GenAIEvaluationRecord.SPAN_ID,
GenAIEvaluationRecord.SPAN_TYPE,
@@ -66,8 +69,11 @@ public class JDBCGenAIEvaluationRecordQueryDAO implements IGenAIEvaluationRecord
private static final List SELECTED_COLUMNS = List.of(
GenAIEvaluationRecord.UNIQUE_ID,
GenAIEvaluationRecord.TRACE_ID,
- GenAIEvaluationRecord.SERVICE_ID,
- GenAIEvaluationRecord.SERVICE_INSTANCE_ID,
+ GenAIEvaluationRecord.SERVICE_NAME,
+ GenAIEvaluationRecord.PROVIDER_NAME,
+ GenAIEvaluationRecord.MODEL_NAME,
+ GenAIEvaluationRecord.OPERATION_NAME,
+ GenAIEvaluationRecord.SCORE_VALUE,
GenAIEvaluationRecord.SEGMENT_ID,
GenAIEvaluationRecord.SPAN_ID,
GenAIEvaluationRecord.SPAN_TYPE,
@@ -85,8 +91,9 @@ public class JDBCGenAIEvaluationRecordQueryDAO implements IGenAIEvaluationRecord
@Override
@SneakyThrows
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
- final String serviceInstanceId,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerName,
+ final String modelName,
+ final Double minScore, final Double maxScore, final String sortField,
final TraceScopeCondition relatedTrace,
final Order queryOrder,
final int from,
@@ -117,7 +124,7 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
final var records = new ArrayList();
for (final var table : tables) {
final var sqlAndParameters = buildSQL(
- serviceId, serviceInstanceId, relatedTrace, queryOrder, from, limit, duration, tags, table);
+ providerName, modelName, minScore, maxScore, sortField, relatedTrace, queryOrder, from, limit, duration, tags, table);
records.addAll(
jdbcClient.executeQuery(
sqlAndParameters.sql(),
@@ -141,8 +148,12 @@ protected ArrayList parseResults(final ResultSet resultSe
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
record.setUniqueId(resultSet.getString(GenAIEvaluationRecord.UNIQUE_ID));
record.setTraceId(resultSet.getString(GenAIEvaluationRecord.TRACE_ID));
- record.setServiceId(resultSet.getString(GenAIEvaluationRecord.SERVICE_ID));
- record.setServiceInstanceId(resultSet.getString(GenAIEvaluationRecord.SERVICE_INSTANCE_ID));
+ record.setServiceName(resultSet.getString(GenAIEvaluationRecord.SERVICE_NAME));
+ record.setProviderName(resultSet.getString(GenAIEvaluationRecord.PROVIDER_NAME));
+ record.setModelName(resultSet.getString(GenAIEvaluationRecord.MODEL_NAME));
+ record.setOperationName(resultSet.getString(GenAIEvaluationRecord.OPERATION_NAME));
+ final long scoreValue = resultSet.getLong(GenAIEvaluationRecord.SCORE_VALUE);
+ record.setScoreValuePpm(resultSet.wasNull() ? null : scoreValue);
record.setSegmentId(resultSet.getString(GenAIEvaluationRecord.SEGMENT_ID));
record.setSpanId(resultSet.getString(GenAIEvaluationRecord.SPAN_ID));
record.setSpanType(resultSet.getString(GenAIEvaluationRecord.SPAN_TYPE));
@@ -158,8 +169,11 @@ protected ArrayList parseResults(final ResultSet resultSe
return records;
}
- protected SQLAndParameters buildSQL(final String serviceId,
- final String serviceInstanceId,
+ protected SQLAndParameters buildSQL(final String providerName,
+ final String modelName,
+ final Double minScore,
+ final Double maxScore,
+ final String sortField,
final TraceScopeCondition relatedTrace,
final Order queryOrder,
final int from,
@@ -190,13 +204,21 @@ protected SQLAndParameters buildSQL(final String serviceId,
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.TIME_BUCKET)).append(" <= ?");
parameters.add(endSecondTB);
}
- if (StringUtil.isNotEmpty(serviceId)) {
- sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SERVICE_ID)).append(" = ?");
- parameters.add(serviceId);
+ if (StringUtil.isNotEmpty(providerName)) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.PROVIDER_NAME)).append(" = ?");
+ parameters.add(providerName);
}
- if (StringUtil.isNotEmpty(serviceInstanceId)) {
- sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SERVICE_INSTANCE_ID)).append(" = ?");
- parameters.add(serviceInstanceId);
+ if (StringUtil.isNotEmpty(modelName)) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.MODEL_NAME)).append(" = ?");
+ parameters.add(modelName);
+ }
+ if (minScore != null) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SCORE_VALUE)).append(" >= ?");
+ parameters.add(GenAIEvaluationRecord.minScoreValuePpm(minScore));
+ }
+ if (maxScore != null) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SCORE_VALUE)).append(" <= ?");
+ parameters.add(GenAIEvaluationRecord.maxScoreValuePpm(maxScore));
}
if (nonNull(relatedTrace)) {
if (StringUtil.isNotEmpty(relatedTrace.getTraceId())) {
@@ -224,7 +246,8 @@ protected SQLAndParameters buildSQL(final String serviceId,
}
sql.append(" order by ")
- .append(storageColumn(GenAIEvaluationRecord.EVALUATION_TIME))
+ .append(storageColumn(GenAIEvaluationRecord.SCORE_VALUE.equals(sortField)
+ ? GenAIEvaluationRecord.SCORE_VALUE : GenAIEvaluationRecord.EVALUATION_TIME))
.append(" ")
.append(Order.DES.equals(queryOrder) ? "desc" : "asc");
sql.append(" limit ").append(from + limit);
From 6e21b8cc6aa39dc19c46b6a1526ffb4c4cf7ceba Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Tue, 4 Aug 2026 00:29:05 +0800
Subject: [PATCH 6/8] fix
---
...anyanDBGenAIEvaluationRecordQueryDAO.java} | 101 ++++++++++--------
1 file changed, 55 insertions(+), 46 deletions(-)
rename oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/{BanyanDBGenAIGenAIEvaluationRecordQueryDAO.java => BanyanDBGenAIEvaluationRecordQueryDAO.java} (60%)
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIGenAIEvaluationRecordQueryDAO.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java
similarity index 60%
rename from oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIGenAIEvaluationRecordQueryDAO.java
rename to oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java
index d0c3cadfaa0a..39afc138bfd9 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java
@@ -19,9 +19,7 @@
package org.apache.skywalking.oap.server.storage.plugin.banyandb.stream;
import com.google.common.collect.ImmutableSet;
-import org.apache.skywalking.library.banyandb.v1.client.AbstractQuery;
import org.apache.skywalking.library.banyandb.v1.client.RowEntity;
-import org.apache.skywalking.library.banyandb.v1.client.StreamQuery;
import org.apache.skywalking.library.banyandb.v1.client.StreamQueryResponse;
import org.apache.skywalking.oap.server.core.analysis.manual.genai.GenAIEvaluationRecord;
import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
@@ -42,11 +40,14 @@
/**
* {@link org.apache.skywalking.oap.server.core.analysis.manual.log.LogRecord} is a gen-ai evaluation result
*/
-public class BanyanDBGenAIGenAIEvaluationRecordQueryDAO extends AbstractBanyanDBDAO implements IGenAIEvaluationRecordQueryDAO {
+public class BanyanDBGenAIEvaluationRecordQueryDAO extends AbstractBanyanDBDAO implements IGenAIEvaluationRecordQueryDAO {
private static final Set TAGS = ImmutableSet.of(
GenAIEvaluationRecord.TRACE_ID,
- GenAIEvaluationRecord.SERVICE_ID,
- GenAIEvaluationRecord.SERVICE_INSTANCE_ID,
+ GenAIEvaluationRecord.SERVICE_NAME,
+ GenAIEvaluationRecord.PROVIDER_NAME,
+ GenAIEvaluationRecord.MODEL_NAME,
+ GenAIEvaluationRecord.OPERATION_NAME,
+ GenAIEvaluationRecord.SCORE_VALUE,
GenAIEvaluationRecord.SEGMENT_ID,
GenAIEvaluationRecord.SPAN_ID,
GenAIEvaluationRecord.SPAN_TYPE,
@@ -59,65 +60,68 @@ public class BanyanDBGenAIGenAIEvaluationRecordQueryDAO extends AbstractBanyanDB
GenAIEvaluationRecord.EVALUATION_TIME
);
- public BanyanDBGenAIGenAIEvaluationRecordQueryDAO(BanyanDBStorageClient client) {
+ public BanyanDBGenAIEvaluationRecordQueryDAO(BanyanDBStorageClient client) {
super(client);
}
@Override
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId, String serviceInstanceId,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName, String modelName, Double minScore, Double maxScore, String sortField,
TraceScopeCondition relatedTrace, Order queryOrder, int from, int limit,
Duration duration, List tags) throws IOException {
final boolean isColdStage = duration != null && duration.isColdStage();
- final QueryBuilder query = new QueryBuilder<>() {
- @Override
- public void apply(StreamQuery query) {
- if (StringUtil.isNotEmpty(serviceId)) {
- query.and(eq(GenAIEvaluationRecord.SERVICE_ID, serviceId));
- }
- if (StringUtil.isNotEmpty(serviceInstanceId)) {
- query.and(eq(GenAIEvaluationRecord.SERVICE_INSTANCE_ID, serviceInstanceId));
- }
+ final Conditions where = Conditions.create();
+ if (StringUtil.isNotEmpty(providerName)) {
+ where.eq(GenAIEvaluationRecord.PROVIDER_NAME, providerName);
+ }
+ if (StringUtil.isNotEmpty(modelName)) {
+ where.eq(GenAIEvaluationRecord.MODEL_NAME, modelName);
+ }
+ if (minScore != null) {
+ where.gte(GenAIEvaluationRecord.SCORE_VALUE, GenAIEvaluationRecord.minScoreValuePpm(minScore));
+ }
+ if (maxScore != null) {
+ where.lte(GenAIEvaluationRecord.SCORE_VALUE, GenAIEvaluationRecord.maxScoreValuePpm(maxScore));
+ }
- if (Objects.nonNull(relatedTrace)) {
- if (StringUtil.isNotEmpty(relatedTrace.getTraceId())) {
- query.and(eq(GenAIEvaluationRecord.TRACE_ID, relatedTrace.getTraceId()));
- }
- if (StringUtil.isNotEmpty(relatedTrace.getSegmentId())) {
- query.and(eq(GenAIEvaluationRecord.SEGMENT_ID, relatedTrace.getSegmentId()));
- }
- if (Objects.nonNull(relatedTrace.getSpanId())) {
- query.and(eq(GenAIEvaluationRecord.SPAN_ID, (long) relatedTrace.getSpanId()));
- }
- }
+ if (Objects.nonNull(relatedTrace)) {
+ if (StringUtil.isNotEmpty(relatedTrace.getTraceId())) {
+ where.eq(GenAIEvaluationRecord.TRACE_ID, relatedTrace.getTraceId());
+ }
+ if (StringUtil.isNotEmpty(relatedTrace.getSegmentId())) {
+ where.eq(GenAIEvaluationRecord.SEGMENT_ID, relatedTrace.getSegmentId());
+ }
+ if (Objects.nonNull(relatedTrace.getSpanId())) {
+ where.eq(GenAIEvaluationRecord.SPAN_ID, (long) relatedTrace.getSpanId());
+ }
+ }
- if (CollectionUtils.isNotEmpty(tags)) {
- for (final Tag tag : tags) {
- if (StringUtil.isNotEmpty(tag.getKey()) && StringUtil.isNotEmpty(tag.getValue())) {
- query.and(eq(tag.getKey(), tag.getValue()));
- }
- }
- }
- if (queryOrder == Order.ASC) {
- query.setOrderBy(
- new AbstractQuery.OrderBy(AbstractQuery.Sort.ASC));
- } else {
- query.setOrderBy(
- new AbstractQuery.OrderBy(AbstractQuery.Sort.DESC));
+ if (CollectionUtils.isNotEmpty(tags)) {
+ for (final Tag tag : tags) {
+ if (StringUtil.isNotEmpty(tag.getKey()) && StringUtil.isNotEmpty(tag.getValue())) {
+ where.eq(tag.getKey(), tag.getValue());
}
- query.setLimit(limit);
- query.setOffset(from);
}
- };
+ }
+ if (queryOrder == Order.ASC) {
+ where.orderBy(sortColumn(sortField), "ASC");
+ } else {
+ where.orderByDesc(sortColumn(sortField));
+ }
+ where.limit(limit).offset(from);
- StreamQueryResponse resp = queryDebuggable(isColdStage, GenAIEvaluationRecord.INDEX_NAME, TAGS, getTimestampRange(duration), query);
+ StreamQueryResponse resp = queryDebuggable(isColdStage, GenAIEvaluationRecord.INDEX_NAME, TAGS, getTimestampRange(duration), where);
GenAIEvaluationRecords genAIEvaluationRecords = new GenAIEvaluationRecords();
for (final RowEntity rowEntity : resp.getElements()) {
GenAIEvaluationRecord evaluationRecord = new GenAIEvaluationRecord();
evaluationRecord.setTraceId(rowEntity.getTagValue(GenAIEvaluationRecord.TRACE_ID));
- evaluationRecord.setServiceId(rowEntity.getTagValue(GenAIEvaluationRecord.SERVICE_ID));
- evaluationRecord.setServiceInstanceId(rowEntity.getTagValue(GenAIEvaluationRecord.SERVICE_INSTANCE_ID));
+ evaluationRecord.setServiceName(rowEntity.getTagValue(GenAIEvaluationRecord.SERVICE_NAME));
+ evaluationRecord.setProviderName(rowEntity.getTagValue(GenAIEvaluationRecord.PROVIDER_NAME));
+ evaluationRecord.setModelName(rowEntity.getTagValue(GenAIEvaluationRecord.MODEL_NAME));
+ evaluationRecord.setOperationName(rowEntity.getTagValue(GenAIEvaluationRecord.OPERATION_NAME));
+ final Number scoreValue = (Number) rowEntity.getTagValue(GenAIEvaluationRecord.SCORE_VALUE);
+ evaluationRecord.setScoreValuePpm(scoreValue == null ? null : scoreValue.longValue());
evaluationRecord.setSegmentId(rowEntity.getTagValue(GenAIEvaluationRecord.SEGMENT_ID));
evaluationRecord.setSpanId(rowEntity.getTagValue(GenAIEvaluationRecord.SPAN_ID));
evaluationRecord.setSpanType(rowEntity.getTagValue(GenAIEvaluationRecord.SPAN_TYPE));
@@ -132,4 +136,9 @@ public void apply(StreamQuery query) {
}
return genAIEvaluationRecords;
}
+
+ private static String sortColumn(final String sortField) {
+ return GenAIEvaluationRecord.SCORE_VALUE.equals(sortField)
+ ? GenAIEvaluationRecord.SCORE_VALUE : GenAIEvaluationRecord.EVALUATION_TIME;
+ }
}
From 65f7381980a6690ca5947f5a996415590bb2143f Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Thu, 6 Aug 2026 00:33:08 +0800
Subject: [PATCH 7/8] fix
---
.../span/SpanAIEvaluationStrategy.java | 2 +-
.../manual/genai/GenAIEvaluationRecord.java | 128 ++++++++++++------
.../GenAIEvaluationRecordQueryService.java | 19 ++-
.../GenAIEvaluationRecordQueryCondition.java | 4 +
.../query/type/GenAIEvaluationRecords.java | 1 -
.../query/IGenAIEvaluationRecordQueryDAO.java | 18 ++-
.../resolver/GenAIEvaluationRecordQuery.java | 4 +
...BanyanDBGenAIEvaluationRecordQueryDAO.java | 48 ++++---
.../GenAIEvaluationRecordQueryEsDAO.java | 38 ++++--
.../JDBCGenAIEvaluationRecordQueryDAO.java | 71 +++++++---
10 files changed, 226 insertions(+), 107 deletions(-)
diff --git a/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java b/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java
index 987c01402bf3..1e62f1eff7a9 100644
--- a/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java
+++ b/oap-server/ai-evaluation/src/main/java/org/apache/skywalking/oap/server/ai/evaluation/service/strategy/span/SpanAIEvaluationStrategy.java
@@ -127,7 +127,7 @@ private void persistResults(final AIEvaluationContext context,
record.setModelName(namingControl.formatInstanceName(context.getModelName()));
record.setOperationName(operationName(context));
record.setSegmentId(context.getSegmentId());
- record.setSpanId(context.getSpanId());
+ record.setSpanId(Integer.parseInt(context.getSpanId()));
record.setSpanType(plan.getSpanType() == null ? "" : plan.getSpanType().name());
record.setTaskName(result.getName());
record.setValueType(result.getValueType() == null ? "" : result.getValueType().name());
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java
index 1c7f0e71205c..88b6c91f587b 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/manual/genai/GenAIEvaluationRecord.java
@@ -20,8 +20,11 @@
import java.math.BigDecimal;
import java.math.RoundingMode;
+
import lombok.Getter;
import lombok.Setter;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+import org.apache.skywalking.oap.server.core.analysis.Layer;
import org.apache.skywalking.oap.server.core.analysis.Stream;
import org.apache.skywalking.oap.server.core.analysis.record.Record;
import org.apache.skywalking.oap.server.core.analysis.worker.RecordStreamProcessor;
@@ -49,9 +52,9 @@ public class GenAIEvaluationRecord extends Record {
public static final String INDEX_NAME = "gen_ai_evaluation_record";
public static final String UNIQUE_ID = "unique_id";
public static final String TRACE_ID = "trace_id";
- public static final String PROVIDER_NAME = "provider_name";
- public static final String MODEL_NAME = "model_name";
- public static final String SERVICE_NAME = "service_name";
+ public static final String PROVIDER_ID = "provider_id";
+ public static final String MODEL_ID = "model_id";
+ public static final String SERVICE_ID = "service_id";
public static final String OPERATION_NAME = "operation_name";
public static final String SCORE_VALUE = "score_value";
public static final long SCORE_SCALE = 1_000_000L;
@@ -74,18 +77,18 @@ public class GenAIEvaluationRecord extends Record {
private String traceId;
@ElasticSearch.EnableDocValues
- @Column(name = SERVICE_NAME, length = 150)
- private String serviceName;
+ @Column(name = SERVICE_ID, length = 150)
+ private String serviceId;
@ElasticSearch.EnableDocValues
- @Column(name = PROVIDER_NAME, length = 150)
+ @Column(name = PROVIDER_ID, length = 150)
@BanyanDB.SeriesID(index = 0)
- private String providerName;
+ private String providerId;
@ElasticSearch.EnableDocValues
- @Column(name = MODEL_NAME, length = 150)
+ @Column(name = MODEL_ID, length = 150)
@BanyanDB.SeriesID(index = 1)
- private String modelName;
+ private String modelId;
@ElasticSearch.EnableDocValues
@Column(name = OPERATION_NAME, length = 150)
@@ -96,36 +99,12 @@ public class GenAIEvaluationRecord extends Record {
@Column(name = SCORE_VALUE)
private Long scoreValuePpm;
- public Double getScoreValue() {
- return scoreValuePpm == null ? null : scoreValuePpm / (double) SCORE_SCALE;
- }
-
- public static Long toScoreValuePpm(final Double score) {
- return score == null ? null : BigDecimal.valueOf(score)
- .movePointRight(6)
- .setScale(0, RoundingMode.HALF_UP)
- .longValueExact();
- }
-
- public static long minScoreValuePpm(final double score) {
- return BigDecimal.valueOf(score)
- .movePointRight(6)
- .setScale(0, RoundingMode.CEILING)
- .longValueExact();
- }
-
- public static long maxScoreValuePpm(final double score) {
- return BigDecimal.valueOf(score)
- .movePointRight(6)
- .setScale(0, RoundingMode.FLOOR)
- .longValueExact();
- }
-
@Column(name = SEGMENT_ID, length = 150, storageOnly = true)
private String segmentId;
- @Column(name = SPAN_ID, length = 150, storageOnly = true)
- private String spanId;
+ @Column(name = SPAN_ID)
+ @BanyanDB.NoIndexing
+ private int spanId;
@Column(name = SPAN_TYPE, length = 64, storageOnly = true)
private String spanType;
@@ -148,13 +127,74 @@ public static long maxScoreValuePpm(final double score) {
@Column(name = REASON, length = 4096, storageOnly = true)
private String reason;
- @Column(name = JUDGE_MODEL, length = 64, storageOnly = true)
+ @Column(name = JUDGE_MODEL, length = 64)
private String judgeModel;
@ElasticSearch.EnableDocValues
@Column(name = EVALUATION_TIME)
private long evaluationTime;
+ public Double getScoreValue() {
+ return scoreValuePpm == null ? null : scoreValuePpm / (double) SCORE_SCALE;
+ }
+
+ public static Long toScoreValuePpm(final Double score) {
+ return score == null ? null : BigDecimal.valueOf(score)
+ .movePointRight(6)
+ .setScale(0, RoundingMode.HALF_UP)
+ .longValueExact();
+ }
+
+ public static long minScoreValuePpm(final double score) {
+ return BigDecimal.valueOf(score)
+ .movePointRight(6)
+ .setScale(0, RoundingMode.CEILING)
+ .longValueExact();
+ }
+
+ public static long maxScoreValuePpm(final double score) {
+ return BigDecimal.valueOf(score)
+ .movePointRight(6)
+ .setScale(0, RoundingMode.FLOOR)
+ .longValueExact();
+ }
+
+ public static String toEntityId(final String name) {
+ return IDManager.ServiceID.buildId(name, Layer.VIRTUAL_GENAI.isNormal());
+ }
+
+ public static String toServiceId(final String name) {
+ return IDManager.ServiceID.buildId(name, Layer.GENAI.isNormal());
+ }
+
+ public String getServiceName() {
+ return decodeName(serviceId);
+ }
+
+ public void setServiceName(final String serviceName) {
+ this.serviceId = toServiceId(serviceName);
+ }
+
+ public String getProviderName() {
+ return decodeName(providerId);
+ }
+
+ public void setProviderName(final String providerName) {
+ this.providerId = toEntityId(providerName);
+ }
+
+ public String getModelName() {
+ return decodeName(modelId);
+ }
+
+ public void setModelName(final String modelName) {
+ this.modelId = toEntityId(modelName);
+ }
+
+ private static String decodeName(final String id) {
+ return id == null ? null : IDManager.ServiceID.analysisId(id).getName();
+ }
+
@Override
public StorageID id() {
return new StorageID().append(UNIQUE_ID, uniqueId);
@@ -166,14 +206,14 @@ public GenAIEvaluationRecord storage2Entity(final Convert2Entity converter) {
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
record.setUniqueId((String) converter.get(UNIQUE_ID));
record.setTraceId((String) converter.get(TRACE_ID));
- record.setServiceName((String) converter.get(SERVICE_NAME));
- record.setProviderName((String) converter.get(PROVIDER_NAME));
- record.setModelName((String) converter.get(MODEL_NAME));
+ record.setServiceId((String) converter.get(SERVICE_ID));
+ record.setProviderId((String) converter.get(PROVIDER_ID));
+ record.setModelId((String) converter.get(MODEL_ID));
record.setOperationName((String) converter.get(OPERATION_NAME));
final Number scoreValue = (Number) converter.get(SCORE_VALUE);
record.setScoreValuePpm(scoreValue == null ? null : scoreValue.longValue());
record.setSegmentId((String) converter.get(SEGMENT_ID));
- record.setSpanId((String) converter.get(SPAN_ID));
+ record.setSpanId(((Number) converter.get(SPAN_ID)).intValue());
record.setSpanType((String) converter.get(SPAN_TYPE));
record.setTaskName((String) converter.get(TASK_NAME));
record.setValueType((String) converter.get(VALUE_TYPE));
@@ -190,9 +230,9 @@ public GenAIEvaluationRecord storage2Entity(final Convert2Entity converter) {
public void entity2Storage(final GenAIEvaluationRecord storageData, final Convert2Storage converter) {
converter.accept(UNIQUE_ID, storageData.getUniqueId());
converter.accept(TRACE_ID, storageData.getTraceId());
- converter.accept(SERVICE_NAME, storageData.getServiceName());
- converter.accept(PROVIDER_NAME, storageData.getProviderName());
- converter.accept(MODEL_NAME, storageData.getModelName());
+ converter.accept(SERVICE_ID, storageData.getServiceId());
+ converter.accept(PROVIDER_ID, storageData.getProviderId());
+ converter.accept(MODEL_ID, storageData.getModelId());
converter.accept(OPERATION_NAME, storageData.getOperationName());
converter.accept(SCORE_VALUE, storageData.getScoreValuePpm());
converter.accept(SEGMENT_ID, storageData.getSegmentId());
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java
index 9aadacd9ae37..7a3c5f2167c3 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java
@@ -51,11 +51,15 @@ private IGenAIEvaluationRecordQueryDAO getGenAIEvaluationRecordQueryDAO() {
return genAIEvaluationRecordQueryDAO;
}
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceName,
+ String providerName,
String modelName,
Double minScore,
Double maxScore,
String sortField,
+ String taskName,
+ String evaluationLevel,
+ String judgeModel,
TraceScopeCondition relatedTrace,
Pagination paging,
Order queryOrder,
@@ -67,6 +71,7 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName,
if (traceContext != null) {
StringBuilder msg = new StringBuilder();
span = traceContext.createSpan("Query Service: queryGenAIEvaluationRecord");
+ msg.append("ServiceName: ").append(serviceName).append(", ");
msg.append("ProviderName: ").append(providerName).append(", ");
msg.append("ModelName: ").append(modelName).append(", ");
msg.append("RelatedTrace: ").append(relatedTrace).append(", ");
@@ -77,7 +82,8 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName,
span.setMsg(msg.toString());
}
return queryGenAIEvaluationRecordInternal(
- providerName, modelName, minScore, maxScore, sortField, relatedTrace, paging, queryOrder, duration, tags
+ serviceName, providerName, modelName, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
+ relatedTrace, paging, queryOrder, duration, tags
);
} finally {
if (traceContext != null) {
@@ -86,11 +92,15 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName,
}
}
- private GenAIEvaluationRecords queryGenAIEvaluationRecordInternal(String providerName,
+ private GenAIEvaluationRecords queryGenAIEvaluationRecordInternal(String serviceName,
+ String providerName,
String modelName,
Double minScore,
Double maxScore,
String sortField,
+ String taskName,
+ String evaluationLevel,
+ String judgeModel,
TraceScopeCondition relatedTrace,
Pagination paging,
Order queryOrder,
@@ -99,7 +109,8 @@ private GenAIEvaluationRecords queryGenAIEvaluationRecordInternal(String provide
PaginationUtils.Page page = PaginationUtils.INSTANCE.exchange(paging);
return getGenAIEvaluationRecordQueryDAO().queryGenAIEvaluationRecordDebuggable(
- providerName, modelName, minScore, maxScore, sortField, relatedTrace, queryOrder, page.getFrom(), page.getLimit(), duration, tags
+ serviceName, providerName, modelName, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
+ relatedTrace, queryOrder, page.getFrom(), page.getLimit(), duration, tags
);
}
}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java
index 7ba1856deaf7..64529eb32417 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java
@@ -30,11 +30,15 @@
@Setter
@ToString
public class GenAIEvaluationRecordQueryCondition {
+ private String serviceName;
private String providerName;
private String modelName;
private Double minScore;
private Double maxScore;
private String sortField;
+ private String taskName;
+ private String evaluationLevel;
+ private String judgeModel;
private TraceScopeCondition relatedTrace;
private Duration queryDuration;
private Pagination paging;
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/GenAIEvaluationRecords.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/GenAIEvaluationRecords.java
index 1d8f20b61d50..29d1b504b05f 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/GenAIEvaluationRecords.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/type/GenAIEvaluationRecords.java
@@ -32,7 +32,6 @@
@Accessors(chain = true)
public class GenAIEvaluationRecords {
private final List genAIEvaluationRecordList;
- private String errorReason;
private DebuggingTrace debuggingTrace;
public GenAIEvaluationRecords() {
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java
index 9f2eb6ecad78..239c937c9de4 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java
@@ -38,11 +38,15 @@
public interface IGenAIEvaluationRecordQueryDAO extends Service {
- default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String providerName,
+ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String serviceName,
+ String providerName,
String modelName,
Double minScore,
Double maxScore,
String sortField,
+ String taskName,
+ String evaluationLevel,
+ String judgeModel,
TraceScopeCondition relatedTrace,
Order queryOrder,
int from,
@@ -55,7 +59,8 @@ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String provi
if (traceContext != null) {
span = traceContext.createSpan("Query Dao: queryGenAIEvaluationRecord");
StringBuilder msg = new StringBuilder();
- msg.append("ProviderName: ").append(providerName)
+ msg.append("ServiceName: ").append(serviceName)
+ .append(", ProviderName: ").append(providerName)
.append(", ModelName: ").append(modelName)
.append(", RelatedTrace: ").append(relatedTrace)
.append(", QueryOrder: ").append(queryOrder)
@@ -66,7 +71,8 @@ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String provi
span.setMsg(msg.toString());
}
return queryGenAIEvaluationRecord(
- providerName, modelName, minScore, maxScore, sortField, relatedTrace, queryOrder, from, limit, duration, tags
+ serviceName, providerName, modelName, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
+ relatedTrace, queryOrder, from, limit, duration, tags
);
} finally {
if (traceContext != null && span != null) {
@@ -75,11 +81,15 @@ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String provi
}
}
- GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName,
+ GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceName,
+ String providerName,
String modelName,
Double minScore,
Double maxScore,
String sortField,
+ String taskName,
+ String evaluationLevel,
+ String judgeModel,
TraceScopeCondition relatedTrace,
Order queryOrder,
int from,
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
index 1588f302450b..5b3f00f479c3 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
@@ -94,11 +94,15 @@ private GenAIEvaluationRecords queryGenAIEvaluationRecord(
});
}
return getQueryService().queryGenAIEvaluationRecord(
+ condition.getServiceName(),
condition.getProviderName(),
condition.getModelName(),
condition.getMinScore(),
condition.getMaxScore(),
condition.getSortField(),
+ condition.getTaskName(),
+ condition.getEvaluationLevel(),
+ condition.getJudgeModel(),
condition.getRelatedTrace(),
condition.getPaging(),
queryOrder,
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java
index 39afc138bfd9..d98d08349f5b 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java
@@ -43,9 +43,9 @@
public class BanyanDBGenAIEvaluationRecordQueryDAO extends AbstractBanyanDBDAO implements IGenAIEvaluationRecordQueryDAO {
private static final Set TAGS = ImmutableSet.of(
GenAIEvaluationRecord.TRACE_ID,
- GenAIEvaluationRecord.SERVICE_NAME,
- GenAIEvaluationRecord.PROVIDER_NAME,
- GenAIEvaluationRecord.MODEL_NAME,
+ GenAIEvaluationRecord.SERVICE_ID,
+ GenAIEvaluationRecord.PROVIDER_ID,
+ GenAIEvaluationRecord.MODEL_ID,
GenAIEvaluationRecord.OPERATION_NAME,
GenAIEvaluationRecord.SCORE_VALUE,
GenAIEvaluationRecord.SEGMENT_ID,
@@ -65,16 +65,20 @@ public BanyanDBGenAIEvaluationRecordQueryDAO(BanyanDBStorageClient client) {
}
@Override
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName, String modelName, Double minScore, Double maxScore, String sortField,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceName, String providerName, String modelName, Double minScore, Double maxScore, String sortField,
+ String taskName, String evaluationLevel, String judgeModel,
TraceScopeCondition relatedTrace, Order queryOrder, int from, int limit,
Duration duration, List tags) throws IOException {
final boolean isColdStage = duration != null && duration.isColdStage();
final Conditions where = Conditions.create();
+ if (StringUtil.isNotEmpty(serviceName)) {
+ where.eq(GenAIEvaluationRecord.SERVICE_ID, GenAIEvaluationRecord.toServiceId(serviceName));
+ }
if (StringUtil.isNotEmpty(providerName)) {
- where.eq(GenAIEvaluationRecord.PROVIDER_NAME, providerName);
+ where.eq(GenAIEvaluationRecord.PROVIDER_ID, GenAIEvaluationRecord.toEntityId(providerName));
}
if (StringUtil.isNotEmpty(modelName)) {
- where.eq(GenAIEvaluationRecord.MODEL_NAME, modelName);
+ where.eq(GenAIEvaluationRecord.MODEL_ID, GenAIEvaluationRecord.toEntityId(modelName));
}
if (minScore != null) {
where.gte(GenAIEvaluationRecord.SCORE_VALUE, GenAIEvaluationRecord.minScoreValuePpm(minScore));
@@ -82,6 +86,15 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName, St
if (maxScore != null) {
where.lte(GenAIEvaluationRecord.SCORE_VALUE, GenAIEvaluationRecord.maxScoreValuePpm(maxScore));
}
+ if (StringUtil.isNotEmpty(taskName)) {
+ where.eq(GenAIEvaluationRecord.TASK_NAME, taskName);
+ }
+ if (StringUtil.isNotEmpty(evaluationLevel)) {
+ where.eq(GenAIEvaluationRecord.EVALUATION_LEVEL, evaluationLevel);
+ }
+ if (StringUtil.isNotEmpty(judgeModel)) {
+ where.eq(GenAIEvaluationRecord.JUDGE_MODEL, judgeModel);
+ }
if (Objects.nonNull(relatedTrace)) {
if (StringUtil.isNotEmpty(relatedTrace.getTraceId())) {
@@ -102,10 +115,12 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName, St
}
}
}
- if (queryOrder == Order.ASC) {
- where.orderBy(sortColumn(sortField), "ASC");
+ if (GenAIEvaluationRecord.SCORE_VALUE.equalsIgnoreCase(sortField)) {
+ where.orderBy(GenAIEvaluationRecord.SCORE_VALUE, queryOrder == Order.ASC ? "ASC" : "DESC");
+ } else if (queryOrder == Order.ASC) {
+ where.orderByAsc();
} else {
- where.orderByDesc(sortColumn(sortField));
+ where.orderByDesc();
}
where.limit(limit).offset(from);
@@ -116,14 +131,14 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName, St
for (final RowEntity rowEntity : resp.getElements()) {
GenAIEvaluationRecord evaluationRecord = new GenAIEvaluationRecord();
evaluationRecord.setTraceId(rowEntity.getTagValue(GenAIEvaluationRecord.TRACE_ID));
- evaluationRecord.setServiceName(rowEntity.getTagValue(GenAIEvaluationRecord.SERVICE_NAME));
- evaluationRecord.setProviderName(rowEntity.getTagValue(GenAIEvaluationRecord.PROVIDER_NAME));
- evaluationRecord.setModelName(rowEntity.getTagValue(GenAIEvaluationRecord.MODEL_NAME));
+ evaluationRecord.setServiceId(rowEntity.getTagValue(GenAIEvaluationRecord.SERVICE_ID));
+ evaluationRecord.setProviderId(rowEntity.getTagValue(GenAIEvaluationRecord.PROVIDER_ID));
+ evaluationRecord.setModelId(rowEntity.getTagValue(GenAIEvaluationRecord.MODEL_ID));
evaluationRecord.setOperationName(rowEntity.getTagValue(GenAIEvaluationRecord.OPERATION_NAME));
- final Number scoreValue = (Number) rowEntity.getTagValue(GenAIEvaluationRecord.SCORE_VALUE);
+ final Number scoreValue = rowEntity.getTagValue(GenAIEvaluationRecord.SCORE_VALUE);
evaluationRecord.setScoreValuePpm(scoreValue == null ? null : scoreValue.longValue());
evaluationRecord.setSegmentId(rowEntity.getTagValue(GenAIEvaluationRecord.SEGMENT_ID));
- evaluationRecord.setSpanId(rowEntity.getTagValue(GenAIEvaluationRecord.SPAN_ID));
+ evaluationRecord.setSpanId(((Number) rowEntity.getTagValue(GenAIEvaluationRecord.SPAN_ID)).intValue());
evaluationRecord.setSpanType(rowEntity.getTagValue(GenAIEvaluationRecord.SPAN_TYPE));
evaluationRecord.setTaskName(rowEntity.getTagValue(GenAIEvaluationRecord.TASK_NAME));
evaluationRecord.setEvaluationTime(((Number) rowEntity.getTagValue(GenAIEvaluationRecord.EVALUATION_TIME)).longValue());
@@ -136,9 +151,4 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String providerName, St
}
return genAIEvaluationRecords;
}
-
- private static String sortColumn(final String sortField) {
- return GenAIEvaluationRecord.SCORE_VALUE.equals(sortField)
- ? GenAIEvaluationRecord.SCORE_VALUE : GenAIEvaluationRecord.EVALUATION_TIME;
- }
}
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
index 2fcc139f4271..4b8ca3d18ab3 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
@@ -48,9 +48,9 @@
public class GenAIEvaluationRecordQueryEsDAO extends EsDAO implements IGenAIEvaluationRecordQueryDAO {
private static final Set QUERYABLE_TAG_KEYS = Set.of(
GenAIEvaluationRecord.TRACE_ID,
- GenAIEvaluationRecord.SERVICE_NAME,
- GenAIEvaluationRecord.PROVIDER_NAME,
- GenAIEvaluationRecord.MODEL_NAME,
+ GenAIEvaluationRecord.SERVICE_ID,
+ GenAIEvaluationRecord.PROVIDER_ID,
+ GenAIEvaluationRecord.MODEL_ID,
GenAIEvaluationRecord.OPERATION_NAME,
GenAIEvaluationRecord.SCORE_VALUE,
GenAIEvaluationRecord.SEGMENT_ID,
@@ -69,9 +69,11 @@ public GenAIEvaluationRecordQueryEsDAO(final ElasticSearchClient client) {
}
@Override
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerName,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceName,
+ final String providerName,
final String modelName,
final Double minScore, final Double maxScore, final String sortField,
+ final String taskName, final String evaluationLevel, final String judgeModel,
final TraceScopeCondition relatedTrace,
final Order queryOrder,
final int from,
@@ -95,11 +97,14 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerNa
if (startSecondTB != 0 && endSecondTB != 0) {
query.must(Query.range(Record.TIME_BUCKET).gte(startSecondTB).lte(endSecondTB));
}
+ if (isNotEmpty(serviceName)) {
+ query.must(Query.term(GenAIEvaluationRecord.SERVICE_ID, GenAIEvaluationRecord.toServiceId(serviceName)));
+ }
if (isNotEmpty(providerName)) {
- query.must(Query.term(GenAIEvaluationRecord.PROVIDER_NAME, providerName));
+ query.must(Query.term(GenAIEvaluationRecord.PROVIDER_ID, GenAIEvaluationRecord.toEntityId(providerName)));
}
if (isNotEmpty(modelName)) {
- query.must(Query.term(GenAIEvaluationRecord.MODEL_NAME, modelName));
+ query.must(Query.term(GenAIEvaluationRecord.MODEL_ID, GenAIEvaluationRecord.toEntityId(modelName)));
}
if (minScore != null || maxScore != null) {
final var scoreRange = Query.range(GenAIEvaluationRecord.SCORE_VALUE);
@@ -107,6 +112,15 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerNa
if (maxScore != null) scoreRange.lte(GenAIEvaluationRecord.maxScoreValuePpm(maxScore));
query.must(scoreRange);
}
+ if (isNotEmpty(taskName)) {
+ query.must(Query.term(GenAIEvaluationRecord.TASK_NAME, taskName));
+ }
+ if (isNotEmpty(evaluationLevel)) {
+ query.must(Query.term(GenAIEvaluationRecord.EVALUATION_LEVEL, evaluationLevel));
+ }
+ if (isNotEmpty(judgeModel)) {
+ query.must(Query.term(GenAIEvaluationRecord.JUDGE_MODEL, judgeModel));
+ }
if (nonNull(relatedTrace)) {
if (isNotEmpty(relatedTrace.getTraceId())) {
query.must(Query.term(GenAIEvaluationRecord.TRACE_ID, relatedTrace.getTraceId()));
@@ -115,7 +129,7 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerNa
query.must(Query.term(GenAIEvaluationRecord.SEGMENT_ID, relatedTrace.getSegmentId()));
}
if (nonNull(relatedTrace.getSpanId())) {
- query.must(Query.term(GenAIEvaluationRecord.SPAN_ID, String.valueOf(relatedTrace.getSpanId())));
+ query.must(Query.term(GenAIEvaluationRecord.SPAN_ID, relatedTrace.getSpanId()));
}
}
if (CollectionUtils.isNotEmpty(tags)) {
@@ -132,7 +146,7 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerNa
final SearchBuilder search = Search.builder()
.query(query)
.sort(
- GenAIEvaluationRecord.SCORE_VALUE.equals(sortField) ? GenAIEvaluationRecord.SCORE_VALUE : GenAIEvaluationRecord.EVALUATION_TIME,
+ GenAIEvaluationRecord.SCORE_VALUE.equalsIgnoreCase(sortField) ? GenAIEvaluationRecord.SCORE_VALUE : GenAIEvaluationRecord.EVALUATION_TIME,
Order.DES.equals(queryOrder) ? Sort.Order.DESC : Sort.Order.ASC
)
.size(limit)
@@ -156,14 +170,14 @@ private GenAIEvaluationRecord parseRecord(final SearchHit searchHit) {
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
record.setUniqueId((String) source.get(GenAIEvaluationRecord.UNIQUE_ID));
record.setTraceId((String) source.get(GenAIEvaluationRecord.TRACE_ID));
- record.setServiceName((String) source.get(GenAIEvaluationRecord.SERVICE_NAME));
- record.setProviderName((String) source.get(GenAIEvaluationRecord.PROVIDER_NAME));
- record.setModelName((String) source.get(GenAIEvaluationRecord.MODEL_NAME));
+ record.setServiceId((String) source.get(GenAIEvaluationRecord.SERVICE_ID));
+ record.setProviderId((String) source.get(GenAIEvaluationRecord.PROVIDER_ID));
+ record.setModelId((String) source.get(GenAIEvaluationRecord.MODEL_ID));
record.setOperationName((String) source.get(GenAIEvaluationRecord.OPERATION_NAME));
final Number scoreValue = (Number) source.get(GenAIEvaluationRecord.SCORE_VALUE);
record.setScoreValuePpm(scoreValue == null ? null : scoreValue.longValue());
record.setSegmentId((String) source.get(GenAIEvaluationRecord.SEGMENT_ID));
- record.setSpanId((String) source.get(GenAIEvaluationRecord.SPAN_ID));
+ record.setSpanId(((Number) source.get(GenAIEvaluationRecord.SPAN_ID)).intValue());
record.setSpanType((String) source.get(GenAIEvaluationRecord.SPAN_TYPE));
record.setTaskName((String) source.get(GenAIEvaluationRecord.TASK_NAME));
record.setValueType((String) source.get(GenAIEvaluationRecord.VALUE_TYPE));
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
index 7fc98f49c02a..6328bcd1ffdf 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
@@ -21,6 +21,7 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
@@ -50,9 +51,9 @@
public class JDBCGenAIEvaluationRecordQueryDAO implements IGenAIEvaluationRecordQueryDAO {
private static final Set QUERYABLE_TAG_KEYS = Set.of(
GenAIEvaluationRecord.TRACE_ID,
- GenAIEvaluationRecord.SERVICE_NAME,
- GenAIEvaluationRecord.PROVIDER_NAME,
- GenAIEvaluationRecord.MODEL_NAME,
+ GenAIEvaluationRecord.SERVICE_ID,
+ GenAIEvaluationRecord.PROVIDER_ID,
+ GenAIEvaluationRecord.MODEL_ID,
GenAIEvaluationRecord.OPERATION_NAME,
GenAIEvaluationRecord.SCORE_VALUE,
GenAIEvaluationRecord.SEGMENT_ID,
@@ -69,9 +70,9 @@ public class JDBCGenAIEvaluationRecordQueryDAO implements IGenAIEvaluationRecord
private static final List SELECTED_COLUMNS = List.of(
GenAIEvaluationRecord.UNIQUE_ID,
GenAIEvaluationRecord.TRACE_ID,
- GenAIEvaluationRecord.SERVICE_NAME,
- GenAIEvaluationRecord.PROVIDER_NAME,
- GenAIEvaluationRecord.MODEL_NAME,
+ GenAIEvaluationRecord.SERVICE_ID,
+ GenAIEvaluationRecord.PROVIDER_ID,
+ GenAIEvaluationRecord.MODEL_ID,
GenAIEvaluationRecord.OPERATION_NAME,
GenAIEvaluationRecord.SCORE_VALUE,
GenAIEvaluationRecord.SEGMENT_ID,
@@ -91,9 +92,11 @@ public class JDBCGenAIEvaluationRecordQueryDAO implements IGenAIEvaluationRecord
@Override
@SneakyThrows
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerName,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceName,
+ final String providerName,
final String modelName,
final Double minScore, final Double maxScore, final String sortField,
+ final String taskName, final String evaluationLevel, final String judgeModel,
final TraceScopeCondition relatedTrace,
final Order queryOrder,
final int from,
@@ -124,7 +127,8 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerNa
final var records = new ArrayList();
for (final var table : tables) {
final var sqlAndParameters = buildSQL(
- providerName, modelName, minScore, maxScore, sortField, relatedTrace, queryOrder, from, limit, duration, tags, table);
+ serviceName, providerName, modelName, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
+ relatedTrace, queryOrder, from, limit, duration, tags, table);
records.addAll(
jdbcClient.executeQuery(
sqlAndParameters.sql(),
@@ -134,9 +138,12 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String providerNa
);
}
- final var comparator = Order.ASC.equals(queryOrder) ?
- comparing(GenAIEvaluationRecord::getEvaluationTime) :
- comparing(GenAIEvaluationRecord::getEvaluationTime).reversed();
+ Comparator comparator = GenAIEvaluationRecord.SCORE_VALUE.equalsIgnoreCase(sortField)
+ ? comparing((GenAIEvaluationRecord record) -> record.getScoreValue() == null ? Double.NEGATIVE_INFINITY : record.getScoreValue())
+ : comparing(GenAIEvaluationRecord::getEvaluationTime);
+ if (Order.DES.equals(queryOrder)) {
+ comparator = comparator.reversed();
+ }
return new GenAIEvaluationRecords(
records.stream().sorted(comparator).skip(from).limit(limit).collect(toList())
);
@@ -148,14 +155,14 @@ protected ArrayList parseResults(final ResultSet resultSe
final GenAIEvaluationRecord record = new GenAIEvaluationRecord();
record.setUniqueId(resultSet.getString(GenAIEvaluationRecord.UNIQUE_ID));
record.setTraceId(resultSet.getString(GenAIEvaluationRecord.TRACE_ID));
- record.setServiceName(resultSet.getString(GenAIEvaluationRecord.SERVICE_NAME));
- record.setProviderName(resultSet.getString(GenAIEvaluationRecord.PROVIDER_NAME));
- record.setModelName(resultSet.getString(GenAIEvaluationRecord.MODEL_NAME));
+ record.setServiceId(resultSet.getString(GenAIEvaluationRecord.SERVICE_ID));
+ record.setProviderId(resultSet.getString(GenAIEvaluationRecord.PROVIDER_ID));
+ record.setModelId(resultSet.getString(GenAIEvaluationRecord.MODEL_ID));
record.setOperationName(resultSet.getString(GenAIEvaluationRecord.OPERATION_NAME));
final long scoreValue = resultSet.getLong(GenAIEvaluationRecord.SCORE_VALUE);
record.setScoreValuePpm(resultSet.wasNull() ? null : scoreValue);
record.setSegmentId(resultSet.getString(GenAIEvaluationRecord.SEGMENT_ID));
- record.setSpanId(resultSet.getString(GenAIEvaluationRecord.SPAN_ID));
+ record.setSpanId(resultSet.getInt(GenAIEvaluationRecord.SPAN_ID));
record.setSpanType(resultSet.getString(GenAIEvaluationRecord.SPAN_TYPE));
record.setTaskName(resultSet.getString(GenAIEvaluationRecord.TASK_NAME));
record.setValueType(resultSet.getString(GenAIEvaluationRecord.VALUE_TYPE));
@@ -169,11 +176,15 @@ protected ArrayList parseResults(final ResultSet resultSe
return records;
}
- protected SQLAndParameters buildSQL(final String providerName,
+ protected SQLAndParameters buildSQL(final String serviceName,
+ final String providerName,
final String modelName,
final Double minScore,
final Double maxScore,
final String sortField,
+ final String taskName,
+ final String evaluationLevel,
+ final String judgeModel,
final TraceScopeCondition relatedTrace,
final Order queryOrder,
final int from,
@@ -204,13 +215,17 @@ protected SQLAndParameters buildSQL(final String providerName,
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.TIME_BUCKET)).append(" <= ?");
parameters.add(endSecondTB);
}
+ if (StringUtil.isNotEmpty(serviceName)) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SERVICE_ID)).append(" = ?");
+ parameters.add(GenAIEvaluationRecord.toServiceId(serviceName));
+ }
if (StringUtil.isNotEmpty(providerName)) {
- sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.PROVIDER_NAME)).append(" = ?");
- parameters.add(providerName);
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.PROVIDER_ID)).append(" = ?");
+ parameters.add(GenAIEvaluationRecord.toEntityId(providerName));
}
if (StringUtil.isNotEmpty(modelName)) {
- sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.MODEL_NAME)).append(" = ?");
- parameters.add(modelName);
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.MODEL_ID)).append(" = ?");
+ parameters.add(GenAIEvaluationRecord.toEntityId(modelName));
}
if (minScore != null) {
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SCORE_VALUE)).append(" >= ?");
@@ -220,6 +235,18 @@ protected SQLAndParameters buildSQL(final String providerName,
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SCORE_VALUE)).append(" <= ?");
parameters.add(GenAIEvaluationRecord.maxScoreValuePpm(maxScore));
}
+ if (StringUtil.isNotEmpty(taskName)) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.TASK_NAME)).append(" = ?");
+ parameters.add(taskName);
+ }
+ if (StringUtil.isNotEmpty(evaluationLevel)) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.EVALUATION_LEVEL)).append(" = ?");
+ parameters.add(evaluationLevel);
+ }
+ if (StringUtil.isNotEmpty(judgeModel)) {
+ sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.JUDGE_MODEL)).append(" = ?");
+ parameters.add(judgeModel);
+ }
if (nonNull(relatedTrace)) {
if (StringUtil.isNotEmpty(relatedTrace.getTraceId())) {
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.TRACE_ID)).append(" = ?");
@@ -231,7 +258,7 @@ protected SQLAndParameters buildSQL(final String providerName,
}
if (nonNull(relatedTrace.getSpanId())) {
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SPAN_ID)).append(" = ?");
- parameters.add(String.valueOf(relatedTrace.getSpanId()));
+ parameters.add(relatedTrace.getSpanId());
}
}
if (CollectionUtils.isNotEmpty(tags)) {
@@ -246,7 +273,7 @@ protected SQLAndParameters buildSQL(final String providerName,
}
sql.append(" order by ")
- .append(storageColumn(GenAIEvaluationRecord.SCORE_VALUE.equals(sortField)
+ .append(storageColumn(GenAIEvaluationRecord.SCORE_VALUE.equalsIgnoreCase(sortField)
? GenAIEvaluationRecord.SCORE_VALUE : GenAIEvaluationRecord.EVALUATION_TIME))
.append(" ")
.append(Order.DES.equals(queryOrder) ? "desc" : "asc");
From d2528bc738c52805f050998b2f1c4a595bb30b40 Mon Sep 17 00:00:00 2001
From: peachisai <2581009893@qq.com>
Date: Thu, 6 Aug 2026 22:39:24 +0800
Subject: [PATCH 8/8] fix
---
.../GenAIEvaluationRecordQueryService.java | 36 +++++++++++++------
.../GenAIEvaluationRecordQueryCondition.java | 6 ++--
.../query/IGenAIEvaluationRecordQueryDAO.java | 20 +++++------
.../resolver/GenAIEvaluationRecordQuery.java | 6 ++--
...BanyanDBGenAIEvaluationRecordQueryDAO.java | 14 ++++----
.../GenAIEvaluationRecordQueryEsDAO.java | 18 +++++-----
.../JDBCGenAIEvaluationRecordQueryDAO.java | 26 +++++++-------
7 files changed, 70 insertions(+), 56 deletions(-)
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java
index 7a3c5f2167c3..23520311dd4b 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/GenAIEvaluationRecordQueryService.java
@@ -18,6 +18,8 @@
package org.apache.skywalking.oap.server.core.query;
+import org.apache.skywalking.oap.server.core.analysis.IDManager;
+import org.apache.skywalking.oap.server.core.analysis.manual.genai.GenAIEvaluationRecord;
import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
import org.apache.skywalking.oap.server.core.query.enumeration.Order;
import org.apache.skywalking.oap.server.core.query.input.Duration;
@@ -51,9 +53,9 @@ private IGenAIEvaluationRecordQueryDAO getGenAIEvaluationRecordQueryDAO() {
return genAIEvaluationRecordQueryDAO;
}
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceName,
- String providerName,
- String modelName,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId,
+ String providerId,
+ String modelId,
Double minScore,
Double maxScore,
String sortField,
@@ -71,9 +73,9 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceName,
if (traceContext != null) {
StringBuilder msg = new StringBuilder();
span = traceContext.createSpan("Query Service: queryGenAIEvaluationRecord");
- msg.append("ServiceName: ").append(serviceName).append(", ");
- msg.append("ProviderName: ").append(providerName).append(", ");
- msg.append("ModelName: ").append(modelName).append(", ");
+ msg.append("ServiceId: ").append(serviceId).append(", ");
+ msg.append("ProviderId: ").append(providerId).append(", ");
+ msg.append("ModelId: ").append(modelId).append(", ");
msg.append("RelatedTrace: ").append(relatedTrace).append(", ");
msg.append("Pagination: ").append(paging).append(", ");
msg.append("QueryOrder: ").append(queryOrder).append(", ");
@@ -82,7 +84,7 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceName,
span.setMsg(msg.toString());
}
return queryGenAIEvaluationRecordInternal(
- serviceName, providerName, modelName, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
+ serviceId, providerId, toStoredModelId(modelId), minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
relatedTrace, paging, queryOrder, duration, tags
);
} finally {
@@ -92,9 +94,9 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceName,
}
}
- private GenAIEvaluationRecords queryGenAIEvaluationRecordInternal(String serviceName,
- String providerName,
- String modelName,
+ private GenAIEvaluationRecords queryGenAIEvaluationRecordInternal(String serviceId,
+ String providerId,
+ String modelId,
Double minScore,
Double maxScore,
String sortField,
@@ -109,8 +111,20 @@ private GenAIEvaluationRecords queryGenAIEvaluationRecordInternal(String service
PaginationUtils.Page page = PaginationUtils.INSTANCE.exchange(paging);
return getGenAIEvaluationRecordQueryDAO().queryGenAIEvaluationRecordDebuggable(
- serviceName, providerName, modelName, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
+ serviceId, providerId, modelId, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
relatedTrace, queryOrder, page.getFrom(), page.getLimit(), duration, tags
);
}
+
+ private String toStoredModelId(final String modelInstanceId) {
+ if (modelInstanceId == null || modelInstanceId.isEmpty()) {
+ return modelInstanceId;
+ }
+ // The UI model picker supplies a ServiceInstanceID. Accept an already
+ // stored model ServiceID as well for non-UI API callers.
+ if (!modelInstanceId.contains("_")) {
+ return modelInstanceId;
+ }
+ return GenAIEvaluationRecord.toEntityId(IDManager.ServiceInstanceID.analysisId(modelInstanceId).getName());
+ }
}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java
index 64529eb32417..4ac76d5a73e2 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/input/GenAIEvaluationRecordQueryCondition.java
@@ -30,9 +30,9 @@
@Setter
@ToString
public class GenAIEvaluationRecordQueryCondition {
- private String serviceName;
- private String providerName;
- private String modelName;
+ private String serviceId;
+ private String providerId;
+ private String modelId;
private Double minScore;
private Double maxScore;
private String sortField;
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java
index 239c937c9de4..09cc9d11f303 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/storage/query/IGenAIEvaluationRecordQueryDAO.java
@@ -38,9 +38,9 @@
public interface IGenAIEvaluationRecordQueryDAO extends Service {
- default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String serviceName,
- String providerName,
- String modelName,
+ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String serviceId,
+ String providerId,
+ String modelId,
Double minScore,
Double maxScore,
String sortField,
@@ -59,9 +59,9 @@ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String servi
if (traceContext != null) {
span = traceContext.createSpan("Query Dao: queryGenAIEvaluationRecord");
StringBuilder msg = new StringBuilder();
- msg.append("ServiceName: ").append(serviceName)
- .append(", ProviderName: ").append(providerName)
- .append(", ModelName: ").append(modelName)
+ msg.append("ServiceId: ").append(serviceId)
+ .append(", ProviderId: ").append(providerId)
+ .append(", ModelId: ").append(modelId)
.append(", RelatedTrace: ").append(relatedTrace)
.append(", QueryOrder: ").append(queryOrder)
.append(", From: ").append(from)
@@ -71,7 +71,7 @@ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String servi
span.setMsg(msg.toString());
}
return queryGenAIEvaluationRecord(
- serviceName, providerName, modelName, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
+ serviceId, providerId, modelId, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
relatedTrace, queryOrder, from, limit, duration, tags
);
} finally {
@@ -81,9 +81,9 @@ default GenAIEvaluationRecords queryGenAIEvaluationRecordDebuggable(String servi
}
}
- GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceName,
- String providerName,
- String modelName,
+ GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId,
+ String providerId,
+ String modelId,
Double minScore,
Double maxScore,
String sortField,
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
index 5b3f00f479c3..267e0f257545 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/GenAIEvaluationRecordQuery.java
@@ -94,9 +94,9 @@ private GenAIEvaluationRecords queryGenAIEvaluationRecord(
});
}
return getQueryService().queryGenAIEvaluationRecord(
- condition.getServiceName(),
- condition.getProviderName(),
- condition.getModelName(),
+ condition.getServiceId(),
+ condition.getProviderId(),
+ condition.getModelId(),
condition.getMinScore(),
condition.getMaxScore(),
condition.getSortField(),
diff --git a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java
index d98d08349f5b..168331dfbe84 100644
--- a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/stream/BanyanDBGenAIEvaluationRecordQueryDAO.java
@@ -65,20 +65,20 @@ public BanyanDBGenAIEvaluationRecordQueryDAO(BanyanDBStorageClient client) {
}
@Override
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceName, String providerName, String modelName, Double minScore, Double maxScore, String sortField,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(String serviceId, String providerId, String modelId, Double minScore, Double maxScore, String sortField,
String taskName, String evaluationLevel, String judgeModel,
TraceScopeCondition relatedTrace, Order queryOrder, int from, int limit,
Duration duration, List tags) throws IOException {
final boolean isColdStage = duration != null && duration.isColdStage();
final Conditions where = Conditions.create();
- if (StringUtil.isNotEmpty(serviceName)) {
- where.eq(GenAIEvaluationRecord.SERVICE_ID, GenAIEvaluationRecord.toServiceId(serviceName));
+ if (StringUtil.isNotEmpty(serviceId)) {
+ where.eq(GenAIEvaluationRecord.SERVICE_ID, serviceId);
}
- if (StringUtil.isNotEmpty(providerName)) {
- where.eq(GenAIEvaluationRecord.PROVIDER_ID, GenAIEvaluationRecord.toEntityId(providerName));
+ if (StringUtil.isNotEmpty(providerId)) {
+ where.eq(GenAIEvaluationRecord.PROVIDER_ID, providerId);
}
- if (StringUtil.isNotEmpty(modelName)) {
- where.eq(GenAIEvaluationRecord.MODEL_ID, GenAIEvaluationRecord.toEntityId(modelName));
+ if (StringUtil.isNotEmpty(modelId)) {
+ where.eq(GenAIEvaluationRecord.MODEL_ID, modelId);
}
if (minScore != null) {
where.gte(GenAIEvaluationRecord.SCORE_VALUE, GenAIEvaluationRecord.minScoreValuePpm(minScore));
diff --git a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
index 4b8ca3d18ab3..fe33cd614fff 100644
--- a/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
+++ b/oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/elasticsearch/query/GenAIEvaluationRecordQueryEsDAO.java
@@ -69,9 +69,9 @@ public GenAIEvaluationRecordQueryEsDAO(final ElasticSearchClient client) {
}
@Override
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceName,
- final String providerName,
- final String modelName,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
+ final String providerId,
+ final String modelId,
final Double minScore, final Double maxScore, final String sortField,
final String taskName, final String evaluationLevel, final String judgeModel,
final TraceScopeCondition relatedTrace,
@@ -97,14 +97,14 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceNam
if (startSecondTB != 0 && endSecondTB != 0) {
query.must(Query.range(Record.TIME_BUCKET).gte(startSecondTB).lte(endSecondTB));
}
- if (isNotEmpty(serviceName)) {
- query.must(Query.term(GenAIEvaluationRecord.SERVICE_ID, GenAIEvaluationRecord.toServiceId(serviceName)));
+ if (isNotEmpty(serviceId)) {
+ query.must(Query.term(GenAIEvaluationRecord.SERVICE_ID, serviceId));
}
- if (isNotEmpty(providerName)) {
- query.must(Query.term(GenAIEvaluationRecord.PROVIDER_ID, GenAIEvaluationRecord.toEntityId(providerName)));
+ if (isNotEmpty(providerId)) {
+ query.must(Query.term(GenAIEvaluationRecord.PROVIDER_ID, providerId));
}
- if (isNotEmpty(modelName)) {
- query.must(Query.term(GenAIEvaluationRecord.MODEL_ID, GenAIEvaluationRecord.toEntityId(modelName)));
+ if (isNotEmpty(modelId)) {
+ query.must(Query.term(GenAIEvaluationRecord.MODEL_ID, modelId));
}
if (minScore != null || maxScore != null) {
final var scoreRange = Query.range(GenAIEvaluationRecord.SCORE_VALUE);
diff --git a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
index 6328bcd1ffdf..17c7257dbf5c 100644
--- a/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
+++ b/oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/jdbc/common/dao/JDBCGenAIEvaluationRecordQueryDAO.java
@@ -92,9 +92,9 @@ public class JDBCGenAIEvaluationRecordQueryDAO implements IGenAIEvaluationRecord
@Override
@SneakyThrows
- public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceName,
- final String providerName,
- final String modelName,
+ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceId,
+ final String providerId,
+ final String modelId,
final Double minScore, final Double maxScore, final String sortField,
final String taskName, final String evaluationLevel, final String judgeModel,
final TraceScopeCondition relatedTrace,
@@ -127,7 +127,7 @@ public GenAIEvaluationRecords queryGenAIEvaluationRecord(final String serviceNam
final var records = new ArrayList();
for (final var table : tables) {
final var sqlAndParameters = buildSQL(
- serviceName, providerName, modelName, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
+ serviceId, providerId, modelId, minScore, maxScore, sortField, taskName, evaluationLevel, judgeModel,
relatedTrace, queryOrder, from, limit, duration, tags, table);
records.addAll(
jdbcClient.executeQuery(
@@ -176,9 +176,9 @@ protected ArrayList parseResults(final ResultSet resultSe
return records;
}
- protected SQLAndParameters buildSQL(final String serviceName,
- final String providerName,
- final String modelName,
+ protected SQLAndParameters buildSQL(final String serviceId,
+ final String providerId,
+ final String modelId,
final Double minScore,
final Double maxScore,
final String sortField,
@@ -215,17 +215,17 @@ protected SQLAndParameters buildSQL(final String serviceName,
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.TIME_BUCKET)).append(" <= ?");
parameters.add(endSecondTB);
}
- if (StringUtil.isNotEmpty(serviceName)) {
+ if (StringUtil.isNotEmpty(serviceId)) {
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SERVICE_ID)).append(" = ?");
- parameters.add(GenAIEvaluationRecord.toServiceId(serviceName));
+ parameters.add(serviceId);
}
- if (StringUtil.isNotEmpty(providerName)) {
+ if (StringUtil.isNotEmpty(providerId)) {
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.PROVIDER_ID)).append(" = ?");
- parameters.add(GenAIEvaluationRecord.toEntityId(providerName));
+ parameters.add(providerId);
}
- if (StringUtil.isNotEmpty(modelName)) {
+ if (StringUtil.isNotEmpty(modelId)) {
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.MODEL_ID)).append(" = ?");
- parameters.add(GenAIEvaluationRecord.toEntityId(modelName));
+ parameters.add(modelId);
}
if (minScore != null) {
sql.append(" and ").append(storageColumn(GenAIEvaluationRecord.SCORE_VALUE)).append(" >= ?");