@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.
Files changed (37) hide show
  1. package/dist/__tests__/ai-defaults.test.d.ts +2 -0
  2. package/dist/__tests__/ai-defaults.test.d.ts.map +1 -0
  3. package/dist/__tests__/ai-defaults.test.js +369 -0
  4. package/dist/__tests__/ai-defaults.test.js.map +1 -0
  5. package/dist/__tests__/synthesizer.test.d.ts +2 -0
  6. package/dist/__tests__/synthesizer.test.d.ts.map +1 -0
  7. package/dist/__tests__/synthesizer.test.js +147 -0
  8. package/dist/__tests__/synthesizer.test.js.map +1 -0
  9. package/dist/ai-client.d.ts +52 -0
  10. package/dist/ai-client.d.ts.map +1 -0
  11. package/dist/ai-client.js +40 -0
  12. package/dist/ai-client.js.map +1 -0
  13. package/dist/confidence.d.ts +22 -0
  14. package/dist/confidence.d.ts.map +1 -0
  15. package/dist/confidence.js +53 -0
  16. package/dist/confidence.js.map +1 -0
  17. package/dist/index.d.ts +79 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +79 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/link-selector.d.ts +23 -0
  22. package/dist/link-selector.d.ts.map +1 -0
  23. package/dist/link-selector.js +67 -0
  24. package/dist/link-selector.js.map +1 -0
  25. package/dist/person-validator.d.ts +21 -0
  26. package/dist/person-validator.d.ts.map +1 -0
  27. package/dist/person-validator.js +63 -0
  28. package/dist/person-validator.js.map +1 -0
  29. package/dist/section-filter.d.ts +22 -0
  30. package/dist/section-filter.d.ts.map +1 -0
  31. package/dist/section-filter.js +54 -0
  32. package/dist/section-filter.js.map +1 -0
  33. package/dist/synthesizer.d.ts +91 -0
  34. package/dist/synthesizer.d.ts.map +1 -0
  35. package/dist/synthesizer.js +127 -0
  36. package/dist/synthesizer.js.map +1 -0
  37. 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
+ }