Skip to content
Merged
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
19 changes: 19 additions & 0 deletions scripts/integrations.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ test('Vertex AI recommends Google Gen AI and keeps bounded legacy migration guid
assert.match(page, /FI_HIDE_OUTPUTS=true/);
});

test('Anthropic Java recommends one native tracing owner and preserves released legacy usage', () => {
const page = read('src/pages/docs/integrations/traceai/java/anthropic.mdx');
const native = page.split('## Existing TracedAnthropicClient applications')[0];
assert.match(native, /opentelemetry-bom/);
assert.match(native, /1\.57\.0/);
assert.match(native, /OpenTelemetryContentMode\.METADATA_ONLY/);
assert.match(native, /W3CTraceContextPropagator/);
assert.match(native, /X-Project-Name/);
assert.match(native, /client\.messages\(\)\.create\(/);
assert.doesNotMatch(native, /new TracedAnthropicClient/);
assert.match(page, /<groupId>com\.futureagi<\/groupId>/);
assert.match(page, /\.openTelemetry\(false\)/);
assert.doesNotMatch(page, /main-SNAPSHOT|wrapper is incompatible/);
assert.equal(integrations.find(i => i.id === 'java-anthropic').method, 'OpenTelemetry');
for (const [, route] of page.matchAll(/\]\((\/docs\/[^)#]+)(?:#[^)]*)?\)/g)) {
assert.ok(exists(route), `Missing guide link: ${route}`);
}
});

test('public prose and catalog labels contain no internal QA label or Alpha release status', () => {
for (const file of routeFiles.filter(f=>f.endsWith('.mdx'))) {
const prose=publicProse(fs.readFileSync(file,'utf8'));
Expand Down
2 changes: 1 addition & 1 deletion src/lib/integrations.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const integrations = [

{ id: 'spring-boot', name: 'Spring Boot & Spring AI', href: '/docs/integrations/traceai/spring-boot', category: 'java', summary: 'Set up tracing with the Spring Boot starter.', method: 'Java starter', logo: 'spring', aliases: ['JVM'] },
{ id: 'java-openai', name: 'OpenAI for Java', href: '/docs/integrations/traceai/java/openai', category: 'java', summary: 'Wrap the OpenAI Java client to capture requests.', method: 'Java wrapper', logo: 'openai' },
{ id: 'java-anthropic', name: 'Anthropic for Java', href: '/docs/integrations/traceai/java/anthropic', category: 'java', summary: 'Capture Claude calls from your Java application.', method: 'Java wrapper', logo: 'anthropic' },
{ id: 'java-anthropic', name: 'Anthropic for Java', href: '/docs/integrations/traceai/java/anthropic', category: 'java', summary: 'Export native Claude SDK spans without wrapping the client.', method: 'OpenTelemetry', logo: 'anthropic' },
{ id: 'java-bedrock', name: 'AWS Bedrock for Java', href: '/docs/integrations/traceai/java/bedrock', category: 'java', summary: 'Wrap Bedrock clients to capture model requests.', method: 'Java wrapper', logo: 'bedrock' },
{ id: 'java-cohere', name: 'Cohere for Java', href: '/docs/integrations/traceai/java/cohere', category: 'java', summary: 'Trace Cohere requests from your Java client.', method: 'Java wrapper', logo: 'cohere' },
{ id: 'java-pinecone', name: 'Pinecone for Java', href: '/docs/integrations/traceai/java/pinecone', category: 'java', summary: 'Capture vector queries and upserts in Java.', method: 'Java wrapper', logo: 'pinecone' },
Expand Down
236 changes: 163 additions & 73 deletions src/pages/docs/integrations/traceai/java/anthropic.mdx
Original file line number Diff line number Diff line change
@@ -1,118 +1,208 @@
---
title: "Anthropic Java Integration with Future AGI Tracing"
description: "Trace Anthropic Messages API calls in Java with TracedAnthropicClient."
description: "Send native Anthropic Java OpenTelemetry spans to Future AGI without wrapping the SDK."
---

`TracedAnthropicClient` wraps Anthropic Messages API calls for Future AGI tracing.

<Warning>
The wrapper is incompatible with SDK parameter objects whose `temperature()` method returns `Optional<Double>`, including the SDK version shown below. A call can fail before reaching Anthropic, even when no temperature is set. Use the Anthropic SDK client directly for those versions rather than the traced wrapper; that bypasses this integration's tracing.
</Warning>
Send Anthropic request timing, model names, token usage, and errors to Future AGI using the SDK's native OpenTelemetry tracing.

## Prerequisites

Complete the [Java SDK setup](/docs/integrations/traceai/java) first.
Use Java 17 or later, Anthropic Java 3.2.0 or later, and OpenTelemetry 1.57.0 or later. You need an Anthropic API key and [Future AGI API credentials](/docs/get-started/send-your-first-trace).

## Installation

<CodeGroup titles={["Maven", "Gradle"]}>

```xml Maven
<dependency>
<groupId>com.github.future-agi.traceAI</groupId>
<artifactId>traceai-java-anthropic</artifactId>
<version>main-SNAPSHOT</version>
</dependency>
```
<Warning>
Use one tracing owner for Anthropic requests. Do not combine native SDK tracing with `TracedAnthropicClient` or another Anthropic instrumentor: both record the same request and can double-count token usage. Existing wrapper users can keep the [legacy setup](#existing-tracedanthropicclient-applications).
</Warning>

```groovy Gradle
implementation 'com.github.future-agi.traceAI:traceai-java-anthropic:main-SNAPSHOT'
## Install dependencies

Add these dependencies to your Maven project's `pom.xml`. The OpenTelemetry BOM keeps its API, SDK, and exporter versions aligned, including when another dependency requests an older API.

```xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.57.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.anthropic</groupId>
<artifactId>anthropic-java</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
</dependencies>
```

</CodeGroup>

You also need the Anthropic Java SDK:
For Gradle:

<CodeGroup titles={["Maven", "Gradle"]}>

```xml Maven
<dependency>
<groupId>com.anthropic</groupId>
<artifactId>anthropic-java</artifactId>
<version>1.0.0</version>
</dependency>
```groovy
implementation platform('io.opentelemetry:opentelemetry-bom:1.57.0')
implementation 'com.anthropic:anthropic-java:3.3.0'
implementation 'io.opentelemetry:opentelemetry-sdk'
implementation 'io.opentelemetry:opentelemetry-exporter-otlp'
```

```groovy Gradle
implementation 'com.anthropic:anthropic-java:1.0.0'
```
This route does not require `traceai-java-anthropic` or `TraceAI.initFromEnvironment()`.

</CodeGroup>
## Configure credentials and destination

---
Set these variables in your application's environment or secret manager:

## Why reflection?
```bash
export ANTHROPIC_API_KEY="<anthropic-api-key>"
export FI_API_KEY="<future-agi-api-key>"
export FI_SECRET_KEY="<future-agi-secret-key>"
export FI_PROJECT_NAME="anthropic-java"
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://api.futureagi.com/tracer/v1/traces"
```

The wrapper accepts the client and message parameters as `Object`. Pass the Anthropic SDK's `MessageCreateParams` object to `createMessage()`.
The traces endpoint is the **complete URL**, including `/tracer/v1/traces`. For a self-hosted deployment, replace the host with your Future AGI API host.

---
## Trace a message

## Wrap the client
Save this as `src/main/java/AnthropicTracingExample.java`. It registers one tracer provider, uses the stock OTLP/HTTP protobuf exporter, and flushes queued spans when the provider closes.

Tracing captures raw input/output, structured messages and error details by default. In Java, `hideInputs` / `hideOutputs` and `hideInputMessages` / `hideOutputMessages` are separate controls; they do not suppress exception details. Do not use this integration for workloads requiring complete content suppression. See the [Java privacy configuration](/docs/integrations/traceai/java#traceconfig-reference).
If your application already configures OpenTelemetry, add the exporter to that provider instead of registering a second global provider. Keep its W3C trace-context propagator so Anthropic requests join your application's traces.

```java
import ai.traceai.TraceAI;
import ai.traceai.anthropic.TracedAnthropicClient;
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.opentelemetry.OpenTelemetryContentMode;
import com.anthropic.core.opentelemetry.OpenTelemetryOptions;
import com.anthropic.core.opentelemetry.OpenTelemetryTraces;
import com.anthropic.models.messages.MessageCreateParams;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;

public final class AnthropicTracingExample {
public static void main(String[] args) {
String project = System.getenv("FI_PROJECT_NAME");
var exporter = OtlpHttpSpanExporter.builder()
.setEndpoint(System.getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"))
.addHeader("X-Api-Key", System.getenv("FI_API_KEY"))
.addHeader("X-Secret-Key", System.getenv("FI_SECRET_KEY"))
.addHeader("X-Project-Name", project)
.build();

try (var provider = SdkTracerProvider.builder()
.setResource(Resource.getDefault().merge(Resource.create(Attributes.of(
AttributeKey.stringKey("service.name"), "anthropic-java-app",
AttributeKey.stringKey("project_name"), project))))
.addSpanProcessor(BatchSpanProcessor.builder(exporter).build())
.build()) {
OpenTelemetrySdk.builder().setTracerProvider(provider)
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
.buildAndRegisterGlobal();

AnthropicClient client = AnthropicOkHttpClient.builder().fromEnv()
.openTelemetry(OpenTelemetryOptions.builder()
.traces(OpenTelemetryTraces.builder()
.contentMode(OpenTelemetryContentMode.METADATA_ONLY).build())
.build())
.build();
try {
var message = client.messages().create(MessageCreateParams.builder()
.model("claude-opus-5-5")
.maxTokens(1024)
.addUserMessage("What is the capital of France?")
.build());
System.out.println(message.id());
} finally {
client.close();
}
}
}
}
```

TraceAI.initFromEnvironment();
Run it from your Maven project:

// Create the Anthropic client normally
AnthropicClient client = AnthropicOkHttpClient.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.build();

// Wrap it - note the client is accepted as Object
TracedAnthropicClient traced = new TracedAnthropicClient(client);
```bash
mvn -Dmaven.compiler.release=17 \
org.apache.maven.plugins:maven-compiler-plugin:3.12.1:compile \
org.codehaus.mojo:exec-maven-plugin:3.5.0:java \
-Dexec.mainClass=AnthropicTracingExample
```

---
Open **Observe** in Future AGI, select the `anthropic-java` project, and open an `anthropic.messages.create` span. Its attributes include `gen_ai.provider.name`, request and response models, and input and output token counts. Native input-token usage includes cache reads and cache writes; do not add those cache counts again.

## Create a message
## Async calls and streaming

Use the same client and provider for async calls:

```java
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
var message = client.async().messages().create(params).join();
```

Message message = traced.createMessage(
MessageCreateParams.builder()
.model("claude-sonnet-4-20250514")
.maxTokens(1024L)
.addUserMessage("What is the capital of France?")
.build()
);
Close streams, including when you stop reading early. A streaming request's span ends when the stream completes or closes, not when the call returns:

System.out.println(message);
```java
try (var stream = client.messages().createStreaming(params)) {
stream.stream().forEach(event -> {
// Handle each SDK event here.
});
}
```

The wrapper returns a generic value; assigning it to `Message` provides the response type.
The SDK's tool runner also emits its own runner and tool spans. Do not wrap each tool or model request with a second LLM instrumentor.

**Span created:** "Anthropic Message" with kind `LLM`
## Prompt and response privacy

---
The example explicitly selects `METADATA_ONLY`: prompts and responses are not exported. To include them, change that setting to `OpenTelemetryContentMode.CONTENT`. This exports structured content in `gen_ai.input.messages` and `gen_ai.output.messages`; review your Future AGI access and retention settings before enabling it.

## Captured data
Native SDK tracing uses Anthropic's content controls, not traceAI's `FI_HIDE_*` settings. Setting the content mode in code overrides the corresponding environment setting. Tool names and other developer-supplied metadata can still be recorded in metadata-only mode, so keep secrets out of identifiers and descriptions.

The wrapper records request and response data, including raw input and output. Consider the sensitivity of the content you send before enabling tracing.
## Existing TracedAnthropicClient applications

---
The published wrapper remains available on Maven Central:

```xml
<dependency>
<groupId>com.futureagi</groupId>
<artifactId>traceai-java-anthropic</artifactId>
<version>1.0.0</version>
</dependency>
```

Keep the [Java SDK setup](/docs/integrations/traceai/java) and your existing `createMessage(params)` calls if you need the wrapper's capture format. It covers blocking message creation; `unwrap()` returns the original client. Calls made through the original client are not traced by the wrapper.

## Accessing the original client
When using the wrapper with a native-tracing SDK, disable native tracing on that client:

```java
Object original = traced.unwrap();
// Cast back if you need typed access
AnthropicClient anthropic = (AnthropicClient) original;
AnthropicClient client = AnthropicOkHttpClient.builder().fromEnv()
.openTelemetry(false)
.build();
TracedAnthropicClient traced = new TracedAnthropicClient(client);
```

Older SDKs without `openTelemetry` do not need that setting. The released wrapper supports optional temperature parameters. Its content capture differs from the native route: it records raw input/output and structured messages by default. Review the [Java privacy configuration](/docs/integrations/traceai/java#traceconfig-reference) before using it for sensitive data; input/output hiding does not by itself hide exception details.

To migrate, remove the wrapper at the call site and call `client.messages().create(params)` directly, replace `TraceAI.initFromEnvironment()` with the provider setup above, and choose a native content mode explicitly. Do not run both initialization paths for the same provider.

## Troubleshooting

- **No spans:** register the tracer provider before creating the Anthropic client. Check `mvn dependency:tree` for an OpenTelemetry API below 1.57.0 and align all OpenTelemetry modules with the BOM.
- **Two observations per request:** remove the wrapper or disable native tracing on the wrapped client. If you also enabled Anthropic server-side API tracing, its server span is distinct from the SDK client span; do not sum usage from both.
- **Authentication or routing errors:** check both Future AGI credentials, the complete traces endpoint, and `X-Project-Name`.
- **Missing prompts or responses:** metadata-only mode is intentional. Enable `CONTENT` only when content export is appropriate for your application.
- **Missing spans at shutdown:** close the client and then the tracer provider. Wait for async calls to finish and close any streams first.
Loading