Skip to content

Commit 77cb23f

Browse files
fix(event_loop): handle MetadataEvents without optional usage and metrics (#1187)
1 parent ded0934 commit 77cb23f

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

src/strands/event_loop/streaming.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,11 @@ def extract_usage_metrics(event: MetadataEvent, time_to_first_byte_ms: int | Non
350350
Returns:
351351
The extracted usage metrics and latency.
352352
"""
353-
usage = Usage(**event["usage"])
354-
metrics = Metrics(**event["metrics"])
353+
# MetadataEvent has total=False, making all fields optional, but Usage and Metrics types
354+
# have Required fields. Provide defaults to handle cases where custom models don't
355+
# provide usage/metrics (e.g., when latency info is unavailable).
356+
usage = Usage(**{"inputTokens": 0, "outputTokens": 0, "totalTokens": 0, **event.get("usage", {})})
357+
metrics = Metrics(**{"latencyMs": 0, **event.get("metrics", {})})
355358
if time_to_first_byte_ms:
356359
metrics["timeToFirstByteMs"] = time_to_first_byte_ms
357360

tests/strands/event_loop/test_streaming.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,43 @@ def test_extract_usage_metrics_with_cache_tokens():
421421
assert tru_usage == exp_usage and tru_metrics == exp_metrics
422422

423423

424+
def test_extract_usage_metrics_without_metrics():
425+
"""Test extract_usage_metrics when metrics field is missing."""
426+
event = {
427+
"usage": {"inputTokens": 5, "outputTokens": 2, "totalTokens": 7},
428+
}
429+
430+
tru_usage, tru_metrics = strands.event_loop.streaming.extract_usage_metrics(event)
431+
exp_usage = {"inputTokens": 5, "outputTokens": 2, "totalTokens": 7}
432+
exp_metrics = {"latencyMs": 0}
433+
434+
assert tru_usage == exp_usage and tru_metrics == exp_metrics
435+
436+
437+
def test_extract_usage_metrics_without_usage():
438+
"""Test extract_usage_metrics when usage field is missing."""
439+
event = {
440+
"metrics": {"latencyMs": 100},
441+
}
442+
443+
tru_usage, tru_metrics = strands.event_loop.streaming.extract_usage_metrics(event)
444+
exp_usage = {"inputTokens": 0, "outputTokens": 0, "totalTokens": 0}
445+
exp_metrics = {"latencyMs": 100}
446+
447+
assert tru_usage == exp_usage and tru_metrics == exp_metrics
448+
449+
450+
def test_extract_usage_metrics_empty_metadata():
451+
"""Test extract_usage_metrics when both fields are missing."""
452+
event = {}
453+
454+
tru_usage, tru_metrics = strands.event_loop.streaming.extract_usage_metrics(event)
455+
exp_usage = {"inputTokens": 0, "outputTokens": 0, "totalTokens": 0}
456+
exp_metrics = {"latencyMs": 0}
457+
458+
assert tru_usage == exp_usage and tru_metrics == exp_metrics
459+
460+
424461
@pytest.mark.parametrize(
425462
("response", "exp_events"),
426463
[

0 commit comments

Comments
 (0)