Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions applications/spring-ai-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,42 @@
- Java 21+
- Langfuse stack ([Cloud](https://cloud.langfuse.com/) or [Self-Hosted](https://langfuse.com/docs/deployment/self-host))
- Langfuse API Keys
- An OpenAI Api Key
- An OpenAI API Key

## Technology Stack

- **Spring Boot**: 4.0.3
- **Spring AI**: 2.0.0-M2
- **Java**: 21
- **OpenTelemetry**: Native support via `spring-boot-starter-opentelemetry`

## How to run

1. Configure environment variables to connect Spring AI demo app with Langfuse.
```
```bash
export SPRING_AI_OPENAI_APIKEY="sk-proj-xxx"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://cloud.langfuse.com/api/public/otel" # 🇪🇺 EU data region
# export OTEL_EXPORTER_OTLP_ENDPOINT="https://us.cloud.langfuse.com/api/public/otel" # 🇺🇸 US data region
# export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:3000/api/public/otel" # 🏠 Local deployment (>= v3.22.0)
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(echo -n "pk-lf-xxx:sk-lf-xxx" | base64)"
export OTEL_EXPORTER_OTLP_HEADERS_AUTHORIZATION="Basic $(echo -n "pk-lf-xxx:sk-lf-xxx" | base64)"
```
2. Run the sample application with `./mvnw clean install spring-boot:run`.
3. Call the chat endpoint with `curl localhost:8080/v1/chat`.

2. Run the sample application:
```bash
./mvnw clean install spring-boot:run
```

3. Call the chat endpoint:
```bash
curl localhost:8080/v1/chat
```

4. Observe the new trace in the Langfuse web UI.

## Configuration

The application uses native OpenTelemetry support introduced in Spring Boot 4.x. The OTLP endpoint is configured in `OtelConfig.java` to point to `http://localhost:3001/api/public/otel/v1/traces`.

For cloud deployments, modify the endpoint in `OtelConfig.java`:
- 🇪🇺 EU: `https://cloud.langfuse.com/api/public/otel/v1/traces`
- 🇺🇸 US: `https://us.cloud.langfuse.com/api/public/otel/v1/traces`
- 🏠 Local: `http://localhost:3000/api/public/otel/v1/traces` (>= v3.22.0)

![sample-trace](./screenshots/spring-ai-demo-trace.png)
37 changes: 15 additions & 22 deletions applications/spring-ai-demo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<version>4.0.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.langfuse</groupId>
Expand All @@ -17,18 +17,11 @@

<properties>
<java.version>21</java.version>
<spring-ai.version>1.0.0</spring-ai.version>
<spring-ai.version>2.0.0-M2</spring-ai.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom</artifactId>
<version>2.17.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
Expand All @@ -44,30 +37,30 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- Spring AI needs a reactive web server to run for some reason-->

<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring AI (OpenAI model) -->
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- Spring Boot Actuator for observability support -->

<!-- ✅ Native OpenTelemetry support (Boot 4 way) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>
<!-- Micrometer Observation -> OpenTelemetry bridge -->

<!-- Actuator (recommended for observability endpoints) -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- OpenTelemetry OTLP exporter for traces -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.langfuse.springai;

import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OtelConfig {

@Value("${otel.exporter.otlp.headers.authorization}")
private String authorizationHeader;

@Bean
public SpanExporter spanExporter() {
return OtlpHttpSpanExporter.builder()
.setEndpoint("http://localhost:3001/api/public/otel/v1/traces")
.addHeader("Authorization", authorizationHeader)
.build();
}

@Bean
public BatchSpanProcessor spanProcessor(SpanExporter exporter) {
return BatchSpanProcessor.builder(exporter).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ management:
observations:
annotations:
enabled: true # Enable @Observed (if you use observation annotations in code)

otel:
exporter:
otlp:
headers:
authorization: {{OTEL_EXPORTER_OTLP_HEADERS_AUTHORIZATION}} # Set via env var for security}