perf(core): optimize callback manager hot paths

- get_child(): pass state directly to constructor instead of calling
  set_handlers/add_tags/add_metadata sequentially (-22%)
- add_tags(): use set-based dedup instead of O(n) list scans per tag,
  early return when tags list is empty (-70% on duplicate tags)
- handle_event/ahandle_event: early return when handlers list is empty
- _configure(): skip throwaway CallbackManager construction on the
  common path where inheritable_callbacks is provided

These are the top bottlenecks identified by profiling langgraph's
react_agent benchmark, where langchain_core callbacks account for ~35%
of runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John Kennedy
2026-02-27 21:37:52 -08:00
parent 67b777f655
commit e2da14595b
2 changed files with 46 additions and 21 deletions

View File

@@ -1109,12 +1109,20 @@ class BaseCallbackManager(CallbackManagerMixin):
tags: The tags to add.
inherit: Whether to inherit the tags.
"""
for tag in tags:
if tag in self.tags:
self.remove_tags([tag])
self.tags.extend(tags)
if not self.tags:
self.tags.extend(tags)
if inherit:
self.inheritable_tags.extend(tags)
return
# Deduplicate: tag order is not meaningful across the codebase
# (merge_configs sorts, tracers deduplicate via sets).
existing = set(self.tags)
new_tags = [t for t in tags if t not in existing]
self.tags.extend(new_tags)
if inherit:
self.inheritable_tags.extend(tags)
existing_inh = set(self.inheritable_tags)
new_inh = [t for t in tags if t not in existing_inh]
self.inheritable_tags.extend(new_inh)
def remove_tags(self, tags: list[str]) -> None:
"""Remove tags from the callback manager.

View File

@@ -269,6 +269,9 @@ def handle_event(
**kwargs: The keyword arguments to pass to the event handler
"""
if not handlers:
return
coros: list[Coroutine[Any, Any, Any]] = []
try:
@@ -433,6 +436,9 @@ async def ahandle_event(
**kwargs: The keyword arguments to pass to the event handler.
"""
if not handlers:
return
for handler in [h for h in handlers if h.run_inline]:
await _ahandle_event_for_handler(
handler, event_name, ignore_condition_name, *args, **kwargs
@@ -574,13 +580,18 @@ class ParentRunManager(RunManager):
The child callback manager.
"""
manager = CallbackManager(handlers=[], parent_run_id=self.run_id)
manager.set_handlers(self.inheritable_handlers)
manager.add_tags(self.inheritable_tags)
manager.add_metadata(self.inheritable_metadata)
tags = list(self.inheritable_tags)
if tag is not None:
manager.add_tags([tag], inherit=False)
return manager
tags.append(tag)
return CallbackManager(
handlers=list(self.inheritable_handlers),
inheritable_handlers=list(self.inheritable_handlers),
parent_run_id=self.run_id,
tags=tags,
inheritable_tags=list(self.inheritable_tags),
metadata=dict(self.inheritable_metadata),
inheritable_metadata=dict(self.inheritable_metadata),
)
class AsyncRunManager(BaseRunManager, ABC):
@@ -658,13 +669,18 @@ class AsyncParentRunManager(AsyncRunManager):
The child callback manager.
"""
manager = AsyncCallbackManager(handlers=[], parent_run_id=self.run_id)
manager.set_handlers(self.inheritable_handlers)
manager.add_tags(self.inheritable_tags)
manager.add_metadata(self.inheritable_metadata)
tags = list(self.inheritable_tags)
if tag is not None:
manager.add_tags([tag], inherit=False)
return manager
tags.append(tag)
return AsyncCallbackManager(
handlers=list(self.inheritable_handlers),
inheritable_handlers=list(self.inheritable_handlers),
parent_run_id=self.run_id,
tags=tags,
inheritable_tags=list(self.inheritable_tags),
metadata=dict(self.inheritable_metadata),
inheritable_metadata=dict(self.inheritable_metadata),
)
class CallbackManagerForLLMRun(RunManager, LLMManagerMixin):
@@ -2340,10 +2356,6 @@ def _configure(
tracing_tags = tracing_context["tags"]
run_tree: Run | None = tracing_context["parent"]
parent_run_id = None if run_tree is None else run_tree.id
callback_manager = callback_manager_cls(
handlers=[],
parent_run_id=parent_run_id,
)
if inheritable_callbacks or local_callbacks:
if isinstance(inheritable_callbacks, list) or inheritable_callbacks is None:
inheritable_callbacks_ = inheritable_callbacks or []
@@ -2381,6 +2393,11 @@ def _configure(
)
for handler in local_handlers_:
callback_manager.add_handler(handler, inherit=False)
else:
callback_manager = callback_manager_cls(
handlers=[],
parent_run_id=parent_run_id,
)
if inheritable_tags or local_tags:
callback_manager.add_tags(inheritable_tags or [])
callback_manager.add_tags(local_tags or [], inherit=False)