Skip to content

Commit 8ecd03b

Browse files
committed
Refactor RequestUsage to use flexible metadata dict instead of specific fields
1 parent cfa7c1e commit 8ecd03b

File tree

4 files changed

+129
-50
lines changed

4 files changed

+129
-50
lines changed

docs/usage.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ The SDK automatically tracks usage for each API request in `request_usage_entrie
5454
```python
5555
result = await Runner.run(agent, "What's the weather in Tokyo?")
5656

57-
for request in enumerate(result.context_wrapper.usage.request_usage_entries):
58-
print(f"Request {i + 1}: Model={request.model_name}, Agent={request.agent_name}, Input={request.input_tokens} tokens, Output={request.output_tokens} tokens")
57+
for i, request in enumerate(result.context_wrapper.usage.request_usage_entries):
58+
print(f"Request {i + 1}: Input={request.input_tokens} tokens, Output={request.output_tokens} tokens, metadata={request.metadata}")
5959
```
6060

6161
## Accessing usage with sessions

src/agents/run.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,9 +1404,11 @@ async def _run_single_turn_streamed(
14041404
)
14051405
context_wrapper.usage.add(
14061406
usage,
1407-
model_name=model.model,
1408-
agent_name=agent.name,
1409-
response_id=event.response.id,
1407+
metadata={
1408+
"model_name": model.model,
1409+
"agent_name": agent.name,
1410+
"response_id": event.response.id,
1411+
},
14101412
)
14111413

14121414
if isinstance(event, ResponseOutputItemDoneEvent):
@@ -1826,9 +1828,11 @@ async def _get_new_response(
18261828

18271829
context_wrapper.usage.add(
18281830
new_response.usage,
1829-
model_name=model.model,
1830-
agent_name=agent.name,
1831-
response_id=new_response.response_id,
1831+
metadata={
1832+
"model_name": model.model,
1833+
"agent_name": agent.name,
1834+
"response_id": new_response.response_id,
1835+
},
18321836
)
18331837

18341838
# If we have run hooks, or if the agent has hooks, we need to call them after the LLM call

src/agents/usage.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from dataclasses import field
4+
from typing import Any
45

56
from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails
67
from pydantic.dataclasses import dataclass
@@ -25,14 +26,8 @@ class RequestUsage:
2526
output_tokens_details: OutputTokensDetails
2627
"""Details about the output tokens for this individual request."""
2728

28-
model_name: str
29-
"""The model name used for this request."""
30-
31-
agent_name: str
32-
"""The agent name that made this request."""
33-
34-
response_id: str | None = None
35-
"""The response ID for this request (i.e. ModelResponse.response_id)."""
29+
metadata: dict[str, Any] = field(default_factory=dict)
30+
"""Additional metadata for this request (e.g., model_name, agent_name, response_id)."""
3631

3732

3833
@dataclass
@@ -84,19 +79,15 @@ def __post_init__(self) -> None:
8479
def add(
8580
self,
8681
other: Usage,
87-
model_name: str,
88-
agent_name: str,
89-
response_id: str | None = None,
82+
metadata: dict[str, Any] | None = None,
9083
) -> None:
9184
"""Add another Usage object to this one, aggregating all fields.
9285
9386
This method automatically preserves request_usage_entries.
9487
9588
Args:
9689
other: The Usage object to add to this one.
97-
model_name: The model name used for this request.
98-
agent_name: The agent name that made this request.
99-
response_id: The response ID for this request.
90+
metadata: Additional metadata for this request
10091
"""
10192
self.requests += other.requests if other.requests else 0
10293
self.input_tokens += other.input_tokens if other.input_tokens else 0
@@ -121,9 +112,7 @@ def add(
121112
total_tokens=other.total_tokens,
122113
input_tokens_details=other.input_tokens_details,
123114
output_tokens_details=other.output_tokens_details,
124-
model_name=model_name,
125-
agent_name=agent_name,
126-
response_id=response_id,
115+
metadata=metadata or {},
127116
)
128117
self.request_usage_entries.append(request_usage)
129118
elif other.request_usage_entries:

tests/test_usage.py

Lines changed: 111 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ def test_usage_add_aggregates_all_fields():
2121
total_tokens=15,
2222
)
2323

24-
u1.add(u2, model_name="gpt-5", agent_name="test-agent", response_id="resp-1")
24+
u1.add(
25+
u2,
26+
metadata={
27+
"model_name": "gpt-5",
28+
"agent_name": "test-agent",
29+
"response_id": "resp-1",
30+
},
31+
)
2532

2633
assert u1.requests == 3
2734
assert u1.input_tokens == 17
@@ -42,7 +49,14 @@ def test_usage_add_aggregates_with_none_values():
4249
total_tokens=15,
4350
)
4451

45-
u1.add(u2, model_name="gpt-5", agent_name="test-agent", response_id="resp-1")
52+
u1.add(
53+
u2,
54+
metadata={
55+
"model_name": "gpt-5",
56+
"agent_name": "test-agent",
57+
"response_id": "resp-1",
58+
},
59+
)
4660

4761
assert u1.requests == 2
4862
assert u1.input_tokens == 7
@@ -60,19 +74,21 @@ def test_request_usage_creation():
6074
total_tokens=300,
6175
input_tokens_details=InputTokensDetails(cached_tokens=10),
6276
output_tokens_details=OutputTokensDetails(reasoning_tokens=20),
63-
model_name="gpt-5",
64-
agent_name="test-agent",
65-
response_id="resp-123",
77+
metadata={
78+
"model_name": "gpt-5",
79+
"agent_name": "test-agent",
80+
"response_id": "resp-123",
81+
},
6682
)
6783

6884
assert request_usage.input_tokens == 100
6985
assert request_usage.output_tokens == 200
7086
assert request_usage.total_tokens == 300
7187
assert request_usage.input_tokens_details.cached_tokens == 10
7288
assert request_usage.output_tokens_details.reasoning_tokens == 20
73-
assert request_usage.model_name == "gpt-5"
74-
assert request_usage.agent_name == "test-agent"
75-
assert request_usage.response_id == "resp-123"
89+
assert request_usage.metadata["model_name"] == "gpt-5"
90+
assert request_usage.metadata["agent_name"] == "test-agent"
91+
assert request_usage.metadata["response_id"] == "resp-123"
7692

7793

7894
def test_usage_add_preserves_single_request():
@@ -87,7 +103,14 @@ def test_usage_add_preserves_single_request():
87103
total_tokens=300,
88104
)
89105

90-
u1.add(u2, model_name="gpt-5", agent_name="test-agent", response_id="resp-1")
106+
u1.add(
107+
u2,
108+
metadata={
109+
"model_name": "gpt-5",
110+
"agent_name": "test-agent",
111+
"response_id": "resp-1",
112+
},
113+
)
91114

92115
# Should preserve the request usage details
93116
assert len(u1.request_usage_entries) == 1
@@ -97,9 +120,9 @@ def test_usage_add_preserves_single_request():
97120
assert request_usage.total_tokens == 300
98121
assert request_usage.input_tokens_details.cached_tokens == 10
99122
assert request_usage.output_tokens_details.reasoning_tokens == 20
100-
assert request_usage.model_name == "gpt-5"
101-
assert request_usage.agent_name == "test-agent"
102-
assert request_usage.response_id == "resp-1"
123+
assert request_usage.metadata["model_name"] == "gpt-5"
124+
assert request_usage.metadata["agent_name"] == "test-agent"
125+
assert request_usage.metadata["response_id"] == "resp-1"
103126

104127

105128
def test_usage_add_ignores_zero_token_requests():
@@ -114,7 +137,14 @@ def test_usage_add_ignores_zero_token_requests():
114137
total_tokens=0,
115138
)
116139

117-
u1.add(u2, model_name="gpt-5", agent_name="test-agent", response_id="resp-1")
140+
u1.add(
141+
u2,
142+
metadata={
143+
"model_name": "gpt-5",
144+
"agent_name": "test-agent",
145+
"response_id": "resp-1",
146+
},
147+
)
118148

119149
# Should not create a request_usage_entry for zero tokens
120150
assert len(u1.request_usage_entries) == 0
@@ -132,7 +162,14 @@ def test_usage_add_ignores_multi_request_usage():
132162
total_tokens=300,
133163
)
134164

135-
u1.add(u2, model_name="gpt-5", agent_name="test-agent", response_id="resp-1")
165+
u1.add(
166+
u2,
167+
metadata={
168+
"model_name": "gpt-5",
169+
"agent_name": "test-agent",
170+
"response_id": "resp-1",
171+
},
172+
)
136173

137174
# Should not create a request usage entry for multi-request usage
138175
assert len(u1.request_usage_entries) == 0
@@ -150,7 +187,14 @@ def test_usage_add_merges_existing_request_usage_entries():
150187
output_tokens_details=OutputTokensDetails(reasoning_tokens=20),
151188
total_tokens=300,
152189
)
153-
u1.add(u2, model_name="gpt-5", agent_name="agent-1", response_id="resp-1")
190+
u1.add(
191+
u2,
192+
metadata={
193+
"model_name": "gpt-5",
194+
"agent_name": "agent-1",
195+
"response_id": "resp-1",
196+
},
197+
)
154198

155199
# Create second usage with request_usage_entries
156200
u3 = Usage(
@@ -162,7 +206,14 @@ def test_usage_add_merges_existing_request_usage_entries():
162206
total_tokens=125,
163207
)
164208

165-
u1.add(u3, model_name="gpt-5", agent_name="agent-2", response_id="resp-2")
209+
u1.add(
210+
u3,
211+
metadata={
212+
"model_name": "gpt-5",
213+
"agent_name": "agent-2",
214+
"response_id": "resp-2",
215+
},
216+
)
166217

167218
# Should have both request_usage_entries
168219
assert len(u1.request_usage_entries) == 2
@@ -172,16 +223,16 @@ def test_usage_add_merges_existing_request_usage_entries():
172223
assert first.input_tokens == 100
173224
assert first.output_tokens == 200
174225
assert first.total_tokens == 300
175-
assert first.agent_name == "agent-1"
176-
assert first.response_id == "resp-1"
226+
assert first.metadata["agent_name"] == "agent-1"
227+
assert first.metadata["response_id"] == "resp-1"
177228

178229
# Second request
179230
second = u1.request_usage_entries[1]
180231
assert second.input_tokens == 50
181232
assert second.output_tokens == 75
182233
assert second.total_tokens == 125
183-
assert second.agent_name == "agent-2"
184-
assert second.response_id == "resp-2"
234+
assert second.metadata["agent_name"] == "agent-2"
235+
assert second.metadata["response_id"] == "resp-2"
185236

186237

187238
def test_usage_add_with_pre_existing_request_usage_entries():
@@ -197,7 +248,14 @@ def test_usage_add_with_pre_existing_request_usage_entries():
197248
output_tokens_details=OutputTokensDetails(reasoning_tokens=20),
198249
total_tokens=300,
199250
)
200-
u1.add(u2, model_name="gpt-5", agent_name="agent-1", response_id="resp-1")
251+
u1.add(
252+
u2,
253+
metadata={
254+
"model_name": "gpt-5",
255+
"agent_name": "agent-1",
256+
"response_id": "resp-1",
257+
},
258+
)
201259

202260
# Create another usage with request_usage_entries
203261
u3 = Usage(
@@ -210,7 +268,14 @@ def test_usage_add_with_pre_existing_request_usage_entries():
210268
)
211269

212270
# Add u3 to u1
213-
u1.add(u3, model_name="gpt-5", agent_name="agent-2", response_id="resp-2")
271+
u1.add(
272+
u3,
273+
metadata={
274+
"model_name": "gpt-5",
275+
"agent_name": "agent-2",
276+
"response_id": "resp-2",
277+
},
278+
)
214279

215280
# Should have both request_usage_entries
216281
assert len(u1.request_usage_entries) == 2
@@ -240,7 +305,14 @@ def test_anthropic_cost_calculation_scenario():
240305
output_tokens_details=OutputTokensDetails(reasoning_tokens=0),
241306
total_tokens=150_000,
242307
)
243-
usage.add(req1, model_name="gpt-5", agent_name="test-agent", response_id="resp-1")
308+
usage.add(
309+
req1,
310+
metadata={
311+
"model_name": "gpt-5",
312+
"agent_name": "test-agent",
313+
"response_id": "resp-1",
314+
},
315+
)
244316

245317
# Second request: 150K input tokens
246318
req2 = Usage(
@@ -251,7 +323,14 @@ def test_anthropic_cost_calculation_scenario():
251323
output_tokens_details=OutputTokensDetails(reasoning_tokens=0),
252324
total_tokens=225_000,
253325
)
254-
usage.add(req2, model_name="gpt-5", agent_name="test-agent", response_id="resp-2")
326+
usage.add(
327+
req2,
328+
metadata={
329+
"model_name": "gpt-5",
330+
"agent_name": "test-agent",
331+
"response_id": "resp-2",
332+
},
333+
)
255334

256335
# Third request: 80K input tokens
257336
req3 = Usage(
@@ -262,7 +341,14 @@ def test_anthropic_cost_calculation_scenario():
262341
output_tokens_details=OutputTokensDetails(reasoning_tokens=0),
263342
total_tokens=120_000,
264343
)
265-
usage.add(req3, model_name="gpt-5", agent_name="test-agent", response_id="resp-3")
344+
usage.add(
345+
req3,
346+
metadata={
347+
"model_name": "gpt-5",
348+
"agent_name": "test-agent",
349+
"response_id": "resp-3",
350+
},
351+
)
266352

267353
# Verify aggregated totals
268354
assert usage.requests == 3

0 commit comments

Comments
 (0)