|
- #!/usr/bin/env bash
- set -euo pipefail
-
- if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
- echo "ANTHROPIC_API_KEY is required" >&2
- exit 1
- fi
-
- if ! command -v jq >/dev/null 2>&1; then
- echo "jq is required to build the JSON payload and summarize usage" >&2
- exit 1
- fi
-
- MODEL="${MODEL:-claude-opus-4-6}"
- ANTHROPIC_VERSION="${ANTHROPIC_VERSION:-2023-06-01}"
- API_URL="${ANTHROPIC_API_URL:-https://lancerouter.ai/v1/messages}"
- CACHE_SENTENCE_COUNT="${CACHE_SENTENCE_COUNT:-240}"
- REQUEST_COUNT="${REQUEST_COUNT:-2}"
- SAME_USER_PROMPT="${SAME_USER_PROMPT:-0}"
- CACHE_MODE="${CACHE_MODE:-explicit}"
- DRY_RUN="${DRY_RUN:-0}"
-
- case "$CACHE_MODE" in
- explicit | automatic)
- ;;
- *)
- echo "CACHE_MODE must be 'explicit' or 'automatic'" >&2
- exit 1
- ;;
- esac
-
- build_payload() {
- local question="$1"
-
- jq -n \
- --arg model "$MODEL" \
- --arg question "$question" \
- --arg cache_mode "$CACHE_MODE" \
- --argjson sentence_count "$CACHE_SENTENCE_COUNT" '
- def zpad4:
- tostring as $raw | ("0000" + $raw) | .[-4:];
-
- (reduce range(1; $sentence_count + 1) as $i
- ("";
- . + "Cache test invariant sentence " + ($i | zpad4) + ": this exact line must remain unchanged across requests. "
- )) as $ctx
- | ({
- model: $model,
- max_tokens: 20,
- system: [
- {
- type: "text",
- text: $ctx
- }
- ],
- messages: [
- {
- role: "user",
- content: $question
- }
- ]
- }
- | if $cache_mode == "automatic" then
- . + { cache_control: { type: "ephemeral" } }
- else
- .system[0].cache_control = { type: "ephemeral" }
- end)
- '
- }
-
- send_request() {
- local question="$1"
-
- build_payload "$question" | curl --silent --show-error "$API_URL" \
- --header "x-api-key: $ANTHROPIC_API_KEY" \
- --header "anthropic-version: $ANTHROPIC_VERSION" \
- --header "content-type: application/json" \
- -D /tmp/cache_test_headers.txt \
- --data @-
- }
-
- extract_header() {
- local header_name="$1"
- grep -i "^${header_name}:" /tmp/cache_test_headers.txt 2>/dev/null \
- | head -1 \
- | sed 's/^[^:]*: *//' \
- | tr -d '\r\n' \
- || echo "N/A"
- }
-
- summarize_response() {
- jq 'if has("error") then
- {
- type,
- error
- }
- elif (.content | type) == "array" then
- {
- id,
- model,
- stop_reason,
- usage,
- text: (.content | map(select(.type == "text") | .text) | join(""))
- }
- else
- .
- end'
- }
-
- for i in $(seq 1 "$REQUEST_COUNT"); do
- if [[ "$SAME_USER_PROMPT" == "1" ]]; then
- question="Reply with only: cache-test"
- else
- question="Reply with only: request-$i"
- fi
-
- if [[ "$DRY_RUN" == "1" ]]; then
- build_payload "$question" | jq
- else
- echo "=== Request $i/$REQUEST_COUNT: $question ==="
- send_request "$question" | tee /tmp/anthropic_cache_response.json | summarize_response
- echo "x-oneapi-request-id: $(extract_header 'X-Oneapi-Request-Id')"
- echo "x-request-id: $(extract_header 'X-Request-Id')"
- echo
- fi
- done
-
- if [[ "$DRY_RUN" != "1" ]]; then
- echo "Expected cache signal:"
- echo "- First response usage.cache_creation_input_tokens should be greater than 0."
- echo "- Later responses usage.cache_read_input_tokens should be greater than 0."
- echo
- echo "Diagnostic mode:"
- echo "REQUEST_COUNT=10 SAME_USER_PROMPT=1 MODEL=\"$MODEL\" ./test/anthropic_cache_test.sh"
- echo
- echo "Automatic caching mode:"
- echo "CACHE_MODE=automatic MODEL=\"$MODEL\" ./test/anthropic_cache_test.sh"
- fi
|