fix(core): Use reference counting for storing inherited run trees to support garbage collection#36660
fix(core): Use reference counting for storing inherited run trees to support garbage collection#36660Jacob Lee (jacoblee93) wants to merge 2 commits intomasterfrom
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
| run_id_str = str(run_tree.id) | ||
| if run_id_str not in handler.run_map: | ||
| handler.run_map[run_id_str] = run_tree | ||
| handler._external_run_ids.setdefault( # noqa: SLF001 | ||
| run_id_str, 0 | ||
| ) |
There was a problem hiding this comment.
hard to follow what's going on here with variable names are not self-explnatory
|
The docstring mentions |
When a langsmith
@traceablefunction invokes a LangChain Runnable or LangGraph subgraph, the callback manager's_configurefunction injects the@traceableRunTree into theLangChainTracer'srun_mapso that child runs can resolve their parent for trace nesting. However, since the RunTree was created outside the tracer's callback lifecycle,_end_tracenever removes it. The entry persists inrun_mapindefinitely, retaining the full RunTree and its entire child tree.In applications with nested subgraph invocations (e.g. an outer investigation graph delegating to skill agent subgraphs, each compiled as their own
StateGraph), this causes RunTree objects to accumulate linearly with every call.Fix: Track which
run_mapentries were injected externally via a shared_external_run_idsrefcount dict on_TracerCore. When_start_traceadds a child under an external parent, it increments the count. When_end_tracefinishes a child, it decrements — and evicts the external parent fromrun_maponce the last child completes.The refcount (rather than a simple set) is necessary because a single external parent may have multiple sibling children in the callback chain (e.g. a
prompt | llmRunnableSequence). Only truly external runs are tracked — the_configureguardif run_id_str not in handler.run_mapprevents tracer-managed runs from being misclassified.