@zansn/langchain 0.1.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.
- package/CHANGELOG.md +10 -0
- package/LICENSE +18 -0
- package/README.md +99 -0
- package/SECURITY.md +15 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +158 -0
- package/examples/langchain.mjs +82 -0
- package/package.json +83 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
Initial release: LangChain adapter for Zansn.
|
|
6
|
+
|
|
7
|
+
- Added `createZansnCollector` returning a LangChain `BaseCallbackHandler` and a `finalize()` that runs the local Zansn assurance checks and returns a `ZansnEvidencePack`.
|
|
8
|
+
- Recorded tool calls (start joined to end/error by run id), token usage, and model spans from the callback stream.
|
|
9
|
+
- Added `usageFromLLMResult` to read provider token usage across `llmOutput.tokenUsage`, `llmOutput.usage`, and message `usage_metadata`.
|
|
10
|
+
- Exported `ZansnCallbackHandler` for direct use.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
https://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Zansn Labs
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
18
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @zansn/langchain
|
|
2
|
+
|
|
3
|
+
[](https://zansn.ai)
|
|
4
|
+
|
|
5
|
+
Turn a [LangChain](https://js.langchain.com) agent run into a portable Zansn evidence pack. Capture traces, tool calls, cost, and a readiness verdict with a callback handler.
|
|
6
|
+
|
|
7
|
+
Website: https://zansn.ai
|
|
8
|
+
|
|
9
|
+
LangChain surfaces a run through callbacks. This adapter gives you a `BaseCallbackHandler` that records tool calls, token usage, and model/chain spans as they happen, then hands the assembled run to the [`zansn`](https://www.npmjs.com/package/zansn) assurance engine, which runs the same local policy, privacy, cost, latency, and readiness checks and returns the same evidence pack. It runs locally and transmits nothing.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @zansn/langchain zansn @langchain/core
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`@langchain/core` and `zansn` are peer dependencies, so you control their versions.
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { createZansnCollector } from "@zansn/langchain";
|
|
23
|
+
|
|
24
|
+
// 1. create a collector (holds this run's traces, tool calls, usage)
|
|
25
|
+
const zansn = createZansnCollector({
|
|
26
|
+
projectId: "support-agent",
|
|
27
|
+
policy: { blockedTools: ["delete_record"], maxLatencyMs: 8000 },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 2. pass its handler in the callbacks of any LangChain run
|
|
31
|
+
await agentExecutor.invoke(
|
|
32
|
+
{ input: "Look up case CASE-40917 and summarise the billing issue." },
|
|
33
|
+
{ callbacks: [zansn.handler] },
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// 3. seal a portable evidence pack from the captured run
|
|
37
|
+
const pack = await zansn.finalize();
|
|
38
|
+
|
|
39
|
+
console.log(pack.summary.readiness); // "ready" | "review" | "blocked"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The pack is a `ZansnEvidencePack`: readiness, findings, traces, cost, and latency. Export it as JSON or Markdown, attach it to a pull request, or later ingest it into hosted Zansn.
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
### `createZansnCollector(options)`
|
|
47
|
+
|
|
48
|
+
Returns a collector for one LangChain run.
|
|
49
|
+
|
|
50
|
+
| Option | Type | Notes |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| `projectId` | `string` | Required. Stable id recorded on the pack. |
|
|
53
|
+
| `policy` | `ZansnPolicy` | Blocked/allowed tools, sensitive-data patterns, cost and latency ceilings. |
|
|
54
|
+
| `experimentId`, `name`, `description` | `string` | Recorded on the pack. |
|
|
55
|
+
| `metadata` | `object` | Merged onto the pack metadata. |
|
|
56
|
+
| `expected`, `score` | | Optional expected output and custom scorer. |
|
|
57
|
+
|
|
58
|
+
The collector exposes:
|
|
59
|
+
|
|
60
|
+
- `handler`: a LangChain `BaseCallbackHandler`. Pass it in the `callbacks` array on your run.
|
|
61
|
+
- `finalize({ output })`: returns a `Promise<ZansnEvidencePack>`.
|
|
62
|
+
- `toolCallCount`: tool calls captured so far.
|
|
63
|
+
|
|
64
|
+
`ZansnCallbackHandler` and `usageFromLLMResult` are also exported for direct use.
|
|
65
|
+
|
|
66
|
+
## What is captured
|
|
67
|
+
|
|
68
|
+
| LangChain callback | Zansn |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `handleToolStart` + `handleToolEnd` / `handleToolError` | `ZansnToolCall` (name, args, result, status) |
|
|
71
|
+
| `handleLLMEnd` (LLMResult usage) | `ZansnUsage` + a model span |
|
|
72
|
+
| `handleChainEnd` | final output |
|
|
73
|
+
|
|
74
|
+
Token usage is read across the places LangChain providers report it (`llmOutput.tokenUsage`, `llmOutput.usage`, message `usage_metadata`), and missing fields are tolerated.
|
|
75
|
+
|
|
76
|
+
`finalize()` feeds the assembled result into `ZansnClient.runEvaluation` as a single completed task, so every existing check applies with no new assurance code.
|
|
77
|
+
|
|
78
|
+
## Fail a pull request when a run is not deployment-ready
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const pack = await zansn.finalize();
|
|
82
|
+
if (pack.summary.readiness === "blocked") {
|
|
83
|
+
console.error("Deployment blocked by Zansn:");
|
|
84
|
+
for (const f of pack.findings) console.error(` [${f.severity}] ${f.kind}: ${f.title}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## What is and is not in scope
|
|
90
|
+
|
|
91
|
+
This package **produces** a portable evidence pack locally. It does not store, compare, or govern packs. Hosted ingestion, run-over-run comparison, team workspaces, and enterprise policy packs are part of the commercial Zansn Assurance Control Plane. You keep calling the same local API; the paid tier adds the evidence graph.
|
|
92
|
+
|
|
93
|
+
## Example
|
|
94
|
+
|
|
95
|
+
A runnable, no-API-key demo lives in [`examples/langchain.mjs`](./examples/langchain.mjs).
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
Apache-2.0. See [LICENSE](./LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
`@zansn/langchain` is pre-1.0 software. Security fixes will target the latest published minor version.
|
|
6
|
+
|
|
7
|
+
## Reporting a Vulnerability
|
|
8
|
+
|
|
9
|
+
Report security issues through https://zansn.ai/contact.
|
|
10
|
+
|
|
11
|
+
Do not include secrets, private customer data, or sensitive production traces in public issues or examples.
|
|
12
|
+
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
`@zansn/langchain` runs locally and does not transmit data to Zansn Labs. It reads the callbacks your LangChain run already emits and turns them into a local evidence pack. Future hosted or enterprise products that ingest those packs have separate security documentation, data-retention controls, and disclosure channels.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
2
|
+
import { LLMResult } from '@langchain/core/outputs';
|
|
3
|
+
import { Serialized } from '@langchain/core/load/serializable';
|
|
4
|
+
import { ZansnToolCall, ZansnTraceInput, ZansnUsage, ZansnEvidencePack, ZansnPolicy, ZansnMetadata, ZansnAgentResult, ZansnClient } from 'zansn';
|
|
5
|
+
export { ZansnAgentResult, ZansnClient, ZansnEvidencePack, ZansnPolicy, ZansnToolCall, ZansnTraceInput, ZansnUsage } from 'zansn';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @zansn/langchain
|
|
9
|
+
*
|
|
10
|
+
* Turn a LangChain agent run into a portable Zansn evidence pack.
|
|
11
|
+
*
|
|
12
|
+
* LangChain surfaces a run through a callback handler. This adapter provides a
|
|
13
|
+
* `BaseCallbackHandler` that records tool calls, token usage, and model/chain
|
|
14
|
+
* spans as they happen, then `finalize()` hands the assembled run to the
|
|
15
|
+
* existing `zansn` engine, which applies the same local policy, privacy, cost,
|
|
16
|
+
* latency, and readiness checks and returns the same `ZansnEvidencePack`.
|
|
17
|
+
*
|
|
18
|
+
* This package writes NO assurance logic of its own. It is a translation layer:
|
|
19
|
+
* LangChain callbacks in, Zansn types out, `ZansnClient.runEvaluation` does the
|
|
20
|
+
* rest. It runs locally and transmits nothing.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Extract token usage from an LLMResult across the several places LangChain
|
|
25
|
+
* providers report it (llmOutput.tokenUsage, llmOutput.usage, or the message's
|
|
26
|
+
* usage_metadata). Returns a partial ZansnUsage; missing fields are omitted.
|
|
27
|
+
*/
|
|
28
|
+
declare function usageFromLLMResult(output: LLMResult): ZansnUsage;
|
|
29
|
+
/** Options for {@link createZansnCollector}. */
|
|
30
|
+
type ZansnCollectorOptions = {
|
|
31
|
+
/** Stable project identifier for the evidence pack (required). */
|
|
32
|
+
projectId: string;
|
|
33
|
+
experimentId?: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
/** Local assurance policy applied by the zansn engine at finalize(). */
|
|
37
|
+
policy?: ZansnPolicy;
|
|
38
|
+
metadata?: ZansnMetadata;
|
|
39
|
+
score?: (args: {
|
|
40
|
+
result: ZansnAgentResult<string>;
|
|
41
|
+
}) => Promise<number> | number;
|
|
42
|
+
expected?: unknown;
|
|
43
|
+
/** Override the ZansnClient (e.g. to set a default policy/organization). */
|
|
44
|
+
client?: ZansnClient;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* A collector accumulates one LangChain run (via its {@link ZansnCallbackHandler})
|
|
48
|
+
* and turns it into a Zansn evidence pack.
|
|
49
|
+
*/
|
|
50
|
+
type ZansnCollector = {
|
|
51
|
+
/** Pass this in the LangChain `callbacks` array on your run. */
|
|
52
|
+
handler: BaseCallbackHandler;
|
|
53
|
+
/**
|
|
54
|
+
* Seal the run into a ZansnEvidencePack. Optionally pass the final output text
|
|
55
|
+
* (otherwise the last chain/LLM output text captured is used).
|
|
56
|
+
*/
|
|
57
|
+
finalize: (final?: {
|
|
58
|
+
output?: string;
|
|
59
|
+
}) => Promise<ZansnEvidencePack<string>>;
|
|
60
|
+
/** Tool calls captured so far. */
|
|
61
|
+
readonly toolCallCount: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Internal callback handler that records a LangChain run into Zansn primitives.
|
|
65
|
+
*/
|
|
66
|
+
declare class ZansnCallbackHandler extends BaseCallbackHandler {
|
|
67
|
+
name: string;
|
|
68
|
+
readonly toolCalls: ZansnToolCall[];
|
|
69
|
+
readonly traces: ZansnTraceInput[];
|
|
70
|
+
readonly usage: ZansnUsage;
|
|
71
|
+
lastText: string;
|
|
72
|
+
private readonly pending;
|
|
73
|
+
private llmStep;
|
|
74
|
+
handleToolStart(tool: Serialized, input: string, runId: string, _parentRunId?: string, _tags?: string[], _metadata?: Record<string, unknown>, _runName?: string, toolCallId?: string): void;
|
|
75
|
+
handleToolEnd(output: unknown, runId: string): void;
|
|
76
|
+
handleToolError(err: unknown, runId: string): void;
|
|
77
|
+
handleLLMEnd(output: LLMResult): void;
|
|
78
|
+
handleChainEnd(outputs: unknown): void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a Zansn collector for a single LangChain run.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* const zansn = createZansnCollector({ projectId: "support-agent" });
|
|
85
|
+
* await agentExecutor.invoke({ input }, { callbacks: [zansn.handler] });
|
|
86
|
+
* const pack = await zansn.finalize();
|
|
87
|
+
*/
|
|
88
|
+
declare function createZansnCollector(options: ZansnCollectorOptions): ZansnCollector;
|
|
89
|
+
|
|
90
|
+
export { ZansnCallbackHandler, type ZansnCollector, type ZansnCollectorOptions, createZansnCollector, usageFromLLMResult };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
3
|
+
import {
|
|
4
|
+
ZansnClient
|
|
5
|
+
} from "zansn";
|
|
6
|
+
function toolNameFrom(serialized) {
|
|
7
|
+
if (!serialized || typeof serialized !== "object") return "tool";
|
|
8
|
+
const s = serialized;
|
|
9
|
+
if (typeof s.name === "string") return s.name;
|
|
10
|
+
if (typeof s.kwargs?.name === "string") return s.kwargs.name;
|
|
11
|
+
if (Array.isArray(s.id) && s.id.length > 0) {
|
|
12
|
+
const last = s.id[s.id.length - 1];
|
|
13
|
+
if (typeof last === "string") return last;
|
|
14
|
+
}
|
|
15
|
+
if (typeof s.id === "string") return s.id;
|
|
16
|
+
return "tool";
|
|
17
|
+
}
|
|
18
|
+
function usageFromLLMResult(output) {
|
|
19
|
+
const candidates = [];
|
|
20
|
+
const llmOut = output.llmOutput;
|
|
21
|
+
if (llmOut && typeof llmOut === "object") {
|
|
22
|
+
for (const key of ["tokenUsage", "usage", "estimatedTokenUsage"]) {
|
|
23
|
+
const v = llmOut[key];
|
|
24
|
+
if (v && typeof v === "object") candidates.push(v);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const firstGen = output.generations?.[0]?.[0];
|
|
28
|
+
const firstMessageUsage = firstGen?.message?.usage_metadata;
|
|
29
|
+
if (firstMessageUsage && typeof firstMessageUsage === "object") {
|
|
30
|
+
candidates.push(firstMessageUsage);
|
|
31
|
+
}
|
|
32
|
+
const usage = {};
|
|
33
|
+
for (const c of candidates) {
|
|
34
|
+
const input = numeric(c.promptTokens ?? c.input_tokens ?? c.inputTokens);
|
|
35
|
+
const out = numeric(c.completionTokens ?? c.output_tokens ?? c.outputTokens);
|
|
36
|
+
const total = numeric(c.totalTokens ?? c.total_tokens);
|
|
37
|
+
if (input !== void 0 && usage.inputTokens === void 0) usage.inputTokens = input;
|
|
38
|
+
if (out !== void 0 && usage.outputTokens === void 0) usage.outputTokens = out;
|
|
39
|
+
if (total !== void 0 && usage.totalTokens === void 0) usage.totalTokens = total;
|
|
40
|
+
}
|
|
41
|
+
return usage;
|
|
42
|
+
}
|
|
43
|
+
function numeric(v) {
|
|
44
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
45
|
+
}
|
|
46
|
+
function accumulateUsage(into, add) {
|
|
47
|
+
if (add.inputTokens !== void 0) into.inputTokens = (into.inputTokens ?? 0) + add.inputTokens;
|
|
48
|
+
if (add.outputTokens !== void 0) into.outputTokens = (into.outputTokens ?? 0) + add.outputTokens;
|
|
49
|
+
if (add.totalTokens !== void 0) into.totalTokens = (into.totalTokens ?? 0) + add.totalTokens;
|
|
50
|
+
}
|
|
51
|
+
var ZansnCallbackHandler = class extends BaseCallbackHandler {
|
|
52
|
+
name = "ZansnCallbackHandler";
|
|
53
|
+
toolCalls = [];
|
|
54
|
+
traces = [];
|
|
55
|
+
usage = {};
|
|
56
|
+
lastText = "";
|
|
57
|
+
// toolCallId (or runId) -> index in toolCalls, so end can attach the result.
|
|
58
|
+
pending = /* @__PURE__ */ new Map();
|
|
59
|
+
llmStep = 0;
|
|
60
|
+
handleToolStart(tool, input, runId, _parentRunId, _tags, _metadata, _runName, toolCallId) {
|
|
61
|
+
const name = toolNameFrom(tool);
|
|
62
|
+
const call = { id: toolCallId ?? runId, name, status: "ok" };
|
|
63
|
+
if (input !== void 0) call.arguments = input;
|
|
64
|
+
const index = this.toolCalls.push(call) - 1;
|
|
65
|
+
this.pending.set(runId, index);
|
|
66
|
+
if (toolCallId) this.pending.set(toolCallId, index);
|
|
67
|
+
}
|
|
68
|
+
handleToolEnd(output, runId) {
|
|
69
|
+
const index = this.pending.get(runId);
|
|
70
|
+
if (index === void 0) return;
|
|
71
|
+
const call = this.toolCalls[index];
|
|
72
|
+
if (!call) return;
|
|
73
|
+
call.result = output;
|
|
74
|
+
}
|
|
75
|
+
handleToolError(err, runId) {
|
|
76
|
+
const index = this.pending.get(runId);
|
|
77
|
+
if (index === void 0) return;
|
|
78
|
+
const call = this.toolCalls[index];
|
|
79
|
+
if (!call) return;
|
|
80
|
+
call.status = "error";
|
|
81
|
+
call.result = err instanceof Error ? err.message : String(err);
|
|
82
|
+
}
|
|
83
|
+
handleLLMEnd(output) {
|
|
84
|
+
accumulateUsage(this.usage, usageFromLLMResult(output));
|
|
85
|
+
const attributes = {
|
|
86
|
+
"zansn.step.index": this.llmStep,
|
|
87
|
+
"gen_ai.system": "langchain"
|
|
88
|
+
};
|
|
89
|
+
this.traces.push({
|
|
90
|
+
type: "model",
|
|
91
|
+
name: `langchain.llm.${this.llmStep}`,
|
|
92
|
+
attributes
|
|
93
|
+
});
|
|
94
|
+
this.llmStep += 1;
|
|
95
|
+
}
|
|
96
|
+
handleChainEnd(outputs) {
|
|
97
|
+
if (typeof outputs === "string") {
|
|
98
|
+
this.lastText = outputs;
|
|
99
|
+
} else if (outputs && typeof outputs === "object") {
|
|
100
|
+
const rec = outputs;
|
|
101
|
+
const text = rec.output ?? rec.text ?? rec.result;
|
|
102
|
+
if (typeof text === "string") this.lastText = text;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
function createZansnCollector(options) {
|
|
107
|
+
if (typeof options.projectId !== "string" || options.projectId.trim().length === 0) {
|
|
108
|
+
throw new Error("createZansnCollector requires a non-empty projectId.");
|
|
109
|
+
}
|
|
110
|
+
const client = options.client ?? new ZansnClient();
|
|
111
|
+
const handler = new ZansnCallbackHandler();
|
|
112
|
+
const finalize = async (final) => {
|
|
113
|
+
const agentResult = {
|
|
114
|
+
output: final?.output ?? handler.lastText,
|
|
115
|
+
toolCalls: handler.toolCalls,
|
|
116
|
+
usage: handler.usage,
|
|
117
|
+
traces: handler.traces
|
|
118
|
+
};
|
|
119
|
+
const runOptions = {
|
|
120
|
+
projectId: options.projectId,
|
|
121
|
+
tasks: [
|
|
122
|
+
{
|
|
123
|
+
id: "langchain-run",
|
|
124
|
+
input: { source: "langchain", toolCalls: handler.toolCalls.length },
|
|
125
|
+
...options.expected !== void 0 ? { expected: options.expected } : {}
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
execute: async () => agentResult,
|
|
129
|
+
metadata: {
|
|
130
|
+
...options.metadata,
|
|
131
|
+
"zansn.adapter": "@zansn/langchain",
|
|
132
|
+
"zansn.source": "langchain"
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
if (options.experimentId !== void 0) runOptions.experimentId = options.experimentId;
|
|
136
|
+
if (options.name !== void 0) runOptions.name = options.name;
|
|
137
|
+
if (options.description !== void 0) runOptions.description = options.description;
|
|
138
|
+
if (options.policy !== void 0) runOptions.policy = options.policy;
|
|
139
|
+
if (options.score !== void 0) {
|
|
140
|
+
const userScore = options.score;
|
|
141
|
+
runOptions.score = ({ result }) => userScore({ result });
|
|
142
|
+
}
|
|
143
|
+
return client.runEvaluation(runOptions);
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
handler,
|
|
147
|
+
finalize,
|
|
148
|
+
get toolCallCount() {
|
|
149
|
+
return handler.toolCalls.length;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
ZansnCallbackHandler,
|
|
155
|
+
ZansnClient,
|
|
156
|
+
createZansnCollector,
|
|
157
|
+
usageFromLLMResult
|
|
158
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zansn/langchain: LangChain example
|
|
3
|
+
*
|
|
4
|
+
* Shows how to capture a LangChain agent run as a Zansn evidence pack.
|
|
5
|
+
*
|
|
6
|
+
* This file runs standalone with no API key: it drives the collector's callback
|
|
7
|
+
* handler with recorded callbacks (the exact shape LangChain emits) so you can
|
|
8
|
+
* see the full evidence pack without calling a model. The commented block shows
|
|
9
|
+
* the real integration.
|
|
10
|
+
*
|
|
11
|
+
* node examples/langchain.mjs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createZansnCollector } from "@zansn/langchain";
|
|
15
|
+
import { exportEvidencePackMarkdown } from "zansn";
|
|
16
|
+
|
|
17
|
+
// ── The real integration (uncomment with your own agent) ────────────────────
|
|
18
|
+
//
|
|
19
|
+
// import { AgentExecutor } from "langchain/agents";
|
|
20
|
+
//
|
|
21
|
+
// const zansn = createZansnCollector({
|
|
22
|
+
// projectId: "support-agent",
|
|
23
|
+
// policy: { blockedTools: ["delete_record"], maxLatencyMs: 8000 },
|
|
24
|
+
// });
|
|
25
|
+
//
|
|
26
|
+
// await agentExecutor.invoke(
|
|
27
|
+
// { input: "Look up case CASE-40917 and summarise the billing issue." },
|
|
28
|
+
// { callbacks: [zansn.handler] }, // <- the one line that captures the run
|
|
29
|
+
// );
|
|
30
|
+
//
|
|
31
|
+
// const pack = await zansn.finalize();
|
|
32
|
+
// console.log(exportEvidencePackMarkdown(pack));
|
|
33
|
+
|
|
34
|
+
// ── Standalone demo (recorded callbacks, no API key needed) ──────────────────
|
|
35
|
+
|
|
36
|
+
const zansn = createZansnCollector({
|
|
37
|
+
projectId: "support-agent",
|
|
38
|
+
name: "Support agent readiness (LangChain)",
|
|
39
|
+
policy: {
|
|
40
|
+
blockedTools: ["delete_record"],
|
|
41
|
+
sensitiveDataPatterns: [/sk-[a-z0-9]{8,}/i],
|
|
42
|
+
maxLatencyMs: 8000,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const h = zansn.handler;
|
|
47
|
+
const serialized = (name) => ({ lc: 1, type: "constructor", id: ["langchain", "tools", name], kwargs: {} });
|
|
48
|
+
|
|
49
|
+
// The agent calls a tool to look up a case.
|
|
50
|
+
h.handleToolStart(
|
|
51
|
+
serialized("crm_lookup_case"),
|
|
52
|
+
JSON.stringify({ case_id: "CASE-40917" }),
|
|
53
|
+
"tool-1",
|
|
54
|
+
undefined,
|
|
55
|
+
undefined,
|
|
56
|
+
undefined,
|
|
57
|
+
undefined,
|
|
58
|
+
"call-1",
|
|
59
|
+
);
|
|
60
|
+
h.handleToolEnd({ status: "open", topic: "billing" }, "tool-1");
|
|
61
|
+
|
|
62
|
+
// The model turn reports token usage.
|
|
63
|
+
h.handleLLMEnd(
|
|
64
|
+
{
|
|
65
|
+
generations: [[{ text: "ok" }]],
|
|
66
|
+
llmOutput: { tokenUsage: { promptTokens: 470, completionTokens: 62, totalTokens: 532 } },
|
|
67
|
+
},
|
|
68
|
+
"llm-1",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// The chain finishes with the final answer.
|
|
72
|
+
h.handleChainEnd(
|
|
73
|
+
{ output: "Case CASE-40917 is an open billing question about a duplicate charge." },
|
|
74
|
+
"chain-1",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const pack = await zansn.finalize();
|
|
78
|
+
|
|
79
|
+
console.log(exportEvidencePackMarkdown(pack));
|
|
80
|
+
console.log("\nReadiness:", pack.summary.readiness);
|
|
81
|
+
console.log("Total tokens:", pack.summary.totalTokens);
|
|
82
|
+
console.log("Findings:", pack.findings.length);
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zansn/langchain",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Turn LangChain agent runs into portable Zansn evidence packs. Capture traces, tool calls, cost, and readiness with a callback handler.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/index.js",
|
|
19
|
+
"dist/index.d.ts",
|
|
20
|
+
"examples",
|
|
21
|
+
"README.md",
|
|
22
|
+
"CHANGELOG.md",
|
|
23
|
+
"SECURITY.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"release:check": "npm run typecheck && npm test && npm run build && npm audit --omit=dev --omit=optional && npm pack --dry-run",
|
|
31
|
+
"publish:dry-run": "npm publish --dry-run",
|
|
32
|
+
"prepack": "npm run typecheck && npm test && npm run build"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"zansn",
|
|
36
|
+
"zansn-labs",
|
|
37
|
+
"langchain",
|
|
38
|
+
"ai-assurance",
|
|
39
|
+
"agent-evaluation",
|
|
40
|
+
"agent-observability",
|
|
41
|
+
"agent-tracing",
|
|
42
|
+
"ai-agents",
|
|
43
|
+
"llm-evals",
|
|
44
|
+
"evidence-pack",
|
|
45
|
+
"opentelemetry"
|
|
46
|
+
],
|
|
47
|
+
"author": "Zansn Labs",
|
|
48
|
+
"license": "Apache-2.0",
|
|
49
|
+
"homepage": "https://zansn.ai",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/hemswayy/zansn-labs.git",
|
|
53
|
+
"directory": "npm-reservation/packages/@zansn/langchain"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://zansn.ai/contact"
|
|
57
|
+
},
|
|
58
|
+
"funding": {
|
|
59
|
+
"type": "individual",
|
|
60
|
+
"url": "https://zansn.ai"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=18"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"sideEffects": false,
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@langchain/core": ">=0.3",
|
|
71
|
+
"zansn": "^0.1.3"
|
|
72
|
+
},
|
|
73
|
+
"overrides": {
|
|
74
|
+
"esbuild": "^0.28.1"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@langchain/core": "^1.2.2",
|
|
78
|
+
"tsup": "^8.5.0",
|
|
79
|
+
"typescript": "^5.9.3",
|
|
80
|
+
"vitest": "^3.2.4",
|
|
81
|
+
"zansn": "^0.1.3"
|
|
82
|
+
}
|
|
83
|
+
}
|