@prometheus-ai/memory 0.5.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 +107 -0
- package/dist/types/cli.d.ts +35 -0
- package/dist/types/config.d.ts +77 -0
- package/dist/types/core/aaak.d.ts +55 -0
- package/dist/types/core/annotations.d.ts +75 -0
- package/dist/types/core/banks.d.ts +33 -0
- package/dist/types/core/beam/consolidate.d.ts +32 -0
- package/dist/types/core/beam/helpers.d.ts +76 -0
- package/dist/types/core/beam/index.d.ts +59 -0
- package/dist/types/core/beam/recall.d.ts +32 -0
- package/dist/types/core/beam/schema.d.ts +2 -0
- package/dist/types/core/beam/store.d.ts +35 -0
- package/dist/types/core/beam/types.d.ts +233 -0
- package/dist/types/core/binary-vectors.d.ts +54 -0
- package/dist/types/core/chat-normalize.d.ts +13 -0
- package/dist/types/core/content-sanitizer.d.ts +18 -0
- package/dist/types/core/cost-log.d.ts +13 -0
- package/dist/types/core/embeddings.d.ts +44 -0
- package/dist/types/core/entities.d.ts +7 -0
- package/dist/types/core/episodic-graph.d.ts +89 -0
- package/dist/types/core/extraction/client.d.ts +31 -0
- package/dist/types/core/extraction/diagnostics.d.ts +51 -0
- package/dist/types/core/extraction/prompts.d.ts +2 -0
- package/dist/types/core/extraction.d.ts +6 -0
- package/dist/types/core/index.d.ts +4 -0
- package/dist/types/core/llm-backends.d.ts +21 -0
- package/dist/types/core/local-llm.d.ts +15 -0
- package/dist/types/core/memory.d.ts +160 -0
- package/dist/types/core/migrations/e6-triplestore-split.d.ts +17 -0
- package/dist/types/core/migrations/index.d.ts +1 -0
- package/dist/types/core/mmr.d.ts +8 -0
- package/dist/types/core/orchestrator.d.ts +20 -0
- package/dist/types/core/patterns.d.ts +61 -0
- package/dist/types/core/plugins.d.ts +109 -0
- package/dist/types/core/polyphonic-recall.d.ts +66 -0
- package/dist/types/core/query-cache.d.ts +46 -0
- package/dist/types/core/query-intent.d.ts +20 -0
- package/dist/types/core/recall-diagnostics.d.ts +48 -0
- package/dist/types/core/runtime-options.d.ts +68 -0
- package/dist/types/core/shmr.d.ts +56 -0
- package/dist/types/core/streaming.d.ts +136 -0
- package/dist/types/core/synonyms.d.ts +46 -0
- package/dist/types/core/temporal-parser.d.ts +16 -0
- package/dist/types/core/token-counter.d.ts +8 -0
- package/dist/types/core/triples.d.ts +63 -0
- package/dist/types/core/typed-memory.d.ts +39 -0
- package/dist/types/core/vector-math.d.ts +1 -0
- package/dist/types/core/veracity-consolidation.d.ts +60 -0
- package/dist/types/core/weibull.d.ts +96 -0
- package/dist/types/db.d.ts +16 -0
- package/dist/types/diagnose.d.ts +24 -0
- package/dist/types/dr/index.d.ts +1 -0
- package/dist/types/dr/recovery.d.ts +68 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/mcp-server.d.ts +40 -0
- package/dist/types/mcp-tools.d.ts +484 -0
- package/dist/types/migrations/e6-triplestore-split.d.ts +1 -0
- package/dist/types/migrations/index.d.ts +1 -0
- package/dist/types/types.d.ts +145 -0
- package/dist/types/util/datetime.d.ts +8 -0
- package/dist/types/util/env.d.ts +10 -0
- package/dist/types/util/ids.d.ts +3 -0
- package/dist/types/util/lru.d.ts +12 -0
- package/dist/types/util/regex.d.ts +10 -0
- package/package.json +85 -0
- package/src/cli.ts +398 -0
- package/src/config.ts +326 -0
- package/src/core/aaak.ts +142 -0
- package/src/core/annotations.ts +457 -0
- package/src/core/banks.ts +133 -0
- package/src/core/beam/consolidate.ts +965 -0
- package/src/core/beam/helpers.ts +977 -0
- package/src/core/beam/index.ts +353 -0
- package/src/core/beam/recall.ts +1100 -0
- package/src/core/beam/schema.ts +423 -0
- package/src/core/beam/store.ts +829 -0
- package/src/core/beam/types.ts +268 -0
- package/src/core/binary-vectors.ts +317 -0
- package/src/core/chat-normalize.ts +160 -0
- package/src/core/content-sanitizer.ts +136 -0
- package/src/core/cost-log.ts +103 -0
- package/src/core/embeddings.ts +423 -0
- package/src/core/entities.ts +259 -0
- package/src/core/episodic-graph.ts +708 -0
- package/src/core/extraction/client.ts +162 -0
- package/src/core/extraction/diagnostics.ts +193 -0
- package/src/core/extraction/prompts.ts +31 -0
- package/src/core/extraction.ts +335 -0
- package/src/core/index.ts +30 -0
- package/src/core/llm-backends.ts +51 -0
- package/src/core/local-llm.ts +436 -0
- package/src/core/memory.ts +630 -0
- package/src/core/migrations/e6-triplestore-split.ts +211 -0
- package/src/core/migrations/index.ts +1 -0
- package/src/core/mmr.ts +71 -0
- package/src/core/orchestrator.ts +62 -0
- package/src/core/patterns.ts +484 -0
- package/src/core/plugins.ts +375 -0
- package/src/core/polyphonic-recall.ts +563 -0
- package/src/core/query-cache.ts +354 -0
- package/src/core/query-intent.ts +139 -0
- package/src/core/recall-diagnostics.ts +157 -0
- package/src/core/runtime-options.ts +119 -0
- package/src/core/shmr.ts +460 -0
- package/src/core/streaming.ts +419 -0
- package/src/core/synonyms.ts +197 -0
- package/src/core/temporal-parser.ts +363 -0
- package/src/core/token-counter.ts +30 -0
- package/src/core/triples.ts +454 -0
- package/src/core/typed-memory.ts +407 -0
- package/src/core/vector-math.ts +23 -0
- package/src/core/veracity-consolidation.ts +477 -0
- package/src/core/weibull.ts +124 -0
- package/src/db.ts +128 -0
- package/src/diagnose.ts +174 -0
- package/src/dr/index.ts +1 -0
- package/src/dr/recovery.ts +405 -0
- package/src/index.ts +33 -0
- package/src/mcp-server.ts +155 -0
- package/src/mcp-tools.ts +970 -0
- package/src/migrations/e6-triplestore-split.ts +1 -0
- package/src/migrations/index.ts +1 -0
- package/src/types.ts +157 -0
- package/src/util/datetime.ts +69 -0
- package/src/util/env.ts +65 -0
- package/src/util/ids.ts +19 -0
- package/src/util/lru.ts +48 -0
- package/src/util/regex.ts +165 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { getDiagnostics } from "./diagnostics";
|
|
2
|
+
import { EXTRACTION_SYSTEM_PROMPT, EXTRACTION_USER_TEMPLATE } from "./prompts";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_EXTRACTION_MODEL = process.env.PROMETHEUS_MEMORY_EXTRACTION_MODEL || "google/gemini-2.5-flash";
|
|
5
|
+
export const OPENROUTER_BASE_URL = (process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api/v1").replace(
|
|
6
|
+
/\/+$/,
|
|
7
|
+
"",
|
|
8
|
+
);
|
|
9
|
+
export const FALLBACK_MODELS = ["google/gemini-flash-latest"] as const;
|
|
10
|
+
const RATE_LIMIT_BACKOFF_BASE_MS = 1_000;
|
|
11
|
+
const RATE_LIMIT_BACKOFF_MAX_MS = 30_000;
|
|
12
|
+
const FALLBACK_MODEL_DELAY_MS = 1_000;
|
|
13
|
+
|
|
14
|
+
export interface ChatMessage {
|
|
15
|
+
role: string;
|
|
16
|
+
content: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ExtractedFact {
|
|
20
|
+
subject?: string;
|
|
21
|
+
predicate?: string;
|
|
22
|
+
object?: string;
|
|
23
|
+
timestamp?: string;
|
|
24
|
+
source?: number;
|
|
25
|
+
confidence?: number;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sleep(ms: number): Promise<void> {
|
|
30
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
31
|
+
setTimeout(resolve, ms);
|
|
32
|
+
return promise;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function authHeader(apiKey: string): Record<string, string> {
|
|
36
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
37
|
+
if (apiKey !== "") {
|
|
38
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
39
|
+
}
|
|
40
|
+
return headers;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class ExtractionClient {
|
|
44
|
+
model: string;
|
|
45
|
+
apiKey: string;
|
|
46
|
+
baseUrl: string;
|
|
47
|
+
callCount = 0;
|
|
48
|
+
|
|
49
|
+
constructor(opts: { model?: string | null; apiKey?: string | null; baseUrl?: string | null } = {}) {
|
|
50
|
+
this.model = opts.model || DEFAULT_EXTRACTION_MODEL;
|
|
51
|
+
this.apiKey = opts.apiKey ?? process.env.OPENROUTER_API_KEY ?? "";
|
|
52
|
+
this.baseUrl = (opts.baseUrl || OPENROUTER_BASE_URL).replace(/\/+$/, "");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async chat(messages: readonly ChatMessage[], temperature = 0, maxTokens = 4096): Promise<string> {
|
|
56
|
+
const diag = getDiagnostics();
|
|
57
|
+
diag.recordAttempt("cloud");
|
|
58
|
+
const models = [this.model, ...FALLBACK_MODELS.filter(m => m !== this.model)];
|
|
59
|
+
let lastError: unknown = null;
|
|
60
|
+
|
|
61
|
+
for (const model of models) {
|
|
62
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
63
|
+
try {
|
|
64
|
+
const result = await this.callApi(model, messages, temperature, maxTokens);
|
|
65
|
+
if (result === "") {
|
|
66
|
+
diag.recordNoOutput("cloud");
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
} catch (exc) {
|
|
70
|
+
lastError = exc;
|
|
71
|
+
const msg = String(exc).toLowerCase();
|
|
72
|
+
if (msg.includes("429") || msg.includes("rate")) {
|
|
73
|
+
await sleep(Math.min(RATE_LIMIT_BACKOFF_MAX_MS, RATE_LIMIT_BACKOFF_BASE_MS * 2 ** attempt));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
await sleep(FALLBACK_MODEL_DELAY_MS);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
diag.recordFailure("cloud", lastError, "all_models_failed");
|
|
83
|
+
return "";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async callApi(
|
|
87
|
+
model: string,
|
|
88
|
+
messages: readonly ChatMessage[],
|
|
89
|
+
temperature: number,
|
|
90
|
+
maxTokens: number,
|
|
91
|
+
): Promise<string> {
|
|
92
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: authHeader(this.apiKey),
|
|
95
|
+
body: JSON.stringify({ model, messages, temperature, max_tokens: maxTokens }),
|
|
96
|
+
signal: AbortSignal.timeout(60000),
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(`${response.status} ${response.statusText}`.trim());
|
|
100
|
+
}
|
|
101
|
+
const data = (await response.json()) as {
|
|
102
|
+
choices?: Array<{ message?: { content?: unknown } }>;
|
|
103
|
+
};
|
|
104
|
+
this.callCount += 1;
|
|
105
|
+
const content = data.choices?.[0]?.message?.content;
|
|
106
|
+
return typeof content === "string" ? content : "";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async extractFacts(messages: readonly ChatMessage[]): Promise<ExtractedFact[]> {
|
|
110
|
+
let conversationText = "";
|
|
111
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
112
|
+
const msg = messages[i];
|
|
113
|
+
if (msg === undefined) continue;
|
|
114
|
+
const content = msg.content.trim();
|
|
115
|
+
if (content !== "") {
|
|
116
|
+
conversationText += `[${i}] [${msg.role || "unknown"}]: ${content}\n`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (conversationText.trim() === "") {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const userPrompt = EXTRACTION_USER_TEMPLATE.replace("{conversation_text}", conversationText);
|
|
124
|
+
const response = await this.chat(
|
|
125
|
+
[
|
|
126
|
+
{ role: "system", content: EXTRACTION_SYSTEM_PROMPT },
|
|
127
|
+
{ role: "user", content: userPrompt },
|
|
128
|
+
],
|
|
129
|
+
0,
|
|
130
|
+
4096,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const diag = getDiagnostics();
|
|
134
|
+
if (response === "") {
|
|
135
|
+
diag.recordCall({ succeeded: false, allEmpty: true });
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const jsonStart = response.indexOf("[");
|
|
141
|
+
const jsonEnd = response.lastIndexOf("]") + 1;
|
|
142
|
+
if (jsonStart >= 0 && jsonEnd > jsonStart) {
|
|
143
|
+
const facts = JSON.parse(response.slice(jsonStart, jsonEnd)) as unknown;
|
|
144
|
+
if (Array.isArray(facts)) {
|
|
145
|
+
diag.recordSuccess("cloud", facts.length);
|
|
146
|
+
diag.recordCall({ succeeded: true });
|
|
147
|
+
return facts as ExtractedFact[];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
diag.recordFailure("cloud", undefined, "no_facts_in_response");
|
|
151
|
+
diag.recordCall({ succeeded: false, allEmpty: true });
|
|
152
|
+
} catch (exc) {
|
|
153
|
+
diag.recordFailure("cloud", exc, "json_parse_failed");
|
|
154
|
+
diag.recordCall({ succeeded: false });
|
|
155
|
+
}
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
xtractFacts(messages: readonly ChatMessage[]): Promise<ExtractedFact[]> {
|
|
160
|
+
return this.extractFacts(messages);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
export const EXTRACTION_TIERS = ["host", "remote", "local", "cloud", "wrapper"] as const;
|
|
2
|
+
export type ExtractionTier = (typeof EXTRACTION_TIERS)[number];
|
|
3
|
+
|
|
4
|
+
const MAX_ERROR_SAMPLES_PER_TIER = 10;
|
|
5
|
+
const ERROR_MESSAGE_CAP = 200;
|
|
6
|
+
|
|
7
|
+
export interface ErrorSample {
|
|
8
|
+
at: string;
|
|
9
|
+
type: string;
|
|
10
|
+
msg: string;
|
|
11
|
+
reason?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TierStatsSnapshot {
|
|
15
|
+
attempts: number;
|
|
16
|
+
successes: number;
|
|
17
|
+
no_output: number;
|
|
18
|
+
failures: number;
|
|
19
|
+
error_samples: ErrorSample[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ExtractionStatsSnapshot {
|
|
23
|
+
created_at: string;
|
|
24
|
+
snapshot_at: string;
|
|
25
|
+
totals: {
|
|
26
|
+
calls: number;
|
|
27
|
+
successes: number;
|
|
28
|
+
failures: number;
|
|
29
|
+
empty: number;
|
|
30
|
+
success_rate: number;
|
|
31
|
+
};
|
|
32
|
+
by_tier: Record<ExtractionTier, TierStatsSnapshot>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface MutableTierStats {
|
|
36
|
+
attempts: number;
|
|
37
|
+
successes: number;
|
|
38
|
+
no_output: number;
|
|
39
|
+
failures: number;
|
|
40
|
+
error_samples: ErrorSample[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function safeForLog(value: unknown): string {
|
|
44
|
+
if (value === null || value === undefined) {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
const s = value instanceof Error ? `${value.name}: ${value.message}` : String(value);
|
|
48
|
+
let out = "";
|
|
49
|
+
for (let i = 0; i < s.length && out.length < ERROR_MESSAGE_CAP; i += 1) {
|
|
50
|
+
const code = s.charCodeAt(i);
|
|
51
|
+
out += code >= 32 && code !== 127 && code !== 27 ? s.charAt(i) : " ";
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function emptyTierStats(): Record<ExtractionTier, MutableTierStats> {
|
|
57
|
+
return {
|
|
58
|
+
host: { attempts: 0, successes: 0, no_output: 0, failures: 0, error_samples: [] },
|
|
59
|
+
remote: { attempts: 0, successes: 0, no_output: 0, failures: 0, error_samples: [] },
|
|
60
|
+
local: { attempts: 0, successes: 0, no_output: 0, failures: 0, error_samples: [] },
|
|
61
|
+
cloud: { attempts: 0, successes: 0, no_output: 0, failures: 0, error_samples: [] },
|
|
62
|
+
wrapper: { attempts: 0, successes: 0, no_output: 0, failures: 0, error_samples: [] },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isTier(tier: string): tier is ExtractionTier {
|
|
67
|
+
return (EXTRACTION_TIERS as readonly string[]).includes(tier);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function truncateError(msg: string): string {
|
|
71
|
+
return msg.length > ERROR_MESSAGE_CAP ? `${msg.slice(0, ERROR_MESSAGE_CAP)}...[truncated]` : msg;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function errorRepr(exc: unknown): string {
|
|
75
|
+
if (exc instanceof Error) {
|
|
76
|
+
return `${exc.name}: ${exc.message}`;
|
|
77
|
+
}
|
|
78
|
+
return String(exc);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class ExtractionDiagnostics {
|
|
82
|
+
private tierStats: Record<ExtractionTier, MutableTierStats> = emptyTierStats();
|
|
83
|
+
private totalCalls = 0;
|
|
84
|
+
private totalSuccesses = 0;
|
|
85
|
+
private totalFailures = 0;
|
|
86
|
+
private totalEmpty = 0;
|
|
87
|
+
private createdAt = new Date().toISOString();
|
|
88
|
+
|
|
89
|
+
private validateTier(tier: string): asserts tier is ExtractionTier {
|
|
90
|
+
if (!isTier(tier)) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`unknown extraction tier ${JSON.stringify(tier)}; valid tiers: ${EXTRACTION_TIERS.join(", ")}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
recordAttempt(tier: ExtractionTier): void {
|
|
98
|
+
this.validateTier(tier);
|
|
99
|
+
this.tierStats[tier].attempts += 1;
|
|
100
|
+
}
|
|
101
|
+
recordSuccess(tier: ExtractionTier, _factCount = 0): void {
|
|
102
|
+
this.validateTier(tier);
|
|
103
|
+
this.tierStats[tier].successes += 1;
|
|
104
|
+
}
|
|
105
|
+
recordNoOutput(tier: ExtractionTier): void {
|
|
106
|
+
this.validateTier(tier);
|
|
107
|
+
this.tierStats[tier].no_output += 1;
|
|
108
|
+
}
|
|
109
|
+
recordFailure(tier: ExtractionTier, exc?: unknown, reason?: string): void {
|
|
110
|
+
this.validateTier(tier);
|
|
111
|
+
const stats = this.tierStats[tier];
|
|
112
|
+
stats.failures += 1;
|
|
113
|
+
const sample: ErrorSample = { at: new Date().toISOString(), type: "unspecified", msg: "" };
|
|
114
|
+
if (exc !== undefined && exc !== null) {
|
|
115
|
+
sample.type = exc instanceof Error ? exc.name : typeof exc;
|
|
116
|
+
sample.msg = truncateError(errorRepr(exc));
|
|
117
|
+
} else if (reason !== undefined) {
|
|
118
|
+
sample.type = "reason";
|
|
119
|
+
sample.msg = truncateError(reason);
|
|
120
|
+
}
|
|
121
|
+
if (reason !== undefined) {
|
|
122
|
+
sample.reason = reason;
|
|
123
|
+
}
|
|
124
|
+
stats.error_samples.push(sample);
|
|
125
|
+
if (stats.error_samples.length > MAX_ERROR_SAMPLES_PER_TIER) {
|
|
126
|
+
stats.error_samples.splice(0, stats.error_samples.length - MAX_ERROR_SAMPLES_PER_TIER);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
recordCall(opts: { succeeded: boolean; allEmpty?: boolean }): void {
|
|
130
|
+
this.totalCalls += 1;
|
|
131
|
+
if (opts.succeeded) {
|
|
132
|
+
this.totalSuccesses += 1;
|
|
133
|
+
} else if (opts.allEmpty === true) {
|
|
134
|
+
this.totalEmpty += 1;
|
|
135
|
+
} else {
|
|
136
|
+
this.totalFailures += 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
successRate(): number {
|
|
140
|
+
return this.totalCalls === 0 ? 0 : this.totalSuccesses / this.totalCalls;
|
|
141
|
+
}
|
|
142
|
+
snapshot(): ExtractionStatsSnapshot {
|
|
143
|
+
const byTier = {} as Record<ExtractionTier, TierStatsSnapshot>;
|
|
144
|
+
for (const tier of EXTRACTION_TIERS) {
|
|
145
|
+
const stats = this.tierStats[tier];
|
|
146
|
+
byTier[tier] = {
|
|
147
|
+
attempts: stats.attempts,
|
|
148
|
+
successes: stats.successes,
|
|
149
|
+
no_output: stats.no_output,
|
|
150
|
+
failures: stats.failures,
|
|
151
|
+
error_samples: stats.error_samples.map(sample => ({ ...sample })),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
created_at: this.createdAt,
|
|
156
|
+
snapshot_at: new Date().toISOString(),
|
|
157
|
+
totals: {
|
|
158
|
+
calls: this.totalCalls,
|
|
159
|
+
successes: this.totalSuccesses,
|
|
160
|
+
failures: this.totalFailures,
|
|
161
|
+
empty: this.totalEmpty,
|
|
162
|
+
success_rate: this.successRate(),
|
|
163
|
+
},
|
|
164
|
+
by_tier: byTier,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
reset(): void {
|
|
169
|
+
this.tierStats = emptyTierStats();
|
|
170
|
+
this.totalCalls = 0;
|
|
171
|
+
this.totalSuccesses = 0;
|
|
172
|
+
this.totalFailures = 0;
|
|
173
|
+
this.totalEmpty = 0;
|
|
174
|
+
this.createdAt = new Date().toISOString();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let singleton: ExtractionDiagnostics | null = null;
|
|
179
|
+
|
|
180
|
+
export function getDiagnostics(): ExtractionDiagnostics {
|
|
181
|
+
if (singleton === null) {
|
|
182
|
+
singleton = new ExtractionDiagnostics();
|
|
183
|
+
}
|
|
184
|
+
return singleton;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function getExtractionStats(): ExtractionStatsSnapshot {
|
|
188
|
+
return getDiagnostics().snapshot();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function resetExtractionStats(): void {
|
|
192
|
+
getDiagnostics().reset();
|
|
193
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const EXTRACTION_SYSTEM_PROMPT = `You extract structured facts from conversation messages. For each message or group of related messages, identify:
|
|
2
|
+
|
|
3
|
+
1. ENTITIES: People, projects, tools, versions, dates, numbers mentioned
|
|
4
|
+
2. RELATIONSHIPS: How entities relate to each other (uses, created, set, changed, prefers)
|
|
5
|
+
3. TEMPORAL ANCHORS: When something happened, deadlines, durations
|
|
6
|
+
4. CONTRADICTIONS: When a fact was later changed or updated
|
|
7
|
+
|
|
8
|
+
Return ONLY a JSON array of fact objects. Each fact must have:
|
|
9
|
+
- subject: the entity the fact is about (string)
|
|
10
|
+
- predicate: the relationship or action (string)
|
|
11
|
+
- object: the value or related entity (string)
|
|
12
|
+
- timestamp: ISO timestamp when this was stated (string, from message context)
|
|
13
|
+
- source: which message index this came from (integer, 0-based)
|
|
14
|
+
- confidence: 0.0-1.0 how certain you are (float)
|
|
15
|
+
|
|
16
|
+
RULES:
|
|
17
|
+
- One fact per relationship. "I use React 18.2 and Node.js 18" = 2 facts.
|
|
18
|
+
- Use lowercase for predicates: "uses", "set", "changed", "created", "prefers"
|
|
19
|
+
- Include versions and numbers as objects when available
|
|
20
|
+
- If a message states something changed, extract BOTH old and new facts
|
|
21
|
+
- If unclear, use confidence < 0.8
|
|
22
|
+
|
|
23
|
+
Format: [{"subject": "...", "predicate": "...", "object": "...", "timestamp": "...", "source": 0, "confidence": 0.95}]
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
export const EXTRACTION_USER_TEMPLATE = `Extract all structured facts from the following conversation messages. Return ONLY the JSON array, no other text.
|
|
27
|
+
|
|
28
|
+
CONVERSATION:
|
|
29
|
+
{conversation_text}
|
|
30
|
+
|
|
31
|
+
FACTS:`;
|