Skip to content

Commit 9ff32d5

Browse files
committed
feat(context): track agent.messages token size
1 parent 0a723bc commit 9ff32d5

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

src/strands/telemetry/metrics.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,20 @@ class EventLoopMetrics:
202202
accumulated_usage: Usage = field(default_factory=lambda: Usage(inputTokens=0, outputTokens=0, totalTokens=0))
203203
accumulated_metrics: Metrics = field(default_factory=lambda: Metrics(latencyMs=0))
204204

205+
@property
206+
def latest_context_tokens(self) -> int:
207+
"""Most recent context size from the last LLM call.
208+
209+
Returns the inputTokens from the most recent cycle of the most recent
210+
invocation. This represents the current context size as reported by the model.
211+
212+
Returns:
213+
The input token count from the most recent cycle, or 0 if no cycles exist.
214+
"""
215+
if self.agent_invocations and self.agent_invocations[-1].cycles:
216+
return self.agent_invocations[-1].cycles[-1].usage.get("inputTokens", 0)
217+
return 0
218+
205219
@property
206220
def _metrics_client(self) -> "MetricsClient":
207221
"""Get the singleton MetricsClient instance."""

tests/strands/telemetry/test_metrics.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,3 +566,50 @@ def test_reset_usage_metrics(usage, event_loop_metrics, mock_get_meter_provider)
566566

567567
# Verify accumulated_usage is NOT cleared
568568
assert event_loop_metrics.accumulated_usage["inputTokens"] == 11
569+
570+
571+
def test_latest_context_tokens_no_invocations(event_loop_metrics):
572+
assert event_loop_metrics.latest_context_tokens == 0
573+
574+
575+
def test_latest_context_tokens_invocation_with_no_cycles(event_loop_metrics):
576+
event_loop_metrics.reset_usage_metrics()
577+
assert event_loop_metrics.latest_context_tokens == 0
578+
579+
580+
def test_latest_context_tokens_returns_last_cycle(event_loop_metrics, mock_get_meter_provider):
581+
event_loop_metrics.reset_usage_metrics()
582+
event_loop_metrics.start_cycle(attributes={"event_loop_cycle_id": "c1"})
583+
event_loop_metrics.update_usage(Usage(inputTokens=100, outputTokens=50, totalTokens=150))
584+
585+
event_loop_metrics.start_cycle(attributes={"event_loop_cycle_id": "c2"})
586+
event_loop_metrics.update_usage(Usage(inputTokens=250, outputTokens=80, totalTokens=330))
587+
588+
assert event_loop_metrics.latest_context_tokens == 250
589+
590+
591+
def test_latest_context_tokens_returns_from_latest_invocation(event_loop_metrics, mock_get_meter_provider):
592+
# First invocation
593+
event_loop_metrics.reset_usage_metrics()
594+
event_loop_metrics.start_cycle(attributes={"event_loop_cycle_id": "c1"})
595+
event_loop_metrics.update_usage(Usage(inputTokens=100, outputTokens=50, totalTokens=150))
596+
597+
# Second invocation
598+
event_loop_metrics.reset_usage_metrics()
599+
event_loop_metrics.start_cycle(attributes={"event_loop_cycle_id": "c2"})
600+
event_loop_metrics.update_usage(Usage(inputTokens=500, outputTokens=80, totalTokens=580))
601+
602+
assert event_loop_metrics.latest_context_tokens == 500
603+
604+
605+
def test_latest_context_tokens_missing_input_tokens_key(event_loop_metrics):
606+
"""Returns 0 when usage dict is missing inputTokens (e.g. provider bug)."""
607+
event_loop_metrics.reset_usage_metrics()
608+
invocation = event_loop_metrics.agent_invocations[-1]
609+
invocation.cycles.append(
610+
strands.telemetry.metrics.EventLoopCycleMetric(
611+
event_loop_cycle_id="c1",
612+
usage={"outputTokens": 50, "totalTokens": 50},
613+
)
614+
)
615+
assert event_loop_metrics.latest_context_tokens == 0

0 commit comments

Comments
 (0)