Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion raven/agent/loop/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2012,7 +2012,12 @@ async def _run_agent_loop(
usage_sink["cost_usd"] = usage_snapshot.estimated_cost_usd
usage_sink["context_max"] = context_max
usage_sink["context_used"] = context_used
usage_sink["context_percent"] = round(100 * context_used / context_max) if context_max else 0
# context_max is a real provider window when resolve_context_window()
# succeeds, but otherwise a guessed default (self.context_window_tokens)
# that can be smaller than the model's true window. Clamp so the gauge
# never reports past full: an unresolved model must not render as
# "over budget" while the request is still well within its real limit.
usage_sink["context_percent"] = min(100, round(100 * context_used / context_max)) if context_max else 0

# Context-window overflow recovery: the structured classifier flags
# should_compress (a smaller window won't help, but eliding the bulk
Expand Down
28 changes: 28 additions & 0 deletions tests/test_agent_loop_usage_sink.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,31 @@ def test_refresh_context_window_on_an_openrouter_model_never_touches_the_network

assert agent.context_window_tokens == rates.DEFAULT_CONTEXT_WINDOW_TOKENS
assert counter["calls"] == 0


@pytest.mark.asyncio
async def test_usage_sink_context_percent_clamps_when_window_is_a_guess(workspace):
"""Usage past the configured fallback window must not render past 100%.

``stub`` never resolves via resolve_context_window(), so context_max here is
the configured (guessed) default. Real usage can legitimately exceed that
guess while staying under the model's true, unresolved window, so the
reported percent must clamp rather than imply an impossible over-budget bar.
"""
provider = UsageProvider("stub", prompt_tokens=6000, completion_tokens=2000)
agent = _make_agent(workspace, provider, model="stub", window=1000)
sink: dict = {}

await agent._process_message(
TurnRequest(
origin=Origin.USER,
source=Source(channel="test", chat_id="c1", sender_id="user", chat_type=ChatType.DM),
text="hi",
),
session_key="s1",
usage_sink=sink,
)

assert sink["context_max"] == 1000
assert sink["context_used"] == 8000
assert sink["context_percent"] == 100