From cd244b3fe9fcc55ec53b4a66e8113cba77932212 Mon Sep 17 00:00:00 2001 From: Eric Dong Date: Mon, 10 Nov 2025 15:05:45 -0500 Subject: [PATCH] fix: lint fix --- gradle.properties | 2 - .../com/langchain/smith/otel/OtelConfig.java | 43 ++--- .../langchain/smith/otel/OtelSpanCreator.java | 161 ++++++++---------- .../smith/otel/OtelTraceExporter.java | 93 +++++----- .../smith/example/OtelJaegerExample.java | 33 ++-- .../smith/example/OtelLangSmithExample.java | 25 ++- .../example/SpringBootLangSmithExample.java | 15 +- .../example/config/OtelConfiguration.java | 16 +- .../example/config/OtelShutdownHook.java | 7 +- .../example/controller/ChatController.java | 80 ++++----- .../smith/example/service/LlmService.java | 59 +++---- 11 files changed, 236 insertions(+), 298 deletions(-) diff --git a/gradle.properties b/gradle.properties index a80bf75d..6680f9ce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,6 @@ org.gradle.caching=true org.gradle.configuration-cache=true org.gradle.parallel=true org.gradle.daemon=false -# Use Java 21 for Gradle build to avoid Kotlin 1.9.20 Java 25 compatibility issues -org.gradle.java.home=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home # These options improve our compilation and test performance. They are inherited by the Kotlin daemon. org.gradle.jvmargs=\ -Xms2g \ diff --git a/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelConfig.java b/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelConfig.java index c0dd434c..6498709b 100644 --- a/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelConfig.java +++ b/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelConfig.java @@ -103,27 +103,24 @@ public final class OtelConfig { * @return a configuration loaded from environment/system properties */ public static OtelConfig fromEnv() { - String endpoint = - System.getProperty("langchain.otel.endpoint") != null - ? System.getProperty("langchain.otel.endpoint") - : System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT") != null - ? System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT") - : "http://localhost:4318/v1/traces"; + String endpoint = System.getProperty("langchain.otel.endpoint") != null + ? System.getProperty("langchain.otel.endpoint") + : System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT") != null + ? System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT") + : "http://localhost:4318/v1/traces"; - String enabledStr = - System.getProperty("langchain.otel.enabled") != null - ? System.getProperty("langchain.otel.enabled") - : System.getenv("OTEL_EXPORTER_OTLP_ENABLED") != null - ? System.getenv("OTEL_EXPORTER_OTLP_ENABLED") - : "false"; + String enabledStr = System.getProperty("langchain.otel.enabled") != null + ? System.getProperty("langchain.otel.enabled") + : System.getenv("OTEL_EXPORTER_OTLP_ENABLED") != null + ? System.getenv("OTEL_EXPORTER_OTLP_ENABLED") + : "false"; boolean enabled = "true".equalsIgnoreCase(enabledStr); - String timeoutStr = - System.getProperty("langchain.otel.timeout") != null - ? System.getProperty("langchain.otel.timeout") - : System.getenv("OTEL_EXPORTER_OTLP_TIMEOUT") != null - ? System.getenv("OTEL_EXPORTER_OTLP_TIMEOUT") - : "10"; + String timeoutStr = System.getProperty("langchain.otel.timeout") != null + ? System.getProperty("langchain.otel.timeout") + : System.getenv("OTEL_EXPORTER_OTLP_TIMEOUT") != null + ? System.getenv("OTEL_EXPORTER_OTLP_TIMEOUT") + : "10"; long timeoutSeconds = 10L; try { timeoutSeconds = Long.parseLong(timeoutStr); @@ -132,12 +129,9 @@ public final class OtelConfig { } Duration timeout = Duration.ofSeconds(timeoutSeconds); - String serviceName = - System.getProperty("langchain.otel.service.name") != null - ? System.getProperty("langchain.otel.service.name") - : System.getenv("OTEL_SERVICE_NAME") != null - ? System.getenv("OTEL_SERVICE_NAME") - : null; + String serviceName = System.getProperty("langchain.otel.service.name") != null + ? System.getProperty("langchain.otel.service.name") + : System.getenv("OTEL_SERVICE_NAME") != null ? System.getenv("OTEL_SERVICE_NAME") : null; Builder configBuilder = builder().endpoint(endpoint).enabled(enabled).timeout(timeout); if (serviceName != null) { @@ -234,4 +228,3 @@ public final class OtelConfig { } } } - diff --git a/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelSpanCreator.java b/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelSpanCreator.java index 7a6c3e1c..d3caf1a8 100644 --- a/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelSpanCreator.java +++ b/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelSpanCreator.java @@ -7,21 +7,19 @@ 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.Tracer; 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; -import java.util.concurrent.TimeUnit; - public final class OtelSpanCreator { - + private OtelSpanCreator() {} - + /** * Creates an LLM span with common gen_ai attributes pre-configured. - * + * * @param tracer the OpenTelemetry tracer * @param name the span name * @param system the AI system (e.g., "openai", "anthropic") @@ -30,11 +28,9 @@ public final class OtelSpanCreator { * @param sessionId optional session ID * @return a started Span with gen_ai attributes set */ - public static Span createLlmSpan(Tracer tracer, String name, String system, String model, - String serviceName, String sessionId) { - Span span = tracer.spanBuilder(name) - .setParent(Context.current()) - .startSpan(); + public static Span createLlmSpan( + Tracer tracer, String name, String system, String model, String serviceName, String sessionId) { + Span span = tracer.spanBuilder(name).setParent(Context.current()).startSpan(); span.setAttribute(AttributeKey.stringKey("gen_ai.operation.name"), "chat"); span.setAttribute(AttributeKey.stringKey("gen_ai.system"), system); span.setAttribute(AttributeKey.stringKey("gen_ai.request.model"), model); @@ -46,10 +42,10 @@ public final class OtelSpanCreator { } return span; } - + /** * Sets the prompt/input on a span. - * + * * @param span the span to set the input on * @param input the input/prompt text */ @@ -58,10 +54,10 @@ public final class OtelSpanCreator { span.setAttribute(AttributeKey.stringKey("gen_ai.prompt"), input); } } - + /** * Sets the completion/output on a span. - * + * * @param span the span to set the output on * @param output the output/completion text */ @@ -70,10 +66,10 @@ public final class OtelSpanCreator { span.setAttribute(AttributeKey.stringKey("gen_ai.completion"), output); } } - + /** * Sets token usage information on a span. - * + * * @param span the span to set token usage on * @param inputTokens number of input tokens * @param outputTokens number of output tokens @@ -82,10 +78,10 @@ public final class OtelSpanCreator { span.setAttribute(AttributeKey.longKey("gen_ai.usage.input_tokens"), (long) inputTokens); span.setAttribute(AttributeKey.longKey("gen_ai.usage.output_tokens"), (long) outputTokens); } - + /** * Creates a tool span with common gen_ai attributes pre-configured. - * + * * @param tracer the OpenTelemetry tracer * @param name the span name * @param toolName the tool name @@ -93,11 +89,9 @@ public final class OtelSpanCreator { * @param sessionId optional session ID * @return a started Span with gen_ai attributes set */ - public static Span createToolSpan(Tracer tracer, String name, String toolName, - String serviceName, String sessionId) { - Span span = tracer.spanBuilder(name) - .setParent(Context.current()) - .startSpan(); + public static Span createToolSpan( + Tracer tracer, String name, String toolName, String serviceName, String sessionId) { + Span span = tracer.spanBuilder(name).setParent(Context.current()).startSpan(); span.setAttribute(AttributeKey.stringKey("gen_ai.operation.name"), "tool"); span.setAttribute(AttributeKey.stringKey("tool.name"), toolName); if (serviceName != null) { @@ -108,21 +102,18 @@ public final class OtelSpanCreator { } return span; } - + /** * Creates a retrieval span with common gen_ai attributes pre-configured. - * + * * @param tracer the OpenTelemetry tracer * @param name the span name * @param serviceName the service name * @param sessionId optional session ID * @return a started Span with gen_ai attributes set */ - public static Span createRetrievalSpan(Tracer tracer, String name, - String serviceName, String sessionId) { - Span span = tracer.spanBuilder(name) - .setParent(Context.current()) - .startSpan(); + public static Span createRetrievalSpan(Tracer tracer, String name, String serviceName, String sessionId) { + Span span = tracer.spanBuilder(name).setParent(Context.current()).startSpan(); span.setAttribute(AttributeKey.stringKey("gen_ai.operation.name"), "retrieval"); if (serviceName != null) { span.setAttribute(AttributeKey.stringKey("service.name"), serviceName); @@ -132,21 +123,18 @@ public final class OtelSpanCreator { } return span; } - + /** * Creates a chain/workflow span with common gen_ai attributes pre-configured. - * + * * @param tracer the OpenTelemetry tracer * @param name the span name * @param serviceName the service name * @param sessionId optional session ID * @return a started Span with gen_ai attributes set */ - public static Span createChainSpan(Tracer tracer, String name, - String serviceName, String sessionId) { - Span span = tracer.spanBuilder(name) - .setParent(Context.current()) - .startSpan(); + public static Span createChainSpan(Tracer tracer, String name, String serviceName, String sessionId) { + Span span = tracer.spanBuilder(name).setParent(Context.current()).startSpan(); span.setAttribute(AttributeKey.stringKey("gen_ai.operation.name"), "chat"); if (serviceName != null) { span.setAttribute(AttributeKey.stringKey("service.name"), serviceName); @@ -156,10 +144,10 @@ 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 @@ -168,54 +156,51 @@ public final class OtelSpanCreator { // 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); - + + 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() - ); - + 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() - ); + 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); - + .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))); - + + 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"); @@ -223,13 +208,12 @@ public final class OtelSpanCreator { 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)); - + 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); @@ -237,18 +221,18 @@ public final class OtelSpanCreator { } 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; @@ -264,18 +248,18 @@ public final class OtelSpanCreator { } } } - + 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); + .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"; @@ -296,7 +280,7 @@ public final class OtelSpanCreator { return "chat"; } } - + private static String normalizeTraceId(String traceId) { if (traceId == null) { traceId = java.util.UUID.randomUUID().toString().replace("-", ""); @@ -309,7 +293,7 @@ public final class OtelSpanCreator { } return traceId; } - + private static String normalizeSpanId(String spanId) { if (spanId == null) { spanId = java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 16); @@ -323,4 +307,3 @@ public final class OtelSpanCreator { return spanId; } } - diff --git a/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelTraceExporter.java b/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelTraceExporter.java index 1b1c4821..cb7511a7 100644 --- a/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelTraceExporter.java +++ b/langsmith-java-core/src/main/java/com/langchain/smith/otel/OtelTraceExporter.java @@ -12,14 +12,12 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.semconv.ResourceAttributes; import java.time.Duration; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; /** * Exports LangSmith runs as OpenTelemetry traces to an OTEL endpoint. @@ -31,7 +29,7 @@ public final class OtelTraceExporter { private static final Logger logger = Logger.getLogger(OtelTraceExporter.class.getName()); private static final String INSTRUMENTATION_NAME = "langsmith-java"; private static final String INSTRUMENTATION_VERSION = "0.1.0"; - + static { logger.setLevel(Level.FINE); java.util.logging.ConsoleHandler consoleHandler = new java.util.logging.ConsoleHandler(); @@ -45,8 +43,12 @@ public final class OtelTraceExporter { private final SdkTracerProvider tracerProvider; private final String projectName; - private OtelTraceExporter(OtelConfig config, OpenTelemetry openTelemetry, Tracer tracer, - SdkTracerProvider tracerProvider, String projectName) { + private OtelTraceExporter( + OtelConfig config, + OpenTelemetry openTelemetry, + Tracer tracer, + SdkTracerProvider tracerProvider, + String projectName) { this.config = config; this.openTelemetry = openTelemetry; this.tracer = tracer; @@ -93,22 +95,24 @@ public final class OtelTraceExporter { 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()}); + 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); @@ -154,26 +158,23 @@ public final class OtelTraceExporter { */ public static OtelTraceExporter fromConfig(OtelConfig config) { String serviceName = config.getServiceName() != null ? config.getServiceName() : "langsmith-app"; - + Resource resource = Resource.getDefault().toBuilder() .put(ResourceAttributes.SERVICE_NAME, serviceName) .put(ResourceAttributes.SERVICE_VERSION, INSTRUMENTATION_VERSION) .build(); - + if (!config.isEnabled()) { SdkTracerProvider tracerProvider = SdkTracerProvider.builder().build(); - OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() - .setTracerProvider(tracerProvider) - .build(); + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build(); Tracer tracer = openTelemetry.getTracer(INSTRUMENTATION_NAME, INSTRUMENTATION_VERSION); return new OtelTraceExporter(config, openTelemetry, tracer, tracerProvider, null); } // Build the OTLP HTTP exporter OtlpHttpSpanExporterBuilder exporterBuilder = - OtlpHttpSpanExporter.builder() - .setEndpoint(config.getEndpoint()) - .setTimeout(config.getTimeout()); + OtlpHttpSpanExporter.builder().setEndpoint(config.getEndpoint()).setTimeout(config.getTimeout()); // Add custom headers for (Map.Entry header : config.getHeaders().entrySet()) { @@ -181,37 +182,36 @@ public final class OtelTraceExporter { } OtlpHttpSpanExporter exporter = exporterBuilder.build(); - + BatchSpanProcessor spanProcessor = BatchSpanProcessor.builder(exporter) - .setScheduleDelay(5, TimeUnit.SECONDS) // Batch every 5 seconds + .setScheduleDelay(5, TimeUnit.SECONDS) // Batch every 5 seconds .setMaxQueueSize(2048) .setMaxExportBatchSize(512) .build(); - + // Create TracerProvider with the processor SdkTracerProvider tracerProvider = SdkTracerProvider.builder() .addResource(resource) .addSpanProcessor(spanProcessor) .build(); - + // Create OpenTelemetry SDK - OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() - .setTracerProvider(tracerProvider) - .build(); - + OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build(); + Tracer tracer = openTelemetry.getTracer(INSTRUMENTATION_NAME, INSTRUMENTATION_VERSION); - + String projectName = config.getHeaders().get("Langsmith-Project"); if (projectName == null) { projectName = "default"; } - + // Log configuration for debugging - logger.log(Level.FINE, "Created OpenTelemetry SDK with endpoint: {0}, timeout: {1}", - new Object[]{config.getEndpoint(), config.getTimeout()}); + logger.log(Level.FINE, "Created OpenTelemetry SDK with endpoint: {0}, timeout: {1}", new Object[] { + config.getEndpoint(), config.getTimeout() + }); logger.log(Level.FINE, "Headers: {0}", config.getHeaders()); - logger.log(Level.FINE, "Service name: {0}, Project name: {1}", - new Object[]{serviceName, projectName}); + logger.log(Level.FINE, "Service name: {0}, Project name: {1}", new Object[] {serviceName, projectName}); return new OtelTraceExporter(config, openTelemetry, tracer, tracerProvider, projectName); } @@ -321,26 +321,21 @@ public final class OtelTraceExporter { * @return a new OtelTraceExporter instance */ public OtelTraceExporter build() { - OtelConfig.Builder configBuilder = - config != null - ? null - : OtelConfig.builder() - .endpoint(endpoint != null ? endpoint : "http://localhost:4318/v1/traces") - .enabled(enabled != null ? enabled : false) - .timeout(timeout != null ? timeout : Duration.ofSeconds(10)) - .headers(headers); - + OtelConfig.Builder configBuilder = config != null + ? null + : OtelConfig.builder() + .endpoint(endpoint != null ? endpoint : "http://localhost:4318/v1/traces") + .enabled(enabled != null ? enabled : false) + .timeout(timeout != null ? timeout : Duration.ofSeconds(10)) + .headers(headers); + if (configBuilder != null && serviceName != null) { configBuilder.serviceName(serviceName); } - - OtelConfig finalConfig = - config != null - ? config - : configBuilder.build(); + + OtelConfig finalConfig = config != null ? config : configBuilder.build(); return fromConfig(finalConfig); } } } - diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelJaegerExample.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelJaegerExample.java index a9edbf24..087c7a4e 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelJaegerExample.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelJaegerExample.java @@ -11,7 +11,7 @@ import java.time.Duration; /** * Example: Send live OpenTelemetry traces to Jaeger. - * + * * Start Jaeger: docker run -d --name jaeger -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one:latest * Run: ./gradlew :langsmith-java-example:run -Pexample=OtelJaegerExample * View: http://localhost:16686 @@ -38,14 +38,13 @@ public class OtelJaegerExample { System.out.println("→ Root span: langchain.chain started"); // CHILD 1: First LLM call - Span llmSpan1 = OtelSpanCreator.createLlmSpan(tracer, "openai.chat", - "openai", "gpt-4", projectName, null); + Span llmSpan1 = OtelSpanCreator.createLlmSpan(tracer, "openai.chat", "openai", "gpt-4", projectName, null); try (Scope scope = llmSpan1.makeCurrent()) { OtelSpanCreator.setInput(llmSpan1, "What's the weather?"); - + System.out.println(" → Child span 1: openai.chat started"); Thread.sleep(500); - + OtelSpanCreator.setOutput(llmSpan1, "I'll check the weather for you."); OtelSpanCreator.setTokenUsage(llmSpan1, 10, 8); llmSpan1.setStatus(StatusCode.OK); @@ -55,10 +54,9 @@ public class OtelJaegerExample { } // CHILD 2: Tool call - Span toolSpan = OtelSpanCreator.createToolSpan(tracer, "weather.tool", - "get_weather", projectName, null); + Span toolSpan = OtelSpanCreator.createToolSpan(tracer, "weather.tool", "get_weather", projectName, null); try (Scope scope = toolSpan.makeCurrent()) { - + System.out.println(" → Child span 2: weather.tool started"); Thread.sleep(300); toolSpan.setStatus(StatusCode.OK); @@ -68,33 +66,32 @@ public class OtelJaegerExample { } // CHILD 3: Second LLM call with nested database query - Span llmSpan2 = OtelSpanCreator.createLlmSpan(tracer, "openai.chat", - "openai", "gpt-4", projectName, null); + Span llmSpan2 = OtelSpanCreator.createLlmSpan(tracer, "openai.chat", "openai", "gpt-4", projectName, null); try (Scope scope2 = llmSpan2.makeCurrent()) { OtelSpanCreator.setInput(llmSpan2, "Provide a detailed weather summary."); - + System.out.println(" → Child span 3: openai.chat started"); - + // NESTED CHILD: Database query - Span dbSpan = OtelSpanCreator.createToolSpan(tracer, "database.query", - "postgresql_query", projectName, null); + Span dbSpan = + OtelSpanCreator.createToolSpan(tracer, "database.query", "postgresql_query", projectName, null); try (Scope dbScope = dbSpan.makeCurrent()) { dbSpan.setAttribute(AttributeKey.stringKey("db.system"), "postgresql"); OtelSpanCreator.setInput(dbSpan, "SELECT * FROM weather_data WHERE city='SF'"); - + System.out.println(" → Nested span: database.query started"); Thread.sleep(200); - + // Simulate error dbSpan.setStatus(StatusCode.ERROR, "Connection timeout"); dbSpan.setAttribute(AttributeKey.booleanKey("error"), true); dbSpan.setAttribute(AttributeKey.stringKey("error.type"), "timeout"); - + System.out.println(" ← Nested span: database.query failed"); } finally { dbSpan.end(); } - + Thread.sleep(400); OtelSpanCreator.setOutput(llmSpan2, "Unable to retrieve detailed data due to database error."); OtelSpanCreator.setTokenUsage(llmSpan2, 20, 15); diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelLangSmithExample.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelLangSmithExample.java index 9112670b..cbf5360e 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelLangSmithExample.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/OtelLangSmithExample.java @@ -8,7 +8,6 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; -import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import java.time.Duration; import java.util.HashMap; import java.util.Map; @@ -16,10 +15,10 @@ import java.util.UUID; /** * Example: Send OpenTelemetry traces to LangSmith UI. - * + * * This is a mock/demo example that simulates LLM calls without requiring API keys. * It demonstrates the tracing structure and waterfall visualization. - * + * * Usage: * export LANGSMITH_API_KEY=your_api_key * ./gradlew :langsmith-java-example:run -Pexample=OtelLangSmith @@ -81,8 +80,8 @@ public class OtelLangSmithExample { try (Scope rootScope = rootSpan.makeCurrent()) { // CHILD 1: First LLM call - Span llmSpan1 = OtelSpanCreator.createLlmSpan(tracer, "openai.llm.call", - "openai", "gpt-4", projectName, sessionId); + Span llmSpan1 = + OtelSpanCreator.createLlmSpan(tracer, "openai.llm.call", "openai", "gpt-4", projectName, sessionId); try (Scope llmScope1 = llmSpan1.makeCurrent()) { OtelSpanCreator.setInput(llmSpan1, "What's the weather in San Francisco?"); Thread.sleep(500); @@ -94,8 +93,8 @@ public class OtelLangSmithExample { } // CHILD 2: Tool call - Span toolSpan = OtelSpanCreator.createToolSpan(tracer, "weather.tool", - "get_weather", projectName, sessionId); + Span toolSpan = + OtelSpanCreator.createToolSpan(tracer, "weather.tool", "get_weather", projectName, sessionId); try (Scope toolScope = toolSpan.makeCurrent()) { toolSpan.setAttribute(AttributeKey.stringKey("tool.input"), "{\"location\":\"San Francisco\"}"); Thread.sleep(300); @@ -105,14 +104,14 @@ public class OtelLangSmithExample { } // CHILD 3: Second LLM call with nested retriever - Span llmSpan2 = OtelSpanCreator.createLlmSpan(tracer, "openai.llm.final", - "openai", "gpt-4", projectName, sessionId); + Span llmSpan2 = OtelSpanCreator.createLlmSpan( + tracer, "openai.llm.final", "openai", "gpt-4", projectName, sessionId); try (Scope llmScope2 = llmSpan2.makeCurrent()) { OtelSpanCreator.setInput(llmSpan2, "Based on the weather data, provide a summary."); // NESTED CHILD: Retriever call inside LLM - Span retrieverSpan = OtelSpanCreator.createRetrievalSpan(tracer, "database.retriever", - projectName, sessionId); + Span retrieverSpan = + OtelSpanCreator.createRetrievalSpan(tracer, "database.retriever", projectName, sessionId); try (Scope retrieverScope = retrieverSpan.makeCurrent()) { OtelSpanCreator.setInput(retrieverSpan, "weather forecast data"); Thread.sleep(200); @@ -123,8 +122,8 @@ public class OtelLangSmithExample { } Thread.sleep(400); - OtelSpanCreator.setOutput(llmSpan2, - "The weather in San Francisco is sunny with a temperature of 72°F."); + OtelSpanCreator.setOutput( + llmSpan2, "The weather in San Francisco is sunny with a temperature of 72°F."); OtelSpanCreator.setTokenUsage(llmSpan2, 25, 18); llmSpan2.setStatus(StatusCode.OK); } finally { diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/SpringBootLangSmithExample.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/SpringBootLangSmithExample.java index c014f9d2..39707de8 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/SpringBootLangSmithExample.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/SpringBootLangSmithExample.java @@ -5,22 +5,22 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Spring Boot example: Send OpenTelemetry traces to LangSmith. - * + * * Usage: * export LANGSMITH_API_KEY=your_api_key * export LANGSMITH_PROJECT=my-project # optional, defaults to "default" * ./gradlew :langsmith-java-example:run -Pexample=SpringBootLangSmith - * + * * Then make requests to: * http://localhost:8080/api/chat * http://localhost:8080/api/analyze?text=hello */ @SpringBootApplication public class SpringBootLangSmithExample { - + public static void main(String[] args) { System.out.println("=== Spring Boot + LangSmith OpenTelemetry Example ===\n"); - + // Check required environment variables String apiKey = System.getenv("LANGSMITH_API_KEY"); if (apiKey == null || apiKey.isEmpty()) { @@ -31,12 +31,12 @@ public class SpringBootLangSmithExample { System.err.println(" ./gradlew :langsmith-java-example:run -Pexample=SpringBootLangSmith"); System.exit(1); } - + String projectName = System.getenv("LANGSMITH_PROJECT"); if (projectName == null || projectName.isEmpty()) { projectName = "default"; } - + System.out.println("Configuration:"); System.out.println(" Project: " + projectName); System.out.println(" Endpoint: https://api.smith.langchain.com/otel/v1/traces"); @@ -45,8 +45,7 @@ public class SpringBootLangSmithExample { System.out.println(" POST http://localhost:8080/api/chat"); System.out.println(" GET http://localhost:8080/api/analyze?text=hello"); System.out.println(); - + SpringApplication.run(SpringBootLangSmithExample.class, args); } } - diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelConfiguration.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelConfiguration.java index 9db509ba..e850feb7 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelConfiguration.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelConfiguration.java @@ -3,19 +3,18 @@ package com.langchain.smith.example.config; import com.langchain.smith.otel.OtelConfig; import com.langchain.smith.otel.OtelTraceExporter; import io.opentelemetry.api.trace.Tracer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - import java.time.Duration; import java.util.HashMap; import java.util.Map; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; /** * Spring configuration for OpenTelemetry integration with LangSmith. */ @Configuration public class OtelConfiguration { - + @Bean public OtelTraceExporter otelTraceExporter() { String apiKey = System.getenv("LANGSMITH_API_KEY"); @@ -23,11 +22,11 @@ public class OtelConfiguration { if (projectName == null || projectName.isEmpty()) { projectName = "default"; } - + Map headers = new HashMap<>(); headers.put("x-api-key", apiKey); headers.put("Langsmith-Project", projectName); - + OtelConfig config = OtelConfig.builder() .enabled(true) .endpoint("https://api.smith.langchain.com/otel/v1/traces") @@ -35,13 +34,12 @@ public class OtelConfiguration { .timeout(Duration.ofSeconds(30)) .serviceName("spring-boot-langsmith") .build(); - + return OtelTraceExporter.fromConfig(config); } - + @Bean public Tracer tracer(OtelTraceExporter exporter) { return exporter.getTracer(); } } - diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelShutdownHook.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelShutdownHook.java index 646c4038..0186eeed 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelShutdownHook.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/config/OtelShutdownHook.java @@ -10,14 +10,14 @@ import org.springframework.stereotype.Component; */ @Component public class OtelShutdownHook { - + private final OtelTraceExporter exporter; - + @Autowired public OtelShutdownHook(OtelTraceExporter exporter) { this.exporter = exporter; } - + @PreDestroy public void onShutdown() { System.out.println("\n→ Flushing OpenTelemetry traces..."); @@ -29,4 +29,3 @@ public class OtelShutdownHook { } } } - diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/controller/ChatController.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/controller/ChatController.java index 2c21cdf1..11288209 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/controller/ChatController.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/controller/ChatController.java @@ -6,59 +6,56 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Scope; +import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import java.util.Map; - /** * REST controller demonstrating OpenTelemetry tracing with LangSmith. */ @RestController @RequestMapping("/api") public class ChatController { - + private final Tracer tracer; private final LlmService llmService; - + @Autowired public ChatController(Tracer tracer, LlmService llmService) { this.tracer = tracer; this.llmService = llmService; } - + @PostMapping("/chat") public Map chat(@RequestBody Map request) { String userMessage = request.getOrDefault("message", "Hello!"); - + // Create a root span for the entire request - Span rootSpan = OtelSpanCreator.createChainSpan( - tracer, - "chat_request", - "spring-boot-langsmith", - null - ); - + Span rootSpan = OtelSpanCreator.createChainSpan(tracer, "chat_request", "spring-boot-langsmith", null); + try (Scope scope = rootSpan.makeCurrent()) { OtelSpanCreator.setInput(rootSpan, userMessage); - + System.out.println("→ Processing chat request: " + userMessage); - + // Call the LLM service (which creates its own span) String response = llmService.generateResponse(userMessage); - + OtelSpanCreator.setOutput(rootSpan, response); rootSpan.setStatus(StatusCode.OK); - + System.out.println("← Chat response generated"); - + return Map.of( - "request", userMessage, - "response", response, - "model", "gpt-4", - "trace_id", rootSpan.getSpanContext().getTraceId() - ); - + "request", + userMessage, + "response", + response, + "model", + "gpt-4", + "trace_id", + rootSpan.getSpanContext().getTraceId()); + } catch (Exception e) { rootSpan.setStatus(StatusCode.ERROR, e.getMessage()); throw e; @@ -66,39 +63,33 @@ public class ChatController { rootSpan.end(); } } - + @GetMapping("/analyze") public Map analyze(@RequestParam String text) { // Create a span for the analysis operation - Span analysisSpan = OtelSpanCreator.createChainSpan( - tracer, - "text_analysis", - "spring-boot-langsmith", - null - ); - + Span analysisSpan = OtelSpanCreator.createChainSpan(tracer, "text_analysis", "spring-boot-langsmith", null); + try (Scope scope = analysisSpan.makeCurrent()) { OtelSpanCreator.setInput(analysisSpan, text); - + System.out.println("→ Analyzing text: " + text); - + // Simulate analysis with nested operations int wordCount = text.split("\\s+").length; String sentiment = llmService.analyzeSentiment(text); - + String result = String.format("Word count: %d, Sentiment: %s", wordCount, sentiment); OtelSpanCreator.setOutput(analysisSpan, result); analysisSpan.setStatus(StatusCode.OK); - + System.out.println("← Analysis complete"); - + return Map.of( - "text", text, - "word_count", wordCount, - "sentiment", sentiment, - "trace_id", analysisSpan.getSpanContext().getTraceId() - ); - + "text", text, + "word_count", wordCount, + "sentiment", sentiment, + "trace_id", analysisSpan.getSpanContext().getTraceId()); + } catch (Exception e) { analysisSpan.setStatus(StatusCode.ERROR, e.getMessage()); throw e; @@ -106,10 +97,9 @@ public class ChatController { analysisSpan.end(); } } - + @GetMapping("/health") public Map health() { return Map.of("status", "healthy", "service", "spring-boot-langsmith"); } } - diff --git a/langsmith-java-example/src/main/java/com/langchain/smith/example/service/LlmService.java b/langsmith-java-example/src/main/java/com/langchain/smith/example/service/LlmService.java index 8014b699..a0d357c2 100644 --- a/langsmith-java-example/src/main/java/com/langchain/smith/example/service/LlmService.java +++ b/langsmith-java-example/src/main/java/com/langchain/smith/example/service/LlmService.java @@ -13,45 +13,39 @@ import org.springframework.stereotype.Service; */ @Service public class LlmService { - + private final Tracer tracer; - + @Autowired public LlmService(Tracer tracer) { this.tracer = tracer; } - + /** * Simulates an LLM API call with tracing. */ public String generateResponse(String input) { - Span llmSpan = OtelSpanCreator.createLlmSpan( - tracer, - "openai.chat", - "openai", - "gpt-4", - "spring-boot-langsmith", - null - ); - + Span llmSpan = + OtelSpanCreator.createLlmSpan(tracer, "openai.chat", "openai", "gpt-4", "spring-boot-langsmith", null); + try (Scope scope = llmSpan.makeCurrent()) { OtelSpanCreator.setInput(llmSpan, input); - + System.out.println(" → Calling OpenAI API..."); - + // Simulate LLM processing time Thread.sleep(500); - + String response = "I received your message: '" + input + "'. How can I help you today?"; - + OtelSpanCreator.setOutput(llmSpan, response); OtelSpanCreator.setTokenUsage(llmSpan, 15, 20); llmSpan.setStatus(StatusCode.OK); - + System.out.println(" ← OpenAI API response received"); - + return response; - + } catch (Exception e) { llmSpan.setStatus(StatusCode.ERROR, e.getMessage()); throw new RuntimeException("LLM call failed", e); @@ -59,28 +53,22 @@ public class LlmService { llmSpan.end(); } } - + /** * Simulates sentiment analysis with tracing. */ public String analyzeSentiment(String text) { Span sentimentSpan = OtelSpanCreator.createLlmSpan( - tracer, - "sentiment_analysis", - "openai", - "gpt-4", - "spring-boot-langsmith", - null - ); - + tracer, "sentiment_analysis", "openai", "gpt-4", "spring-boot-langsmith", null); + try (Scope scope = sentimentSpan.makeCurrent()) { OtelSpanCreator.setInput(sentimentSpan, text); - + System.out.println(" → Analyzing sentiment..."); - + // Simulate analysis time Thread.sleep(300); - + // Simple sentiment detection String sentiment; if (text.toLowerCase().contains("good") || text.toLowerCase().contains("great")) { @@ -90,15 +78,15 @@ public class LlmService { } else { sentiment = "neutral"; } - + OtelSpanCreator.setOutput(sentimentSpan, sentiment); OtelSpanCreator.setTokenUsage(sentimentSpan, 8, 2); sentimentSpan.setStatus(StatusCode.OK); - + System.out.println(" ← Sentiment: " + sentiment); - + return sentiment; - + } catch (Exception e) { sentimentSpan.setStatus(StatusCode.ERROR, e.getMessage()); throw new RuntimeException("Sentiment analysis failed", e); @@ -107,4 +95,3 @@ public class LlmService { } } } -