diff --git a/raven/agent/loop/main.py b/raven/agent/loop/main.py index 3c097cf6..e6a3915e 100644 --- a/raven/agent/loop/main.py +++ b/raven/agent/loop/main.py @@ -1551,7 +1551,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 diff --git a/tests/test_agent_loop_usage_sink.py b/tests/test_agent_loop_usage_sink.py index 6bd90c08..5ace052d 100644 --- a/tests/test_agent_loop_usage_sink.py +++ b/tests/test_agent_loop_usage_sink.py @@ -145,3 +145,31 @@ def client_factory(*args, **kwargs): assert sink["context_max"] == 163840 assert sink["context_used"] == 1500 + + +@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