mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
185 lines
5.7 KiB
Python
185 lines
5.7 KiB
Python
|
|
"""Regression tests for web-tool result passthrough."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from headroom.config import DEFAULT_EXCLUDE_TOOLS
|
||
|
|
from headroom.proxy.server import HeadroomProxy, ProxyConfig
|
||
|
|
from headroom.transforms.content_detector import ContentType
|
||
|
|
from headroom.transforms.content_router import (
|
||
|
|
CompressionStrategy,
|
||
|
|
ContentRouter,
|
||
|
|
RouterCompressionResult,
|
||
|
|
RoutingDecision,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class _Tokenizer:
|
||
|
|
def count_text(self, text: str) -> int:
|
||
|
|
return max(1, len(text) // 4)
|
||
|
|
|
||
|
|
|
||
|
|
def _messages(tool_name: str, payload: str) -> list[dict[str, object]]:
|
||
|
|
return [
|
||
|
|
{
|
||
|
|
"role": "assistant",
|
||
|
|
"content": [
|
||
|
|
{
|
||
|
|
"type": "tool_use",
|
||
|
|
"id": "tool-1",
|
||
|
|
"name": tool_name,
|
||
|
|
"input": {},
|
||
|
|
}
|
||
|
|
],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"role": "user",
|
||
|
|
"content": [
|
||
|
|
{
|
||
|
|
"type": "tool_result",
|
||
|
|
"tool_use_id": "tool-1",
|
||
|
|
"content": payload,
|
||
|
|
}
|
||
|
|
],
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def _router() -> ContentRouter:
|
||
|
|
proxy = HeadroomProxy(
|
||
|
|
ProxyConfig(
|
||
|
|
optimize=False,
|
||
|
|
cache_enabled=False,
|
||
|
|
rate_limit_enabled=False,
|
||
|
|
cost_tracking_enabled=False,
|
||
|
|
code_aware_enabled=False,
|
||
|
|
mode="token",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
router = proxy.anthropic_pipeline.transforms[-1]
|
||
|
|
assert isinstance(router, ContentRouter)
|
||
|
|
router.config.min_section_tokens = 1
|
||
|
|
router.config.min_chars_for_block_compression = 1
|
||
|
|
return router
|
||
|
|
|
||
|
|
|
||
|
|
def test_web_tools_are_default_exclusions() -> None:
|
||
|
|
assert {"WebSearch", "WebFetch", "web_search", "web_fetch"} <= DEFAULT_EXCLUDE_TOOLS
|
||
|
|
|
||
|
|
|
||
|
|
def test_web_tool_results_bypass_compressor() -> None:
|
||
|
|
router = _router()
|
||
|
|
calls = 0
|
||
|
|
|
||
|
|
def fake_compress(*args: object, **kwargs: object) -> RouterCompressionResult:
|
||
|
|
nonlocal calls
|
||
|
|
calls += 1
|
||
|
|
content = str(args[0])
|
||
|
|
return RouterCompressionResult(
|
||
|
|
compressed="mutated",
|
||
|
|
original=content,
|
||
|
|
strategy_used=CompressionStrategy.TEXT,
|
||
|
|
routing_log=[
|
||
|
|
RoutingDecision(ContentType.PLAIN_TEXT, CompressionStrategy.TEXT, 100, 10)
|
||
|
|
],
|
||
|
|
)
|
||
|
|
|
||
|
|
router.compress = fake_compress # type: ignore[method-assign]
|
||
|
|
payload = (
|
||
|
|
"{\n"
|
||
|
|
' "results": [\n'
|
||
|
|
' {"title": "Headroom", "snippet": "reference payload reference payload reference payload"},\n'
|
||
|
|
' {"title": "Docs", "snippet": "structured web payload with spacing that must remain verbatim"}\n'
|
||
|
|
" ],\n"
|
||
|
|
' "source": "web"\n'
|
||
|
|
"}"
|
||
|
|
)
|
||
|
|
|
||
|
|
for tool_name in ("WebSearch", "WebFetch", "web_search", "web_fetch"):
|
||
|
|
messages = _messages(tool_name, payload)
|
||
|
|
result = router.apply(messages, _Tokenizer())
|
||
|
|
|
||
|
|
tool_result = result.messages[1]["content"][0] # type: ignore[index]
|
||
|
|
assert tool_result["content"] == payload # type: ignore[index]
|
||
|
|
assert "router:excluded:tool" in result.transforms_applied
|
||
|
|
|
||
|
|
assert calls == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_web_tool_results_stay_verbatim_outside_token_age_window() -> None:
|
||
|
|
router = _router()
|
||
|
|
calls = 0
|
||
|
|
|
||
|
|
def fake_compress(*args: object, **kwargs: object) -> RouterCompressionResult:
|
||
|
|
nonlocal calls
|
||
|
|
calls += 1
|
||
|
|
content = str(args[0])
|
||
|
|
return RouterCompressionResult(
|
||
|
|
compressed="mutated",
|
||
|
|
original=content,
|
||
|
|
strategy_used=CompressionStrategy.TEXT,
|
||
|
|
routing_log=[
|
||
|
|
RoutingDecision(ContentType.PLAIN_TEXT, CompressionStrategy.TEXT, 100, 10)
|
||
|
|
],
|
||
|
|
)
|
||
|
|
|
||
|
|
router.compress = fake_compress # type: ignore[method-assign]
|
||
|
|
payload = (
|
||
|
|
"{\n"
|
||
|
|
' "results": [\n'
|
||
|
|
' {"title": "Headroom", "snippet": "reference payload reference payload reference payload"}\n'
|
||
|
|
" ]\n"
|
||
|
|
"}"
|
||
|
|
)
|
||
|
|
messages = _messages("WebSearch", payload)
|
||
|
|
messages.extend({"role": "user", "content": f"later turn {i}"} for i in range(18))
|
||
|
|
|
||
|
|
result = router.apply(messages, _Tokenizer())
|
||
|
|
tool_result = result.messages[1]["content"][0] # type: ignore[index]
|
||
|
|
|
||
|
|
assert tool_result["content"] == payload # type: ignore[index]
|
||
|
|
assert calls == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_web_tool_results_skip_cross_turn_dedup() -> None:
|
||
|
|
router = _router()
|
||
|
|
payload = (
|
||
|
|
"{\n"
|
||
|
|
' "results": [\n'
|
||
|
|
' {"title": "Headroom", "snippet": "structured web payload with spacing that must remain verbatim"}\n'
|
||
|
|
" ]\n"
|
||
|
|
"}"
|
||
|
|
)
|
||
|
|
messages = _messages("WebSearch", payload) + _messages("WebSearch", payload)
|
||
|
|
|
||
|
|
result = router.apply(messages, _Tokenizer())
|
||
|
|
|
||
|
|
first = result.messages[1]["content"][0] # type: ignore[index]
|
||
|
|
second = result.messages[3]["content"][0] # type: ignore[index]
|
||
|
|
assert first["content"] == payload # type: ignore[index]
|
||
|
|
assert second["content"] == payload # type: ignore[index]
|
||
|
|
|
||
|
|
|
||
|
|
def test_bash_remains_compressible() -> None:
|
||
|
|
router = _router()
|
||
|
|
calls = 0
|
||
|
|
|
||
|
|
def fake_compress(*args: object, **kwargs: object) -> RouterCompressionResult:
|
||
|
|
nonlocal calls
|
||
|
|
calls += 1
|
||
|
|
content = str(args[0])
|
||
|
|
return RouterCompressionResult(
|
||
|
|
compressed="compressed bash output",
|
||
|
|
original=content,
|
||
|
|
strategy_used=CompressionStrategy.TEXT,
|
||
|
|
routing_log=[
|
||
|
|
RoutingDecision(ContentType.PLAIN_TEXT, CompressionStrategy.TEXT, 100, 10)
|
||
|
|
],
|
||
|
|
)
|
||
|
|
|
||
|
|
router.compress = fake_compress # type: ignore[method-assign]
|
||
|
|
payload = "bash output " * 100
|
||
|
|
result = router.apply(_messages("Bash", payload), _Tokenizer())
|
||
|
|
|
||
|
|
assert calls == 1
|
||
|
|
assert "router:excluded:tool" not in result.transforms_applied
|