fix(engine): Chunk 5.2 — wire CCR + memory into OpenAI chat engine path

OpenAI CCR (mirrors handle_openai_chat lines 1593-1629):
- CCRToolInjector(provider="openai") — OpenAI message/tool shapes
- apply_session_sticky_ccr_tool(provider="openai") for sticky-on
- NO frozen_message_count guard (handler has none for OpenAI)
- NO compression tracking or proactive expansion (handler omits both)
- tools updated in body["tools"] after injection

OpenAI memory (mirrors handle_openai_chat lines 1643-1733):
- Uses append_text_to_latest_user_chat_message (backward scan, no frozen)
- Does NOT skip in cache mode (handler injects in all modes)
- MemoryDecision.decide gate + inject_context sub-gate preserved

Both CCR/memory are no-ops when components=None — all 22 OpenAI + all
Anthropic + all existing tests remain byte-identical (239/239 green).

Responses + CCR/memory: intentionally deferred; memory injection runs
pre-compression in the live handler which requires restructuring the
responses path; documented in method docstring.

New structural tests: test_facade_openai_ccr.py (9 tests) +
test_facade_openai_memory.py (11 tests) — all pass.
This commit is contained in:
chopratejas 2026-05-31 23:28:22 -07:00
parent 4fe18bf328
commit 42901e418b
3 changed files with 1150 additions and 10 deletions

View file

@ -1,4 +1,4 @@
"""HeadroomEngine — request/response hook facade (Chunks 2 + 4.2a/4.2b/4.2c + 4.3-i + 5 + 5R).
"""HeadroomEngine — request/response hook facade (Chunks 2 + 4.2a/4.2b/4.2c + 4.3-i + 5 + 5R + 5.2).
Composes the existing compression subsystems behind a clean hook interface.
Does NOT reimplement compression; delegates to injected ``CompressionPipeline``
@ -54,6 +54,39 @@ Design notes
``stream_options`` before forwarding (unlike chat);
* passthrough (no compression): returns raw inbound bytes byte-identical;
* compressed: canonical re-serialization of the mutated body.
- **Chunk 5.2 OpenAI chat CCR + memory**: ``ccr_components`` and
``memory_components`` are now wired into ``_on_request_openai_chat``.
Key differences from the Anthropic CCR/memory blocks:
CCR differences (mirrors ``OpenAIHandlerMixin.handle_openai_chat`` lines
~1593-1629):
* ``CCRToolInjector(provider="openai", ...)`` OpenAI message shapes.
* NO ``frozen_message_count`` guard on system-instruction injection or tool
injection the live OpenAI handler does not apply those guards.
* NO compression tracking (step 3) or proactive expansion (step 4) the
live OpenAI handler omits those CCR phases entirely.
* Tools updated in ``body["tools"]`` after CCR injection (same as handler
at lines ~1755-1756).
Memory differences (mirrors lines ~1643-1733):
* Uses ``append_text_to_latest_user_chat_message`` (OpenAI helper that
scans backwards ignoring frozen count) instead of the Anthropic frozen-
aware helper.
* Does NOT skip injection in cache mode (the OpenAI handler injects in all
modes).
* When CCR components is None but memory components is set, memory injection
still fires (the two are independent gates).
Responses + CCR/memory:
The live ``handle_openai_responses`` handler does run memory injection
(into ``body["input"]``) BEFORE compression. The engine's
``_on_request_openai_responses`` currently does not wire CCR or memory
(the golden corpus has both features off and the handler position is
pre-compression rather than post-compression). This is intentional: the
Responses path does not expose ``messages`` to the engine after the
compression step, and wiring it pre-compression would require restructuring
the responses path. Memory for Responses is deferred; the omission is
documented here and in the method docstring.
"""
from __future__ import annotations
@ -980,14 +1013,16 @@ class HeadroomEngine:
),
)
# ── Real OpenAI chat orchestration (Chunk 5) ─────────────────────────────
# ── Real OpenAI chat orchestration (Chunk 5 + 5.2) ──────────────────────
def _on_request_openai_chat(self, ctx: RequestContext) -> RequestDecision:
"""Reproduce the OpenAI /v1/chat/completions compression-core.
Mirrors ``OpenAIHandlerMixin.handle_openai_chat`` through:
CompressionDecision mode-branch pipeline.apply (streaming:
inject stream_options) canonical serialization.
CompressionDecision mode-branch pipeline.apply CCR marker-scan +
tool-inject (when ccr_components set, not bypass) memory inject
(when memory_components set, not bypass) (streaming: inject
stream_options) canonical serialization.
Intentional differences from the Anthropic path that are faithfully
preserved here to match the live handler byte-for-byte:
@ -1008,11 +1043,21 @@ class HeadroomEngine:
BEFORE forwarding the bytes. This produces a byte-level difference
vs the inbound body and is captured in the streaming golden fixtures.
Excluded from this chunk: CCR injection, memory injection, hooks,
pipeline_extension events, prefix-tracker.update_from_response,
cache, rate-limiting, image compression. These are all controlled OFF
in the golden recorder's ``_DEFAULT_CONFIG_KWARGS``, so the 16 chat
golden fixtures do not exercise them.
4. **CCR differences from Anthropic** (Chunk 5.2):
- ``CCRToolInjector(provider="openai", ...)`` OpenAI shapes.
- NO ``frozen_message_count`` guard on system-instruction or tool
injection (handler lines 1603-1629 have no such guard).
- NO compression tracking or proactive expansion the live OpenAI
handler omits CCR phases 3 and 4 entirely.
5. **Memory differences from Anthropic** (Chunk 5.2):
- Uses ``append_text_to_latest_user_chat_message`` (OpenAI helper
that scans backwards without frozen-count awareness).
- Does NOT skip in cache mode (the handler injects in all modes).
When ``ccr_components`` or ``memory_components`` is None, those steps
are a no-op all 22 existing golden fixtures (CCR/memory off) remain
byte-identical.
"""
from headroom.proxy.helpers import serialize_body_canonical
from headroom.proxy.modes import is_cache_mode, is_token_mode
@ -1053,6 +1098,11 @@ class HeadroomEngine:
)
optimized_messages = messages
# session_id is derived once (outside the compress block) so CCR can
# use it even when compression was skipped (sticky tool logic needs it).
session_id: str | None = None
frozen_message_count = 0
request_id = ctx.request_id
if _decision.should_compress and not _bypass:
# --- Session / frozen-count derivation ---
@ -1068,9 +1118,9 @@ class HeadroomEngine:
context_limit = oc.provider.get_context_limit(model)
biases = None
request_id = ctx.request_id
# --- Mode branch: token / non-cache / cache-delta ---
assert session_id is not None # set 3 lines above; mypy flow narrowing
if is_token_mode(oc.config.mode):
comp_cache = oc.get_compression_cache(session_id)
@ -1111,8 +1161,105 @@ class HeadroomEngine:
if result.messages != messages:
optimized_messages = result.messages
# ── CCR request-side (Chunk 5.2 — OpenAI) ────────────────────────────
# Mirrors ``handle_openai_chat`` lines ~1593-1629.
# Steps wired: marker scan + system-instruction injection +
# session-sticky tool injection.
# NOT wired: compression tracking (step 3) + proactive expansion
# (step 4) — the live OpenAI handler does not implement those phases.
# Gate: ccr_components not None AND not bypass (same as handler).
ccr = self._ccr_components
tools = body.get("tools")
ccr_tool_injected = False
if ccr is not None and not _bypass:
# Derive session_id for sticky-tool registration even when
# compression was skipped (_decision.should_compress=False).
if session_id is None:
session_id = oc.session_tracker_store.compute_session_id(ctx, model, messages)
if oc.config.ccr_inject_tool or oc.config.ccr_inject_system_instructions:
from headroom.ccr import CCRToolInjector
from headroom.proxy.helpers import apply_session_sticky_ccr_tool
injector = CCRToolInjector(
provider="openai",
inject_tool=False, # routed through sticky helper below
inject_system_instructions=oc.config.ccr_inject_system_instructions,
)
injector.scan_for_markers(optimized_messages)
# System-instruction injection — NO frozen guard (handler
# line 1611 has no such guard for OpenAI).
if oc.config.ccr_inject_system_instructions and injector.has_compressed_content:
optimized_messages = injector.inject_into_system_message(optimized_messages)
if oc.config.ccr_inject_tool:
tools, ccr_tool_injected = apply_session_sticky_ccr_tool(
provider="openai",
session_id=session_id,
request_id=request_id,
existing_tools=tools,
has_compressed_content_this_turn=injector.has_compressed_content,
)
if ccr_tool_injected:
logger.debug(
"[%s] CCR(engine/openai): tool registered (session=%s, "
"compressed_this_turn=%s, hashes_seen=%d)",
request_id,
session_id,
injector.has_compressed_content,
len(injector.detected_hashes),
)
# ── Memory injection (Chunk 5.2 — OpenAI) ────────────────────────────
# Mirrors ``handle_openai_chat`` lines ~1643-1733.
# Uses ``append_text_to_latest_user_chat_message`` (OpenAI helper)
# which scans backwards through all messages without frozen-count
# awareness — the live handler does not apply a frozen guard here.
# Does NOT skip in cache mode (handler injects in all modes).
# Gate: memory_components not None AND not bypass.
mc = self._memory_components
if mc is not None and not _bypass:
from headroom.proxy.helpers import get_memory_injection_mode
from headroom.proxy.memory_decision import MemoryDecision
_header_user_id = headers.get("x-headroom-user-id", "")
memory_user_id: str | None = _header_user_id if _header_user_id else mc.default_user_id
mem_decision = MemoryDecision.decide(
headers=ctx.headers_view,
memory_handler=mc.memory_handler,
memory_user_id=memory_user_id,
mode_name=get_memory_injection_mode(),
)
if mem_decision.inject:
_inject_ctx = mc.memory_handler is not None and getattr(
getattr(mc.memory_handler, "config", None), "inject_context", True
)
if _inject_ctx:
memory_context: str | None = ctx.prefetched_memory_context
if memory_context:
from headroom.proxy.helpers import (
append_text_to_latest_user_chat_message,
)
new_messages, bytes_appended = append_text_to_latest_user_chat_message(
optimized_messages, memory_context
)
if bytes_appended > 0:
optimized_messages = new_messages
logger.debug(
"[%s] Memory(engine/openai): injected %d bytes into "
"latest user message tail",
request_id,
bytes_appended,
)
# --- Reassemble body ---
body["messages"] = optimized_messages
if tools is not None:
body["tools"] = tools
# --- Streaming: inject stream_options (mirrors handler lines ~2026-2029) ---
# The live handler injects this BEFORE forwarding bytes, so it appears in the
@ -1138,6 +1285,7 @@ class HeadroomEngine:
telemetry=ResponseTelemetry(
bytes_saved=bytes_saved,
compressed=compressed,
ccr_fired=ccr_tool_injected,
),
)
@ -1178,6 +1326,17 @@ class HeadroomEngine:
Excluded: memory injection, CCR, beta-header sticky merge, image
compression, hooks all off in the golden corpus's
``_DEFAULT_CONFIG_KWARGS``.
Memory / CCR for Responses (intentionally deferred):
The live ``handle_openai_responses`` handler runs memory injection
into ``body["input"]`` BEFORE compression, using
``append_text_to_latest_user_input_item``. Wiring it here would
require restructuring the engine to expose ``body["input"]`` after
the ``_compress_openai_responses_payload`` step (the compressor
already returns the mutated body dict). This is deferred; when
wired it should run AFTER compression (mirrors the chat path) and
use the same ``append_text_to_latest_user_input_item`` helper.
The 6 golden Responses fixtures are not affected (memory/CCR off).
"""
from headroom.proxy.helpers import serialize_body_canonical

View file

@ -0,0 +1,470 @@
"""Structural tests for OpenAI CCR request-side steps (Chunk 5.2).
Mirrors ``test_facade_ccr.py`` (Anthropic) but for the OpenAI chat path.
Key differences from the Anthropic CCR tests:
- ``provider="openai"`` throughout (CCRToolInjector + apply_session_sticky_ccr_tool).
- NO frozen_message_count guard on system-instruction or tool injection
the live OpenAI handler does not apply those guards.
- NO compression tracking (step 3) or proactive expansion (step 4)
the live OpenAI handler omits those CCR phases.
- The engine uses ``_on_request_openai_chat`` via ``OpenAIComponents``
(not ``AnthropicComponents``).
Running
-------
.venv/bin/python -m pytest tests/engine/test_facade_openai_ccr.py -v
"""
from __future__ import annotations
import json
from typing import Any
from unittest.mock import MagicMock
import pytest
pytest.importorskip("fastapi")
# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------
_CCR_TEST_HASH = "abcdef123456789012345678"
_CCR_MARKER = f"[100 items compressed to 10. Retrieve more: hash={_CCR_TEST_HASH}]"
def _make_engine(
*,
config_overrides: dict[str, Any] | None = None,
frozen_count: int = 0,
with_ccr: bool = True,
ccr_context_tracker: Any | None = None,
get_compression_store: Any | None = None,
session_turn_counters: dict[str, int] | None = None,
) -> Any:
"""Build a HeadroomEngine with OpenAIComponents + CCRComponents."""
from headroom.engine.contract import Flavor, Provider
from headroom.engine.facade import CCRComponents, HeadroomEngine, OpenAIComponents
from headroom.proxy.models import ProxyConfig
from headroom.proxy.server import HeadroomProxy
config_kwargs: dict[str, Any] = {
"optimize": True,
"mode": "token",
"cache_enabled": False,
"rate_limit_enabled": False,
"cost_tracking_enabled": False,
"log_requests": False,
"ccr_inject_tool": True,
"ccr_inject_system_instructions": True,
"ccr_handle_responses": False,
"ccr_context_tracking": False,
"ccr_proactive_expansion": False,
"image_optimize": False,
}
if config_overrides:
config_kwargs.update(config_overrides)
config = ProxyConfig(**config_kwargs)
proxy = HeadroomProxy(config)
class _FixedStore:
def compute_session_id(self, ctx: Any, model: str, msgs: Any) -> str:
return "ccr-openai-structural-test-session"
def get_or_create(self, session_id: str, provider: str) -> Any:
class _T:
def get_frozen_message_count(self) -> int:
return frozen_count
def get_last_original_messages(self) -> list[Any]:
return []
def get_last_forwarded_messages(self) -> list[Any]:
return []
return _T()
def get_fresh_cache(self, session_id: str) -> Any:
class _C:
def apply_cached(self, msgs: list[Any]) -> list[Any]:
return list(msgs)
def compute_frozen_count(self, msgs: list[Any]) -> int:
return 0
def update_from_result(self, orig: Any, compr: Any) -> None:
pass
def mark_stable_from_messages(self, msgs: Any, up_to: int) -> None:
pass
return _C()
oc = OpenAIComponents(
pipeline=proxy.openai_pipeline,
provider=proxy.openai_provider,
session_tracker_store=_FixedStore(),
get_compression_cache=_FixedStore().get_fresh_cache,
config=proxy.config,
usage_reporter=None,
)
ccr = None
if with_ccr:
ccr = CCRComponents(
ccr_context_tracker=ccr_context_tracker,
get_compression_store=get_compression_store or (lambda: MagicMock()),
session_turn_counters=session_turn_counters
if session_turn_counters is not None
else {},
)
engine = HeadroomEngine(
pipelines={(Provider.OPENAI, Flavor.CHAT): proxy.openai_pipeline},
config=proxy.config,
usage_reporter=None,
salt=b"ccr-openai-structural-test-salt",
openai_components=oc,
ccr_components=ccr,
)
return engine
def _make_ctx(
body: dict[str, Any],
*,
headers: dict[str, str] | None = None,
cwd: str | None = None,
) -> Any:
"""Build a RequestContext for OpenAI chat structural tests."""
from headroom.engine.contract import Flavor, Provider, RequestContext
h: dict[str, str] = {
"authorization": "Bearer sk-test-openai-key",
"content-type": "application/json",
}
if cwd:
h["x-headroom-cwd"] = cwd
if headers:
h.update(headers)
return RequestContext(
provider=Provider.OPENAI,
flavor=Flavor.CHAT,
headers_view=h,
raw_body=json.dumps(body, separators=(",", ":"), ensure_ascii=False).encode(),
session_key="ccr-openai-structural",
request_id="req-openai-ccr-test",
)
# ---------------------------------------------------------------------------
# 1. CCR is a no-op when ccr_components is None
# ---------------------------------------------------------------------------
def test_ccr_noop_when_components_none() -> None:
"""Engine without CCRComponents is byte-identical — OpenAI golden fixtures unaffected."""
engine = _make_engine(with_ccr=False)
body = {
"model": "gpt-4o",
"messages": [
{
"role": "tool",
"content": f"Compressed result. {_CCR_MARKER}",
"tool_call_id": "call_x",
}
],
}
ctx = _make_ctx(body)
decision = engine.on_request(ctx)
out = json.loads(decision.body)
# CCR tool must NOT have been injected (no ccr_components).
tools = out.get("tools")
assert tools is None, "No tools must appear when CCRComponents is None"
# ---------------------------------------------------------------------------
# 2. Tool injection with OpenAI shapes
# ---------------------------------------------------------------------------
def test_ccr_tool_injected_openai_shape() -> None:
"""CCR tool injection produces OpenAI tool shapes: [{type:function, function:{...}}].
When a CCR marker is present and ccr_inject_tool=True, the engine adds
the headroom_retrieve tool to body["tools"] in OpenAI format.
"""
engine = _make_engine()
body = {
"model": "gpt-4o",
"messages": [
{
"role": "tool",
"content": f"Fetched data. {_CCR_MARKER}",
"tool_call_id": "call_fetch",
}
],
}
ctx = _make_ctx(body)
decision = engine.on_request(ctx)
out = json.loads(decision.body)
tools = out.get("tools")
assert isinstance(tools, list), "tools must be a list after CCR injection"
assert len(tools) > 0, "At least one tool must be injected"
# Each injected tool must have OpenAI shape: {type: function, function: {...}}
for tool in tools:
assert tool.get("type") == "function", (
f"OpenAI tool must have type='function', got {tool.get('type')!r}"
)
assert "function" in tool, "OpenAI tool must have a 'function' sub-dict"
assert "name" in tool["function"], "OpenAI tool.function must have 'name'"
def test_ccr_tool_injected_appended_to_existing_tools() -> None:
"""CCR tool injection appends to existing tools (OpenAI sticky merge)."""
engine = _make_engine()
body = {
"model": "gpt-4o",
"messages": [
{
"role": "tool",
"content": f"Result. {_CCR_MARKER}",
"tool_call_id": "call_y",
}
],
"tools": [
{
"type": "function",
"function": {
"name": "existing_tool",
"description": "pre-existing tool",
"parameters": {"type": "object"},
},
}
],
}
ctx = _make_ctx(body)
decision = engine.on_request(ctx)
out = json.loads(decision.body)
tools = out.get("tools", [])
tool_names = [t["function"]["name"] for t in tools if "function" in t]
# Original tool must be preserved.
assert "existing_tool" in tool_names, "Existing tool must be preserved after CCR injection"
# CCR tool must be added.
assert any("retrieve" in name.lower() or "headroom" in name.lower() for name in tool_names), (
f"CCR retrieve tool must be present in tool names; got: {tool_names!r}"
)
def test_ccr_no_tool_injected_when_inject_tool_false() -> None:
"""ccr_inject_tool=False → no tool injection regardless of marker presence."""
engine = _make_engine(
config_overrides={
"ccr_inject_tool": False,
"ccr_inject_system_instructions": False,
}
)
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "No injection configured."}],
}
ctx = _make_ctx(body)
decision = engine.on_request(ctx)
out = json.loads(decision.body)
# ccr_inject_tool=False → no tool injection at all.
tools = out.get("tools")
assert tools is None, "No CCR tool should be injected when ccr_inject_tool=False"
# ---------------------------------------------------------------------------
# 3. No frozen guard on system-instruction injection (OpenAI differs from Anthropic)
# ---------------------------------------------------------------------------
def test_ccr_system_instruction_injected_even_with_frozen_prefix() -> None:
"""OpenAI CCR: system-instruction injection has NO frozen_message_count guard.
The Anthropic engine guards injection when frozen_count > 0; the OpenAI
handler does not. This test confirms the engine matches the handler.
"""
engine = _make_engine(
frozen_count=2, # non-zero frozen prefix
config_overrides={
"optimize": True,
"mode": "token",
"ccr_inject_tool": False,
"ccr_inject_system_instructions": True,
},
)
# Use a message body with a CCR marker so system instruction injection triggers.
body = {
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": f"Compressed data was found. {_CCR_MARKER}",
}
],
}
ctx = _make_ctx(body)
decision = engine.on_request(ctx)
out = json.loads(decision.body)
# The system message should have been injected (no frozen guard).
# CCRToolInjector.inject_into_system_message prepends a system message
# when no system message exists, or injects into the existing one.
# We just confirm the call completed without error (no frozen guard raised it).
# The body is a valid dict — that is the contract.
assert isinstance(out, dict), "Engine must return a valid body even with frozen_count > 0"
# ---------------------------------------------------------------------------
# 4. No compression tracking or proactive expansion (OpenAI omits steps 3 + 4)
# ---------------------------------------------------------------------------
def test_no_compression_tracking_for_openai() -> None:
"""CCR context tracker is NOT called for OpenAI (steps 3+4 are Anthropic-only).
Even when ccr_context_tracker is set on CCRComponents and a CCR marker
is present, the engine must NOT call track_compression for the OpenAI
path (the live handler does not implement that phase).
"""
mock_tracker = MagicMock()
mock_store = MagicMock()
mock_store.get_metadata.return_value = {
"tool_name": "search_files",
"original_item_count": 100,
"compressed_item_count": 10,
"query_context": "find auth files",
"compressed_content": "auth_middleware.py\n",
}
engine = _make_engine(
ccr_context_tracker=mock_tracker,
get_compression_store=lambda: mock_store,
config_overrides={
"ccr_inject_tool": True,
"ccr_inject_system_instructions": False,
"ccr_context_tracking": True,
"ccr_proactive_expansion": False,
},
)
body = {
"model": "gpt-4o",
"messages": [
{
"role": "tool",
"content": f"Found files. {_CCR_MARKER}",
"tool_call_id": "call_search",
}
],
}
ctx = _make_ctx(body, cwd="/home/user/myproject")
engine.on_request(ctx)
# track_compression must NOT be called for OpenAI (step 3 not wired).
mock_tracker.track_compression.assert_not_called()
# analyze_query must NOT be called for OpenAI (step 4 not wired).
mock_tracker.analyze_query.assert_not_called()
# ---------------------------------------------------------------------------
# 5. Bypass gate
# ---------------------------------------------------------------------------
def test_ccr_skipped_on_bypass_header_openai() -> None:
"""x-headroom-bypass: true → CCR steps are skipped for OpenAI."""
engine = _make_engine(
config_overrides={
"ccr_inject_tool": True,
"ccr_inject_system_instructions": True,
},
)
body = {
"model": "gpt-4o",
"messages": [
{
"role": "tool",
"content": f"Marker present: {_CCR_MARKER}",
"tool_call_id": "call_z",
}
],
}
ctx = _make_ctx(body, headers={"x-headroom-bypass": "true"})
decision = engine.on_request(ctx)
out = json.loads(decision.body)
# No CCR tool must be injected under bypass.
assert out.get("tools") is None, "No CCR tool should be injected under bypass"
# ---------------------------------------------------------------------------
# 6. ccr_tool_injected reflected in telemetry
# ---------------------------------------------------------------------------
def test_ccr_fired_telemetry_true_when_tool_injected() -> None:
"""ccr_fired is True in telemetry when CCR tool is injected."""
engine = _make_engine(
config_overrides={
"ccr_inject_tool": True,
"ccr_inject_system_instructions": False,
},
)
body = {
"model": "gpt-4o",
"messages": [
{
"role": "tool",
"content": f"CCR content: {_CCR_MARKER}",
"tool_call_id": "call_t",
}
],
}
ctx = _make_ctx(body)
decision = engine.on_request(ctx)
assert decision.telemetry.ccr_fired is True, (
"telemetry.ccr_fired must be True when CCR tool was injected"
)
def test_ccr_fired_telemetry_false_when_inject_tool_disabled() -> None:
"""ccr_fired is False when ccr_inject_tool=False (tool injection disabled)."""
engine = _make_engine(
config_overrides={
"ccr_inject_tool": False,
"ccr_inject_system_instructions": False,
}
)
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Plain message, no injection."}],
}
ctx = _make_ctx(body)
decision = engine.on_request(ctx)
assert decision.telemetry.ccr_fired is False, (
"telemetry.ccr_fired must be False when ccr_inject_tool=False"
)

View file

@ -0,0 +1,511 @@
"""Structural tests for OpenAI memory injection (Chunk 5.2).
Mirrors ``test_facade_memory.py`` (Anthropic) but for the OpenAI chat path.
Key differences from the Anthropic memory tests:
- Uses ``append_text_to_latest_user_chat_message`` (OpenAI helper), which
scans backwards through all messages without frozen-count awareness.
- Does NOT skip injection in cache mode (the OpenAI handler injects in all
modes there is no is_cache_mode gate around memory).
- The engine uses ``_on_request_openai_chat`` via ``OpenAIComponents``.
- prefetched_memory_context is supplied via RequestContext (4.3-i pattern).
Running
-------
.venv/bin/python -m pytest tests/engine/test_facade_openai_memory.py -v
"""
from __future__ import annotations
import json
from typing import Any
from unittest.mock import MagicMock
import pytest
pytest.importorskip("fastapi")
# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------
_FIXED_CONTEXT = (
"## Relevant memory\n- auth_middleware.py: JWT validation\n- auth_router.py: /login route"
)
def _make_engine(
*,
memory_handler: Any | None = None,
with_memory: bool = True,
config_overrides: dict[str, Any] | None = None,
frozen_count: int = 0,
) -> Any:
"""Build a HeadroomEngine with OpenAIComponents + MemoryComponents."""
from headroom.engine.contract import Flavor, Provider
from headroom.engine.facade import HeadroomEngine, MemoryComponents, OpenAIComponents
from headroom.proxy.models import ProxyConfig
from headroom.proxy.server import HeadroomProxy
config_kwargs: dict[str, Any] = {
"optimize": True,
"mode": "token",
"cache_enabled": False,
"rate_limit_enabled": False,
"cost_tracking_enabled": False,
"log_requests": False,
"ccr_inject_tool": False,
"ccr_inject_system_instructions": False,
"ccr_handle_responses": False,
"ccr_context_tracking": False,
"ccr_proactive_expansion": False,
"image_optimize": False,
}
if config_overrides:
config_kwargs.update(config_overrides)
config = ProxyConfig(**config_kwargs)
proxy = HeadroomProxy(config)
class _FixedStore:
def compute_session_id(self, ctx: Any, model: str, msgs: Any) -> str:
return "memory-openai-structural-test-session"
def get_or_create(self, session_id: str, provider: str) -> Any:
class _T:
def get_frozen_message_count(self) -> int:
return frozen_count
def get_last_original_messages(self) -> list[Any]:
return []
def get_last_forwarded_messages(self) -> list[Any]:
return []
return _T()
def get_fresh_cache(self, session_id: str) -> Any:
class _C:
def apply_cached(self, msgs: list[Any]) -> list[Any]:
return list(msgs)
def compute_frozen_count(self, msgs: list[Any]) -> int:
return 0
def update_from_result(self, orig: Any, compr: Any) -> None:
pass
def mark_stable_from_messages(self, msgs: Any, up_to: int) -> None:
pass
return _C()
oc = OpenAIComponents(
pipeline=proxy.openai_pipeline,
provider=proxy.openai_provider,
session_tracker_store=_FixedStore(),
get_compression_cache=_FixedStore().get_fresh_cache,
config=proxy.config,
usage_reporter=None,
)
mc = None
if with_memory:
_handler = memory_handler
if _handler is None:
_handler = MagicMock()
_handler.config.inject_context = True
mc = MemoryComponents(
memory_handler=_handler,
default_user_id="test-user",
)
engine = HeadroomEngine(
pipelines={(Provider.OPENAI, Flavor.CHAT): proxy.openai_pipeline},
config=proxy.config,
usage_reporter=None,
salt=b"memory-openai-structural-test-salt",
openai_components=oc,
memory_components=mc,
)
return engine
def _make_ctx(
body: dict[str, Any],
*,
headers: dict[str, str] | None = None,
prefetched_memory_context: str | None = None,
) -> Any:
"""Build a RequestContext for OpenAI structural tests."""
from headroom.engine.contract import Flavor, Provider, RequestContext
h: dict[str, str] = {
"authorization": "Bearer sk-test-openai-key",
"content-type": "application/json",
}
if headers:
h.update(headers)
return RequestContext(
provider=Provider.OPENAI,
flavor=Flavor.CHAT,
headers_view=h,
raw_body=json.dumps(body, separators=(",", ":"), ensure_ascii=False).encode(),
session_key="memory-openai-structural",
request_id="req-openai-mem-test",
prefetched_memory_context=prefetched_memory_context,
)
# ---------------------------------------------------------------------------
# 1. Memory off → no-op (MemoryComponents=None)
# ---------------------------------------------------------------------------
def test_memory_noop_when_components_none_openai() -> None:
"""Engine without MemoryComponents is byte-identical — OpenAI golden fixtures unaffected."""
engine = _make_engine(with_memory=False)
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "hello"}],
}
ctx = _make_ctx(body, prefetched_memory_context=_FIXED_CONTEXT)
decision = engine.on_request(ctx)
out = json.loads(decision.body)
assert _FIXED_CONTEXT not in out["messages"][-1]["content"], (
"Memory context must not appear when MemoryComponents is None"
)
# ---------------------------------------------------------------------------
# 2. Byte-exact placement: string content
# ---------------------------------------------------------------------------
def test_memory_injection_string_content_openai() -> None:
"""Memory-on: context appended to latest user message (string content).
Placement rule: ``original_text + "\\n\\n" + context`` same as
``append_text_to_latest_user_chat_message`` in the handler.
"""
user_text = "How does authentication work?"
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": user_text}],
}
ctx = _make_ctx(body, prefetched_memory_context=_FIXED_CONTEXT)
engine = _make_engine()
decision = engine.on_request(ctx)
out = json.loads(decision.body)
last_msg = out["messages"][-1]
assert last_msg["role"] == "user"
content = last_msg["content"]
assert isinstance(content, str)
assert content == user_text + "\n\n" + _FIXED_CONTEXT
def test_memory_injection_list_content_openai() -> None:
"""Memory-on: context appended to first text block (list content)."""
original_text = "What is the rate limit?"
body = {
"model": "gpt-4o",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": original_text},
{"type": "text", "text": "Additional detail."},
],
}
],
}
ctx = _make_ctx(body, prefetched_memory_context=_FIXED_CONTEXT)
engine = _make_engine()
decision = engine.on_request(ctx)
out = json.loads(decision.body)
last_msg = out["messages"][-1]
blocks = last_msg["content"]
assert isinstance(blocks, list)
# First text block gets the injection.
assert blocks[0]["text"] == original_text + "\n\n" + _FIXED_CONTEXT
# Second text block unchanged.
assert blocks[1]["text"] == "Additional detail."
# ---------------------------------------------------------------------------
# 3. Cache mode: memory injection NOT skipped (OpenAI differs from Anthropic)
# ---------------------------------------------------------------------------
def test_memory_injection_not_skipped_in_cache_mode_openai() -> None:
"""OpenAI: memory injection is NOT skipped in cache mode.
The live ``handle_openai_chat`` handler has no is_cache_mode gate around
the memory injection block. The Anthropic engine skips injection in cache
mode; the OpenAI engine must NOT.
"""
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Cache mode test"}],
}
ctx = _make_ctx(body, prefetched_memory_context=_FIXED_CONTEXT)
engine = _make_engine(config_overrides={"mode": "cache"})
decision = engine.on_request(ctx)
out = json.loads(decision.body)
last_msg = out["messages"][-1]
content = last_msg.get("content", "")
assert _FIXED_CONTEXT in content, "OpenAI memory injection must NOT be skipped in cache mode"
# ---------------------------------------------------------------------------
# 4. Memory injection scans backwards (frozen-count agnostic)
# ---------------------------------------------------------------------------
def test_memory_injection_scans_backwards_to_latest_user_openai() -> None:
"""Memory is injected into the latest user message regardless of frozen count.
``append_text_to_latest_user_chat_message`` scans backwards from the end
of the message array without frozen-count awareness the OpenAI handler
does not pass a frozen count to the helper.
"""
body = {
"model": "gpt-4o",
"messages": [
{"role": "user", "content": "Turn 1 (earlier)"},
{"role": "assistant", "content": "Turn 1 answer"},
{"role": "user", "content": "Turn 2 (latest)"},
],
}
ctx = _make_ctx(body, prefetched_memory_context=_FIXED_CONTEXT)
# frozen_count=2 — but the helper ignores it; latest user turn still receives context.
engine = _make_engine(frozen_count=2)
decision = engine.on_request(ctx)
out = json.loads(decision.body)
msgs = out["messages"]
# Latest (last) user turn must have context injected.
assert msgs[-1]["role"] == "user"
assert _FIXED_CONTEXT in msgs[-1]["content"]
# Earlier user turn must be unchanged.
assert _FIXED_CONTEXT not in msgs[0]["content"]
# ---------------------------------------------------------------------------
# 5. Empty / None prefetched context → no-op
# ---------------------------------------------------------------------------
def test_memory_injection_skipped_on_empty_context_openai() -> None:
"""Empty prefetched_memory_context → no injection."""
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello"}],
}
ctx = _make_ctx(body, prefetched_memory_context="")
engine = _make_engine()
decision = engine.on_request(ctx)
out = json.loads(decision.body)
assert out["messages"][-1]["content"] == "Hello"
def test_memory_injection_skipped_on_none_context_openai() -> None:
"""None prefetched_memory_context → no injection."""
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello"}],
}
ctx = _make_ctx(body, prefetched_memory_context=None)
engine = _make_engine()
decision = engine.on_request(ctx)
out = json.loads(decision.body)
assert out["messages"][-1]["content"] == "Hello"
# ---------------------------------------------------------------------------
# 6. inject_context=False gate
# ---------------------------------------------------------------------------
def test_memory_injection_skipped_when_inject_context_false_openai() -> None:
"""memory_handler.config.inject_context=False → injection skipped."""
handler_mock = MagicMock()
handler_mock.config.inject_context = False
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "No inject_context test"}],
}
ctx = _make_ctx(body, prefetched_memory_context=_FIXED_CONTEXT)
engine = _make_engine(memory_handler=handler_mock)
decision = engine.on_request(ctx)
out = json.loads(decision.body)
assert _FIXED_CONTEXT not in out["messages"][-1]["content"]
# ---------------------------------------------------------------------------
# 7. Bypass gate
# ---------------------------------------------------------------------------
def test_memory_injection_skipped_on_bypass_openai() -> None:
"""x-headroom-bypass: true → memory injection is skipped."""
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Bypass test"}],
}
ctx = _make_ctx(
body,
headers={"x-headroom-bypass": "true"},
prefetched_memory_context=_FIXED_CONTEXT,
)
engine = _make_engine()
decision = engine.on_request(ctx)
assert _FIXED_CONTEXT.encode() not in decision.body, (
"Memory context must not appear in body under bypass"
)
# ---------------------------------------------------------------------------
# 8. No user_id → MemoryDecision gate fails
# ---------------------------------------------------------------------------
def test_memory_injection_skipped_when_no_user_id_openai() -> None:
"""No user_id → MemoryDecision.inject=False → no injection.
Supply MemoryComponents with default_user_id="" and no x-headroom-user-id
header. MemoryDecision.decide gates on memory_user_id being non-empty.
"""
from headroom.engine.contract import Flavor, Provider
from headroom.engine.facade import HeadroomEngine, MemoryComponents, OpenAIComponents
from headroom.proxy.models import ProxyConfig
from headroom.proxy.server import HeadroomProxy
config = ProxyConfig(
optimize=True,
mode="token",
cache_enabled=False,
rate_limit_enabled=False,
cost_tracking_enabled=False,
log_requests=False,
image_optimize=False,
)
proxy = HeadroomProxy(config)
class _S:
def compute_session_id(self, *a: Any, **kw: Any) -> str:
return "s"
def get_or_create(self, *a: Any, **kw: Any) -> Any:
class _T:
def get_frozen_message_count(self) -> int:
return 0
def get_last_original_messages(self) -> list:
return []
def get_last_forwarded_messages(self) -> list:
return []
return _T()
def get_fresh_cache(self, sid: str) -> Any:
class _C:
def apply_cached(self, m: list) -> list:
return list(m)
def compute_frozen_count(self, m: list) -> int:
return 0
def update_from_result(self, *a: Any) -> None:
pass
def mark_stable_from_messages(self, *a: Any) -> None:
pass
return _C()
handler_mock = MagicMock()
handler_mock.config.inject_context = True
oc = OpenAIComponents(
pipeline=proxy.openai_pipeline,
provider=proxy.openai_provider,
session_tracker_store=_S(),
get_compression_cache=_S().get_fresh_cache,
config=proxy.config,
usage_reporter=None,
)
mc = MemoryComponents(
memory_handler=handler_mock,
default_user_id="", # empty → gate fails
)
engine = HeadroomEngine(
pipelines={(Provider.OPENAI, Flavor.CHAT): proxy.openai_pipeline},
config=proxy.config,
usage_reporter=None,
salt=b"s",
openai_components=oc,
memory_components=mc,
)
body = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "No user_id test"}],
}
# No x-headroom-user-id header and default_user_id="" → gate fails.
from headroom.engine.contract import RequestContext
ctx = RequestContext(
provider=Provider.OPENAI,
flavor=Flavor.CHAT,
headers_view={"authorization": "Bearer sk-test"},
raw_body=json.dumps(body, separators=(",", ":")).encode(),
session_key="s",
request_id="",
prefetched_memory_context=_FIXED_CONTEXT,
)
decision = engine.on_request(ctx)
out = json.loads(decision.body)
assert _FIXED_CONTEXT not in out["messages"][-1]["content"]
# ---------------------------------------------------------------------------
# 9. Multi-turn body: context lands in latest user message (not assistant)
# ---------------------------------------------------------------------------
def test_memory_injection_skips_assistant_tail_openai() -> None:
"""Memory injection finds the last user message even when last message is assistant."""
body = {
"model": "gpt-4o",
"messages": [
{"role": "user", "content": "Turn 1"},
{"role": "assistant", "content": "Ack"},
],
}
ctx = _make_ctx(body, prefetched_memory_context=_FIXED_CONTEXT)
engine = _make_engine()
decision = engine.on_request(ctx)
out = json.loads(decision.body)
msgs = out["messages"]
# Last message is assistant — the helper scans back and finds the user turn.
assert msgs[-2]["role"] == "user"
assert _FIXED_CONTEXT in msgs[-2]["content"]
# Assistant message is unchanged.
assert _FIXED_CONTEXT not in msgs[-1]["content"]