Submission checklist
Package (Required)
Related Issues / PRs
No response
Reproduction Steps / Example Code (Python)
import asyncio
from langchain_core.callbacks import AsyncCallbackManager, AsyncCallbackHandler
received = []
class H(AsyncCallbackHandler):
def __init__(self, name, run_inline):
self.name = name
self.run_inline = run_inline
async def on_llm_start(self, *args, **kwargs):
received.append(self.name)
mgr = AsyncCallbackManager(handlers=[H("inline", True), H("non_inline", False)])
asyncio.run(mgr.on_llm_start({}, ["hi"]))
print(received) # got: ['inline'] expected: ['inline', 'non_inline']
Error Message and Stack Trace (if applicable)
Description
AsyncCallbackManager.on_llm_start dispatches to non-inline handlers only when there are zero inline handlers.
Location: libs/core/langchain_core/callbacks/manager.py (the if inline_handlers: ... else: non_inline_tasks... block in on_llm_start). Because the else runs only when no inline handler exists, registering any inline handler suppresses on_llm_start for every non-inline handler.
Real-world trigger: LangChainTracer (the LangSmith tracer) sets run_inline = True. So in any async app with tracing enabled, a custom AsyncCallbackHandler (non-inline by default) never receives on_llm_start for completion-style LLMs, yet still receives on_llm_end (dispatched to all handlers). Handlers that track state per run get an end with no start.
The sibling on_chat_model_start is correct: it loops over self.handlers, builds a per-handler task, and classifies each by handler.run_inline. on_llm_start should mirror that.
Disclosure: this was found and fixed with AI assistance. I have a branch with the fix (mirroring on_chat_model_start) plus a regression test that fails before and passes after, and would like to be assigned before opening the PR.
System Info
langchain-core current master / latest. Python 3.13.
Submission checklist
Package (Required)
Related Issues / PRs
No response
Reproduction Steps / Example Code (Python)
Error Message and Stack Trace (if applicable)
Description
AsyncCallbackManager.on_llm_startdispatches to non-inline handlers only when there are zero inline handlers.Location:
libs/core/langchain_core/callbacks/manager.py(theif inline_handlers: ... else: non_inline_tasks...block inon_llm_start). Because theelseruns only when no inline handler exists, registering any inline handler suppresseson_llm_startfor every non-inline handler.Real-world trigger:
LangChainTracer(the LangSmith tracer) setsrun_inline = True. So in any async app with tracing enabled, a customAsyncCallbackHandler(non-inline by default) never receiveson_llm_startfor completion-style LLMs, yet still receiveson_llm_end(dispatched to all handlers). Handlers that track state per run get an end with no start.The sibling
on_chat_model_startis correct: it loops overself.handlers, builds a per-handler task, and classifies each byhandler.run_inline.on_llm_startshould mirror that.Disclosure: this was found and fixed with AI assistance. I have a branch with the fix (mirroring
on_chat_model_start) plus a regression test that fails before and passes after, and would like to be assigned before opening the PR.System Info
langchain-core current master / latest. Python 3.13.