1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass , field
2
2
from typing import TypeVar
3
3
4
4
from openai .types .responses .response_usage import InputTokensDetails , OutputTokensDetails
5
+ from pydantic import BaseModel
5
6
6
- T = TypeVar ("T" , bound = "InputTokensDetails | OutputTokensDetails" )
7
+ T = TypeVar ("T" , bound = BaseModel )
7
8
8
9
9
- def add_numeric_fields (current : T , other : T ) -> None :
10
- for field in current .__dataclass_fields__ :
11
- v1 = getattr (current , field , 0 )
12
- v2 = getattr (other , field , 0 )
10
+ def add_numeric_fields (current : T , other : T ) -> T :
11
+ """
12
+ Add numeric fields from other to current.
13
+ """
14
+ clone = current .model_copy ()
15
+ for key , v1 in current .model_dump ().items ():
16
+ v2 = getattr (other , key , 0 )
13
17
if isinstance (v1 , (int , float )) and isinstance (v2 , (int , float )):
14
- setattr (current , field , (v1 or 0 ) + (v2 or 0 ))
18
+ setattr (clone , key , (v1 or 0 ) + (v2 or 0 ))
19
+ return clone
20
+
21
+
22
+ def add_input_tokens_details (
23
+ current : InputTokensDetails , other : InputTokensDetails
24
+ ) -> InputTokensDetails :
25
+ return add_numeric_fields (current , other )
26
+
27
+
28
+ def add_output_tokens_details (
29
+ current : OutputTokensDetails , other : OutputTokensDetails
30
+ ) -> OutputTokensDetails :
31
+ return add_numeric_fields (current , other )
15
32
16
33
17
34
@dataclass
@@ -22,12 +39,17 @@ class Usage:
22
39
input_tokens : int = 0
23
40
"""Total input tokens sent, across all requests."""
24
41
25
- input_tokens_details : InputTokensDetails = InputTokensDetails (cached_tokens = 0 )
26
-
42
+ input_tokens_details : InputTokensDetails = field (
43
+ default_factory = lambda : InputTokensDetails (cached_tokens = 0 )
44
+ )
45
+ """Details about the input tokens, matching responses API usage details."""
27
46
output_tokens : int = 0
28
47
"""Total output tokens received, across all requests."""
29
48
30
- output_tokens_details : OutputTokensDetails = OutputTokensDetails (reasoning_tokens = 0 )
49
+ output_tokens_details : OutputTokensDetails = field (
50
+ default_factory = lambda : OutputTokensDetails (reasoning_tokens = 0 )
51
+ )
52
+ """Details about the output tokens, matching responses API usage details."""
31
53
32
54
total_tokens : int = 0
33
55
"""Total tokens sent and received, across all requests."""
@@ -37,5 +59,9 @@ def add(self, other: "Usage") -> None:
37
59
self .input_tokens += other .input_tokens if other .input_tokens else 0
38
60
self .output_tokens += other .output_tokens if other .output_tokens else 0
39
61
self .total_tokens += other .total_tokens if other .total_tokens else 0
40
- add_numeric_fields (self .input_tokens_details , other .input_tokens_details )
41
- add_numeric_fields (self .output_tokens_details , other .output_tokens_details )
62
+ self .input_tokens_details = add_input_tokens_details (
63
+ self .input_tokens_details , other .input_tokens_details
64
+ )
65
+ self .output_tokens_details = add_output_tokens_details (
66
+ self .output_tokens_details , other .output_tokens_details
67
+ )
0 commit comments