@evalguard/langchain 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,231 @@
1
+ /**
2
+ * EvalGuard instrumentation for LangChain + LangGraph.
3
+ *
4
+ * LangChain's instrumentation surface is `@langchain/core/callbacks`. The
5
+ * recommended integration shape is a class that extends `BaseCallbackHandler`
6
+ * and overrides `handleLLMStart` / `handleLLMEnd` (and friends). The same
7
+ * handler works across LangChain.js + LangGraph because LangGraph reuses
8
+ * LangChain's callback dispatching for node-level events.
9
+ *
10
+ * Two usage patterns:
11
+ *
12
+ * // 1. Pass directly to a chain/runnable
13
+ * import { ChatOpenAI } from "@langchain/openai";
14
+ * import { EvalGuardCallbackHandler } from "@evalguard/langchain";
15
+ *
16
+ * const handler = new EvalGuardCallbackHandler({
17
+ * apiKey: process.env.EVALGUARD_API_KEY!,
18
+ * projectId: "proj-123",
19
+ * });
20
+ * const model = new ChatOpenAI({ model: "gpt-4o", callbacks: [handler] });
21
+ *
22
+ * // 2. Or globally via env (auto-installed when present)
23
+ * import { autoInstall } from "@evalguard/langchain";
24
+ * autoInstall(); // reads EVALGUARD_API_KEY + EVALGUARD_PROJECT_ID
25
+ *
26
+ * Implementation notes:
27
+ * - We do NOT depend on `@langchain/core` at install time. The handler
28
+ * extends a duck-typed `BaseCallbackHandler` shape so the package
29
+ * compiles + tests without LangChain present. Customers get full types
30
+ * from their own `@langchain/core` install via TypeScript's module
31
+ * resolution.
32
+ * - Fail-CLOSED semantics match openai-wrapper / anthropic-wrapper /
33
+ * llamaindex-wrapper (changed 2026-05-28): when `blockOnViolation` is
34
+ * on (the default), an UNREACHABLE guardrail blocks the run rather
35
+ * than waving it through. Set `blockOnViolation: false` for
36
+ * monitor-only/availability-first deployments.
37
+ * - `blockOnViolation` raises `EvalguardBlockedError` from `handleLLMStart`,
38
+ * which aborts the run ONLY because this handler sets
39
+ * `raiseError = true` and `awaitHandlers = true` (see the class body).
40
+ * LangChain swallows handler throws by default. The customer catches
41
+ * via `try { await chain.invoke(...) } catch {}`. Verified end-to-end
42
+ * against @langchain/core@1.1.48 through `model.invoke`, `model.stream`
43
+ * and a `RunnableSequence` in
44
+ * `src/__tests__/blocking-enforcement.test.ts` — NOT by inspection.
45
+ * - Because `raiseError = true` makes every throw from this class abort
46
+ * the customer's run, the split is strict: the guardrail path is the
47
+ * ONLY thing allowed to throw. Prompt-extraction failure fails CLOSED
48
+ * (an unscannable prompt is rejected, never silently skipped); model-name
49
+ * resolution degrades to "unknown"; every logging hook is wrapped in
50
+ * `safeLog` so a trace-log failure can never bubble into the chain.
51
+ * - Options that cannot be honoured are rejected in the constructor
52
+ * (`EvalguardConfigError`) rather than accepted and ignored.
53
+ * - **OpenInference compatibility**: every trace emits a `openinference`
54
+ * sidecar with the standard `openinference.span.kind`, `llm.input_messages`,
55
+ * `llm.output_messages`, `llm.model_name`, `llm.token_count.*` attributes.
56
+ * This frees rendering in Phoenix / Arize / any OpenInference-aware viewer.
57
+ * - LangGraph: the same handler emits `chain` + `tool` + `agent` events
58
+ * so node-level activity in a graph shows up as nested spans, mirroring
59
+ * LangSmith's per-node evaluator surface.
60
+ */
61
+ import { type GuardrailCheckResult } from "./guardrail-client.js";
62
+ /** Minimal LangChain message shape used in chat-model handler args. */
63
+ interface LangChainMessage {
64
+ content: string | unknown;
65
+ type?: string;
66
+ _getType?: () => string;
67
+ }
68
+ interface LangChainSerialized {
69
+ name?: string;
70
+ id?: string[];
71
+ kwargs?: Record<string, unknown>;
72
+ }
73
+ interface LangChainLLMResult {
74
+ generations: Array<Array<{
75
+ text?: string;
76
+ message?: {
77
+ content?: string;
78
+ };
79
+ generationInfo?: Record<string, unknown>;
80
+ }>>;
81
+ llmOutput?: {
82
+ tokenUsage?: {
83
+ promptTokens?: number;
84
+ completionTokens?: number;
85
+ totalTokens?: number;
86
+ };
87
+ modelName?: string;
88
+ model_name?: string;
89
+ model?: string;
90
+ [k: string]: unknown;
91
+ };
92
+ }
93
+ export interface EvalGuardHandlerConfig {
94
+ apiKey: string;
95
+ projectId?: string;
96
+ baseUrl?: string;
97
+ blockOnViolation?: boolean;
98
+ disableLogging?: boolean;
99
+ disableGuardrails?: boolean;
100
+ metadata?: Record<string, unknown>;
101
+ }
102
+ export declare class EvalguardBlockedError extends Error {
103
+ readonly violations: GuardrailCheckResult["violations"];
104
+ constructor(message: string, violations: GuardrailCheckResult["violations"]);
105
+ }
106
+ /**
107
+ * Thrown from the constructor when the supplied options cannot all be
108
+ * honoured. A security option that the handler is structurally unable to
109
+ * enforce must fail loudly at wiring time — silently accepting it is how
110
+ * a customer ends up believing they are protected when they are not.
111
+ */
112
+ export declare class EvalguardConfigError extends Error {
113
+ constructor(message: string);
114
+ }
115
+ /**
116
+ * The handler keeps state per runId so async overlapping calls are tracked
117
+ * correctly. LangChain assigns each LLM/chain/tool invocation a unique runId
118
+ * (UUID) which is passed to every handle* method.
119
+ */
120
+ export declare class EvalGuardCallbackHandler {
121
+ readonly name = "evalguard-handler";
122
+ readonly awaitHandlers = true;
123
+ readonly raiseError = true;
124
+ private readonly client;
125
+ private readonly config;
126
+ private readonly inflight;
127
+ private readonly blockOnViolation;
128
+ private readonly enableLogging;
129
+ private readonly enableGuardrails;
130
+ constructor(config: EvalGuardHandlerConfig);
131
+ /**
132
+ * Called when an LLM (completion-style) starts. LangChain emits both
133
+ * `handleLLMStart` and `handleChatModelStart` — we cover both so the
134
+ * same handler works across `LLM` and `BaseChatModel` subclasses.
135
+ */
136
+ handleLLMStart(llm: LangChainSerialized, prompts: string[], runId: string, _parentRunId?: string, extraParams?: Record<string, unknown>): Promise<void>;
137
+ /**
138
+ * Called when a chat-style LLM (e.g. ChatOpenAI, ChatAnthropic) starts.
139
+ * `messages` is `BaseMessage[][]` — one inner array per parallel
140
+ * generation. We collapse EVERY role into a single prompt for the
141
+ * guardrail check (A287: user-only collapse hid tool results and
142
+ * retrieved documents from the firewall).
143
+ */
144
+ handleChatModelStart(llm: LangChainSerialized, messages: LangChainMessage[][], runId: string, _parentRunId?: string, extraParams?: Record<string, unknown>): Promise<void>;
145
+ /**
146
+ * Called when the LLM finishes (success). Emits a trace with latency,
147
+ * token usage, cost, and OpenInference-compliant span attributes.
148
+ */
149
+ handleLLMEnd(output: LangChainLLMResult, runId: string): Promise<void>;
150
+ /**
151
+ * Called when the LLM call errors. We still emit a trace so customers
152
+ * can see failed calls in the dashboard (latency + error type + model).
153
+ */
154
+ handleLLMError(err: unknown, runId: string): Promise<void>;
155
+ /**
156
+ * Chain start — covers LangChain Runnables + LangGraph node entry.
157
+ * We log these as nested spans via OpenInference `chain` kind so the
158
+ * trace tree mirrors the customer's actual graph topology.
159
+ */
160
+ handleChainStart(chain: LangChainSerialized, inputs: Record<string, unknown>, runId: string): Promise<void>;
161
+ /**
162
+ * Tool execution start (LangGraph node calling a tool). Treated as its
163
+ * own span so trajectory-style assertions can recover the tool-call
164
+ * sequence from the trace.
165
+ */
166
+ handleToolStart(tool: LangChainSerialized, input: string, runId: string): Promise<void>;
167
+ /**
168
+ * Agent action (LangChain agents + LangGraph). Captures the decided
169
+ * action so trajectory-grading metrics can score whether the action
170
+ * was the right next step.
171
+ */
172
+ handleAgentAction(action: {
173
+ tool: string;
174
+ toolInput: unknown;
175
+ log?: string;
176
+ }, runId: string): Promise<void>;
177
+ /** Shared startup path for both `handleLLMStart` and `handleChatModelStart`. */
178
+ private startRun;
179
+ /**
180
+ * Model name is trace METADATA, not a security input. A failure to
181
+ * resolve it must never abort the customer's run and must never skip the
182
+ * firewall — degrade to "unknown" and carry on to the scan.
183
+ */
184
+ private safeResolveModel;
185
+ /**
186
+ * The prompt could not be read, so the firewall cannot see the bytes that
187
+ * are about to reach the model. Silently returning here (what this handler
188
+ * did until 2026-07-30) skipped the scan entirely and let the call through
189
+ * unguarded — a total bypass for any customer using a message class whose
190
+ * `_getType()`/content accessor throws.
191
+ *
192
+ * Fail CLOSED instead, with an explicit `prompt_unreadable` marker so the
193
+ * trace records that nothing was scanned. Note this is a REJECTION, not a
194
+ * shortened/partial scan: never hand the scanner less than the real input.
195
+ */
196
+ private onUnreadablePrompt;
197
+ /**
198
+ * Fire-and-forget trace logging that can NEVER throw into the customer's
199
+ * chain. This class sets `raiseError = true` so LangChain propagates our
200
+ * throws — that is required for a guardrail block to actually block, but
201
+ * it also means an incidental failure in a logging-only hook would abort a
202
+ * perfectly good LLM call. The published contract (README, "Outage
203
+ * semantics") is that a trace-log failure never bubbles into your chain;
204
+ * this is where that contract is enforced.
205
+ *
206
+ * `GuardrailClient.logTrace` already resolves to null on transport failure
207
+ * (wrapper-core `withRetry`), so this guards the synchronous payload
208
+ * construction and any future rejection path.
209
+ */
210
+ private safeLog;
211
+ private resolveModel;
212
+ }
213
+ /**
214
+ * Auto-install path for the lazy customer. Reads EVALGUARD_API_KEY from env.
215
+ * Returns the handler instance if installed; null if EVALGUARD_API_KEY is
216
+ * not set (no-op safe to call at module load).
217
+ *
218
+ * Usage:
219
+ *
220
+ * import { autoInstall } from "@evalguard/langchain";
221
+ * autoInstall();
222
+ *
223
+ * Then pass the returned handler to a Runnable's `callbacks` array, OR set
224
+ * the global LangChain callbacks via `setGlobalCallbacks([handler])` from
225
+ * `@langchain/core/callbacks/manager`.
226
+ */
227
+ export declare function autoInstall(): EvalGuardCallbackHandler | null;
228
+ export type { GuardrailCheckResult, GuardrailViolation, TraceLogData } from "./guardrail-client.js";
229
+ export { estimateCost, estimateCostDetailed, isModelPriced } from "./cost.js";
230
+ export type { CostEstimate, PricingSource } from "./cost.js";
231
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}