Framework support
Frameworks need no integration, because they call the provider clients the SDK patches. What the SDK adds is caller attribution that resolves past framework internals.
There is no LangChain integration to install.
Framework calls are recorded automatically
Every framework here calls a provider client. LangChain's ChatOpenAI constructs an
openai client, CrewAI delegates to LiteLLM which delegates to openai, and LlamaIndex
wraps the same libraries. Since the SDK patches those classes, framework calls are
recorded with no extra setup.
This also means a framework released next week is recorded, as long as it uses a provider SDK underneath.
What frameworks affect
Not capture, but attribution.
Every event records the file and line that made the call, found by walking the stack to the first frame that is not Capsera and not a provider library. Inside a framework, that frame is in the framework:
caller_file = langchain_core/language_models/chat_models.py:412
The SDK keeps a list of framework paths to skip, so the walk continues to the function you wrote. These are recognised automatically when installed:
LangChain, LangGraph, CrewAI, LlamaIndex, AutoGen, Haystack, DSPy.
LiteLLM is also on the list. See LiteLLM and gateways.
If caller_file points into a library not on that list, capture is still correct and only
the reported location is unhelpful. Report the library and it will be added.
Attribution must come from you
The framework does not know your agent names. Without a scope, framework calls are
attributed to unknown.
The plain decorator works everywhere:
@capsera.agent("researcher", team="core")
def research(question):
return chain.invoke({"question": question})
Three helpers also record which framework produced the work, so reports can separate graph nodes from crew agents from your own functions:
from capsera import langgraph_node, crewai_agent, crewai_task
@langgraph_node("planner")
def plan(state): ...
@crewai_agent("researcher")
def build_researcher(): ...
@crewai_task("summarize")
def summary_task(): ...
They accept the same optional team, task_type, customer_id, and cost_center as
@agent(). Using @agent() everywhere instead works fine.
When there is no function to decorate
Sometimes the LLM call happens inside framework machinery, such as a chain assembled at module level and invoked from a handler. Two options.
Use tag() around the invocation:
with capsera.tag("classifier", task_type="triage"):
result = chain.invoke(payload)
Or, for LangChain, use the callback handler, which attaches attribution for the duration of each LLM call:
from capsera import LangChainCapseraCallback
chain.invoke(
payload,
config={"callbacks": [LangChainCapseraCallback(agent="classifier", team="support")]},
)
The callback is useful when the invocation site is not yours to wrap, such as inside an agent executor or a retriever chain.
Per-framework pages
LangChain, LangGraph, CrewAI, LlamaIndex, LiteLLM and gateways, AutoGen, Haystack and DSPy.
Detection timing
The skip-list is built when init() runs, from the frameworks importable at that moment. A
framework imported for the first time after init() is not added, so its internals can
appear as caller_file.
This is rare, because frameworks are normally imported at module load. It can occur if you lazy-import a framework inside a request path.