Skip to main content
This example demonstrates how to use OpenInference instrumentation with LangChain4j for building LLM applications in Java.

Prerequisites

  • Java 11+
  • Gradle or Maven
  • OpenAI API key
  • Phoenix running (or another OTLP-compatible collector)

Installation

1

Add dependencies to build.gradle

dependencies {
    implementation 'dev.langchain4j:langchain4j:0.28.0'
    implementation 'dev.langchain4j:langchain4j-open-ai:0.28.0'
    implementation 'io.openinference:openinference-instrumentation-langchain4j:0.1.0'
    implementation 'io.opentelemetry:opentelemetry-sdk:1.32.0'
    implementation 'io.opentelemetry:opentelemetry-exporter-otlp:1.32.0'
}
2

Set environment variables

export OPENAI_API_KEY="your-api-key"
# Optional: Set if Phoenix has authentication enabled
export PHOENIX_API_KEY="your-phoenix-key"

Complete Example

package io.openinference.examples;

import static com.arize.semconv.trace.SemanticResourceAttributes.SEMRESATTRS_PROJECT_NAME;

import com.arize.instrumentation.langchain4j.LangChain4jInstrumentor;
import com.arize.instrumentation.langchain4j.LangChain4jModelListener;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.agent.tool.ToolSpecifications;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ToolExecutionResultMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiChatRequestParameters;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * Example demonstrating OpenInference instrumentation with LangChain4j.
 *
 * To run this example with Phoenix:
 * 1. Start Phoenix: `docker run -p 6006:6006 -p 4317:4317 arizephoenix/phoenix:latest`
 * 2. Set your OpenAI API key: `export OPENAI_API_KEY=your-key-here`
 * 3. (Optional) if you have auth enabled on Phoenix, `export PHOENIX_API_KEY=your-key-here`
 * 4. Run the example: `./gradlew :examples:langchain4j-example:run`
 * 5. View traces in Phoenix: http://localhost:6006
 */
public class LangChain4jExample {

    private static SdkTracerProvider tracerProvider;
    private static final Logger logger = Logger.getLogger(LangChain4jExample.class.getName());

    static class WeatherTools {

        @Tool("Returns the weather forecast for a given city")
        String getWeather(@P("The city for which the weather forecast should be returned") String city) {
            return "85 degrees";
        }
    }

    public static void main(String[] args) {
        initializeOpenTelemetry();

        LangChain4jInstrumentor instrumentor = LangChain4jInstrumentor.instrument();
        LangChain4jModelListener listener = instrumentor.createModelListener();

        String apiKey = System.getenv("OPENAI_API_KEY");
        if (apiKey == null) {
            logger.log(Level.SEVERE, "Please set OPENAI_API_KEY environment variable");
            System.exit(1);
        }

        List<ToolSpecification> toolSpecifications = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);

        OpenAiChatRequestParameters openAiChatRequestParameters = OpenAiChatRequestParameters.builder()
                .modelName("gpt-4o-mini")
                .temperature(0.7)
                .maxOutputTokens(100)
                .toolSpecifications(toolSpecifications)
                .build();

        ChatModel model = OpenAiChatModel.builder()
                .apiKey(apiKey)
                .defaultRequestParameters(openAiChatRequestParameters)
                .modelName("gpt-4o-mini")
                .temperature(0.7)
                .maxTokens(100)
                .listeners(List.of(listener))
                .timeout(Duration.ofSeconds(30))
                .build();
        
        // Use the model - traces will be automatically created
        logger.info("Sending request to OpenAI...");
        ChatResponse response = model.chat(UserMessage.from("What is the capital of France? Answer in one sentence."));
        logger.info("Response: " + response.aiMessage().text());

        // Example with multiple messages to show conversation tracing
        logger.info("\nSending another request...");
        ChatResponse response2 = model.chat(
                UserMessage.from("What is the capital of France? Answer in one sentence."),
                response.aiMessage(),
                UserMessage.from("What about Germany? also whats the weather like in germany"));
        logger.info("Response: " + response2);

        List<ToolExecutionResultMessage> toolExecutionResultMessages =
                response2.aiMessage().toolExecutionRequests().stream()
                        .map(t -> ToolExecutionResultMessage.from(t, "The weather will be 80 degrees."))
                        .collect(Collectors.toList());
        ArrayList<ChatMessage> messages = new ArrayList<>(List.of(
                UserMessage.from("What is the capital of France? Answer in one sentence."),
                response.aiMessage(),
                UserMessage.from("What about Germany? also whats the weather like in germany"),
                response2.aiMessage()));
        messages.addAll(toolExecutionResultMessages);

        model.chat(messages);

        if (tracerProvider != null) {
            logger.info("Flushing and shutting down trace provider...");

            // Force flush all pending spans
            CompletableResultCode flushResult = tracerProvider.forceFlush();
            flushResult.join(10, java.util.concurrent.TimeUnit.SECONDS);

            if (flushResult.isSuccess()) {
                logger.info("Successfully flushed all traces");
            } else {
                logger.warning("Failed to flush all traces");
            }

            // Shutdown the trace provider
            CompletableResultCode shutdownResult = tracerProvider.shutdown();
            shutdownResult.join(10, java.util.concurrent.TimeUnit.SECONDS);

            if (!shutdownResult.isSuccess()) {
                logger.warning("Failed to shutdown trace provider cleanly");
            }
        }

        System.out.println("\nTraces have been sent to Phoenix at http://localhost:6006");
    }

    private static void initializeOpenTelemetry() {
        // Create resource with service name
        Resource resource = Resource.getDefault()
                .merge(Resource.create(Attributes.of(
                        AttributeKey.stringKey("service.name"), "langchain4j",
                        AttributeKey.stringKey(SEMRESATTRS_PROJECT_NAME), "langchain4j-project",
                        AttributeKey.stringKey("service.version"), "0.1.0")));

        String apiKey = System.getenv("PHOENIX_API_KEY");
        OtlpGrpcSpanExporterBuilder otlpExporterBuilder = OtlpGrpcSpanExporter.builder()
                .setEndpoint("http://localhost:4317")
                .setTimeout(Duration.ofSeconds(2));
        OtlpGrpcSpanExporter otlpExporter = null;
        if (apiKey != null && !apiKey.isEmpty()) {
            otlpExporter = otlpExporterBuilder
                    .setHeaders(() -> Map.of("Authorization", String.format("Bearer %s", apiKey)))
                    .build();
        } else {
            logger.log(Level.WARNING, "Please set PHOENIX_API_KEY environment variable if auth is enabled.");
            otlpExporter = otlpExporterBuilder.build();
        }

        // Create tracer provider with both OTLP (for Phoenix) and console exporters
        tracerProvider = SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(otlpExporter)
                        .setScheduleDelay(Duration.ofSeconds(1))
                        .build())
                .addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create()))
                .setResource(resource)
                .build();

        // Build OpenTelemetry SDK
        OpenTelemetrySdk.builder()
                .setTracerProvider(tracerProvider)
                .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
                .buildAndRegisterGlobal();

        System.out.println("OpenTelemetry initialized. Traces will be sent to Phoenix at http://localhost:6006");
    }
}

Key Features

Model Listener Pattern

LangChain4j uses a listener pattern for instrumentation:
LangChain4jInstrumentor instrumentor = LangChain4jInstrumentor.instrument();
LangChain4jModelListener listener = instrumentor.createModelListener();

// Add listener to the model
ChatModel model = OpenAiChatModel.builder()
    .listeners(List.of(listener))
    .build();

Tool/Function Calling

Define tools using annotations:
class WeatherTools {
    @Tool("Returns the weather forecast for a given city")
    String getWeather(@P("The city name") String city) {
        // Tool implementation
        return "Sunny, 75°F";
    }
}

// Register tools
List<ToolSpecification> tools = ToolSpecifications.toolSpecificationsFrom(WeatherTools.class);

Resource Attributes

Use resource attributes to organize traces:
import static com.arize.semconv.trace.SemanticResourceAttributes.SEMRESATTRS_PROJECT_NAME;

Resource resource = Resource.create(Attributes.of(
    AttributeKey.stringKey("service.name"), "my-service",
    AttributeKey.stringKey(SEMRESATTRS_PROJECT_NAME), "my-project"
));

Graceful Shutdown

Always flush and shutdown the tracer provider:
tracerProvider.forceFlush().join(10, TimeUnit.SECONDS);
tracerProvider.shutdown().join(10, TimeUnit.SECONDS);

Running with Gradle

./gradlew :examples:langchain4j-example:run

Next Steps

Build docs developers (and LLMs) love