Skip to content

[BOT ISSUE] Anthropic system prompt silently dropped from span input when using array format (prompt caching) #81

@braintrust-bot

Description

@braintrust-bot

Summary

The Anthropic request tagger in InstrumentationSemConv.tagAnthropicRequest() silently drops the system prompt from braintrust.input_json when it is provided in array-of-content-blocks format. This array format is required when using Anthropic prompt caching on system prompts (each block needs a cache_control marker), and is explicitly documented by both Anthropic and Braintrust.

When system is a plain string (e.g. "system": "You are helpful"), it is correctly appended to the input array as a synthetic {role: "system", content: "..."} entry. But when system is an array (e.g. "system": [{"type":"text","text":"You are helpful","cache_control":{"type":"ephemeral"}}]), the guard condition fails and the system prompt is silently omitted.

What is missing

In InstrumentationSemConv.tagAnthropicRequest() (lines 192–199):

if (requestJson.has("system")
        && !requestJson.get("system").isNull()
        && !requestJson.get("system").asText().isEmpty()) {
    var systemNode = BraintrustJsonMapper.get().createObjectNode();
    systemNode.put("role", "system");
    systemNode.set("content", requestJson.get("system"));
    inputArray.add(systemNode);
}

The problem is requestJson.get("system").asText() — for a JSON array node, Jackson's asText() returns "" (empty string). The .isEmpty() check then evaluates to true, and the entire block is skipped. The systemNode.set("content", ...) on line 197 would correctly handle both string and array values, but it is never reached.

Fix

Replace the string-based emptiness check with a type-aware check:

if (requestJson.has("system") && !requestJson.get("system").isNull()) {
    JsonNode systemValue = requestJson.get("system");
    boolean isEmpty = systemValue.isTextual() && systemValue.asText().isEmpty();
    if (!isEmpty) {
        var systemNode = BraintrustJsonMapper.get().createObjectNode();
        systemNode.put("role", "system");
        systemNode.set("content", systemValue);
        inputArray.add(systemNode);
    }
}

Impact

When a user enables Anthropic prompt caching on their system prompt (a key cost-optimization feature), the system prompt disappears from the Braintrust span input. The trace shows the user messages but no system instructions, making it impossible to understand the model's behavior from the trace alone.

This affects:

  • Direct Anthropic SDK calls via BraintrustAnthropic.wrapAnthropic() with system in array format
  • Any request using cache_control on system prompt blocks

Non-streaming and streaming calls are both affected (the request tagging is shared).

Braintrust docs status

Upstream sources

Local files inspected

  • braintrust-sdk/src/main/java/dev/braintrust/instrumentation/InstrumentationSemConv.java — lines 192–199 (tagAnthropicRequest: asText() on array returns "", causing skip)
  • braintrust-sdk/instrumentation/anthropic_2_2_0/src/main/java/dev/braintrust/instrumentation/anthropic/v2_2_0/TracingHttpClient.java — delegates to InstrumentationSemConv
  • braintrust-sdk/instrumentation/anthropic_2_2_0/src/test/java/dev/braintrust/instrumentation/anthropic/v2_2_0/BraintrustAnthropicTest.java — no test uses array-format system prompts

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions