@debriefer/ai 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.
- package/dist/__tests__/ai-defaults.test.d.ts +2 -0
- package/dist/__tests__/ai-defaults.test.d.ts.map +1 -0
- package/dist/__tests__/ai-defaults.test.js +369 -0
- package/dist/__tests__/ai-defaults.test.js.map +1 -0
- package/dist/__tests__/synthesizer.test.d.ts +2 -0
- package/dist/__tests__/synthesizer.test.d.ts.map +1 -0
- package/dist/__tests__/synthesizer.test.js +147 -0
- package/dist/__tests__/synthesizer.test.js.map +1 -0
- package/dist/ai-client.d.ts +52 -0
- package/dist/ai-client.d.ts.map +1 -0
- package/dist/ai-client.js +40 -0
- package/dist/ai-client.js.map +1 -0
- package/dist/confidence.d.ts +22 -0
- package/dist/confidence.d.ts.map +1 -0
- package/dist/confidence.js +53 -0
- package/dist/confidence.js.map +1 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/link-selector.d.ts +23 -0
- package/dist/link-selector.d.ts.map +1 -0
- package/dist/link-selector.js +67 -0
- package/dist/link-selector.js.map +1 -0
- package/dist/person-validator.d.ts +21 -0
- package/dist/person-validator.d.ts.map +1 -0
- package/dist/person-validator.js +63 -0
- package/dist/person-validator.js.map +1 -0
- package/dist/section-filter.d.ts +22 -0
- package/dist/section-filter.d.ts.map +1 -0
- package/dist/section-filter.js +54 -0
- package/dist/section-filter.js.map +1 -0
- package/dist/synthesizer.d.ts +91 -0
- package/dist/synthesizer.d.ts.map +1 -0
- package/dist/synthesizer.js +127 -0
- package/dist/synthesizer.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI synthesis module for distilling scored findings into structured output.
|
|
3
|
+
*
|
|
4
|
+
* Provides ClaudeSynthesizer (Anthropic Claude API). NoopSynthesizer
|
|
5
|
+
* (pass-through) remains in @debriefer/core.
|
|
6
|
+
*
|
|
7
|
+
* The consumer controls domain-specific behavior via promptBuilder and
|
|
8
|
+
* responseParser callbacks. The synthesizer handles API calls, cost
|
|
9
|
+
* calculation, JSON parsing, and code fence stripping.
|
|
10
|
+
*/
|
|
11
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
12
|
+
import { stripMarkdownCodeFences } from "@debriefer/core";
|
|
13
|
+
/**
|
|
14
|
+
* Approximate cost per million tokens by model family (USD).
|
|
15
|
+
* Used for cost estimation in SynthesisResult.
|
|
16
|
+
*
|
|
17
|
+
* These are approximate and may not reflect current Anthropic pricing.
|
|
18
|
+
* Consumers should verify costs against https://docs.anthropic.com/en/docs/about-claude/models
|
|
19
|
+
* Falls back to Sonnet pricing for unrecognized model families.
|
|
20
|
+
*/
|
|
21
|
+
const MODEL_COSTS = {
|
|
22
|
+
"claude-sonnet": { input: 3, output: 15 },
|
|
23
|
+
"claude-opus": { input: 15, output: 75 },
|
|
24
|
+
"claude-haiku": { input: 0.25, output: 1.25 },
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Look up per-million-token costs for a model string.
|
|
28
|
+
* Falls back to Sonnet pricing if the model family is unrecognized.
|
|
29
|
+
*/
|
|
30
|
+
function getModelCosts(model) {
|
|
31
|
+
for (const [family, costs] of Object.entries(MODEL_COSTS)) {
|
|
32
|
+
if (model.includes(family))
|
|
33
|
+
return costs;
|
|
34
|
+
}
|
|
35
|
+
// Default to Sonnet pricing if unknown
|
|
36
|
+
return { input: 3, output: 15 };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Claude-powered synthesizer that distills scored findings into structured output.
|
|
40
|
+
*
|
|
41
|
+
* The consumer provides:
|
|
42
|
+
* - `promptBuilder`: converts subject + findings into system/user prompts
|
|
43
|
+
* - `responseParser`: validates/transforms the raw JSON response
|
|
44
|
+
*
|
|
45
|
+
* The synthesizer handles:
|
|
46
|
+
* - Anthropic API calls with configurable model and max tokens
|
|
47
|
+
* - JSON parsing with code fence stripping
|
|
48
|
+
* - Cost calculation from token usage
|
|
49
|
+
* - Sorting findings by reliability before passing to the prompt builder
|
|
50
|
+
*
|
|
51
|
+
* @typeParam TSubject - The research subject type
|
|
52
|
+
* @typeParam TOutput - The structured output type
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* const synthesizer = new ClaudeSynthesizer<ActorSubject, DeathInfo>({
|
|
57
|
+
* promptBuilder: (actor, findings) => ({
|
|
58
|
+
* system: "You are extracting death information...",
|
|
59
|
+
* user: `Actor: ${actor.name}\nFindings:\n${findings.map(f => f.text).join("\n")}`,
|
|
60
|
+
* }),
|
|
61
|
+
* responseParser: (raw) => DeathInfoSchema.parse(raw),
|
|
62
|
+
* })
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export class ClaudeSynthesizer {
|
|
66
|
+
client;
|
|
67
|
+
options;
|
|
68
|
+
constructor(options) {
|
|
69
|
+
this.options = options;
|
|
70
|
+
this.client = new Anthropic({ apiKey: options.apiKey });
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Synthesize scored findings into structured output via Claude API.
|
|
74
|
+
*
|
|
75
|
+
* Findings are sorted by reliability score (highest first) before being
|
|
76
|
+
* passed to the prompt builder, so higher-quality sources appear first
|
|
77
|
+
* in the prompt.
|
|
78
|
+
*
|
|
79
|
+
* @param subject - The research subject
|
|
80
|
+
* @param findings - Scored findings from source lookups
|
|
81
|
+
* @param options - Per-call overrides for model, max tokens, etc.
|
|
82
|
+
* @returns Synthesis result with structured output and cost metadata
|
|
83
|
+
* @throws Error if Claude returns no text content or unparseable JSON
|
|
84
|
+
*/
|
|
85
|
+
async synthesize(subject, findings, options = {}) {
|
|
86
|
+
const model = options.model ?? this.options.defaultModel ?? "claude-sonnet-4-20250514";
|
|
87
|
+
const maxTokens = options.maxTokens ?? this.options.defaultMaxTokens ?? 4096;
|
|
88
|
+
// Sort findings by reliability score (highest first) so the prompt
|
|
89
|
+
// builder receives the most trustworthy sources at the top
|
|
90
|
+
const sortedFindings = [...findings].sort((a, b) => b.reliabilityScore - a.reliabilityScore);
|
|
91
|
+
// Build prompt via consumer-provided builder
|
|
92
|
+
const { system, user } = this.options.promptBuilder(subject, sortedFindings);
|
|
93
|
+
// Call Claude API
|
|
94
|
+
const response = await this.client.messages.create({
|
|
95
|
+
model,
|
|
96
|
+
max_tokens: maxTokens,
|
|
97
|
+
system,
|
|
98
|
+
messages: [{ role: "user", content: user }],
|
|
99
|
+
});
|
|
100
|
+
// Extract text content from response blocks
|
|
101
|
+
const responseText = response.content
|
|
102
|
+
.filter((block) => block.type === "text")
|
|
103
|
+
.map((block) => block.text)
|
|
104
|
+
.join("");
|
|
105
|
+
if (!responseText) {
|
|
106
|
+
throw new Error("No text response from Claude");
|
|
107
|
+
}
|
|
108
|
+
// Parse JSON, stripping any code fences Claude may have added
|
|
109
|
+
const cleanJson = stripMarkdownCodeFences(responseText);
|
|
110
|
+
const rawParsed = JSON.parse(cleanJson);
|
|
111
|
+
// Validate through consumer's parser (required for type safety)
|
|
112
|
+
const data = this.options.responseParser(rawParsed);
|
|
113
|
+
// Calculate cost from token usage
|
|
114
|
+
const inputTokens = response.usage.input_tokens;
|
|
115
|
+
const outputTokens = response.usage.output_tokens;
|
|
116
|
+
const costs = getModelCosts(model);
|
|
117
|
+
const costUsd = (inputTokens * costs.input) / 1_000_000 + (outputTokens * costs.output) / 1_000_000;
|
|
118
|
+
return {
|
|
119
|
+
data,
|
|
120
|
+
costUsd,
|
|
121
|
+
inputTokens,
|
|
122
|
+
outputTokens,
|
|
123
|
+
model,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=synthesizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synthesizer.js","sourceRoot":"","sources":["../src/synthesizer.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@debriefer/ai",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-first defaults for debriefer: Claude-powered synthesis, section filtering, confidence scoring, link selection, and person validation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"lint": "eslint src/",
|
|
21
|
+
"type-check": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"debriefer",
|
|
25
|
+
"ai",
|
|
26
|
+
"claude",
|
|
27
|
+
"anthropic",
|
|
28
|
+
"research",
|
|
29
|
+
"synthesis"
|
|
30
|
+
],
|
|
31
|
+
"author": "Chris Henderson",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/chenders/debriefer.git",
|
|
36
|
+
"directory": "packages/ai"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/chenders/debriefer#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/chenders/debriefer/issues"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@anthropic-ai/sdk": ">=0.39.0 <1"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@debriefer/core": ">=2.0.0",
|
|
47
|
+
"@debriefer/sources": ">=1.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@debriefer/sources": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|