Docs / Integrations / LangChain

LangChain

Mesedi ships as a LangChain.js callback handler. Wrap your entry function with mesedi.wrap() and attach a MesediLangChainCallbackHandlerinstance 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.

Install

npm install mesedi@^0.7 @langchain/core

Mesedi'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.

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 callbackMesedi eventNotes
handleLLMStart + handleLLMEndllm_call (status ok)Plain (non-chat) LLM. Model, prompt, response, tokens.
handleChatModelStart + handleLLMEndllm_call (status ok)Chat model. Extracts last user + system message from the conversation.
handleLLMErrorllm_call (status failed)Response text and token counts are omitted on the failure path.
handleToolStart + handleToolEndtool_call (status ok)Tool name, input, output. Payload shape matches @mesedi.tool.
handleToolErrortool_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.

DetectorModeFires via
loopsNativehandleLLMStart + handleToolStart callbacks (identical_call, similar_call, step_count)
tool_failuresNativehandleToolError callback
driftNativehandleLLMStart user_message divergence
cost_velocityNativehandleLLMEnd token counts + provider pricing
prompt_injectionNativehandleLLMStart user_message pattern match
data_leakageNativehandleLLMStart user_message DLP match
context_overflowNativehandleLLMStart prompt-size vs model window
token_wasteNativehandleLLMStart shared prefix across 3+ user_messages
tool_schema_driftNativehandleToolEnd structured return_value fingerprint (attach callbacks on tool.invoke())
sandbox_escapeNativehandleToolStart arguments match the dangerous-call regex
crashesHybridthrow inside wrap() — same pattern every SDK language uses
semantic_loopHybridCall checkpoint(name, metadata) at each phase — LangChain has no chain-boundary equivalent
validator_failuresHybridCall validatorResult() after your schema check
infrastructure_throttledHybridCall emitInfrastructureEvent() when you observe a rate-limit / retry
grounding_failureHybridCall emitEvalScore() with your RAG / faithfulness score
cascading_failureHybridCall emitAgentHandoff() to a child execution
coordination_deadlockHybridCall emitAgentHandoff()— Tarjan's SCC catches the cycle
provider_incidentHybridEmit a failed llm_call event with error_class="timeout" / "rate_limited" when your provider client throws
hitl_timeoutHybridrequestHumanIntervention() + completeHumanIntervention() with timeout kind
hitl_rejection_spikeHybridRepeated 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 one llm_call event with the assembled final response on handleLLMEnd. Per-token attribution needs an event-payload schema change and lands in a later release.
  • Per-chain depth tracking is not exposed. handleChainStart and handleChainEnd fire for every nested runnable but the handler ignores them; wrap() already owns the execution boundary. A chain-as-execution mode (no wrap() 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