fix: lint fix

This commit is contained in:
Eric Dong
2025-11-10 15:19:05 -05:00
parent cd244b3fe9
commit 16f01c8edb
3 changed files with 14 additions and 226 deletions
@@ -1,18 +1,14 @@
package com.langchain.smith.otel;
import com.langchain.smith.models.runs.Run;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import java.time.Instant;
/**
* Utility class for creating OpenTelemetry spans with Gen AI semantic conventions.
* Provides helper methods to create spans for LLM, tool, retrieval, and chain operations.
*/
public final class OtelSpanCreator {
private OtelSpanCreator() {}
@@ -144,166 +140,4 @@ public final class OtelSpanCreator {
}
return span;
}
/**
* Creates and exports a span from a LangSmith Run using the native OTEL API.
*
* @param tracer the OpenTelemetry tracer
* @param run the LangSmith Run to convert
* @param projectName the project name for langsmith.project.name attribute
*/
public static void createSpanFromRun(Tracer tracer, Run run, String projectName) {
// Parse timestamps
Instant startTime = parseTime(run.startTime().orElse(null));
Instant endTime = parseTime(run.endTime().orElse(null));
if (startTime == null) {
startTime = Instant.now().minusSeconds(1);
}
if (endTime == null) {
endTime = Instant.now();
}
String traceIdHex = normalizeTraceId(run.traceId()
.orElseGet(() -> java.util.UUID.randomUUID().toString().replace("-", "")));
String spanIdHex = normalizeSpanId(run.id()
.orElseGet(() ->
java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 16)));
String parentSpanIdHex =
run.parentRunId().map(OtelSpanCreator::normalizeSpanId).orElse(null);
// Create SpanContext for this span
SpanContext spanContext =
SpanContext.create(traceIdHex, spanIdHex, TraceFlags.getSampled(), TraceState.getDefault());
// Create parent context if we have a parent span ID
Context parentContext = Context.root();
if (parentSpanIdHex != null) {
SpanContext parentSpanContext =
SpanContext.create(traceIdHex, parentSpanIdHex, TraceFlags.getSampled(), TraceState.getDefault());
parentContext = Context.root().with(Span.wrap(parentSpanContext));
}
SpanKind spanKind = determineSpanKind(run);
String spanName = run.name().orElse("langsmith.run");
SpanBuilder spanBuilder = tracer.spanBuilder(spanName)
.setSpanKind(spanKind)
.setParent(parentContext)
.setStartTimestamp(startTime);
Span span = spanBuilder.startSpan();
try {
// Set OpenTelemetry GenAI semantic convention attributes
span.setAttribute(
AttributeKey.stringKey("gen_ai.operation.name"),
mapRunTypeToOperation(run.runType().orElse(null)));
run.name().ifPresent(name -> {
if (name.contains("openai") || name.contains("gpt")) {
span.setAttribute(AttributeKey.stringKey("gen_ai.system"), "openai");
} else if (name.contains("anthropic") || name.contains("claude")) {
span.setAttribute(AttributeKey.stringKey("gen_ai.system"), "anthropic");
}
});
if (projectName != null) {
span.setAttribute(AttributeKey.stringKey("service.name"), projectName);
}
run.sessionId().ifPresent(id -> span.setAttribute(AttributeKey.stringKey("session.id"), id));
if (run.error().isPresent()) {
span.setStatus(StatusCode.ERROR, run.error().get());
span.setAttribute(AttributeKey.booleanKey("error"), true);
span.setAttribute(AttributeKey.stringKey("error.type"), "runtime_error");
} else {
span.setStatus(StatusCode.OK);
}
run.tags().ifPresent(tags -> {
for (int i = 0; i < tags.size(); i++) {
span.setAttribute(AttributeKey.stringKey("tag." + i), tags.get(i));
}
});
} finally {
span.end(endTime);
}
}
private static Instant parseTime(String timeString) {
if (timeString == null || timeString.isEmpty()) {
return null;
}
try {
return Instant.parse(timeString);
} catch (Exception e) {
try {
long epochMilli = Long.parseLong(timeString);
return Instant.ofEpochMilli(epochMilli);
} catch (Exception e2) {
return null;
}
}
}
private static SpanKind determineSpanKind(Run run) {
return run.runType()
.map(rt -> {
if (rt == Run.RunType.LLM) return SpanKind.CLIENT;
if (rt == Run.RunType.CHAIN) return SpanKind.INTERNAL;
if (rt == Run.RunType.TOOL) return SpanKind.CLIENT;
return SpanKind.INTERNAL;
})
.orElse(SpanKind.INTERNAL);
}
private static String mapRunTypeToOperation(Run.RunType runType) {
if (runType == null) {
return "chat";
}
if (runType == Run.RunType.LLM) {
return "chat";
} else if (runType == Run.RunType.CHAIN) {
return "chat";
} else if (runType == Run.RunType.TOOL) {
return "tool";
} else if (runType == Run.RunType.RETRIEVER) {
return "retrieval";
} else if (runType == Run.RunType.PARSER) {
return "parse";
} else if (runType == Run.RunType.PROMPT) {
return "prompt";
} else {
return "chat";
}
}
private static String normalizeTraceId(String traceId) {
if (traceId == null) {
traceId = java.util.UUID.randomUUID().toString().replace("-", "");
}
traceId = traceId.toLowerCase();
if (traceId.length() < 32) {
traceId = String.format("%32s", traceId).replace(' ', '0');
} else if (traceId.length() > 32) {
traceId = traceId.substring(0, 32);
}
return traceId;
}
private static String normalizeSpanId(String spanId) {
if (spanId == null) {
spanId = java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 16);
}
spanId = spanId.toLowerCase();
if (spanId.length() < 16) {
spanId = String.format("%16s", spanId).replace(' ', '0');
} else if (spanId.length() > 16) {
spanId = spanId.substring(0, 16);
}
return spanId;
}
}
@@ -1,6 +1,5 @@
package com.langchain.smith.otel;
import com.langchain.smith.models.runs.Run;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
@@ -20,10 +19,10 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Exports LangSmith runs as OpenTelemetry traces to an OTEL endpoint.
* Manages OpenTelemetry SDK for exporting traces to OTLP endpoints.
*
* <p>This exporter converts LangSmith Run objects to OpenTelemetry spans and sends them to a
* configured OTEL endpoint using the OTLP HTTP protocol.
* <p>This class initializes the OpenTelemetry SDK with OTLP HTTP export capabilities,
* providing a Tracer for creating spans with Gen AI semantic conventions.
*/
public final class OtelTraceExporter {
private static final Logger logger = Logger.getLogger(OtelTraceExporter.class.getName());
@@ -72,55 +71,6 @@ public final class OtelTraceExporter {
return projectName;
}
/**
* Exports a single run as a span.
*
* @param run the run to export
* @return a CompletableResultCode indicating success or failure
*/
public CompletableResultCode export(Run run) {
return export(Collections.singletonList(run));
}
/**
* Exports multiple runs as spans.
*
* @param runs the runs to export
* @return a CompletableResultCode indicating success or failure
*/
public CompletableResultCode export(List<Run> runs) {
if (!config.isEnabled() || runs == null || runs.isEmpty()) {
return CompletableResultCode.ofSuccess();
}
try {
logger.log(Level.FINE, "Creating {0} spans using native OTEL API", runs.size());
for (Run run : runs) {
OtelSpanCreator.createSpanFromRun(tracer, run, projectName);
}
// Force flush to ensure spans are exported immediately
CompletableResultCode result = tracerProvider.forceFlush();
result.whenComplete(() -> {
if (!result.isSuccess()) {
logger.log(
Level.SEVERE, "Export failed! Check OpenTelemetry logs above for HTTP response details.");
logger.log(Level.SEVERE, "Failed to export {0} spans to {1}", new Object[] {
runs.size(), config.getEndpoint()
});
}
});
return result;
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception while exporting runs to OTEL", e);
e.printStackTrace();
return CompletableResultCode.ofFailure();
}
}
/**
* Shuts down the exporter and flushes any pending exports.
*
@@ -354,7 +354,9 @@ private constructor(
put("data_type", dataTypes.joinToString(",") { it.toString() })
}
override fun visitDataType(dataType: com.langchain.smith.models.datasets.DataType) {
override fun visitDataType(
dataType: com.langchain.smith.models.datasets.DataType
) {
put("data_type", dataType.toString())
}
}
@@ -384,7 +386,8 @@ private constructor(
fun dataTypes(): Optional<List<DataType>> = Optional.ofNullable(dataTypes)
/** Enum for dataset data types. */
fun dataType(): Optional<com.langchain.smith.models.datasets.DataType> = Optional.ofNullable(dataType)
fun dataType(): Optional<com.langchain.smith.models.datasets.DataType> =
Optional.ofNullable(dataType)
fun isDataTypes(): Boolean = dataTypes != null
@@ -393,7 +396,8 @@ private constructor(
fun asDataTypes(): List<DataType> = dataTypes.getOrThrow("dataTypes")
/** Enum for dataset data types. */
fun asDataType(): com.langchain.smith.models.datasets.DataType = dataType.getOrThrow("dataType")
fun asDataType(): com.langchain.smith.models.datasets.DataType =
dataType.getOrThrow("dataType")
fun <T> accept(visitor: Visitor<T>): T =
when {