litellm/tests/integration/providers/test_responses_compaction_items_wire.py
Mateo Wang a8113b0dd7
fix(responses)!: round-trip Anthropic compaction blocks as compaction items (#46754)
* fix(responses): round-trip Anthropic compaction blocks as compaction items

/v1/responses on Anthropic routes now returns Claude's compaction block as a
compaction output item (and streams it as output_item.added/done), and a
compaction input item is replayed to Claude as its native compaction block,
so the next turn reuses the summary instead of compacting again. Other routes
receive the summary as assistant text. The Anthropic stream handler now keeps
the summary text that arrives in compaction_delta.

Fixes #41456

* fix(responses): replay compaction blocks natively only when the request compacts

* fix(responses): carry compaction through dict-form edits, SDK replay, affinity, and guardrails

Read Anthropic's dict-form context_management, accept SDK ResponseCompactionItem
objects on replay, strip the encrypted_content affinity wrap before decoding,
scan and rewrite compaction summaries in Responses guardrails, drop the history
a summary replay replaces, send the summary as a user message so thinking
ordering holds, and send the streamed compaction .done with the newest block.

* fix(responses): count, route, guard, and redact compaction items consistently

A compaction item now reads as its summary everywhere the proxy counts or routes
a Responses request: pre-call context checks, the TPM limiter, the input token
endpoint, the stream usage estimate, and the previous_response_id session rebuild
all drop the history the item replaces. A Claude compaction item sent to a
non-Anthropic model (including a fallback) becomes a user summary message instead
of an encrypted blob the provider rejects, and Claude on Bedrock Claude Platform
takes the summary path. Output guardrails see the summary of a returned
compaction item, and logging redaction hides it.

* fix(responses): start previous_response_id replay at a stored compaction and count reservations from it

* fix(responses): keep a stored compaction summary whole in spend logs

* fix(responses): keep stored compaction items summary-only, redact compaction_delta, and finish compaction before reasoning

A spend-log row now stores a top-level compaction input item as its summary alone, so extra keys in a
client-built item no longer reach the database whole. Message redaction also nulls the streamed
compaction_delta, and the streaming bridge sends the compaction item's done event before the reasoning
item that follows it. Adds the audit's integration tests for compaction items on the wire.

* test(responses): cover stored compaction items, compaction_delta redaction, and compaction done order

Adds integration cells for the summary-only spend row of a caller-sent compaction item and its previous_response_id replay, a redacted chat stream row that holds no streamed compaction_delta, and a compaction then thinking stream whose done events follow output order.

* fix(spend_logs): honor the redaction header on failure rows for litellm_metadata routes

A failed /v1/responses or /v1/messages call keeps its proxy request headers in
litellm_metadata, but the failure row read redaction headers from metadata only,
so x-litellm-enable-message-redaction never redacted those rows.

* fix(spend_logs): lift only the redaction opt-in header into failure rows

The failure hook on litellm_metadata routes copied every real request header into the caller's own metadata dict, so the stored request body gained the headers, a caller's metadata.headers was replaced, and a disable header loosened global redaction on failed calls. It now copies only the enable headers into a new litellm_params metadata dict, and only when the caller sent no metadata.headers, so no failure row is less redacted than on main

* test(responses): cover failure-row redaction headers and caller metadata on litellm_metadata routes

* fix(responses): keep earlier-turn instructions after compaction and count compaction items by their summary

A previous_response_id rebuild that starts at a compaction now keeps the system and developer
messages of every earlier turn. Token estimates for raw Responses input count a compaction item
as its summary text instead of the serialized item.

* fix(responses): drop the stored history a caller-sent compaction item replaces on previous_response_id

* fix(responses): count only message items with a string role as compaction instructions

* test(responses): flatten the last two-for comprehension in the compaction wire test

* fix(responses): replay from a compaction in stored Responses output and SDK compaction items

A previous_response_id whose stored row is a Responses output holding a compaction item now starts its replay at that compaction, and the handler dumps SDK compaction objects before checking for a summary. Adds tests for the uncovered compaction paths.

* fix(responses): keep the stored tool call a function_call_output answers when a compaction item replaces history

* fix(responses): keep a stored tool call on every turn after a compaction item and bound the stored summary

A previous_response_id rebuild that starts at a turn holding a compaction item now sends again each stored call a
replayed tool output answers, so a retry or a later turn no longer loses the turn-1 call. The start is picked only from
rows up to the referenced response. A spend row keeps a compaction summary up to the larger of 64K chars and
MAX_STRING_LENGTH_PROMPT_IN_DB instead of with no bound.

* fix(responses): replay a compacted session only through the referenced response

A later sibling turn that sent a compaction item put its summary between a stored tool call and its tool output, so Claude answered 400 on a turn that continued the first of two retried turns, and on a retry sent without the item

* fix(spend-logs): keep main's stored value for a prompt length limit under 2

At a MAX_STRING_LENGTH_PROMPT_IN_DB of 0 or 1 the shared truncation helper stored only the truncation marker, where
main stores the marker followed by the whole value

* fix(spend-logs): keep main's stored tail for a negative prompt length limit

At a negative MAX_STRING_LENGTH_PROMPT_IN_DB the shared truncation helper stored one more character of each value than main, since it dropped main's clamp of the end slice to the limit minus the start

* fix(responses): keep a compaction item when a guardrail rewrites its summary through structured_messages

* test(responses): run the compaction wire tests on the worker database under isolated workers

* fix(router): keep the encrypted content affinity pin out of provider requests

* fix(proxy): inspect compaction summaries in guardrails, strip a client-sent affinity pin, and price failures from the newest compaction

* fix(proxy): count a failed chat or /v1/messages call on its whole history, not from a compaction-typed message

* fix(proxy): trim a failed call's input at the newest compaction only when the client called /v1/responses

* fix(proxy): trim a failed call's input at the newest compaction when the proxy dispatched it as a Responses call

* test(proxy): run the dispatched-call-type failure test through the real request setup

* fix(proxy): count a failed Cursor stream's input from the newest compaction item

The Cursor route maps to no call type, so the stream generator's failure hook fell
back to the route and counted the whole history. Pass the processor's route_type.

* fix(proxy): count a broken Cursor stream's prompt from the newest compaction item

* fix(responses): keep a tool call a later output answers when trimming before a compaction item

* fix(responses): move a compaction-split tool call's turn to right before its output on both compaction routes

* perf(responses): look up moved turn items in a set so a long compaction-split turn trims in linear time

* test(responses): measure the compaction-split turn move as a CodSpeed benchmark instead of a clock read in unit tests

* fix(responses): move only calls still open at the compaction, and bill a disconnected stream from the newest compaction item

* fix(responses): restore only the newest stored call when a reused call_id is answered after a compaction item

* fix(spend logs): bound a Responses compaction item's summary like a chat block instead of cutting its encrypted_content

* test(proxy): price a failed dispatched call through a real ProxyLogging instead of mocked hooks

* fix(responses): finish a stream whose Claude compaction failed and group answered call ids by turn once

* fix(responses): count input_tokens after a compaction item the way inference sends it
2026-10-21 08:50:19 +00:00

1057 lines
44 KiB
Python

import json
import os
import re
import uuid
from collections.abc import Iterator, Mapping, Sequence
from dataclasses import dataclass
from itertools import chain
from pathlib import Path
from typing import Final, Literal, TypeAlias
from urllib.parse import urlsplit
import httpx
import openai
import pytest
import yaml
from integration._support import claude_code as cc
from integration._support.client import (
Gateway,
Scenario,
eventually,
gateway_from_environment,
object_value,
string_value,
)
from integration._support.database import read_rows
from integration._support.process import graceful_stop_seconds, owned_proxy
from integration._support.wire import Reply, Request, Wire, wire_server
from integration._support.worker_readiness import wait_for_worker_readiness
from pydantic import JsonValue, TypeAdapter
from litellm.constants import LITELLM_TRUNCATED_PAYLOAD_FIELD
from litellm.responses.utils import ResponsesAPIRequestUtils
pytestmark: Final = [pytest.mark.timeout(2 * graceful_stop_seconds() + 120), pytest.mark.shares_worker_state]
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
_CLAUDE: Final = cc.OPUS
_GEMINI: Final = "gemini-3.8-flash"
_OPENAI: Final = "gpt-5.6"
_PROXY_WORKERS: Final = max(1, int(os.environ.get("INTEGRATION_PROXY_WORKERS", "1")))
_CACHE_BUST: Final[Mapping[str, JsonValue]] = {"cache": {"no-cache": True}}
_COMPACTING: Final[list[JsonValue]] = [{"type": "compaction", "compact_threshold": 150000}]
_COMPACT_BETA: Final = "compact-2026-01-12"
_CITY: Final = "Tokyo"
_MASKED_CITY: Final = "[CITY]"
_MASK_INPUT: Final = "compaction-mask-input"
_MASK_OUTPUT: Final = "compaction-mask-output"
_BLOCK_OUTPUT: Final = "compaction-block-output"
_REDACT_HEADER: Final = {"x-litellm-enable-message-redaction": "true"}
_UNREDACT_HEADER: Final = {"litellm-disable-message-redaction": "true"}
_SCRIPTED_REJECTION: Final = json.dumps(
{"type": "error", "error": {"type": "invalid_request_error", "message": "scripted rejection"}}
).encode()
_ASK: Final = re.compile(r"ask-([0-9a-f]{32})")
Route: TypeAlias = Literal["claude-compacting", "claude-plain", "gemini", "openai", "openai-compact", "openai-opaque"]
def _new_marker() -> str:
return uuid.uuid4().hex
def _ask(marker: str, *, compact: bool = False) -> str:
return f"ask-{marker} compact-now-{marker}" if compact else f"ask-{marker}"
def _fail(marker: str) -> str:
return f"fail-now-{marker}"
def _think(marker: str) -> str:
return f"think-now-{marker}"
def _thinking(marker: str) -> dict[str, JsonValue]:
return {"type": "thinking", "thinking": f"thought-{marker}", "signature": f"signature-{marker}"}
def _answer(marker: str) -> str:
return f"answer-{marker}"
def _summary(marker: str) -> str:
return f"kept-{marker} {'a' * 1500} middle-{marker} {'b' * 1500} the user lives in {_CITY}"
def _block(summary: str) -> dict[str, JsonValue]:
return {"type": "compaction", "content": summary}
def _compaction_item(summary: str) -> dict[str, JsonValue]:
return {
"type": "compaction",
"id": f"cmp_{uuid.uuid4().hex}",
"encrypted_content": json.dumps(_block(summary), separators=(",", ":")),
}
def _newest_marker(raw: bytes) -> str:
found: Final = _ASK.findall(raw.decode())
assert found, raw[:400]
return str(found[-1])
def _anthropic_usage() -> dict[str, JsonValue]:
return {"input_tokens": 20, "output_tokens": 5}
def _anthropic_block_frames(index: int, block: Mapping[str, JsonValue]) -> tuple[bytes, ...]:
def start(content_block: Mapping[str, JsonValue]) -> bytes:
return cc.sse_frame(
"content_block_start", {"type": "content_block_start", "index": index, "content_block": dict(content_block)}
)
def delta(fields: Mapping[str, JsonValue]) -> bytes:
return cc.sse_frame(
"content_block_delta", {"type": "content_block_delta", "index": index, "delta": dict(fields)}
)
stop: Final = cc.sse_frame("content_block_stop", {"type": "content_block_stop", "index": index})
match block["type"]:
case "compaction":
summary: Final = string_value(block["content"])
half: Final = len(summary) // 2
return (
start({"type": "compaction", "content": ""}),
delta({"type": "compaction_delta", "content": summary[:half]}),
delta({"type": "compaction_delta", "content": summary[half:]}),
stop,
)
case "thinking":
return (
start({"type": "thinking", "thinking": ""}),
delta({"type": "thinking_delta", "thinking": block["thinking"]}),
delta({"type": "signature_delta", "signature": block["signature"]}),
stop,
)
case _:
return (start({"type": "text", "text": ""}), delta({"type": "text_delta", "text": block["text"]}), stop)
def _anthropic_stream(identity: str, content: Sequence[Mapping[str, JsonValue]]) -> tuple[bytes, ...]:
start: Final = cc.sse_frame(
"message_start",
{
"type": "message_start",
"message": {
"id": identity,
"type": "message",
"role": "assistant",
"model": _CLAUDE,
"content": [],
"stop_reason": None,
"stop_sequence": None,
"usage": {"input_tokens": 20, "output_tokens": 1},
},
},
)
blocks: Final = tuple(
chain.from_iterable(_anthropic_block_frames(index, block) for index, block in enumerate(content))
)
end: Final = cc.sse_frame(
"message_delta",
{
"type": "message_delta",
"delta": {"stop_reason": "end_turn", "stop_sequence": None},
"usage": {"output_tokens": 5},
},
)
return (start, *blocks, end, cc.sse_frame("message_stop", {"type": "message_stop"}))
def _claude_reply(body: Mapping[str, JsonValue], raw: bytes) -> Reply:
marker: Final = _newest_marker(raw)
if _fail(marker).encode() in raw:
return Reply(status=400, body=_SCRIPTED_REJECTION)
compaction: Final = (_block(_summary(marker)),) if f"compact-now-{marker}".encode() in raw else ()
thinking: Final = (_thinking(marker),) if _think(marker).encode() in raw else ()
content: Final = (*compaction, *thinking, {"type": "text", "text": _answer(marker)})
identity: Final = f"msg_{uuid.uuid4().hex}"
if body.get("stream") is True:
return Reply(content_type="text/event-stream", chunks=_anthropic_stream(identity, content))
return Reply(body=cc.message_reply(identity, _CLAUDE, content, _anthropic_usage()))
def _gemini_reply(raw: bytes) -> Reply:
frame: Final = {
"responseId": f"gemini-{uuid.uuid4().hex}",
"candidates": [
{
"content": {"role": "model", "parts": [{"text": _answer(_newest_marker(raw))}]},
"index": 0,
"finishReason": "STOP",
}
],
"usageMetadata": {"promptTokenCount": 20, "candidatesTokenCount": 5, "totalTokenCount": 25},
"modelVersion": _GEMINI,
}
return Reply(body=json.dumps(frame).encode())
_OPENAI_USAGE: Final = {
"input_tokens": 20,
"input_tokens_details": {"cached_tokens": 0},
"output_tokens": 5,
"output_tokens_details": {"reasoning_tokens": 0},
"total_tokens": 25,
}
def _openai_reply(raw: bytes) -> Reply:
tag: Final = uuid.uuid4().hex
message: Final = {
"id": f"msg_{tag}",
"type": "message",
"role": "assistant",
"status": "completed",
"content": [{"type": "output_text", "text": _answer(_newest_marker(raw)), "annotations": []}],
}
response: Final = {
"id": f"resp_{tag}",
"object": "response",
"created_at": 1,
"status": "completed",
"model": _OPENAI,
"output": [message],
"usage": _OPENAI_USAGE,
}
return Reply(body=json.dumps(response).encode())
def _openai_compact_reply() -> Reply:
compacted: Final = {
"id": f"resp_compact_{uuid.uuid4().hex}",
"created_at": 1,
"object": "response.compaction",
"output": [],
"usage": _OPENAI_USAGE,
}
return Reply(body=json.dumps(compacted).encode())
def _vendor(request: Request) -> Reply:
if request.method == "GET":
return Reply(body=json.dumps({"object": "list", "data": [{"id": _OPENAI, "object": "model"}]}).encode())
path: Final = urlsplit(request.target).path
if path.endswith("/v1/messages/count_tokens"):
return Reply(body=json.dumps({"input_tokens": _word_count(_JSON_OBJECT.validate_json(request.body))}).encode())
if path.endswith("/v1/messages"):
return _claude_reply(_JSON_OBJECT.validate_json(request.body), request.body)
if path.endswith(":generateContent"):
return _gemini_reply(request.body)
if path.endswith("/responses/compact"):
return _openai_compact_reply()
assert path.endswith("/responses"), request.target
return _openai_reply(request.body)
def _provider_calls(wire: Wire, marker: str) -> tuple[Request, ...]:
needle: Final = f"ask-{marker}".encode()
return tuple(request for request in wire.drain() if request.method == "POST" and needle in request.body)
def _only_call(wire: Wire, marker: str) -> tuple[Request, dict[str, JsonValue]]:
calls: Final = _provider_calls(wire, marker)
assert len(calls) == 1, [call.target for call in calls]
return calls[0], _JSON_OBJECT.validate_json(calls[0].body)
def _objects(value: JsonValue) -> tuple[dict[str, JsonValue], ...]:
assert isinstance(value, list), value
return tuple(object_value(item) for item in value)
def _message_texts(message: Mapping[str, JsonValue], key: str = "content") -> tuple[str, ...]:
content: Final = message.get(key)
if isinstance(content, str):
return (content,)
if not isinstance(content, list):
return ()
return tuple(string_value(part["text"]) for part in _objects(content) if isinstance(part.get("text"), str))
def _anthropic_texts(body: Mapping[str, JsonValue], role: str) -> tuple[str, ...]:
messages: Final = tuple(message for message in _objects(body["messages"]) if message["role"] == role)
return tuple(chain.from_iterable(map(_message_texts, messages)))
def _word_count(body: Mapping[str, JsonValue]) -> int:
texts: Final = (*_anthropic_texts(body, "user"), *_anthropic_texts(body, "assistant"))
return sum(len(text.split()) for text in texts)
def _anthropic_block_types(body: Mapping[str, JsonValue]) -> tuple[JsonValue, ...]:
contents: Final = tuple(message["content"] for message in _objects(body["messages"]))
blocks: Final = chain.from_iterable(_objects(content) for content in contents if isinstance(content, list))
return tuple(block.get("type") for block in blocks)
def _gemini_texts(body: Mapping[str, JsonValue], role: str) -> tuple[str, ...]:
turns: Final = tuple(turn for turn in _objects(body["contents"]) if turn["role"] == role)
return tuple(chain.from_iterable(_message_texts(turn, "parts") for turn in turns))
def _openai_user_texts(body: Mapping[str, JsonValue]) -> tuple[str, ...]:
items: Final = tuple(item for item in _objects(body["input"]) if item.get("role") == "user")
return tuple(chain.from_iterable(map(_message_texts, items)))
def _openai_item_types(body: Mapping[str, JsonValue]) -> tuple[JsonValue, ...]:
return tuple(item.get("type") for item in _objects(body["input"]))
def _register(scenario: Scenario, wire: Wire, provider: Literal["claude", "gemini", "openai"]) -> str:
match provider:
case "claude":
model = scenario.model(model=f"anthropic/{_CLAUDE}", api_base=wire.url, api_key=cc.ANTHROPIC_API_KEY)
case "gemini":
model = scenario.model(model=f"gemini/{_GEMINI}", api_base=wire.url, api_key="synthetic-gemini-key")
case "openai":
model = scenario.model(model=f"openai/{_OPENAI}", api_base=f"{wire.url}/v1", api_key="synthetic-openai-key")
wait_for_worker_readiness(scenario.gateway, model, 1, _PROXY_WORKERS)
return model
def _openai_client(gateway: Gateway) -> openai.OpenAI:
return openai.OpenAI(
base_url=f"{gateway.client.base_url}/v1",
api_key=gateway.key,
max_retries=0,
http_client=httpx.Client(trust_env=False, timeout=60),
)
def _decoded(encrypted_content: str) -> dict[str, JsonValue]:
return _JSON_OBJECT.validate_json(encrypted_content)
def _responses(
gateway: Gateway, body: Mapping[str, JsonValue], headers: Mapping[str, str] | None = None
) -> dict[str, JsonValue]:
response: Final = gateway.request("POST", "/v1/responses", {**body, **_CACHE_BUST}, headers=headers)
assert response.status_code == 200, response.text
return _JSON_OBJECT.validate_json(response.content)
def _sse_events(text: str) -> tuple[dict[str, JsonValue], ...]:
payloads: Final = tuple(
line.removeprefix("data:").strip() for line in text.splitlines() if line.startswith("data:")
)
return tuple(_JSON_OBJECT.validate_json(payload) for payload in payloads if payload != "[DONE]")
def _stream(
gateway: Gateway, path: str, body: Mapping[str, JsonValue], headers: Mapping[str, str] | None = None
) -> tuple[dict[str, JsonValue], ...]:
sent_headers: Final = {"Authorization": f"Bearer {gateway.key}", **(headers or {})}
with gateway.client.stream(
"POST", path, json={**body, **_CACHE_BUST, "stream": True}, headers=sent_headers
) as response:
text: Final = response.read().decode()
assert response.status_code == 200, text
return _sse_events(text)
def _delta_provider_fields(chunk: Mapping[str, JsonValue]) -> tuple[dict[str, JsonValue], ...]:
return tuple(
fields
for choice in _objects(chunk.get("choices", []))
if isinstance(fields := object_value(choice.get("delta", {})).get("provider_specific_fields"), dict)
)
def _streamed_compaction_blocks(chunks: Sequence[Mapping[str, JsonValue]]) -> tuple[JsonValue, ...]:
fields: Final = chain.from_iterable(_delta_provider_fields(chunk) for chunk in chunks)
return tuple(field["compaction_blocks"] for field in fields if "compaction_blocks" in field)
def _output_items(response: Mapping[str, JsonValue], item_type: str) -> tuple[dict[str, JsonValue], ...]:
return tuple(item for item in _objects(response["output"]) if item["type"] == item_type)
def _listed(value: JsonValue) -> list[JsonValue]:
assert isinstance(value, list), value
return value
def _only(items: Sequence[dict[str, JsonValue]]) -> dict[str, JsonValue]:
assert len(items) == 1, items
return items[0]
@pytest.mark.parametrize("stream", (False, True), ids=("json", "stream"))
def test_claude_compaction_block_is_returned_as_a_compaction_output_item(gateway: Gateway, stream: bool) -> None:
marker: Final = _new_marker()
with wire_server(_vendor) as wire, gateway.scenario() as scenario:
model: Final = _register(scenario, wire, "claude")
client: Final = _openai_client(gateway)
request: Final = {
"input": _ask(marker, compact=True),
"extra_body": {"context_management": _COMPACTING, **_CACHE_BUST},
}
if not stream:
reply: Final = client.responses.create(model=model, **request)
assert [item.type for item in reply.output] == ["compaction", "message"]
compaction: Final = reply.output[0]
assert compaction.type == "compaction"
assert _decoded(compaction.encrypted_content) == _block(_summary(marker))
assert reply.output_text == _answer(marker)
else:
events: Final = tuple(client.responses.create(model=model, stream=True, **request))
added: Final = [e for e in events if e.type == "response.output_item.added" and e.item.type == "compaction"]
done: Final = [e for e in events if e.type == "response.output_item.done" and e.item.type == "compaction"]
completed: Final = [e for e in events if e.type == "response.completed"]
assert len(added) == 1 and len(done) == 1 and len(completed) == 1, [e.type for e in events]
assert added[0].item.encrypted_content == ""
assert done[0].output_index == added[0].output_index
assert done[0].item.id == added[0].item.id
assert _decoded(done[0].item.encrypted_content) == _block(_summary(marker))
finished: Final = [item for item in completed[0].response.output if item.type == "compaction"]
assert [(item.id, item.encrypted_content) for item in finished] == [
(done[0].item.id, done[0].item.encrypted_content)
]
message_indexes: Final = {
e.output_index for e in events if e.type == "response.output_item.added" and e.item.type == "message"
}
assert message_indexes and added[0].output_index not in message_indexes
assert completed[0].response.output_text == _answer(marker)
call, body = _only_call(wire, marker)
assert _COMPACT_BETA in call.headers.get("anthropic-beta", ""), dict(call.headers)
def _history(marker: str, compaction: Mapping[str, JsonValue]) -> list[JsonValue]:
return [
{"role": "developer", "content": f"rules-{marker}"},
{"role": "user", "content": f"old-{marker}"},
{
"type": "message",
"role": "assistant",
"content": [{"type": "output_text", "text": f"old-answer-{marker}", "annotations": []}],
},
dict(compaction),
{
"type": "message",
"role": "assistant",
"content": [{"type": "output_text", "text": f"recap-{marker}", "annotations": []}],
},
{"role": "user", "content": _ask(marker)},
]
_ROUTES: Final[tuple[Route, ...]] = (
"claude-compacting",
"claude-plain",
"gemini",
"openai",
"openai-compact",
"openai-opaque",
)
def _route_provider(route: Route) -> Literal["claude", "gemini", "openai"]:
if route.startswith("claude"):
return "claude"
return "gemini" if route == "gemini" else "openai"
def _assert_reached(
route: Route, marker: str, sent: Mapping[str, JsonValue], call: Request, body: Mapping[str, JsonValue]
) -> None:
summary: Final = _summary(marker)
dumped: Final = json.dumps(body)
if route == "openai-opaque":
assert urlsplit(call.target).path.endswith("/responses"), call.target
assert sent in _objects(body["input"]), body["input"]
assert f"old-{marker}" in dumped
return
if route != "claude-compacting":
assert f"old-{marker}" not in dumped and f"old-answer-{marker}" not in dumped, dumped[:2000]
assert f"rules-{marker}" in dumped
assert f"recap-{marker}" in dumped
match route:
case "claude-compacting":
assistant: Final = tuple(
message for message in _objects(body["messages"]) if message["role"] == "assistant"
)
blocks: Final = tuple(
chain.from_iterable(
_objects(message["content"]) for message in assistant if isinstance(message["content"], list)
)
)
assert _block(summary) in blocks, body["messages"]
assert summary not in _anthropic_texts(body, "user")
assert f"rules-{marker}" in json.dumps(body["system"])
assert _COMPACT_BETA in call.headers.get("anthropic-beta", ""), dict(call.headers)
case "claude-plain":
assert "compaction" not in _anthropic_block_types(body), body["messages"]
assert summary in _anthropic_texts(body, "user"), body["messages"]
case "gemini":
assert summary in _gemini_texts(body, "user"), body["contents"]
case "openai" | "openai-compact":
suffix: Final = "/responses/compact" if route == "openai-compact" else "/responses"
assert urlsplit(call.target).path.endswith(suffix), call.target
assert "compaction" not in _openai_item_types(body), body["input"]
assert summary in _openai_user_texts(body), body["input"]
@pytest.mark.parametrize("route", _ROUTES)
def test_compaction_item_reaches_each_route_in_the_shape_it_reads(gateway: Gateway, route: Route) -> None:
marker: Final = _new_marker()
compaction: Final = (
{"type": "compaction", "id": f"cmp_{marker}", "encrypted_content": f"gAAAAA-opaque-{marker}"}
if route == "openai-opaque"
else _compaction_item(_summary(marker))
)
compacting: Final[dict[str, JsonValue]] = (
{"context_management": _COMPACTING} if route == "claude-compacting" else {}
)
dropping: Final[dict[str, JsonValue]] = (
{"context_management": _COMPACTING, "drop_params": True} if route == "gemini" else {}
)
path: Final = "/v1/responses/compact" if route == "openai-compact" else "/v1/responses"
with wire_server(_vendor) as wire, gateway.scenario() as scenario:
model: Final = _register(scenario, wire, _route_provider(route))
response: Final = gateway.request(
"POST",
path,
{"model": model, "input": _history(marker, compaction), **compacting, **dropping, **_CACHE_BUST},
)
assert response.status_code == 200, response.text
if route != "openai-compact":
assert _answer(marker) in response.text, response.text
call, body = _only_call(wire, marker)
_assert_reached(route, marker, compaction, call, body)
def _input_tokens(gateway: Gateway, model: str, items: list[JsonValue]) -> int:
counted: Final = gateway.post("/v1/responses/input_tokens", {"model": model, "input": items})
tokens: Final = counted["input_tokens"]
assert isinstance(tokens, int), counted
return tokens
def test_input_tokens_count_from_the_newest_compaction(gateway: Gateway) -> None:
marker: Final = _new_marker()
summary: Final = _summary(marker)
old_history: Final = " ".join(f"history-{index}" for index in range(600))
compacted: Final[list[JsonValue]] = [
{"role": "user", "content": old_history},
_compaction_item(summary),
{"role": "user", "content": _ask(marker)},
]
equivalent: Final[list[JsonValue]] = [
{"role": "user", "content": summary},
{"role": "user", "content": _ask(marker)},
]
with wire_server(_vendor) as wire, gateway.scenario() as scenario:
model: Final = _register(scenario, wire, "claude")
assert _input_tokens(gateway, model, compacted) == _input_tokens(gateway, model, equivalent)
counted: Final = tuple(
request.body for request in wire.drain() if urlsplit(request.target).path.endswith("/count_tokens")
)
assert len(counted) == 2, counted
assert all(b"history-" not in body for body in counted), counted[0][:2000]
def test_streamed_compaction_item_is_done_before_the_reasoning_after_it(gateway: Gateway) -> None:
marker: Final = _new_marker()
with wire_server(_vendor) as wire, gateway.scenario() as scenario:
model: Final = _register(scenario, wire, "claude")
events: Final = _stream(
gateway,
"/v1/responses",
{
"model": model,
"input": f"{_ask(marker, compact=True)} {_think(marker)}",
"context_management": _COMPACTING,
},
)
done: Final = tuple(
(position, event["output_index"], object_value(event["item"])["type"])
for position, event in enumerate(events)
if event["type"] == "response.output_item.done"
)
completed: Final = _only([event for event in events if event["type"] == "response.completed"])
assert [(index, kind) for _, index, kind in done] == [(0, "compaction"), (1, "reasoning"), (2, "message")], [
event["type"] for event in events
]
assert [item["type"] for item in _objects(object_value(completed["response"])["output"])] == [
"compaction",
"reasoning",
"message",
]
reasoning_done: Final = done[1][0]
sequence_numbers: Final = [
number for event in events[: reasoning_done + 1] if isinstance(number := event.get("sequence_number"), int)
]
assert sequence_numbers == sorted(set(sequence_numbers)), sequence_numbers
_only_call(wire, marker)
def test_chat_stream_reports_the_whole_compaction_block(gateway: Gateway) -> None:
marker: Final = _new_marker()
with wire_server(_vendor) as wire, gateway.scenario() as scenario:
model: Final = _register(scenario, wire, "claude")
chunks: Final = _stream(
gateway,
"/v1/chat/completions",
{
"model": model,
"messages": [{"role": "user", "content": _ask(marker, compact=True)}],
"context_management": {"edits": [{"type": "compact_20260112"}]},
},
)
reported: Final = _streamed_compaction_blocks(chunks)
assert reported, chunks
assert _objects(reported[-1])[-1] == _block(_summary(marker)), reported[-1]
assert len(_provider_calls(wire, marker)) == 1
def _guardrail_peer(request: Request) -> Reply:
payload: Final = _JSON_OBJECT.validate_json(request.body)
texts: Final = payload.get("texts") or []
assert isinstance(texts, list), payload
if urlsplit(request.target).path.startswith("/block/"):
found: Final = any(isinstance(text, str) and _CITY in text for text in texts)
verdict: Final = {"action": "BLOCKED", "blocked_reason": "city in summary"} if found else {"action": "NONE"}
return Reply(body=json.dumps(verdict).encode())
masked: Final = [text.replace(_CITY, _MASKED_CITY) if isinstance(text, str) else text for text in texts]
return Reply(body=json.dumps({"action": "GUARDRAIL_INTERVENED", "texts": masked}).encode())
def _guardrail(name: str, mode: str, api_base: str, **fields: JsonValue) -> dict[str, JsonValue]:
return {
"guardrail_name": name,
"litellm_params": {
"guardrail": "generic_guardrail_api",
"mode": mode,
"default_on": False,
"api_base": api_base,
"api_key": "synthetic-guardrail-key",
**fields,
},
}
def _deployment(name: str, model: str, api_base: str, **fields: JsonValue) -> dict[str, JsonValue]:
return {"model_name": name, "litellm_params": {"model": model, "api_base": api_base, **fields}}
_STORED_CLAUDE: Final = "compaction-stored-claude"
_SMALL_CLAUDE: Final = "compaction-small-claude"
_STORED_GEMINI: Final = "compaction-stored-gemini"
_STORED_OPENAI: Final = "compaction-stored-openai"
_SMALL_CONTEXT: Final = 2000
def _stored_config(directory: Path, provider_url: str, guardrail_url: str, **litellm_settings: JsonValue) -> Path:
stock: Final = _JSON_OBJECT.validate_python(yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text()))
claude: Final = {"api_key": cc.ANTHROPIC_API_KEY}
config: Final[dict[str, JsonValue]] = {
**stock,
"model_list": [
_deployment(_STORED_CLAUDE, f"anthropic/{_CLAUDE}", provider_url, **claude),
{
**_deployment(_SMALL_CLAUDE, f"anthropic/{_CLAUDE}", provider_url, **claude),
"model_info": {"max_input_tokens": _SMALL_CONTEXT},
},
_deployment(_STORED_GEMINI, f"gemini/{_GEMINI}", provider_url, api_key="synthetic-gemini-key"),
_deployment(_STORED_OPENAI, f"openai/{_OPENAI}", f"{provider_url}/v1", api_key="synthetic-openai-key"),
],
"general_settings": {
**object_value(stock["general_settings"]),
"store_prompts_in_spend_logs": True,
"disable_responses_id_security": True,
},
"litellm_settings": {**object_value(stock["litellm_settings"]), **litellm_settings},
"router_settings": {**object_value(stock["router_settings"]), "num_retries": 0, "enable_pre_call_checks": True},
"guardrails": [
_guardrail(_MASK_INPUT, "pre_call", f"{guardrail_url}/input"),
_guardrail(_MASK_OUTPUT, "post_call", f"{guardrail_url}/output"),
_guardrail(_BLOCK_OUTPUT, "post_call", f"{guardrail_url}/block", streaming_buffer_until_moderated=True),
],
}
path: Final = directory / "compaction-stored.yaml"
path.write_text(yaml.safe_dump(config))
return path
@dataclass(frozen=True, slots=True)
class _StoredRig:
gateway: Gateway
provider: Wire
guardrail: Wire
def _stored_rig(directory: Path, **litellm_settings: JsonValue) -> Iterator[_StoredRig]:
with (
wire_server(_vendor) as provider,
wire_server(_guardrail_peer) as guardrail,
gateway_from_environment() as gateway,
):
config: Final = _stored_config(directory, provider.url, guardrail.url, **litellm_settings)
with owned_proxy(gateway, directory, {}, config=config, workers=2) as owned:
yield _StoredRig(owned, provider, guardrail)
@pytest.fixture(scope="module")
def stored(tmp_path_factory: pytest.TempPathFactory) -> Iterator[_StoredRig]:
yield from _stored_rig(tmp_path_factory.mktemp("compaction-stored"))
@pytest.fixture(scope="module")
def redacting(tmp_path_factory: pytest.TempPathFactory) -> Iterator[_StoredRig]:
yield from _stored_rig(tmp_path_factory.mktemp("compaction-redacting"), turn_off_message_logging=True)
def _logged_row(request_id: str, status: Literal["success", "failure"]) -> dict[str, JsonValue]:
rows: Final = eventually(
lambda: read_rows(
'SELECT request_id, proxy_server_request, response, metadata, status FROM "LiteLLM_SpendLogs"'
" WHERE request_id=%s",
(request_id,),
),
lambda values: len(values) == 1,
seconds=70,
)
assert rows[0]["status"] == status, rows[0]
return rows[0]
def _spend_row(response_id: str) -> dict[str, JsonValue]:
decoded: Final = ResponsesAPIRequestUtils.decode_responses_api_response_id(response_id).get("response_id")
return _logged_row(decoded if isinstance(decoded, str) else response_id, "success")
_REPLAY_ROUTES: Final = (
pytest.param(_STORED_CLAUDE, True, id="claude-compacting"),
pytest.param(_STORED_CLAUDE, False, id="claude-plain"),
pytest.param(_STORED_GEMINI, True, id="gemini"),
)
@pytest.mark.parametrize(("follow_up_model", "compacting"), _REPLAY_ROUTES)
def test_previous_response_id_after_a_compaction_turn_replays_the_whole_summary(
stored: _StoredRig, follow_up_model: str, compacting: bool
) -> None:
first: Final = _new_marker()
second: Final = _new_marker()
summary: Final = _summary(first)
long_text: Final = f"long-{first} {'z' * 3000} tail-{first}"
compaction_request: Final[dict[str, JsonValue]] = {"context_management": _COMPACTING}
opened: Final = _responses(
stored.gateway,
{
"model": _STORED_CLAUDE,
"input": [{"role": "user", "content": long_text}, {"role": "user", "content": _ask(first, compact=True)}],
**compaction_request,
},
)
assert _decoded(string_value(_only(_output_items(opened, "compaction"))["encrypted_content"])) == _block(summary)
row: Final = _spend_row(string_value(opened["id"]))
assert summary in json.dumps(row["response"]), json.dumps(row["response"])[:600]
stored_request: Final = json.dumps(row["proxy_server_request"])
assert LITELLM_TRUNCATED_PAYLOAD_FIELD in stored_request and "z" * 3000 not in stored_request
_only_call(stored.provider, first)
follow_up: Final = _responses(
stored.gateway,
{
"model": follow_up_model,
"input": _ask(second),
"previous_response_id": opened["id"],
**(compaction_request if compacting else {}),
**({"drop_params": True} if follow_up_model == _STORED_GEMINI else {}),
},
)
assert _answer(second) in json.dumps(follow_up)
call, body = _only_call(stored.provider, second)
dumped: Final = json.dumps(body)
assert f"long-{first}" not in dumped and f"ask-{first}" not in dumped, dumped[:2000]
assert _answer(first) in dumped
if follow_up_model == _STORED_GEMINI:
assert summary in _gemini_texts(body, "user"), body["contents"]
elif compacting:
replayed: Final = _objects(body["messages"])[0]
assert replayed["role"] == "assistant", body["messages"]
assert _objects(replayed["content"])[0] == _block(summary), replayed
assert _COMPACT_BETA in call.headers.get("anthropic-beta", ""), dict(call.headers)
else:
assert "compaction" not in _anthropic_block_types(body), body["messages"]
assert summary in _anthropic_texts(body, "user"), body["messages"]
def test_router_pre_call_check_counts_from_the_newest_compaction(stored: _StoredRig) -> None:
compacted_marker: Final = _new_marker()
control_marker: Final = _new_marker()
old_history: Final = " ".join(f"history-{index}" for index in range(3 * _SMALL_CONTEXT))
def send(marker: str, items: list[JsonValue]) -> httpx.Response:
return stored.gateway.request(
"POST",
"/v1/responses",
{"model": _SMALL_CLAUDE, "input": [*items, {"role": "user", "content": _ask(marker)}], **_CACHE_BUST},
)
compacted: Final = send(
compacted_marker,
[{"role": "user", "content": old_history}, _compaction_item(f"short summary {compacted_marker}")],
)
assert compacted.status_code == 200, compacted.text
_only_call(stored.provider, compacted_marker)
control: Final = send(control_marker, [{"role": "user", "content": old_history}])
assert control.status_code == 400, control.text
assert _provider_calls(stored.provider, control_marker) == ()
_GUARDED_ROUTES: Final = (
pytest.param(_STORED_OPENAI, id="openai"),
pytest.param(_STORED_CLAUDE, id="claude-compacting"),
)
@pytest.mark.parametrize("model", _GUARDED_ROUTES)
def test_pre_call_guardrail_rewrites_the_compaction_summary(stored: _StoredRig, model: str) -> None:
marker: Final = _new_marker()
summary: Final = _summary(marker)
masked: Final = summary.replace(_CITY, _MASKED_CITY)
compacting: Final[dict[str, JsonValue]] = {"context_management": _COMPACTING} if model == _STORED_CLAUDE else {}
_responses(
stored.gateway,
{
"model": model,
"input": [_compaction_item(summary), {"role": "user", "content": _ask(marker)}],
"guardrails": [_MASK_INPUT],
**compacting,
},
)
scanned: Final = tuple(
_JSON_OBJECT.validate_json(request.body)
for request in stored.guardrail.drain()
if f"kept-{marker}".encode() in request.body
)
assert any(summary in _listed(payload["texts"]) for payload in scanned), scanned
call, body = _only_call(stored.provider, marker)
assert _CITY not in json.dumps(body), json.dumps(body)[:2000]
if model == _STORED_OPENAI:
assert masked in _openai_user_texts(body), body["input"]
else:
assert _objects(_objects(body["messages"])[0]["content"])[0] == _block(masked), body["messages"]
def test_post_call_guardrail_rewrites_the_returned_compaction_summary(stored: _StoredRig) -> None:
marker: Final = _new_marker()
returned: Final = _responses(
stored.gateway,
{
"model": _STORED_CLAUDE,
"input": _ask(marker, compact=True),
"context_management": _COMPACTING,
"guardrails": [_MASK_OUTPUT],
},
)
masked: Final = _block(_summary(marker).replace(_CITY, _MASKED_CITY))
assert _decoded(string_value(_only(_output_items(returned, "compaction"))["encrypted_content"])) == masked
_only_call(stored.provider, marker)
def test_buffered_stream_guardrail_blocks_on_the_compaction_summary(stored: _StoredRig) -> None:
marker: Final = _new_marker()
body: Final[dict[str, JsonValue]] = {
"model": _STORED_CLAUDE,
"input": _ask(marker, compact=True),
"context_management": _COMPACTING,
"guardrails": [_BLOCK_OUTPUT],
"stream": True,
**_CACHE_BUST,
}
headers: Final = {"Authorization": f"Bearer {stored.gateway.key}"}
with stored.gateway.client.stream("POST", "/v1/responses", json=body, headers=headers) as response:
text: Final = response.read().decode()
assert response.status_code == 200, text
peer_calls: Final = tuple(
(urlsplit(request.target).path, _JSON_OBJECT.validate_json(request.body).get("texts"))
for request in stored.guardrail.drain()
if marker.encode() in request.body
)
assert any(path.startswith("/block/") and _summary(marker) in _listed(texts) for path, texts in peer_calls), [
(path, [str(value)[:80] for value in _listed(texts or [])]) for path, texts in peer_calls
]
event_types: Final = tuple(event["type"] for event in _sse_events(text))
assert "response.failed" in event_types, event_types
assert "response.completed" not in event_types, event_types
assert f"kept-{marker}" not in text and _CITY not in text, text[:2000]
_only_call(stored.provider, marker)
def test_redacted_spend_row_holds_no_compaction_summary(stored: _StoredRig) -> None:
marker: Final = _new_marker()
returned: Final = _responses(
stored.gateway,
{"model": _STORED_CLAUDE, "input": _ask(marker, compact=True), "context_management": _COMPACTING},
headers=_REDACT_HEADER,
)
assert _decoded(string_value(_only(_output_items(returned, "compaction"))["encrypted_content"])) == _block(
_summary(marker)
)
row: Final = _spend_row(string_value(returned["id"]))
assert f"kept-{marker}" not in json.dumps(row["response"]), json.dumps(row["response"])[:2000]
_only_call(stored.provider, marker)
def test_stored_compaction_input_item_keeps_its_whole_summary_and_nothing_else(stored: _StoredRig) -> None:
first: Final = _new_marker()
second: Final = _new_marker()
summary: Final = _summary(first)
padded_item: Final = {
**_compaction_item(summary),
"encrypted_content": json.dumps({**_block(summary), "padding": f"pad-{first} {'p' * 3000}"}),
}
opened: Final = _responses(
stored.gateway,
{
"model": _STORED_CLAUDE,
"input": [padded_item, {"role": "user", "content": _ask(first)}],
"context_management": _COMPACTING,
},
)
stored_input: Final = _objects(
object_value(_spend_row(string_value(opened["id"]))["proxy_server_request"])["input"]
)
assert _decoded(string_value(stored_input[0]["encrypted_content"])) == _block(summary), stored_input[0]
_only_call(stored.provider, first)
_responses(
stored.gateway,
{
"model": _STORED_CLAUDE,
"input": _ask(second),
"previous_response_id": opened["id"],
"context_management": _COMPACTING,
},
)
_, body = _only_call(stored.provider, second)
assert summary in _anthropic_texts(body, "user"), body["messages"]
assert f"pad-{first}" not in json.dumps(body), body["messages"]
def test_redacted_chat_stream_spend_row_holds_no_compaction_delta(stored: _StoredRig) -> None:
marker: Final = _new_marker()
summary: Final = _summary(marker)
chunks: Final = _stream(
stored.gateway,
"/v1/chat/completions",
{
"model": _STORED_CLAUDE,
"messages": [{"role": "user", "content": _ask(marker, compact=True)}],
"context_management": {"edits": [{"type": "compact_20260112"}]},
},
headers=_REDACT_HEADER,
)
streamed_blocks: Final = _streamed_compaction_blocks(chunks)
assert streamed_blocks and _objects(streamed_blocks[-1])[-1] == _block(summary), chunks
secrets: Final = (f"kept-{marker}", summary[len(summary) // 2 :])
logged: Final = {column: json.dumps(value) for column, value in _spend_row(string_value(chunks[0]["id"])).items()}
leaked: Final = {column: text for column, text in logged.items() if any(secret in text for secret in secrets)}
assert not leaked, {column: text[max(0, text.find(secrets[0]) - 600) :][:1500] for column, text in leaked.items()}
_only_call(stored.provider, marker)
def _failing_request(path: str, marker: str) -> dict[str, JsonValue]:
ask: Final = f"{_ask(marker)} {_fail(marker)}"
if path == "/v1/responses":
return {
"model": _STORED_CLAUDE,
"input": [_compaction_item(_summary(marker)), {"role": "user", "content": ask}],
}
return {"model": _STORED_CLAUDE, "max_tokens": 64, "messages": [{"role": "user", "content": ask}]}
def _failed_call_row(
rig: _StoredRig,
path: str,
marker: str,
headers: Mapping[str, str],
key: str | None = None,
**fields: JsonValue,
) -> dict[str, JsonValue]:
body: Final = {**_failing_request(path, marker), **fields}
response: Final = rig.gateway.request("POST", path, body, headers=headers, key=key)
assert response.status_code == 400, response.text
_only_call(rig.provider, marker)
return _logged_row(response.headers["x-litellm-call-id"], "failure")
_LEGACY_REDACT_HEADER: Final = {"litellm-enable-message-redaction": "true"}
_REDACTED_FAILURES: Final = (
pytest.param("/v1/responses", _REDACT_HEADER, id="responses"),
pytest.param("/v1/responses", _LEGACY_REDACT_HEADER, id="responses-legacy-header"),
pytest.param("/v1/messages", _REDACT_HEADER, id="messages"),
pytest.param("/v1/chat/completions", _REDACT_HEADER, id="chat"),
)
@pytest.mark.parametrize(("path", "headers"), _REDACTED_FAILURES)
def test_failed_call_spend_row_holds_no_request_text_with_the_redaction_header(
stored: _StoredRig, path: str, headers: Mapping[str, str]
) -> None:
marker: Final = _new_marker()
logged: Final = json.dumps(_failed_call_row(stored, path, marker, headers))
assert marker not in logged, logged[max(0, logged.find(marker) - 600) :][:1500]
def test_failed_responses_call_spend_row_keeps_the_whole_compaction_summary_without_the_header(
stored: _StoredRig,
) -> None:
marker: Final = _new_marker()
row: Final = _failed_call_row(stored, "/v1/responses", marker, {})
stored_input: Final = _objects(object_value(row["proxy_server_request"])["input"])
assert _decoded(string_value(stored_input[0]["encrypted_content"])) == _block(_summary(marker)), stored_input[0]
assert f"{_ask(marker)} {_fail(marker)}" in _message_texts(stored_input[1]), stored_input[1]
def test_failed_responses_call_stores_the_caller_metadata_without_the_request_headers(stored: _StoredRig) -> None:
marker: Final = _new_marker()
row: Final = _failed_call_row(stored, "/v1/responses", marker, _REDACT_HEADER, metadata={"caller": marker})
stored_metadata: Final = object_value(object_value(row["proxy_server_request"])["metadata"])
assert stored_metadata.get("caller") == marker, stored_metadata
assert "headers" not in stored_metadata, stored_metadata
def test_failed_responses_call_keeps_the_caller_metadata_headers_over_the_redaction_header(stored: _StoredRig) -> None:
marker: Final = _new_marker()
caller_headers: Final = {"caller": marker}
row: Final = _failed_call_row(stored, "/v1/responses", marker, _REDACT_HEADER, metadata={"headers": caller_headers})
stored_request: Final = object_value(row["proxy_server_request"])
assert object_value(stored_request["metadata"])["headers"] == caller_headers, stored_request["metadata"]
assert _ask(marker) in json.dumps(stored_request), json.dumps(stored_request)[:1500]
_GLOBALLY_REDACTED_FAILURES: Final = (
pytest.param("/v1/responses", _UNREDACT_HEADER, False, id="responses-disable-header"),
pytest.param("/v1/chat/completions", _UNREDACT_HEADER, True, id="chat-disable-header"),
pytest.param("/v1/responses", {}, False, id="responses-no-header"),
)
@pytest.mark.parametrize(("path", "headers", "kept"), _GLOBALLY_REDACTED_FAILURES)
def test_opted_out_key_failed_call_spend_row_takes_the_disable_header_over_global_redaction_only_on_chat(
redacting: _StoredRig, path: str, headers: Mapping[str, str], kept: bool
) -> None:
marker: Final = _new_marker()
with redacting.gateway.scenario() as scenario:
key: Final = scenario.key(metadata={"allow_client_message_redaction_opt_out": True})
row: Final = _failed_call_row(redacting, path, marker, headers, key)
stored_request: Final = json.dumps(row["proxy_server_request"])
assert (_ask(marker) in stored_request) is kept, stored_request[:1500]