Skip to content

UsageCallbackHandler shares token/cost state across instances (class-level _model_usage) #349

Description

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions