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 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.1.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",