agent-ablation 0.1.0 → 0.2.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/README.md +62 -0
- package/dist/index.cjs +59 -0
- package/dist/index.d.cts +64 -1
- package/dist/index.d.ts +64 -1
- package/dist/index.js +55 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,6 +59,43 @@ console.log(summary.averageLoadBearingRatio);
|
|
|
59
59
|
console.log(summary.perAgentInfluence); // { transaction_pattern: 0.17, identity_signal: 0.83, ... }
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
## Using with LangGraph
|
|
63
|
+
|
|
64
|
+
If you are orchestrating multi-agent systems with LangGraph, `fromLangGraphMessages` provides a zero-dependency convenience mapper for common LangGraph message shapes (this is a structural mapper, not an official LangGraph integration). It extracts named agent messages from `state.messages` and converts them into `Finding[]`:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { runAblation, fromLangGraphMessages, type Finding } from "agent-ablation";
|
|
68
|
+
|
|
69
|
+
// In your LangGraph supervisor node / decision step:
|
|
70
|
+
function evaluateState(state: { messages: any[] }) {
|
|
71
|
+
// Maps named specialist messages to Finding[]
|
|
72
|
+
const findings = fromLangGraphMessages(state.messages, {
|
|
73
|
+
scoreOf: (msg) => (msg.content as any).score,
|
|
74
|
+
confidenceOf: (msg) => (msg.content as any).confidence,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const decide = (fs: Finding[]) => {
|
|
78
|
+
const risk = 1 - fs.reduce((p, f) => p * (1 - f.score / 100), 1);
|
|
79
|
+
return risk >= 0.7 ? "decline" : "approve";
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const result = runAblation(findings, decide);
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
For arbitrary custom structures or telemetry traces, `fromRecords()` is also available to map any record array with custom extraction callbacks:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { fromRecords } from "agent-ablation";
|
|
91
|
+
|
|
92
|
+
const findings = fromRecords(customAuditRecords, {
|
|
93
|
+
agentId: (r) => r.specialistId,
|
|
94
|
+
scoreOf: (r) => r.riskScore,
|
|
95
|
+
confidenceOf: (r) => r.confidenceLevel, // optional
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
62
99
|
## Worked example: reproducing SentryMesh's 33% multi-signal-share finding
|
|
63
100
|
|
|
64
101
|
[SentryMesh](https://github.com/AyushCipher/Sentry-Mesh) is a four-specialist
|
|
@@ -166,6 +203,31 @@ function batchAblation<TVerdict>(
|
|
|
166
203
|
decide: DecisionFn<TVerdict>,
|
|
167
204
|
equals?: (a: TVerdict, b: TVerdict) => boolean
|
|
168
205
|
): { results: AblationResult<TVerdict>[]; summary: BatchAblationSummary };
|
|
206
|
+
|
|
207
|
+
interface LangGraphAgentMessage {
|
|
208
|
+
name?: string | null;
|
|
209
|
+
content?: unknown;
|
|
210
|
+
[key: string]: unknown;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
interface LangGraphAdapterOptions<TMessage extends LangGraphAgentMessage = LangGraphAgentMessage> {
|
|
214
|
+
scoreOf: (message: TMessage) => number;
|
|
215
|
+
confidenceOf?: (message: TMessage) => number | undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function fromLangGraphMessages<TMessage extends LangGraphAgentMessage = LangGraphAgentMessage>(
|
|
219
|
+
messages: readonly TMessage[] | TMessage[],
|
|
220
|
+
options: LangGraphAdapterOptions<TMessage>
|
|
221
|
+
): Finding[];
|
|
222
|
+
|
|
223
|
+
function fromRecords<T>(
|
|
224
|
+
records: readonly T[] | T[],
|
|
225
|
+
options: {
|
|
226
|
+
agentId: (record: T, index: number) => string;
|
|
227
|
+
scoreOf: (record: T, index: number) => number;
|
|
228
|
+
confidenceOf?: (record: T, index: number) => number | undefined;
|
|
229
|
+
}
|
|
230
|
+
): Finding[];
|
|
169
231
|
```
|
|
170
232
|
|
|
171
233
|
`equals` defaults to `===`. If `TVerdict` is an object (or anything else compared
|
package/dist/index.cjs
CHANGED
|
@@ -21,9 +21,66 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
batchAblation: () => batchAblation,
|
|
24
|
+
fromLangGraphMessages: () => fromLangGraphMessages,
|
|
25
|
+
fromRecords: () => fromRecords,
|
|
24
26
|
runAblation: () => runAblation
|
|
25
27
|
});
|
|
26
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/adapters/langgraph.ts
|
|
31
|
+
function fromLangGraphMessages(messages, options) {
|
|
32
|
+
const findings = [];
|
|
33
|
+
for (const message of messages) {
|
|
34
|
+
if (!message || typeof message !== "object") {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (typeof message.name !== "string" || message.name.trim().length === 0) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const agentId = message.name;
|
|
41
|
+
const score = options.scoreOf(message);
|
|
42
|
+
const confidence = options.confidenceOf ? options.confidenceOf(message) : void 0;
|
|
43
|
+
let metadata;
|
|
44
|
+
if (typeof message.content === "string") {
|
|
45
|
+
metadata = { raw: message.content };
|
|
46
|
+
} else if (typeof message.content === "object" && message.content !== null && !Array.isArray(message.content)) {
|
|
47
|
+
metadata = { ...message.content };
|
|
48
|
+
} else if (message.content !== void 0 && message.content !== null) {
|
|
49
|
+
metadata = { raw: message.content };
|
|
50
|
+
}
|
|
51
|
+
const finding = {
|
|
52
|
+
agentId,
|
|
53
|
+
score
|
|
54
|
+
};
|
|
55
|
+
if (confidence !== void 0) {
|
|
56
|
+
finding.confidence = confidence;
|
|
57
|
+
}
|
|
58
|
+
if (metadata !== void 0) {
|
|
59
|
+
finding.metadata = metadata;
|
|
60
|
+
}
|
|
61
|
+
findings.push(finding);
|
|
62
|
+
}
|
|
63
|
+
return findings;
|
|
64
|
+
}
|
|
65
|
+
function fromRecords(records, options) {
|
|
66
|
+
return records.map((record, index) => {
|
|
67
|
+
const agentId = options.agentId(record, index);
|
|
68
|
+
const score = options.scoreOf(record, index);
|
|
69
|
+
const confidence = options.confidenceOf ? options.confidenceOf(record, index) : void 0;
|
|
70
|
+
const metadata = typeof record === "object" && record !== null && !Array.isArray(record) ? { ...record } : { raw: record };
|
|
71
|
+
const finding = {
|
|
72
|
+
agentId,
|
|
73
|
+
score,
|
|
74
|
+
metadata
|
|
75
|
+
};
|
|
76
|
+
if (confidence !== void 0) {
|
|
77
|
+
finding.confidence = confidence;
|
|
78
|
+
}
|
|
79
|
+
return finding;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/index.ts
|
|
27
84
|
function defaultEquals(a, b) {
|
|
28
85
|
return a === b;
|
|
29
86
|
}
|
|
@@ -77,5 +134,7 @@ function batchAblation(cases, decide, equals = defaultEquals) {
|
|
|
77
134
|
// Annotate the CommonJS export names for ESM import in node:
|
|
78
135
|
0 && (module.exports = {
|
|
79
136
|
batchAblation,
|
|
137
|
+
fromLangGraphMessages,
|
|
138
|
+
fromRecords,
|
|
80
139
|
runAblation
|
|
81
140
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural representation of a message produced by a LangGraph / LangChain agent.
|
|
3
|
+
*
|
|
4
|
+
* Convenience mapper type designed without importing any LangChain / LangGraph packages.
|
|
5
|
+
*/
|
|
6
|
+
interface LangGraphAgentMessage {
|
|
7
|
+
/**
|
|
8
|
+
* The name of the agent or node that produced the message.
|
|
9
|
+
* Messages without a valid name are skipped during conversion.
|
|
10
|
+
*/
|
|
11
|
+
name?: string | null;
|
|
12
|
+
/**
|
|
13
|
+
* The message content. Can be a string, a structured object (e.g. parsed JSON),
|
|
14
|
+
* or any arbitrary payload.
|
|
15
|
+
*/
|
|
16
|
+
content?: unknown;
|
|
17
|
+
/**
|
|
18
|
+
* Arbitrary additional properties from the message structure.
|
|
19
|
+
*/
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Options for mapping LangGraph messages into findings.
|
|
24
|
+
*/
|
|
25
|
+
interface LangGraphAdapterOptions<TMessage extends LangGraphAgentMessage = LangGraphAgentMessage> {
|
|
26
|
+
/**
|
|
27
|
+
* Required mapping function to extract the numerical score from each message.
|
|
28
|
+
*/
|
|
29
|
+
scoreOf: (message: TMessage) => number;
|
|
30
|
+
/**
|
|
31
|
+
* Optional mapping function to extract a numerical confidence from each message.
|
|
32
|
+
*/
|
|
33
|
+
confidenceOf?: (message: TMessage) => number | undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Converts an array of LangGraph-style agent messages into `Finding` objects for ablation.
|
|
37
|
+
*
|
|
38
|
+
* Rules:
|
|
39
|
+
* - Entries without a valid string `name` are ignored.
|
|
40
|
+
* - `scoreOf(message)` is invoked for each named message to extract its score.
|
|
41
|
+
* - `confidenceOf(message)` is invoked if provided.
|
|
42
|
+
* - Object `content` is stored directly as `metadata`.
|
|
43
|
+
* - String `content` is wrapped inside `{ raw: content }` as `metadata`.
|
|
44
|
+
*
|
|
45
|
+
* @param messages Array of message-like objects (e.g. `state.messages`).
|
|
46
|
+
* @param options Mapping options requiring `scoreOf` and optional `confidenceOf`.
|
|
47
|
+
* @returns Array of `Finding` objects ready for `runAblation`.
|
|
48
|
+
*/
|
|
49
|
+
declare function fromLangGraphMessages<TMessage extends LangGraphAgentMessage = LangGraphAgentMessage>(messages: readonly TMessage[] | TMessage[], options: LangGraphAdapterOptions<TMessage>): Finding[];
|
|
50
|
+
/**
|
|
51
|
+
* Converts an arbitrary array of records into `Finding` objects using user-supplied mapping functions.
|
|
52
|
+
* Preserves the original record in `metadata`.
|
|
53
|
+
*
|
|
54
|
+
* @param records Array of arbitrary records.
|
|
55
|
+
* @param options Mapping configuration providing `agentId`, `scoreOf`, and optional `confidenceOf`.
|
|
56
|
+
* @returns Array of `Finding` objects ready for `runAblation`.
|
|
57
|
+
*/
|
|
58
|
+
declare function fromRecords<T>(records: readonly T[] | T[], options: {
|
|
59
|
+
agentId: (record: T, index: number) => string;
|
|
60
|
+
scoreOf: (record: T, index: number) => number;
|
|
61
|
+
confidenceOf?: (record: T, index: number) => number | undefined;
|
|
62
|
+
}): Finding[];
|
|
63
|
+
|
|
1
64
|
interface Finding {
|
|
2
65
|
agentId: string;
|
|
3
66
|
score: number;
|
|
@@ -44,4 +107,4 @@ declare function batchAblation<TVerdict>(cases: Finding[][], decide: DecisionFn<
|
|
|
44
107
|
summary: BatchAblationSummary;
|
|
45
108
|
};
|
|
46
109
|
|
|
47
|
-
export { type AblationResult, type BatchAblationSummary, type DecisionFn, type Finding, type PerAgentAblation, batchAblation, runAblation };
|
|
110
|
+
export { type AblationResult, type BatchAblationSummary, type DecisionFn, type Finding, type LangGraphAdapterOptions, type LangGraphAgentMessage, type PerAgentAblation, batchAblation, fromLangGraphMessages, fromRecords, runAblation };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural representation of a message produced by a LangGraph / LangChain agent.
|
|
3
|
+
*
|
|
4
|
+
* Convenience mapper type designed without importing any LangChain / LangGraph packages.
|
|
5
|
+
*/
|
|
6
|
+
interface LangGraphAgentMessage {
|
|
7
|
+
/**
|
|
8
|
+
* The name of the agent or node that produced the message.
|
|
9
|
+
* Messages without a valid name are skipped during conversion.
|
|
10
|
+
*/
|
|
11
|
+
name?: string | null;
|
|
12
|
+
/**
|
|
13
|
+
* The message content. Can be a string, a structured object (e.g. parsed JSON),
|
|
14
|
+
* or any arbitrary payload.
|
|
15
|
+
*/
|
|
16
|
+
content?: unknown;
|
|
17
|
+
/**
|
|
18
|
+
* Arbitrary additional properties from the message structure.
|
|
19
|
+
*/
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Options for mapping LangGraph messages into findings.
|
|
24
|
+
*/
|
|
25
|
+
interface LangGraphAdapterOptions<TMessage extends LangGraphAgentMessage = LangGraphAgentMessage> {
|
|
26
|
+
/**
|
|
27
|
+
* Required mapping function to extract the numerical score from each message.
|
|
28
|
+
*/
|
|
29
|
+
scoreOf: (message: TMessage) => number;
|
|
30
|
+
/**
|
|
31
|
+
* Optional mapping function to extract a numerical confidence from each message.
|
|
32
|
+
*/
|
|
33
|
+
confidenceOf?: (message: TMessage) => number | undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Converts an array of LangGraph-style agent messages into `Finding` objects for ablation.
|
|
37
|
+
*
|
|
38
|
+
* Rules:
|
|
39
|
+
* - Entries without a valid string `name` are ignored.
|
|
40
|
+
* - `scoreOf(message)` is invoked for each named message to extract its score.
|
|
41
|
+
* - `confidenceOf(message)` is invoked if provided.
|
|
42
|
+
* - Object `content` is stored directly as `metadata`.
|
|
43
|
+
* - String `content` is wrapped inside `{ raw: content }` as `metadata`.
|
|
44
|
+
*
|
|
45
|
+
* @param messages Array of message-like objects (e.g. `state.messages`).
|
|
46
|
+
* @param options Mapping options requiring `scoreOf` and optional `confidenceOf`.
|
|
47
|
+
* @returns Array of `Finding` objects ready for `runAblation`.
|
|
48
|
+
*/
|
|
49
|
+
declare function fromLangGraphMessages<TMessage extends LangGraphAgentMessage = LangGraphAgentMessage>(messages: readonly TMessage[] | TMessage[], options: LangGraphAdapterOptions<TMessage>): Finding[];
|
|
50
|
+
/**
|
|
51
|
+
* Converts an arbitrary array of records into `Finding` objects using user-supplied mapping functions.
|
|
52
|
+
* Preserves the original record in `metadata`.
|
|
53
|
+
*
|
|
54
|
+
* @param records Array of arbitrary records.
|
|
55
|
+
* @param options Mapping configuration providing `agentId`, `scoreOf`, and optional `confidenceOf`.
|
|
56
|
+
* @returns Array of `Finding` objects ready for `runAblation`.
|
|
57
|
+
*/
|
|
58
|
+
declare function fromRecords<T>(records: readonly T[] | T[], options: {
|
|
59
|
+
agentId: (record: T, index: number) => string;
|
|
60
|
+
scoreOf: (record: T, index: number) => number;
|
|
61
|
+
confidenceOf?: (record: T, index: number) => number | undefined;
|
|
62
|
+
}): Finding[];
|
|
63
|
+
|
|
1
64
|
interface Finding {
|
|
2
65
|
agentId: string;
|
|
3
66
|
score: number;
|
|
@@ -44,4 +107,4 @@ declare function batchAblation<TVerdict>(cases: Finding[][], decide: DecisionFn<
|
|
|
44
107
|
summary: BatchAblationSummary;
|
|
45
108
|
};
|
|
46
109
|
|
|
47
|
-
export { type AblationResult, type BatchAblationSummary, type DecisionFn, type Finding, type PerAgentAblation, batchAblation, runAblation };
|
|
110
|
+
export { type AblationResult, type BatchAblationSummary, type DecisionFn, type Finding, type LangGraphAdapterOptions, type LangGraphAgentMessage, type PerAgentAblation, batchAblation, fromLangGraphMessages, fromRecords, runAblation };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
+
// src/adapters/langgraph.ts
|
|
2
|
+
function fromLangGraphMessages(messages, options) {
|
|
3
|
+
const findings = [];
|
|
4
|
+
for (const message of messages) {
|
|
5
|
+
if (!message || typeof message !== "object") {
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
if (typeof message.name !== "string" || message.name.trim().length === 0) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const agentId = message.name;
|
|
12
|
+
const score = options.scoreOf(message);
|
|
13
|
+
const confidence = options.confidenceOf ? options.confidenceOf(message) : void 0;
|
|
14
|
+
let metadata;
|
|
15
|
+
if (typeof message.content === "string") {
|
|
16
|
+
metadata = { raw: message.content };
|
|
17
|
+
} else if (typeof message.content === "object" && message.content !== null && !Array.isArray(message.content)) {
|
|
18
|
+
metadata = { ...message.content };
|
|
19
|
+
} else if (message.content !== void 0 && message.content !== null) {
|
|
20
|
+
metadata = { raw: message.content };
|
|
21
|
+
}
|
|
22
|
+
const finding = {
|
|
23
|
+
agentId,
|
|
24
|
+
score
|
|
25
|
+
};
|
|
26
|
+
if (confidence !== void 0) {
|
|
27
|
+
finding.confidence = confidence;
|
|
28
|
+
}
|
|
29
|
+
if (metadata !== void 0) {
|
|
30
|
+
finding.metadata = metadata;
|
|
31
|
+
}
|
|
32
|
+
findings.push(finding);
|
|
33
|
+
}
|
|
34
|
+
return findings;
|
|
35
|
+
}
|
|
36
|
+
function fromRecords(records, options) {
|
|
37
|
+
return records.map((record, index) => {
|
|
38
|
+
const agentId = options.agentId(record, index);
|
|
39
|
+
const score = options.scoreOf(record, index);
|
|
40
|
+
const confidence = options.confidenceOf ? options.confidenceOf(record, index) : void 0;
|
|
41
|
+
const metadata = typeof record === "object" && record !== null && !Array.isArray(record) ? { ...record } : { raw: record };
|
|
42
|
+
const finding = {
|
|
43
|
+
agentId,
|
|
44
|
+
score,
|
|
45
|
+
metadata
|
|
46
|
+
};
|
|
47
|
+
if (confidence !== void 0) {
|
|
48
|
+
finding.confidence = confidence;
|
|
49
|
+
}
|
|
50
|
+
return finding;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
1
54
|
// src/index.ts
|
|
2
55
|
function defaultEquals(a, b) {
|
|
3
56
|
return a === b;
|
|
@@ -51,5 +104,7 @@ function batchAblation(cases, decide, equals = defaultEquals) {
|
|
|
51
104
|
}
|
|
52
105
|
export {
|
|
53
106
|
batchAblation,
|
|
107
|
+
fromLangGraphMessages,
|
|
108
|
+
fromRecords,
|
|
54
109
|
runAblation
|
|
55
110
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-ablation",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Leave-one-out ablation testing for multi-agent decision systems — find out which agents' findings actually change the outcome.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|