Docs / Integrations / LangChain
LangChain
Mesedi ships as a LangChain callback handler, in both Python and TypeScript. Wrap your entry function with mesedi.wrap and attach the handler to LangChain's existing callbacks slot. Every LLM invocation and tool call inside the wrap flows into Mesedi as an llm_call or tool_call event.
The examples below are TypeScript. Skip to Python if that is your stack.
Install
npm install mesedi@^0.7 @langchain/coreMesedi's adapter treats @langchain/core as an optional peer dependency. If you never import mesedi/integrations/langchain, the library is not loaded and there is no install cost for non-LangChain customers. Peer range is >=0.3.0 <0.5.0.
Wire it up
import { configure, wrap } from "mesedi";
import { MesediLangChainCallbackHandler } from "mesedi/integrations/langchain";
import { ChatOpenAI } from "@langchain/openai";
// Configure Mesedi once (usually at app startup).
configure({ apiKey: process.env.MESEDI_API_KEY });
export const runAgent = wrap(
{ agentName: "support-triage" },
async (question: string) => {
const llm = new ChatOpenAI({ model: "gpt-4o" });
const result = await llm.invoke(
[{ role: "user", content: question }],
{ callbacks: [new MesediLangChainCallbackHandler()] },
);
return String(result.content);
},
);wrap() owns the execution boundary (started / completed / crash signature). The callback handler owns the intra-execution events. Cross-cutting Mesedi features (per-project detector thresholds, custom security patterns, hard-halt budgets) all keep working through LangChain runs without any additional wiring.
Python
The Python SDK ships the same adapter against langchain-core. Note the class name differs: it is MesediCallbackHandler in Python and MesediLangChainCallbackHandler in TypeScript.
pip install "mesedi[langchain]"import os
import mesedi
from mesedi.integrations.langchain import MesediCallbackHandler
mesedi.configure(api_key=os.environ["MESEDI_API_KEY"])
@mesedi.wrap
def run_agent(question: str) -> str:
chain = build_chain()
result = chain.invoke(
{"input": question},
config={"callbacks": [MesediCallbackHandler()]},
)
return result["output"]The adapter imports BaseCallbackHandler from langchain_core.callbacks and falls back to the legacy langchain.callbacks.base location, so both modern and older installs work. Peer range is langchain-core>=0.1. Importing mesedi never requires LangChain to be installed.
Not yet supported in the Python adapter: async callbacks (sync only), streaming attribution (on_llm_new_token is ignored, you see the assembled response), per-chain depth tracking (on_chain_start fires but is discarded because @mesedi.wrap owns the boundary), and multi-modal message blocks (the text part is extracted, the rest dropped).
Where to attach the handler
The callbacks: option exists on every LangChain runnable's invoke(), stream(), and batch() methods, and on the RunnableConfig passed to agent executors. Attach one MesediLangChainCallbackHandler per top-level call; LangChain propagates it to every nested runnable automatically.
Callback-to-event mapping
LangChain fires typed callbacks for every LLM invocation and tool call. The handler translates each pair (start + end, or start + error) into the corresponding Mesedi event on the end/error callback.
| LangChain callback | Mesedi event | Notes |
|---|---|---|
handleLLMStart + handleLLMEnd | llm_call (status ok) | Plain (non-chat) LLM. Model, prompt, response, tokens. |
handleChatModelStart + handleLLMEnd | llm_call (status ok) | Chat model. Extracts last user + system message from the conversation. |
handleLLMError | llm_call (status failed) | Response text and token counts are omitted on the failure path. |
handleToolStart + handleToolEnd | tool_call (status ok) | Tool name, input, output. Payload shape matches @mesedi.tool. |
handleToolError | tool_call (status failed) | Carries exception_type + exception_message. |
Detector coverage
All 20 Mesedi failure-class detectors fire when driven through this adapter. Ten are native: the callback-to-event mapping above feeds them directly, as long as you attach MesediLangChainCallbackHandler at the callbacks: slot on the runnable. Ten are hybrid: you call an SDK emit helper inside your wrap() and the detector picks up the shape it needs. The LangChain integration harness exercises all 20 end-to-end against a real backend.
| Detector | Mode | Fires via |
|---|---|---|
loops | Native | handleLLMStart + handleToolStart callbacks (identical_call, similar_call, step_count) |
tool_failures | Native | handleToolError callback |
drift | Native | handleLLMStart user_message divergence |
cost_velocity | Native | handleLLMEnd token counts + provider pricing |
prompt_injection | Native | handleLLMStart user_message pattern match |
data_leakage | Native | handleLLMStart user_message DLP match |
context_overflow | Native | handleLLMStart prompt-size vs model window |
token_waste | Native | handleLLMStart shared prefix across 3+ user_messages |
tool_schema_drift | Native | handleToolEnd structured return_value fingerprint (attach callbacks on tool.invoke()) |
sandbox_escape | Native | handleToolStart arguments match the dangerous-call regex |
crashes | Hybrid | throw inside wrap(): same pattern every SDK language uses |
semantic_loop | Hybrid | Call checkpoint(name, metadata) at each phase: LangChain has no chain-boundary equivalent |
validator_failures | Hybrid | Call validatorResult() after your schema check |
infrastructure_throttled | Hybrid | Call emitInfrastructureEvent() when you observe a rate-limit / retry |
grounding_failure | Hybrid | Call emitEvalScore() with your RAG / faithfulness score |
cascading_failure | Hybrid | Call emitAgentHandoff() to a child execution |
coordination_deadlock | Hybrid | Call emitAgentHandoff(): Tarjan's SCC catches the cycle |
provider_incident | Hybrid | Emit a failed llm_call event with error_class="timeout" / "rate_limited" when your provider client throws |
hitl_timeout | Hybrid | requestHumanIntervention() + completeHumanIntervention() with timeout kind |
hitl_rejection_spike | Hybrid | Repeated validatorResult() with human_verdict=rejected over a window |
Hybrid rows use exactly the same SDK helpers you'd call from any Mesedi integration (or from plain wrap()-only code). The callback handler and the helpers coexist in one execution: no double-counting, no setup order dependency.
Halt-safe boundaries
Every emitted llm_call and tool_call is a halt-safe boundary. If you pass a budget to wrap(), the handler checks it at each callback, increments the step counter, and on LLM calls adds any observed input/output tokens toward the per-execution cap. Behavior matches @mesedi.tool and the Anthropic patch.
Current limitations
- Streaming (
handleLLMNewToken) is not currently consumed. Mesedi receives onellm_callevent with the assembled final response onhandleLLMEnd. Per-token attribution needs an event-payload schema change and lands in a later release. - Per-chain depth tracking is not exposed.
handleChainStartandhandleChainEndfire for every nested runnable but the handler ignores them;wrap()already owns the execution boundary. A chain-as-execution mode (nowrap()required) is a later iteration. - Multi-modal content blocks in chat messages are consumed for their text parts only. Image / audio blocks are ignored on the input side; the wire format doesn't yet carry them.
- Outside a wrap, all callbacks silently no-op. Attach the handler inside a
wrap()-decorated function for events to reach Mesedi.
Related
- TypeScript SDK reference :
configure(),wrap(),tool(), and the other framework adapters. - Mastra integration : sibling adapter for the Mastra framework.
- LangChain.js callbacks docs : how the
callbacks:option propagates through LangChain runnables.