From 6080fef4cc42150c7a9d836368aec76b5d547a51 Mon Sep 17 00:00:00 2001 From: Nikhil Pareek <8347993+nik13@users.noreply.github.com> Date: Sat, 31 Oct 2026 19:51:03 -0700 Subject: [PATCH] docs(anthropic): use native Java tracing with legacy migration Co-Authored-By: Claude Code --- scripts/integrations.test.mjs | 19 ++ src/lib/integrations.mjs | 2 +- .../integrations/traceai/java/anthropic.mdx | 236 ++++++++++++------ 3 files changed, 183 insertions(+), 74 deletions(-) diff --git a/scripts/integrations.test.mjs b/scripts/integrations.test.mjs index a3f766827..baefaa86e 100644 --- a/scripts/integrations.test.mjs +++ b/scripts/integrations.test.mjs @@ -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, /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')); diff --git a/src/lib/integrations.mjs b/src/lib/integrations.mjs index 8ba7cf620..d4db1c3cc 100644 --- a/src/lib/integrations.mjs +++ b/src/lib/integrations.mjs @@ -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' }, diff --git a/src/pages/docs/integrations/traceai/java/anthropic.mdx b/src/pages/docs/integrations/traceai/java/anthropic.mdx index 12d5f42ca..1c3d9766f 100644 --- a/src/pages/docs/integrations/traceai/java/anthropic.mdx +++ b/src/pages/docs/integrations/traceai/java/anthropic.mdx @@ -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. - - -The wrapper is incompatible with SDK parameter objects whose `temperature()` method returns `Optional`, 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. - +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 - - - -```xml Maven - - com.github.future-agi.traceAI - traceai-java-anthropic - main-SNAPSHOT - -``` + +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). + -```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 + + + + io.opentelemetry + opentelemetry-bom + 1.57.0 + pom + import + + + + + + com.anthropic + anthropic-java + 3.3.0 + + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-exporter-otlp + + ``` - - -You also need the Anthropic Java SDK: +For Gradle: - - -```xml Maven - - com.anthropic - anthropic-java - 1.0.0 - +```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()`. - +## Configure credentials and destination ---- +Set these variables in your application's environment or secret manager: -## Why reflection? +```bash +export ANTHROPIC_API_KEY="" +export FI_API_KEY="" +export FI_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 + + com.futureagi + traceai-java-anthropic + 1.0.0 + +``` + +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.