Bug
UsageCallbackHandler._model_usage (and price_map / llm_output) are class-level mutable attributes, so token/cost statistics are shared across every UsageCallbackHandler instance instead of being per-instance. A freshly constructed handler inherits the accumulated totals of any earlier one.
Reproduce
from unittest.mock import MagicMock
from langchain_core.outputs import LLMResult
from langchain_nvidia_ai_endpoints.callbacks import UsageCallbackHandler
result = LLMResult(
generations=[],
llm_output={"token_usage": {"total_tokens": 10, "prompt_tokens": 4, "completion_tokens": 6},
"model_name": "meta/llama-3.1-8b-instruct"},
)
h1 = UsageCallbackHandler()
h1.on_llm_end(result)
print(h1.total_tokens) # 10
h2 = UsageCallbackHandler()
print(h2.total_tokens) # 10 ← expected 0; inherits h1's totals
Two independent trackers — e.g. two sequential get_usage_callback() contexts — therefore contaminate each other's token/cost counts.
Why this looks like a bug (not "just call reset()")
- Each handler creates its own
self._lock, but that lock guards the class-shared _model_usage. A per-instance lock protecting shared state provides no real mutual exclusion — which only makes sense if _model_usage was intended to be per-instance too.
- The handler advertises itself as compatible with
OpenAICallbackHandler / get_openai_callback, both of which are per-instance and need no reset between separate tracking sessions.
Fix direction
Construct _model_usage in __init__ (and give price_map / llm_output per-instance copies) so each handler owns its state. Happy to open a PR with a regression test if this direction looks right.
Bug
UsageCallbackHandler._model_usage(andprice_map/llm_output) are class-level mutable attributes, so token/cost statistics are shared across everyUsageCallbackHandlerinstance instead of being per-instance. A freshly constructed handler inherits the accumulated totals of any earlier one.Reproduce
Two independent trackers — e.g. two sequential
get_usage_callback()contexts — therefore contaminate each other's token/cost counts.Why this looks like a bug (not "just call
reset()")self._lock, but that lock guards the class-shared_model_usage. A per-instance lock protecting shared state provides no real mutual exclusion — which only makes sense if_model_usagewas intended to be per-instance too.OpenAICallbackHandler/get_openai_callback, both of which are per-instance and need no reset between separate tracking sessions.Fix direction
Construct
_model_usagein__init__(and giveprice_map/llm_outputper-instance copies) so each handler owns its state. Happy to open a PR with a regression test if this direction looks right.