@@ -18,8 +18,9 @@ def __init__(self) -> None:
1818 self ._total_usage = Usage ()
1919 self ._agent_usage : dict [str , Usage ] = {}
2020 self ._agent_metadata : dict [str , dict [str , str ]] = {}
21- self ._total_cost = 0.0
22- self ._agent_cost : dict [str , float ] = {}
21+ self ._estimated_cost = 0.0
22+ self ._agent_estimated_cost : dict [str , float ] = {}
23+ self ._observed_cost = 0.0
2324
2425 def record (
2526 self ,
@@ -33,8 +34,6 @@ def record(
3334 return False
3435
3536 normalized_agent_id = str (agent_id or "unknown" )
36- estimated_cost = _estimate_litellm_cost (usage , model )
37-
3837 self ._total_usage .add (usage )
3938 self ._agent_usage .setdefault (normalized_agent_id , Usage ()).add (usage )
4039
@@ -44,31 +43,49 @@ def record(
4443 if model :
4544 metadata ["model" ] = model
4645
47- if estimated_cost is not None :
48- self ._total_cost += estimated_cost
49- self ._agent_cost [normalized_agent_id ] = (
50- self ._agent_cost .get (normalized_agent_id , 0.0 ) + estimated_cost
51- )
46+ if not _is_litellm_routed (model ):
47+ estimated_cost = _estimate_litellm_cost (usage , model )
48+ if estimated_cost is not None :
49+ self ._estimated_cost += estimated_cost
50+ self ._agent_estimated_cost [normalized_agent_id ] = (
51+ self ._agent_estimated_cost .get (normalized_agent_id , 0.0 ) + estimated_cost
52+ )
5253
5354 return True
5455
56+ def record_observed_cost (self , cost : float ) -> None :
57+ if isinstance (cost , int | float ) and cost > 0 :
58+ self ._observed_cost += float (cost )
59+
5560 def to_record (self ) -> dict [str , Any ]:
5661 record = serialize_usage (self ._total_usage )
57- record ["cost" ] = _round_cost (self ._total_cost )
58- record ["cost_source" ] = "litellm_estimate"
62+ grand_total = self ._estimated_cost + self ._observed_cost
63+ record ["cost" ] = _round_cost (grand_total )
64+ record ["cost_source" ] = _cost_source_label (self ._estimated_cost , self ._observed_cost )
5965 record ["agents" ] = []
6066
67+ total_tokens = max (0 , int (self ._total_usage .total_tokens or 0 ))
68+
6169 for agent_id in sorted (self ._agent_usage ):
6270 usage = self ._agent_usage [agent_id ]
6371 metadata = self ._agent_metadata .get (agent_id , {})
72+ agent_tokens = max (0 , int (usage .total_tokens or 0 ))
73+ observed_share = (
74+ self ._observed_cost * (agent_tokens / total_tokens ) if total_tokens else 0.0
75+ )
76+ agent_total = self ._agent_estimated_cost .get (agent_id , 0.0 ) + observed_share
77+
6478 agent_record = serialize_usage (usage )
6579 agent_record .update (
6680 {
6781 "agent_id" : agent_id ,
6882 "agent_name" : metadata .get ("agent_name" ) or agent_id ,
6983 "model" : metadata .get ("model" ),
70- "cost" : _round_cost (self ._agent_cost .get (agent_id , 0.0 )),
71- "cost_source" : "litellm_estimate" ,
84+ "cost" : _round_cost (agent_total ),
85+ "cost_source" : _cost_source_label (
86+ self ._agent_estimated_cost .get (agent_id , 0.0 ),
87+ observed_share ,
88+ ),
7289 }
7390 )
7491 record ["agents" ].append (agent_record )
@@ -79,8 +96,9 @@ def hydrate(self, raw_usage: Any) -> None:
7996 self ._total_usage = Usage ()
8097 self ._agent_usage .clear ()
8198 self ._agent_metadata .clear ()
82- self ._total_cost = 0.0
83- self ._agent_cost .clear ()
99+ self ._estimated_cost = 0.0
100+ self ._agent_estimated_cost .clear ()
101+ self ._observed_cost = 0.0
84102
85103 if not isinstance (raw_usage , dict ):
86104 return
@@ -91,7 +109,9 @@ def hydrate(self, raw_usage: Any) -> None:
91109 logger .exception ("Failed to hydrate aggregate llm_usage from run.json" )
92110 self ._total_usage = Usage ()
93111
94- self ._total_cost = _float_or_zero (raw_usage .get ("cost" ))
112+ # Resumed runs have already-aggregated cost; treat as estimated. New calls
113+ # in this resume add observed cost on top.
114+ self ._estimated_cost = _float_or_zero (raw_usage .get ("cost" ))
95115 agents = raw_usage .get ("agents" ) or []
96116 if not isinstance (agents , list ):
97117 return
@@ -116,7 +136,24 @@ def hydrate(self, raw_usage: Any) -> None:
116136 if isinstance (model , str ) and model :
117137 metadata ["model" ] = model
118138 self ._agent_metadata [agent_id ] = metadata
119- self ._agent_cost [agent_id ] = _float_or_zero (raw_agent .get ("cost" ))
139+ self ._agent_estimated_cost [agent_id ] = _float_or_zero (raw_agent .get ("cost" ))
140+
141+
142+ def _is_litellm_routed (model : str | None ) -> bool :
143+ if not model :
144+ return False
145+ name = model .strip ().lower ()
146+ if "/" not in name :
147+ return False
148+ return not name .startswith ("openai/" )
149+
150+
151+ def _cost_source_label (estimated : float , observed : float ) -> str :
152+ if observed > 0 and estimated > 0 :
153+ return "mixed"
154+ if observed > 0 :
155+ return "litellm_observed"
156+ return "litellm_estimate"
120157
121158
122159def _usage_has_activity (usage : Usage ) -> bool :
@@ -171,18 +208,25 @@ def _estimate_litellm_entry_cost(entry: Any, model: str) -> float | None:
171208 if completion_details :
172209 usage_payload ["completion_tokens_details" ] = completion_details
173210
174- try :
175- from litellm import completion_cost
176-
177- cost = completion_cost (
178- completion_response = {
179- "model" : model .split ("/" , 1 )[- 1 ],
180- "usage" : usage_payload ,
181- },
182- model = model ,
183- )
184- except Exception : # noqa: BLE001 - LiteLLM raises plain Exception for unknown model prices.
185- logger .debug ("LiteLLM cost estimate unavailable for model %s" , model , exc_info = True )
211+ from litellm import completion_cost
212+
213+ candidates = [model ]
214+ if "/" in model :
215+ candidates .append (model .split ("/" , 1 )[- 1 ])
216+
217+ cost : Any = None
218+ for candidate in candidates :
219+ try :
220+ cost = completion_cost (
221+ completion_response = {"model" : candidate , "usage" : usage_payload },
222+ model = model ,
223+ )
224+ break
225+ except Exception : # nosec B112 # noqa: BLE001, S112
226+ continue
227+
228+ if cost is None :
229+ logger .debug ("LiteLLM cost estimate unavailable for model %s" , model )
186230 return None
187231
188232 return cost if isinstance (cost , int | float ) and cost >= 0 else None
0 commit comments