207 lines
5.7 KiB
Python
207 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from opensquilla.result_budget import (
|
|
ToolRunBudgetExceededError,
|
|
ToolRunBudgetPolicy,
|
|
ToolRunBudgetTracker,
|
|
clamp_tool_arguments,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_web_search_counts_as_external_search() -> None:
|
|
tracker = ToolRunBudgetTracker()
|
|
|
|
reservation = await tracker.reserve_tool_call(
|
|
tool_name="web_search",
|
|
arguments={"query": "python release", "max_results": 10, "fetch_top_k": 3},
|
|
)
|
|
await tracker.commit_tool_result(reservation, "x" * 100)
|
|
snapshot = await tracker.snapshot()
|
|
|
|
assert reservation.counted_as_search is True
|
|
assert reservation.counted_as_fetch is False
|
|
assert reservation.counted_as_external_text is True
|
|
assert snapshot["web_search_calls_used"] == 1
|
|
assert snapshot["web_fetch_calls_used"] == 0
|
|
assert snapshot["external_text_chars_used"] == 100
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_web_discover_counts_as_external_search_budget() -> None:
|
|
tracker = ToolRunBudgetTracker(ToolRunBudgetPolicy(max_web_search_calls_per_turn=1))
|
|
|
|
reservation = await tracker.reserve_tool_call(
|
|
tool_name="web_discover",
|
|
arguments={"query": "python release", "max_results": 5},
|
|
)
|
|
snapshot = await tracker.snapshot()
|
|
|
|
assert reservation.counted_as_search is True
|
|
assert reservation.counted_as_fetch is False
|
|
assert snapshot["web_search_calls_used"] == 1
|
|
|
|
with pytest.raises(ToolRunBudgetExceededError) as exc_info:
|
|
await tracker.reserve_tool_call(
|
|
tool_name="web_search",
|
|
arguments={"query": "another search"},
|
|
)
|
|
|
|
assert exc_info.value.tool_name == "web_search"
|
|
|
|
|
|
def test_web_clamps_leave_bool_arguments_for_validation() -> None:
|
|
search_args = {
|
|
"query": "q",
|
|
"max_results": True,
|
|
"fetch_top_k": True,
|
|
"max_chars_per_source": False,
|
|
}
|
|
discover_args = {"query": "q", "max_results": True}
|
|
fetch_args = {"url": "https://example.com", "max_chars": False}
|
|
|
|
assert (
|
|
clamp_tool_arguments(
|
|
"web_search",
|
|
search_args,
|
|
ToolRunBudgetPolicy(max_web_search_results=8),
|
|
)
|
|
== search_args
|
|
)
|
|
assert (
|
|
clamp_tool_arguments(
|
|
"web_discover",
|
|
discover_args,
|
|
ToolRunBudgetPolicy(max_web_search_results=8),
|
|
)
|
|
== discover_args
|
|
)
|
|
assert (
|
|
clamp_tool_arguments(
|
|
"web_fetch",
|
|
fetch_args,
|
|
ToolRunBudgetPolicy(max_single_fetch_chars=900),
|
|
)
|
|
== fetch_args
|
|
)
|
|
|
|
|
|
def test_web_search_clamps_source_backed_arguments() -> None:
|
|
clamped = clamp_tool_arguments(
|
|
"web_search",
|
|
{
|
|
"query": "q",
|
|
"max_results": 1000,
|
|
"fetch_top_k": 1000,
|
|
"max_chars_per_source": 1000,
|
|
},
|
|
ToolRunBudgetPolicy(
|
|
max_web_search_results=8,
|
|
max_web_search_fetch_top_k=2,
|
|
max_web_search_chars_per_source=900,
|
|
),
|
|
)
|
|
|
|
assert clamped == {
|
|
"query": "q",
|
|
"max_results": 8,
|
|
"fetch_top_k": 2,
|
|
"max_chars_per_source": 900,
|
|
}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_loop_guard_blocks_repeated_identical_web_search() -> None:
|
|
tracker = ToolRunBudgetTracker(
|
|
ToolRunBudgetPolicy(max_repeated_retrievals_per_turn=2)
|
|
)
|
|
|
|
await tracker.reserve_tool_call(
|
|
tool_name="web_search",
|
|
arguments={
|
|
"query": " Python Release ",
|
|
"provider": "tavily",
|
|
"mode": "auto",
|
|
},
|
|
)
|
|
await tracker.reserve_tool_call(
|
|
tool_name="web_search",
|
|
arguments={"query": "python release", "provider": "tavily", "mode": "auto"},
|
|
)
|
|
|
|
with pytest.raises(ToolRunBudgetExceededError) as exc_info:
|
|
await tracker.reserve_tool_call(
|
|
tool_name="web_search",
|
|
arguments={
|
|
"query": "PYTHON RELEASE",
|
|
"provider": "tavily",
|
|
"mode": "auto",
|
|
},
|
|
)
|
|
|
|
assert exc_info.value.tool_name == "web_search"
|
|
assert "repeated retrieval" in str(exc_info.value).lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_loop_guard_counts_web_search_repeated_queries_too() -> None:
|
|
tracker = ToolRunBudgetTracker(
|
|
ToolRunBudgetPolicy(max_repeated_retrievals_per_turn=1)
|
|
)
|
|
|
|
await tracker.reserve_tool_call(
|
|
tool_name="web_search",
|
|
arguments={"query": "OpenSquilla"},
|
|
)
|
|
|
|
with pytest.raises(ToolRunBudgetExceededError):
|
|
await tracker.reserve_tool_call(
|
|
tool_name="web_search",
|
|
arguments={"query": " opensquilla "},
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_loop_guard_counts_web_discover_repeated_queries_too() -> None:
|
|
tracker = ToolRunBudgetTracker(
|
|
ToolRunBudgetPolicy(max_repeated_retrievals_per_turn=1)
|
|
)
|
|
|
|
await tracker.reserve_tool_call(
|
|
tool_name="web_discover",
|
|
arguments={"query": "OpenSquilla"},
|
|
)
|
|
|
|
with pytest.raises(ToolRunBudgetExceededError):
|
|
await tracker.reserve_tool_call(
|
|
tool_name="web_discover",
|
|
arguments={"query": " opensquilla "},
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_loop_guard_snapshot_exposes_counts() -> None:
|
|
tracker = ToolRunBudgetTracker()
|
|
|
|
await tracker.reserve_tool_call(
|
|
tool_name="web_search",
|
|
arguments={
|
|
"query": "Python Release",
|
|
"provider": "tavily",
|
|
"mode": "news",
|
|
},
|
|
)
|
|
snapshot = await tracker.snapshot()
|
|
|
|
assert snapshot["retrieval_loop_guard"] == [
|
|
{
|
|
"tool_name": "web_search",
|
|
"query": "python release",
|
|
"provider": "tavily",
|
|
"mode": "news",
|
|
"count": 1,
|
|
}
|
|
]
|