99Persisted shape on `eval_run.cost`:
1010
1111 {
12- "response": {model, input_tokens, output_tokens, total_tokens, cost_usd},
13- "embedding": {model, input_tokens, output_tokens, total_tokens, cost_usd},
12+ "response": {model, input_tokens, output_tokens, total_tokens, cost_usd},
13+ "embedding": {model, input_tokens, output_tokens, total_tokens, cost_usd},
14+ "judge": {model, input_tokens, output_tokens, total_tokens, cost_usd},
1415 "total_cost_usd": float,
1516 }
1617
17- Either stage entry is optional. Embedding entries use output_tokens=0.
18+ Any stage entry is optional. Embedding entries use output_tokens=0. One combined call
19+ grades every metric, so judge tokens can't be split per metric and form a single stage,
20+ priced from per-row usage like the response stage.
1821"""
1922
2023import logging
@@ -112,9 +115,22 @@ def _build_embedding_cost_entry(
112115 return _build_cost_entry (session = session , model = model , totals = totals )
113116
114117
118+ def _build_judge_cost_entry (
119+ session : Session , model : str , results : list [dict [str , Any ]]
120+ ) -> dict [str , Any ]:
121+ """Build a judge-stage cost entry from per-row judge usage results."""
122+ totals = _sum_tokens (
123+ items = results ,
124+ usage_extractor = lambda r : r .get ("usage" ),
125+ input_key = "input_tokens" ,
126+ )
127+ return _build_cost_entry (session = session , model = model , totals = totals )
128+
129+
115130def _build_cost_dict (
116131 response_entry : dict [str , Any ] | None ,
117132 embedding_entry : dict [str , Any ] | None ,
133+ judge_entry : dict [str , Any ] | None ,
118134) -> dict [str , Any ]:
119135 """Combine per-stage entries into the `eval_run.cost` payload with a grand total."""
120136 cost : dict [str , Any ] = {}
@@ -128,6 +144,10 @@ def _build_cost_dict(
128144 cost ["embedding" ] = embedding_entry
129145 total += embedding_entry .get ("cost_usd" , 0.0 )
130146
147+ if judge_entry :
148+ cost ["judge" ] = judge_entry
149+ total += judge_entry .get ("cost_usd" , 0.0 )
150+
131151 cost ["total_cost_usd" ] = round (total , COST_USD_DECIMALS )
132152 return cost
133153
@@ -141,10 +161,12 @@ def attach_cost(
141161 response_results : list [dict [str , Any ]] | None = None ,
142162 embedding_model : str | None = None ,
143163 embedding_raw_results : list [dict [str , Any ]] | None = None ,
164+ judge_model : str | None = None ,
165+ judge_results : list [dict [str , Any ]] | None = None ,
144166) -> None :
145167 """Compute cost for the given stage(s) and attach to `eval_run.cost`, never raising.
146168
147- Caller is responsible for persisting `eval_run` afterwards. Either stage's
169+ Caller is responsible for persisting `eval_run` afterwards. Any stage's
148170 previously-computed entry on `eval_run.cost` is preserved when that stage's
149171 inputs are not supplied, so partial updates never clobber prior data.
150172 """
@@ -167,9 +189,17 @@ def attach_cost(
167189 else :
168190 embedding_entry = existing_cost .get ("embedding" )
169191
192+ if judge_model is not None and judge_results is not None :
193+ judge_entry = _build_judge_cost_entry (
194+ session = session , model = judge_model , results = judge_results
195+ )
196+ else :
197+ judge_entry = existing_cost .get ("judge" )
198+
170199 eval_run .cost = _build_cost_dict (
171200 response_entry = response_entry ,
172201 embedding_entry = embedding_entry ,
202+ judge_entry = judge_entry ,
173203 )
174204 except Exception as cost_err :
175205 logger .warning (
0 commit comments