Files
unslothai--unsloth/studio/backend/tests/test_anthropic_cache_ttl.py
T
wehub-resource-sync e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:59:56 +08:00

191 lines
6.2 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Unit tests for prompt_cache_ttl threading on the Anthropic path.
Anthropic's ``cache_control`` marker takes an optional ``ttl``: default 5m
pool, ``ttl:"1h"`` the 1h pool. These tests pin the outbound body shape:
"1h" puts ``ttl:"1h"`` on both markers; default omits the field; garbage
values are silently dropped.
"""
import asyncio
import json
import httpx
import pytest
from core.inference import external_provider as ep_mod
from core.inference.external_provider import ExternalProviderClient
def _drive(coro):
return asyncio.new_event_loop().run_until_complete(coro)
def _make_client() -> ExternalProviderClient:
return ExternalProviderClient(
provider_type = "anthropic",
base_url = "https://api.anthropic.com/v1",
api_key = "sk-ant-test",
)
def _capture(monkeypatch, ttl = None) -> dict:
captured: dict = {}
def handler(request: httpx.Request) -> httpx.Response:
captured["body"] = json.loads(request.content.decode("utf-8"))
captured["headers"] = dict(request.headers)
return httpx.Response(
200,
content = (b"event: message_stop\n" b'data: {"type": "message_stop"}\n\n'),
headers = {"content-type": "text/event-stream"},
)
monkeypatch.setattr(
ep_mod,
"_http_client",
httpx.AsyncClient(transport = httpx.MockTransport(handler)),
)
async def run():
client = _make_client()
async for _ in client.stream_chat_completion(
messages = [
{"role": "system", "content": "Be brief."},
{"role": "user", "content": "hi"},
],
model = "claude-opus-4-7",
temperature = 0.7,
top_p = 0.95,
max_tokens = 32,
enable_prompt_caching = True,
prompt_cache_ttl = ttl,
):
pass
await client.close()
_drive(run())
return captured
def _cache_controls(body: dict) -> list[dict]:
"""Pull every cache_control marker from the system block + tail message."""
out = []
sys_blocks = body.get("system") or []
if isinstance(sys_blocks, list):
for b in sys_blocks:
if isinstance(b, dict) and "cache_control" in b:
out.append(b["cache_control"])
msgs = body.get("messages") or []
if msgs:
tail = msgs[-1].get("content")
if isinstance(tail, list):
for b in tail:
if isinstance(b, dict) and "cache_control" in b:
out.append(b["cache_control"])
return out
# ── default (omitted) writes into the 5m pool ──────────────────────
def test_omitted_ttl_uses_default_5m_pool(monkeypatch):
captured = _capture(monkeypatch, ttl = None)
ccs = _cache_controls(captured["body"])
assert len(ccs) == 2, ccs
for cc in ccs:
assert cc == {"type": "ephemeral"}, cc
# ── explicit 5m round-trips as-is ─────────────────────────────────
def test_explicit_5m_ttl_round_trips(monkeypatch):
captured = _capture(monkeypatch, ttl = "5m")
ccs = _cache_controls(captured["body"])
assert len(ccs) == 2, ccs
for cc in ccs:
assert cc == {"type": "ephemeral", "ttl": "5m"}, cc
# ── 1h writes the new pool field on every marker ───────────────────
def test_1h_ttl_writes_into_1h_pool(monkeypatch):
captured = _capture(monkeypatch, ttl = "1h")
ccs = _cache_controls(captured["body"])
assert len(ccs) == 2, ccs
for cc in ccs:
assert cc == {"type": "ephemeral", "ttl": "1h"}, cc
def test_1h_ttl_does_not_send_extended_cache_ttl_beta_header(monkeypatch):
# The extended-cache-ttl-2025-04-11 beta header is now GA (verified live
# 2026-05-22); 1h TTL works with no beta header. Pin so a regression that
# re-adds the header surfaces here.
captured = _capture(monkeypatch, ttl = "1h")
beta = captured["headers"].get("anthropic-beta", "")
assert "extended-cache-ttl-2025-04-11" not in beta, beta
def test_5m_ttl_does_not_send_extended_cache_ttl_beta_header(monkeypatch):
captured = _capture(monkeypatch, ttl = "5m")
beta = captured["headers"].get("anthropic-beta", "")
assert "extended-cache-ttl-2025-04-11" not in beta, beta
# ── unknown values are dropped, not forwarded ──────────────────────
@pytest.mark.parametrize("bogus", ["6m", "2h", "", "forever", "1d", "0", "1"])
def test_unknown_ttl_silently_dropped(monkeypatch, bogus):
captured = _capture(monkeypatch, ttl = bogus)
ccs = _cache_controls(captured["body"])
assert len(ccs) == 2, ccs
for cc in ccs:
# Bogus TTLs must not round-trip; marker stays at default (no ttl = 5m).
assert cc == {"type": "ephemeral"}, cc
# ── opt-out still skips cache_control entirely ─────────────────────
def test_opt_out_skips_cache_control(monkeypatch):
captured: dict = {}
def handler(request: httpx.Request) -> httpx.Response:
captured["body"] = json.loads(request.content.decode("utf-8"))
return httpx.Response(
200,
content = b'event: message_stop\ndata: {"type": "message_stop"}\n\n',
headers = {"content-type": "text/event-stream"},
)
monkeypatch.setattr(
ep_mod,
"_http_client",
httpx.AsyncClient(transport = httpx.MockTransport(handler)),
)
async def run():
client = _make_client()
async for _ in client.stream_chat_completion(
messages = [
{"role": "system", "content": "Be brief."},
{"role": "user", "content": "hi"},
],
model = "claude-opus-4-7",
temperature = 0.7,
top_p = 0.95,
max_tokens = 32,
enable_prompt_caching = False,
prompt_cache_ttl = "1h", # ignored when caching is off
):
pass
await client.close()
_drive(run())
assert _cache_controls(captured["body"]) == []