@kontourai/survey 1.19.0 → 2.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/README.md +1 -1
- package/dist/src/agent-utterance.js +4 -5
- package/dist/src/canonical-reviewed-trust-input.d.ts +31 -0
- package/dist/src/canonical-reviewed-trust-input.js +265 -0
- package/dist/src/extraction-envelope.d.ts +2 -0
- package/dist/src/extraction-envelope.js +11 -3
- package/dist/src/extraction-improvement-proposal.d.ts +17 -0
- package/dist/src/extraction-improvement-proposal.js +33 -0
- package/dist/src/index.d.ts +6 -2
- package/dist/src/index.js +3 -1
- package/dist/src/pdf-layout.d.ts +53 -0
- package/dist/src/pdf-layout.js +201 -0
- package/dist/src/review-workbench/extraction-inspector.d.ts +4 -0
- package/dist/src/review-workbench/extraction-inspector.js +43 -8
- package/package.json +5 -13
- package/dist/src/anthropic.d.ts +0 -104
- package/dist/src/anthropic.js +0 -383
package/dist/src/anthropic.js
DELETED
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anthropic production adapters for Survey's pluggable interfaces.
|
|
3
|
-
*
|
|
4
|
-
* ADR 0003 §4 compliance: these implementations are PROPOSERS only. Every
|
|
5
|
-
* output is a proposal (MappingProposal / ExtractedStatement) that goes
|
|
6
|
-
* through the existing review/auto-accept machinery before counting.
|
|
7
|
-
* Nothing here bypasses review.
|
|
8
|
-
*
|
|
9
|
-
* Subpath export: import from "@kontourai/survey/anthropic" — this module is
|
|
10
|
-
* NOT re-exported from the main index.ts so consumers without @anthropic-ai/sdk
|
|
11
|
-
* pay nothing.
|
|
12
|
-
*
|
|
13
|
-
* Injected client: both factories accept an optional pre-built client so tests
|
|
14
|
-
* can inject a fake without hitting the network. If no client is provided, one
|
|
15
|
-
* is constructed from opts.apiKey (falling back to process.env.ANTHROPIC_API_KEY).
|
|
16
|
-
*/
|
|
17
|
-
const DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
18
|
-
/**
|
|
19
|
-
* Build or return a messages client from options.
|
|
20
|
-
* Dynamic-imports @anthropic-ai/sdk only when no client is injected,
|
|
21
|
-
* keeping the optional peer dep out of the eager module graph.
|
|
22
|
-
*/
|
|
23
|
-
async function resolveClient(opts) {
|
|
24
|
-
if (opts.client)
|
|
25
|
-
return opts.client;
|
|
26
|
-
// Dynamically load the SDK — only reachable when no client is injected.
|
|
27
|
-
// Uses a variable module specifier so TypeScript does not try to resolve
|
|
28
|
-
// the optional peer dep at compile time. At runtime the SDK must be installed.
|
|
29
|
-
const sdkModule = "@anthropic-ai/sdk";
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
31
|
-
const sdkImport = await Function("m", "return import(m)")(sdkModule);
|
|
32
|
-
const { default: Anthropic } = sdkImport;
|
|
33
|
-
const apiKey = opts.apiKey ?? process.env["ANTHROPIC_API_KEY"];
|
|
34
|
-
if (!apiKey) {
|
|
35
|
-
throw new Error("AnthropicAdapter: no API key. Provide opts.apiKey, set ANTHROPIC_API_KEY, or inject opts.client.");
|
|
36
|
-
}
|
|
37
|
-
const sdk = new Anthropic({ apiKey });
|
|
38
|
-
return sdk.messages;
|
|
39
|
-
}
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// JSON tool schemas
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
const MAPPING_PROPOSAL_TOOL = {
|
|
44
|
-
name: "submit_mapping_proposals",
|
|
45
|
-
description: "Submit an array of candidate mappings from the natural-language question to registered canonical claim targets or derivation rules. " +
|
|
46
|
-
"You are PROPOSING for human review — every proposal must carry a rationale and confidence score. " +
|
|
47
|
-
"Per ADR 0003 §4, proposals are reviewable records; they do not resolve questions by themselves.",
|
|
48
|
-
input_schema: {
|
|
49
|
-
type: "object",
|
|
50
|
-
properties: {
|
|
51
|
-
proposals: {
|
|
52
|
-
type: "array",
|
|
53
|
-
items: {
|
|
54
|
-
type: "object",
|
|
55
|
-
properties: {
|
|
56
|
-
proposedTargetSubjectType: {
|
|
57
|
-
type: "string",
|
|
58
|
-
description: "subjectType of the canonical claim target (omit if proposing a rule)",
|
|
59
|
-
},
|
|
60
|
-
proposedTargetSubjectId: {
|
|
61
|
-
type: "string",
|
|
62
|
-
description: "subjectId of the canonical claim target (omit if proposing a rule)",
|
|
63
|
-
},
|
|
64
|
-
proposedTargetFieldOrBehavior: {
|
|
65
|
-
type: "string",
|
|
66
|
-
description: "fieldOrBehavior of the canonical claim target (omit if proposing a rule)",
|
|
67
|
-
},
|
|
68
|
-
proposedRuleId: {
|
|
69
|
-
type: "string",
|
|
70
|
-
description: "Id of the derivation rule this question maps to (omit if proposing a target)",
|
|
71
|
-
},
|
|
72
|
-
confidence: {
|
|
73
|
-
type: "number",
|
|
74
|
-
description: "Confidence in this mapping (0.0–1.0)",
|
|
75
|
-
},
|
|
76
|
-
rationale: {
|
|
77
|
-
type: "string",
|
|
78
|
-
description: "Human-readable explanation of why this mapping is proposed",
|
|
79
|
-
},
|
|
80
|
-
excerpt: {
|
|
81
|
-
type: "string",
|
|
82
|
-
description: "Verbatim excerpt from the question that drove the suggestion",
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
required: ["confidence", "rationale"],
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
required: ["proposals"],
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
const UTTERANCE_EXTRACTION_TOOL = {
|
|
93
|
-
name: "submit_extracted_statements",
|
|
94
|
-
description: "Submit an array of factual statements extracted from the agent utterance. " +
|
|
95
|
-
"Each statement maps to a canonical claim target with full provenance (excerpt, span, confidence). " +
|
|
96
|
-
"You are EXTRACTING FOR REVIEW — output is a proposal queue, not authoritative truth. " +
|
|
97
|
-
"Per ADR 0003 §4, every extracted statement requires a rationale and confidence score.",
|
|
98
|
-
input_schema: {
|
|
99
|
-
type: "object",
|
|
100
|
-
properties: {
|
|
101
|
-
statements: {
|
|
102
|
-
type: "array",
|
|
103
|
-
items: {
|
|
104
|
-
type: "object",
|
|
105
|
-
properties: {
|
|
106
|
-
subjectType: {
|
|
107
|
-
type: "string",
|
|
108
|
-
description: "The canonical subjectType (use 'unknown' if uncertain)",
|
|
109
|
-
},
|
|
110
|
-
subjectId: {
|
|
111
|
-
type: "string",
|
|
112
|
-
description: "The entity or resource the statement is about",
|
|
113
|
-
},
|
|
114
|
-
fieldOrBehavior: {
|
|
115
|
-
type: "string",
|
|
116
|
-
description: "The property or behavior being claimed",
|
|
117
|
-
},
|
|
118
|
-
value: {
|
|
119
|
-
description: "The claimed value (string, number, boolean, or null)",
|
|
120
|
-
},
|
|
121
|
-
excerpt: {
|
|
122
|
-
type: "string",
|
|
123
|
-
description: "Verbatim text from the utterance that contains this claim",
|
|
124
|
-
},
|
|
125
|
-
spanStart: {
|
|
126
|
-
type: "number",
|
|
127
|
-
description: "0-indexed character offset where the excerpt starts in the utterance",
|
|
128
|
-
},
|
|
129
|
-
spanEnd: {
|
|
130
|
-
type: "number",
|
|
131
|
-
description: "0-indexed character offset where the excerpt ends in the utterance",
|
|
132
|
-
},
|
|
133
|
-
confidence: {
|
|
134
|
-
type: "number",
|
|
135
|
-
description: "Extraction confidence (0.0–1.0)",
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
required: ["subjectId", "fieldOrBehavior", "excerpt", "confidence"],
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
required: ["statements"],
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
// Tool output parsing helpers
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
/**
|
|
149
|
-
* Extract the first tool_use block with the given name from a message.
|
|
150
|
-
* Returns undefined if not found (malformed output is rejected, never silently accepted).
|
|
151
|
-
*/
|
|
152
|
-
function extractToolUseInput(message, toolName) {
|
|
153
|
-
for (const block of message.content) {
|
|
154
|
-
if (block.type === "tool_use" && block.name === toolName) {
|
|
155
|
-
return block.input;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return undefined;
|
|
159
|
-
}
|
|
160
|
-
function isRecord(value) {
|
|
161
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
162
|
-
}
|
|
163
|
-
function isArray(value) {
|
|
164
|
-
return Array.isArray(value);
|
|
165
|
-
}
|
|
166
|
-
function stringOrUndefined(value) {
|
|
167
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
168
|
-
}
|
|
169
|
-
function numberInRange(value, min, max) {
|
|
170
|
-
if (typeof value !== "number" || !isFinite(value))
|
|
171
|
-
return undefined;
|
|
172
|
-
if (value < min || value > max)
|
|
173
|
-
return undefined;
|
|
174
|
-
return value;
|
|
175
|
-
}
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
// createAnthropicMappingProposer
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
/**
|
|
180
|
-
* Create a MappingProposer backed by Anthropic's API using forced tool-use.
|
|
181
|
-
*
|
|
182
|
-
* ADR 0003 §4: returns PROPOSALS only — they flow through the existing
|
|
183
|
-
* review/auto-accept machinery before counting as mappings.
|
|
184
|
-
*
|
|
185
|
-
* Tool output is validated strictly: malformed items (missing required fields,
|
|
186
|
-
* out-of-range confidence, no target and no rule) are filtered out rather than
|
|
187
|
-
* silently accepted.
|
|
188
|
-
*/
|
|
189
|
-
export function createAnthropicMappingProposer(opts = {}) {
|
|
190
|
-
const model = opts.model ?? DEFAULT_MODEL;
|
|
191
|
-
return {
|
|
192
|
-
name: `anthropic-mapping-proposer:${model}`,
|
|
193
|
-
async propose(question, context) {
|
|
194
|
-
const client = await resolveClient(opts);
|
|
195
|
-
// Build context summary for the prompt
|
|
196
|
-
const claimsContext = buildClaimsContext(context.bundle);
|
|
197
|
-
const rulesContext = buildRulesContext(context.rules);
|
|
198
|
-
const systemPrompt = [
|
|
199
|
-
"You are a mapping proposer for the Kontour trust ledger.",
|
|
200
|
-
"Your role is to PROPOSE (not decide) how a natural-language question maps to a registered canonical claim or derivation rule.",
|
|
201
|
-
"Every proposal you return will be reviewed by a human or auto-accept policy before it counts.",
|
|
202
|
-
"Do not make up claim targets that are not in the registered list below.",
|
|
203
|
-
"Return only proposals you genuinely believe are plausible mappings — with honest confidence scores.",
|
|
204
|
-
"",
|
|
205
|
-
claimsContext,
|
|
206
|
-
rulesContext,
|
|
207
|
-
]
|
|
208
|
-
.filter(Boolean)
|
|
209
|
-
.join("\n");
|
|
210
|
-
const userMessage = `Question to map: "${question}"`;
|
|
211
|
-
const message = await client.create({
|
|
212
|
-
model,
|
|
213
|
-
max_tokens: 1024,
|
|
214
|
-
messages: [{ role: "user", content: `${systemPrompt}\n\n${userMessage}` }],
|
|
215
|
-
tools: [MAPPING_PROPOSAL_TOOL],
|
|
216
|
-
tool_choice: { type: "tool", name: "submit_mapping_proposals" },
|
|
217
|
-
});
|
|
218
|
-
const input = extractToolUseInput(message, "submit_mapping_proposals");
|
|
219
|
-
if (!isRecord(input))
|
|
220
|
-
return [];
|
|
221
|
-
const rawProposals = input["proposals"];
|
|
222
|
-
if (!isArray(rawProposals))
|
|
223
|
-
return [];
|
|
224
|
-
const proposedAt = new Date().toISOString();
|
|
225
|
-
const results = [];
|
|
226
|
-
for (const item of rawProposals) {
|
|
227
|
-
const proposal = parseMappingProposalItem(item, question, model, proposedAt);
|
|
228
|
-
if (proposal)
|
|
229
|
-
results.push(proposal);
|
|
230
|
-
}
|
|
231
|
-
return results;
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function parseMappingProposalItem(item, question, proposedBy, proposedAt) {
|
|
236
|
-
if (!isRecord(item))
|
|
237
|
-
return undefined;
|
|
238
|
-
const raw = item;
|
|
239
|
-
const confidence = numberInRange(raw.confidence, 0, 1);
|
|
240
|
-
const rationale = stringOrUndefined(raw.rationale);
|
|
241
|
-
// Both required fields must be present
|
|
242
|
-
if (confidence === undefined || rationale === undefined)
|
|
243
|
-
return undefined;
|
|
244
|
-
const subjectType = stringOrUndefined(raw.proposedTargetSubjectType);
|
|
245
|
-
const subjectId = stringOrUndefined(raw.proposedTargetSubjectId);
|
|
246
|
-
const fieldOrBehavior = stringOrUndefined(raw.proposedTargetFieldOrBehavior);
|
|
247
|
-
const ruleId = stringOrUndefined(raw.proposedRuleId);
|
|
248
|
-
const excerpt = stringOrUndefined(raw.excerpt);
|
|
249
|
-
// Exactly one of (target triple) or ruleId must be present
|
|
250
|
-
const hasTarget = subjectType !== undefined && subjectId !== undefined && fieldOrBehavior !== undefined;
|
|
251
|
-
const hasRule = ruleId !== undefined;
|
|
252
|
-
if (!hasTarget && !hasRule)
|
|
253
|
-
return undefined;
|
|
254
|
-
const proposedTarget = hasTarget
|
|
255
|
-
? { subjectType: subjectType, subjectId: subjectId, fieldOrBehavior: fieldOrBehavior }
|
|
256
|
-
: undefined;
|
|
257
|
-
const id = `proposal.anthropic.${encodeId(question)}.${Date.now()}`;
|
|
258
|
-
return {
|
|
259
|
-
id,
|
|
260
|
-
question,
|
|
261
|
-
proposedTarget,
|
|
262
|
-
proposedRuleId: hasRule ? ruleId : undefined,
|
|
263
|
-
confidence,
|
|
264
|
-
rationale,
|
|
265
|
-
excerpt,
|
|
266
|
-
proposedBy,
|
|
267
|
-
proposedAt,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
// ---------------------------------------------------------------------------
|
|
271
|
-
// createAnthropicUtteranceExtractor
|
|
272
|
-
// ---------------------------------------------------------------------------
|
|
273
|
-
/**
|
|
274
|
-
* Create a UtteranceClaimExtractor backed by Anthropic's API using forced tool-use.
|
|
275
|
-
*
|
|
276
|
-
* ADR 0003 §4: returns EXTRACTED STATEMENTS only — they carry full provenance
|
|
277
|
-
* (excerpt, span, extractor name, confidence) and flow through the Inquiry
|
|
278
|
-
* pipeline. They are never treated as authoritative.
|
|
279
|
-
*
|
|
280
|
-
* Malformed tool output is rejected/filtered — items missing required fields
|
|
281
|
-
* (subjectId, fieldOrBehavior, excerpt, confidence) are dropped.
|
|
282
|
-
*/
|
|
283
|
-
export function createAnthropicUtteranceExtractor(opts = {}) {
|
|
284
|
-
const model = opts.model ?? DEFAULT_MODEL;
|
|
285
|
-
return {
|
|
286
|
-
name: `anthropic-utterance-extractor:${model}`,
|
|
287
|
-
async extract(utterance) {
|
|
288
|
-
const client = await resolveClient(opts);
|
|
289
|
-
const systemPrompt = [
|
|
290
|
-
"You are a factual statement extractor for the Kontour trust ledger.",
|
|
291
|
-
"Your role is to identify every factual claim in the agent utterance and extract it with full provenance.",
|
|
292
|
-
"Each extracted statement will be reviewed for trust coverage — you are NOT deciding truth, only extracting for review.",
|
|
293
|
-
"Extract only statements that assert factual properties of named entities.",
|
|
294
|
-
"Skip opinions, predictions, and procedural descriptions.",
|
|
295
|
-
"Provide honest confidence scores — low confidence for ambiguous phrasing.",
|
|
296
|
-
"Include the exact verbatim excerpt and 0-indexed character span offsets.",
|
|
297
|
-
].join("\n");
|
|
298
|
-
const userMessage = `Extract factual statements from this agent utterance:\n\n"${utterance}"`;
|
|
299
|
-
const message = await client.create({
|
|
300
|
-
model,
|
|
301
|
-
max_tokens: 2048,
|
|
302
|
-
messages: [{ role: "user", content: `${systemPrompt}\n\n${userMessage}` }],
|
|
303
|
-
tools: [UTTERANCE_EXTRACTION_TOOL],
|
|
304
|
-
tool_choice: { type: "tool", name: "submit_extracted_statements" },
|
|
305
|
-
});
|
|
306
|
-
const input = extractToolUseInput(message, "submit_extracted_statements");
|
|
307
|
-
if (!isRecord(input))
|
|
308
|
-
return [];
|
|
309
|
-
const rawStatements = input["statements"];
|
|
310
|
-
if (!isArray(rawStatements))
|
|
311
|
-
return [];
|
|
312
|
-
const results = [];
|
|
313
|
-
for (const item of rawStatements) {
|
|
314
|
-
const statement = parseExtractedStatementItem(item, utterance);
|
|
315
|
-
if (statement)
|
|
316
|
-
results.push(statement);
|
|
317
|
-
}
|
|
318
|
-
return results;
|
|
319
|
-
},
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
function parseExtractedStatementItem(item, utterance) {
|
|
323
|
-
if (!isRecord(item))
|
|
324
|
-
return undefined;
|
|
325
|
-
const raw = item;
|
|
326
|
-
const subjectId = stringOrUndefined(raw.subjectId);
|
|
327
|
-
const fieldOrBehavior = stringOrUndefined(raw.fieldOrBehavior);
|
|
328
|
-
const excerpt = stringOrUndefined(raw.excerpt);
|
|
329
|
-
const confidence = numberInRange(raw.confidence, 0, 1);
|
|
330
|
-
// All required fields must be present
|
|
331
|
-
if (!subjectId || !fieldOrBehavior || !excerpt || confidence === undefined)
|
|
332
|
-
return undefined;
|
|
333
|
-
const subjectType = stringOrUndefined(raw.subjectType) ?? "unknown";
|
|
334
|
-
// Validate span if provided — both start and end must be valid integers
|
|
335
|
-
// within the utterance length
|
|
336
|
-
let span;
|
|
337
|
-
if (typeof raw.spanStart === "number" && typeof raw.spanEnd === "number") {
|
|
338
|
-
const start = Math.trunc(raw.spanStart);
|
|
339
|
-
const end = Math.trunc(raw.spanEnd);
|
|
340
|
-
if (Number.isFinite(start) &&
|
|
341
|
-
Number.isFinite(end) &&
|
|
342
|
-
start >= 0 &&
|
|
343
|
-
end > start &&
|
|
344
|
-
end <= utterance.length) {
|
|
345
|
-
span = { start, end };
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return {
|
|
349
|
-
target: { subjectType, subjectId, fieldOrBehavior },
|
|
350
|
-
value: raw.value ?? undefined,
|
|
351
|
-
excerpt,
|
|
352
|
-
span,
|
|
353
|
-
confidence,
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
// ---------------------------------------------------------------------------
|
|
357
|
-
// Prompt context builders
|
|
358
|
-
// ---------------------------------------------------------------------------
|
|
359
|
-
function buildClaimsContext(bundle) {
|
|
360
|
-
if (!bundle || bundle.claims.length === 0)
|
|
361
|
-
return "";
|
|
362
|
-
const lines = [
|
|
363
|
-
"Registered canonical claim targets (use ONLY these as proposedTarget):",
|
|
364
|
-
...bundle.claims.map((c) => ` - subjectType="${c.subjectType}" subjectId="${c.subjectId}" fieldOrBehavior="${c.fieldOrBehavior}"`),
|
|
365
|
-
];
|
|
366
|
-
return lines.join("\n");
|
|
367
|
-
}
|
|
368
|
-
function buildRulesContext(rules) {
|
|
369
|
-
if (!rules || rules.length === 0)
|
|
370
|
-
return "";
|
|
371
|
-
const lines = [
|
|
372
|
-
"Registered derivation rules (use rule id as proposedRuleId):",
|
|
373
|
-
...rules.map((r) => ` - id="${r.id}" name="${r.name}"`),
|
|
374
|
-
];
|
|
375
|
-
return lines.join("\n");
|
|
376
|
-
}
|
|
377
|
-
function encodeId(value) {
|
|
378
|
-
return value
|
|
379
|
-
.toLowerCase()
|
|
380
|
-
.replace(/\s+/g, "-")
|
|
381
|
-
.replace(/[^a-z0-9\-]/g, "")
|
|
382
|
-
.slice(0, 40);
|
|
383
|
-
}
|